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,@uppy/companion-client: pass query params along more consistently #4551

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 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
27 changes: 17 additions & 10 deletions packages/@uppy/companion-client/src/Provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ const getName = (id) => {
return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
}

const queryString = (params, prefix = '?') => {
const str = new URLSearchParams(params).toString()
return str ? `${prefix}${str}` : ''
}

export default class Provider extends RequestClient {
#refreshingTokenPromise

Expand Down Expand Up @@ -72,20 +77,22 @@ export default class Provider extends RequestClient {
}

authUrl (queries = {}) {
const params = new URLSearchParams(queries)
if (this.preAuthToken) {
params.set('uppyPreAuthToken', this.preAuthToken)
}

return `${this.hostname}/${this.id}/connect?${params}`
const qs = queryString({
...queries,
...this.cutomQueryParams,
...(this.preAuthToken && {
uppyPreAuthToken: this.preAuthToken,
}),
})
return `${this.hostname}/${this.id}/connect${qs}`
}

refreshTokenUrl () {
return `${this.hostname}/${this.id}/refresh-token`
return `${this.hostname}/${this.id}/refresh-token${queryString(this.cutomQueryParams)}`
}

fileUrl (id) {
return `${this.hostname}/${this.id}/get/${id}`
return `${this.hostname}/${this.id}/get/${id}${queryString(this.cutomQueryParams)}`
}

/** @protected */
Expand Down Expand Up @@ -132,11 +139,11 @@ export default class Provider extends RequestClient {
}

list (directory, options) {
return this.get(`${this.id}/list/${directory || ''}`, options)
return this.get(`${this.id}/list/${directory || ''}${queryString(this.cutomQueryParams)}`, options)
}

async logout (options) {
const response = await this.get(`${this.id}/logout`, options)
const response = await this.get(`${this.id}/logout${queryString(this.cutomQueryParams)}`, options)
await this.#removeAuthToken()
return response
}
Expand Down
1 change: 1 addition & 0 deletions packages/@uppy/companion/src/companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ module.exports.app = (optionsArg = {}) => {
const providers = providerManager.getDefaultProviders()

providerManager.addProviderOptions(options, grantConfig)
options.providerOptions = merge(options.providerOptions, grantConfig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this change

Copy link
Contributor Author

@dschmidt dschmidt Jul 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is here because grant does interpolation for certain settings passed as GET parameters to the connect handler if they are marked as dynamic, e.g. if you add subdomain to the dynamic array, you can use it in authorize_url and access_url (c.f. https://github.com/simov/grant#dynamic-http and https://github.com/owncloud/uppy/blob/3403194fd4692c679e03d993c040c6d8ac5ac29a/packages/%40uppy/companion/src/config/grant.js#L47-L57 - yes, it should only be used as a subdomain not as a fqdn but that's a dlfferent topic).

To make this work in companion we need to forward the dynamic paramaters in the redirect from our connect handler to the grant connect handler.
In order to achieve that, I merge the grant config including the dynamic option back to providerOptions and loop over the dynamic array in our connect handler (which is the other part of the code you commented on)

edit: fixed link

Copy link
Contributor Author

@dschmidt dschmidt Jul 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const { customProviders } = options
if (customProviders) {
Expand Down
12 changes: 11 additions & 1 deletion packages/@uppy/companion/src/server/controllers/connect.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
const atob = require('atob')
const oAuthState = require('../helpers/oauth-state')

const queryString = (params, prefix = '') => {
const str = new URLSearchParams(params).toString()
return str ? `${prefix}${str}` : ''
}

/**
* initializes the oAuth flow for a provider.
*
Expand All @@ -27,5 +32,10 @@ module.exports = function connect (req, res) {
state = oAuthState.addToState(state, { preAuthToken: req.query.uppyPreAuthToken }, secret)
}

res.redirect(req.companion.buildURL(`/connect/${req.companion.provider.authProvider}?state=${state}`, true))
const providerName = req.companion.provider.authProvider
const qs = queryString(Object.fromEntries(
req.companion.options.providerOptions[providerName]?.dynamic?.map(p => [p, req.query[p]]) || [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

), '&')

res.redirect(req.companion.buildURL(`/connect/${providerName}?state=${state}${qs}`, true))
}
5 changes: 5 additions & 0 deletions packages/@uppy/companion/src/server/controllers/list.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
const { respondWithError } = require('../provider/error')

/**
*
* @param {{ query: object, params: object, companion: object }} req
* @param {object} res
*/
async function list ({ query, params, companion }, res, next) {
const { accessToken } = companion.providerTokens

Expand Down
15 changes: 7 additions & 8 deletions packages/@uppy/companion/src/server/controllers/logout.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ const { respondWithError } = require('../provider/error')

/**
*
* @param {object} req
* @param {{ query: object, params: object, companion: object, session: object }} req
* @param {object} res
*/
async function logout (req, res, next) {
async function logout ({ query, params, companion, session }, res, next) {
const cleanSession = () => {
if (req.session.grant) {
req.session.grant.state = null
req.session.grant.dynamic = null
if (session.grant) {
session.grant.state = null
session.grant.dynamic = null
}
}
const { providerName } = req.params
const { companion } = req
const { providerName } = params
const tokens = companion.allProvidersTokens ? companion.allProvidersTokens[providerName] : null

if (!tokens) {
Expand All @@ -25,7 +24,7 @@ async function logout (req, res, next) {

try {
const { accessToken } = tokens
const data = await companion.provider.logout({ token: accessToken, companion })
const data = await companion.provider.logout({ companion, token: accessToken, query })
delete companion.allProvidersTokens[providerName]
tokenService.removeFromCookies(res, companion.options, companion.provider.authProvider)
cleanSession()
Expand Down
12 changes: 6 additions & 6 deletions packages/@uppy/companion/src/server/controllers/thumbnail.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/**
*
* @param {object} req
* @param {{ params: object, companion: object, query: object }} req
* @param {object} res
*/
async function thumbnail (req, res, next) {
const { providerName, id } = req.params
const { accessToken } = req.companion.allProvidersTokens[providerName]
const { provider } = req.companion
async function thumbnail ({ params, companion, query }, res, next) {
const { providerName, id } = params
const { accessToken } = companion.allProvidersTokens[providerName]
const { provider } = companion

try {
const { stream } = await provider.thumbnail({ id, token: accessToken })
const { stream } = await provider.thumbnail({ id, token: accessToken, query })
stream.pipe(res)
} catch (err) {
if (err.isAuthError) res.sendStatus(401)
Expand Down
2 changes: 1 addition & 1 deletion packages/@uppy/companion/src/server/controllers/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const downloadURL = async (url, blockLocalIPs, traceId) => {
}

/**
* Fteches the size and content type of a URL
* Fetches the size and content type of a URL
*
* @param {object} req expressJS request object
* @param {object} res expressJS response object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async function fetchKeys (url, providerName, credentialRequestParams) {
}

/**
* Fetches for a providers OAuth credentials. If the config for thtat provider allows fetching
* Fetches for a providers OAuth credentials. If the config for that provider allows fetching
* of the credentials via http, and the `credentialRequestParams` argument is provided, the oauth
* credentials will be fetched via http. Otherwise, the credentials provided via companion options
* will be used instead.
Expand Down