diff --git a/packages/@uppy/companion-client/src/Provider.js b/packages/@uppy/companion-client/src/Provider.js index d1f03ddac4..2734868b69 100644 --- a/packages/@uppy/companion-client/src/Provider.js +++ b/packages/@uppy/companion-client/src/Provider.js @@ -7,6 +7,26 @@ const getName = (id) => { return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ') } +function getOrigin () { + // eslint-disable-next-line no-restricted-globals + return location.origin +} + +function getRegex (value) { + if (typeof value === 'string') { + return new RegExp(`^${value}$`) + } if (value instanceof RegExp) { + return value + } + return undefined +} + +function isOriginAllowed (origin, allowedOrigin) { + const patterns = Array.isArray(allowedOrigin) ? allowedOrigin.map(getRegex) : [getRegex(allowedOrigin)] + return patterns + .some((pattern) => pattern?.test(origin) || pattern?.test(`${origin}/`)) // allowing for trailing '/' +} + export default class Provider extends RequestClient { #refreshingTokenPromise @@ -19,7 +39,6 @@ export default class Provider extends RequestClient { this.tokenKey = `companion-${this.pluginId}-auth-token` this.companionKeysParams = this.opts.companionKeysParams this.preAuthToken = null - this.authentication = opts.authentication ?? true } async headers () { @@ -74,6 +93,7 @@ export default class Provider extends RequestClient { authUrl (queries = {}) { const params = new URLSearchParams(queries) + params.set('state', btoa(JSON.stringify({ origin: getOrigin() }))) if (this.preAuthToken) { params.set('uppyPreAuthToken', this.preAuthToken) } @@ -81,6 +101,46 @@ export default class Provider extends RequestClient { return `${this.hostname}/${this.id}/connect?${params}` } + async login (queries) { + await this.ensurePreAuth() + + return new Promise((resolve, reject) => { + const link = this.authUrl(queries) + const authWindow = window.open(link, '_blank') + const handleToken = (e) => { + if (e.source !== authWindow) { + reject(new Error('rejecting event from unknown source')) + } + + const { companionAllowedHosts } = this.uppy.getPlugin(this.pluginId).opts + if (!isOriginAllowed(e.origin, companionAllowedHosts) || e.source !== authWindow) { + reject(new Error(`rejecting event from ${e.origin} vs allowed pattern ${companionAllowedHosts}`)) + } + + // Check if it's a string before doing the JSON.parse to maintain support + // for older Companion versions that used object references + const data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data + + if (data.error) { + const { uppy } = this + const message = uppy.i18n('authAborted') + uppy.info({ message }, 'warning', 5000) + reject(new Error('auth aborted')) + } + + if (!data.token) { + reject(new Error('did not receive token from auth window')) + } + + authWindow.close() + window.removeEventListener('message', handleToken) + this.setAuthToken(data.token) + resolve() + } + window.addEventListener('message', handleToken) + }) + } + refreshTokenUrl () { return `${this.hostname}/${this.id}/refresh-token` } @@ -137,9 +197,6 @@ export default class Provider extends RequestClient { } async logout () { - if (!this.authentication) { - return Promise.resolve({ ok: true, revoked: true }) - } const response = await this.get(`${this.id}/logout`) await this.#removeAuthToken() return response diff --git a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx index 2920e6ae52..7f25322149 100644 --- a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx +++ b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx @@ -13,25 +13,6 @@ import View from '../View.js' import packageJson from '../../package.json' -function getOrigin () { - // eslint-disable-next-line no-restricted-globals - return location.origin -} - -function getRegex (value) { - if (typeof value === 'string') { - return new RegExp(`^${value}$`) - } if (value instanceof RegExp) { - return value - } - return undefined -} -function isOriginAllowed (origin, allowedOrigin) { - const patterns = Array.isArray(allowedOrigin) ? allowedOrigin.map(getRegex) : [getRegex(allowedOrigin)] - return patterns - .some((pattern) => pattern?.test(origin) || pattern?.test(`${origin}/`)) // allowing for trailing '/' -} - /** * Class to easily generate generic views for Provider plugins */ @@ -178,51 +159,14 @@ export default class ProviderView extends View { } async handleAuth () { - if (!this.provider.authentication) { - this.plugin.setPluginState({ authenticated: true }) - this.preFirstRender() - return - } - - await this.provider.ensurePreAuth() - - const authState = btoa(JSON.stringify({ origin: getOrigin() })) const clientVersion = `@uppy/provider-views=${ProviderView.VERSION}` - const link = this.provider.authUrl({ state: authState, uppyVersions: clientVersion }) - - const authWindow = window.open(link, '_blank') - const handleToken = (e) => { - if (e.source !== authWindow) { - this.plugin.uppy.log('rejecting event from unknown source') - return - } - if (!isOriginAllowed(e.origin, this.plugin.opts.companionAllowedHosts) || e.source !== authWindow) { - this.plugin.uppy.log(`rejecting event from ${e.origin} vs allowed pattern ${this.plugin.opts.companionAllowedHosts}`) - } - - // Check if it's a string before doing the JSON.parse to maintain support - // for older Companion versions that used object references - const data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data - - if (data.error) { - this.plugin.uppy.log('auth aborted', 'warning') - const { uppy } = this.plugin - const message = uppy.i18n('authAborted') - uppy.info({ message }, 'warning', 5000) - return - } - - if (!data.token) { - this.plugin.uppy.log('did not receive token from auth window', 'error') - return - } - - authWindow.close() - window.removeEventListener('message', handleToken) - this.provider.setAuthToken(data.token) + try { + await this.provider.login({ uppyVersions: clientVersion }) + this.plugin.setPluginState({ authenticated: true }) this.preFirstRender() + } catch (e) { + this.plugin.uppy.log(`login failed: ${e.message}`) } - window.addEventListener('message', handleToken) } async handleScroll (event) {