Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@uppy/companion: pass fetched origins to window.postMessage() #5529

Merged
merged 3 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@uppy/companion/src/companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ module.exports.app = (optionsArg = {}) => {
key,
secret,
redirect_uri: getRedirectUri(),
origins: ['http://localhost:5173'],
},
})
})
Expand Down
8 changes: 4 additions & 4 deletions packages/@uppy/companion/src/server/controllers/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ const oAuthState = require('../helpers/oauth-state')
/**
* Derived from `cors` npm package.
* @see https://github.com/expressjs/cors/blob/791983ebc0407115bc8ae8e64830d440da995938/lib/index.js#L19-L34
* @param {string} origin
* @param {*} allowedOrigins
* @param {string} origin
* @param {*} allowedOrigins
* @returns {boolean}
*/
function isOriginAllowed(origin, allowedOrigins) {
Expand All @@ -17,7 +17,6 @@ function isOriginAllowed(origin, allowedOrigins) {
return allowedOrigins.test?.(origin) ?? !!allowedOrigins;
}


const queryString = (params, prefix = '?') => {
const str = new URLSearchParams(params).toString()
return str ? `${prefix}${str}` : ''
Expand Down Expand Up @@ -66,7 +65,7 @@ function getClientOrigin(base64EncodedState) {
*
* The client has open a new tab and is about to be redirected to the auth
* provider. When the user will return to companion, we'll have to send the auth
* token back to Uppy with `window.postMessage()`.
* token back to Uppy with `window.postMessage()`.
* To prevent other tabs and unauthorized origins from accessing that token, we
* reuse origin(s) from `corsOrigins` to limit the scope of `postMessage()`, which
* has `targetOrigin` parameter, required for cross-origin messages (i.e. if Uppy
Expand Down Expand Up @@ -113,3 +112,4 @@ module.exports = function connect(req, res, next) {
}
encodeStateAndRedirect(req, res, stateObj)
}
module.exports.isOriginAllowed = isOriginAllowed
33 changes: 23 additions & 10 deletions packages/@uppy/companion/src/server/controllers/send-token.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const serialize = require('serialize-javascript')
const { isOriginAllowed } = require('./connect')

const oAuthState = require('../helpers/oauth-state')

Expand Down Expand Up @@ -46,18 +47,30 @@ const htmlContent = (token, origin) => {

/**
*
* @param {object} req
* @param {object} res
* @param {Function} next
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
*/
module.exports = function sendToken (req, res, next) {
const uppyAuthToken = req.companion.authToken
module.exports = function sendToken(req, res, next) {
// @ts-expect-error untyped
const { companion } = req
const uppyAuthToken = companion.authToken

const { state } = oAuthState.getGrantDynamicFromRequest(req)
if (state) {
const origin = oAuthState.getFromState(state, 'origin', req.companion.options.secret)
res.send(htmlContent(uppyAuthToken, origin))
return

if (!state) {
return next()
}
next()

const clientOrigin = oAuthState.getFromState(state, 'origin', companion.options.secret)
const customerDefinedAllowedOrigins = oAuthState.getFromState(state, 'customerDefinedAllowedOrigins', companion.options.secret)

if (
customerDefinedAllowedOrigins &&
!isOriginAllowed(clientOrigin, customerDefinedAllowedOrigins)
) {
return next()
}

return res.send(htmlContent(uppyAuthToken, clientOrigin))
}
22 changes: 22 additions & 0 deletions packages/@uppy/companion/src/server/provider/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,32 @@ exports.getCredentialsOverrideMiddleware = (providers, companionOptions) => {

const credentials = await fetchProviderKeys(providerName, companionOptions, payload)

// Besides the key and secret the fetched credentials can also contain `origins`,
// which is an array of strings of allowed origins to prevent any origin from getting the OAuth
// token through window.postMessage (see comment in connect.js).
// postMessage happens in send-token.js, which is a different request, so we need to put the allowed origins
// on the encrypted session state to access it later there.
if (Array.isArray(credentials.origins) && credentials.origins.length > 0) {
const decodedState = oAuthState.decodeState(state, companionOptions.secret)
decodedState.customerDefinedAllowedOrigins = credentials.origins
const newState = oAuthState.encodeState(decodedState, companionOptions.secret)
// @ts-expect-error untyped
req.session.grant = {
// @ts-expect-error untyped
...req.session.grant,
dynamic: {
// @ts-expect-error untyped
...req.session.grant?.dynamic,
state: newState,
},
}
}

res.locals.grant = {
dynamic: {
key: credentials.key,
secret: credentials.secret,
origins: credentials.origins,
},
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@uppy/companion/src/server/provider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ module.exports.addProviderOptions = (companionOptions, grantConfig, getOauthProv
grantConfig[oauthProvider].secret = providerOptions[providerName].secret
if (providerOptions[providerName].credentialsURL) {
// eslint-disable-next-line no-param-reassign
grantConfig[oauthProvider].dynamic = ['key', 'secret', 'redirect_uri']
grantConfig[oauthProvider].dynamic = ['key', 'secret', 'redirect_uri', 'origins']
}

const provider = exports.getDefaultProviders()[providerName]
Expand Down
Loading