From 682ae7fe43d3f139b90aed055ad0ac64dc94cabd Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 6 Sep 2023 21:35:30 +0200 Subject: [PATCH] Onedrive refresh tokens (#4655) * handle google drive refresh token revoked * implement onedrive refresh tokens #2721 --- packages/@uppy/companion/src/companion.js | 2 +- .../src/server/controllers/refresh-token.js | 3 ++- .../companion/src/server/provider/drive/index.js | 7 +++++-- .../@uppy/companion/src/server/provider/index.js | 3 ++- .../companion/src/server/provider/onedrive/index.js | 12 ++++++++++++ 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/@uppy/companion/src/companion.js b/packages/@uppy/companion/src/companion.js index 3e314e72d0..aeb642d91c 100644 --- a/packages/@uppy/companion/src/companion.js +++ b/packages/@uppy/companion/src/companion.js @@ -136,7 +136,7 @@ module.exports.app = (optionsArg = {}) => { app.get('/:providerName/thumbnail/:id', middlewares.hasSessionAndProvider, middlewares.hasOAuthProvider, middlewares.cookieAuthToken, middlewares.verifyToken, controllers.thumbnail) - app.param('providerName', providerManager.getProviderMiddleware(providers)) + app.param('providerName', providerManager.getProviderMiddleware(providers, grantConfig)) if (app.get('env') !== 'test') { jobs.startCleanUpJob(options.filePath) diff --git a/packages/@uppy/companion/src/server/controllers/refresh-token.js b/packages/@uppy/companion/src/server/controllers/refresh-token.js index 99203ebe3c..71cd12d6de 100644 --- a/packages/@uppy/companion/src/server/controllers/refresh-token.js +++ b/packages/@uppy/companion/src/server/controllers/refresh-token.js @@ -9,6 +9,7 @@ async function refreshToken (req, res, next) { const { providerName } = req.params const { key: clientId, secret: clientSecret } = req.companion.options.providerOptions[providerName] + const { redirect_uri: redirectUri } = req.companion.providerGrantConfig const providerTokens = req.companion.allProvidersTokens[providerName] @@ -20,7 +21,7 @@ async function refreshToken (req, res, next) { try { const data = await req.companion.provider.refreshToken({ - clientId, clientSecret, refreshToken: providerTokens.refreshToken, + redirectUri, clientId, clientSecret, refreshToken: providerTokens.refreshToken, }) const newAllProvidersTokens = { diff --git a/packages/@uppy/companion/src/server/provider/drive/index.js b/packages/@uppy/companion/src/server/provider/drive/index.js index 44fb38d960..a5dd5f185f 100644 --- a/packages/@uppy/companion/src/server/provider/drive/index.js +++ b/packages/@uppy/companion/src/server/provider/drive/index.js @@ -171,7 +171,7 @@ class Drive extends Provider { async refreshToken ({ clientId, clientSecret, refreshToken }) { return this.#withErrorHandling('provider.drive.token.refresh.error', async () => { - const { access_token: accessToken } = await getOauthClient().post('token', { form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret } }).json() + const { access_token: accessToken } = await getOauthClient().post('token', { responseType: 'json', form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret } }).json() return { accessToken } }) } @@ -181,7 +181,10 @@ class Drive extends Provider { fn, tag, providerName: this.authProvider, - isAuthError: (response) => response.statusCode === 401, + isAuthError: (response) => ( + response.statusCode === 401 + || (response.statusCode === 400 && response.body?.error === 'invalid_grant') // Refresh token has expired or been revoked + ), getJsonErrorMessage: (body) => body?.error?.message, }) } diff --git a/packages/@uppy/companion/src/server/provider/index.js b/packages/@uppy/companion/src/server/provider/index.js index e49aff660d..02e2470eba 100644 --- a/packages/@uppy/companion/src/server/provider/index.js +++ b/packages/@uppy/companion/src/server/provider/index.js @@ -42,7 +42,7 @@ const providerNameToAuthName = (name, options) => { // eslint-disable-line no-un * * @param {Record} providers */ -module.exports.getProviderMiddleware = (providers) => { +module.exports.getProviderMiddleware = (providers, grantConfig) => { /** * * @param {object} req @@ -56,6 +56,7 @@ module.exports.getProviderMiddleware = (providers) => { const { allowLocalUrls } = req.companion.options req.companion.provider = new ProviderClass({ providerName, allowLocalUrls }) req.companion.providerClass = ProviderClass + req.companion.providerGrantConfig = grantConfig[ProviderClass.authProvider] if (isOAuthProvider(ProviderClass.authProvider)) { req.companion.getProviderCredentials = getCredentialsResolver(providerName, req.companion.options, req) diff --git a/packages/@uppy/companion/src/server/provider/onedrive/index.js b/packages/@uppy/companion/src/server/provider/onedrive/index.js index 38f138081c..e12a3c5ac2 100644 --- a/packages/@uppy/companion/src/server/provider/onedrive/index.js +++ b/packages/@uppy/companion/src/server/provider/onedrive/index.js @@ -13,6 +13,10 @@ const getClient = ({ token }) => got.extend({ }, }) +const getOauthClient = () => got.extend({ + prefixUrl: 'https://login.live.com', +}) + const getRootPath = (query) => (query.driveId ? `drives/${query.driveId}` : 'me/drive') /** @@ -81,9 +85,17 @@ class OneDrive extends Provider { // eslint-disable-next-line class-methods-use-this async logout () { + // apparently M$ doesn't support programmatic oauth2 revoke return { revoked: false, manual_revoke_url: 'https://account.live.com/consent/Manage' } } + async refreshToken ({ clientId, clientSecret, refreshToken, redirectUri }) { + return this.#withErrorHandling('provider.onedrive.token.refresh.error', async () => { + const { access_token: accessToken } = await getOauthClient().post('oauth20_token.srf', { responseType: 'json', form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri } }).json() + return { accessToken } + }) + } + async #withErrorHandling (tag, fn) { return withProviderErrorHandling({ fn,