mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-17 16:50:22 +00:00
all changes squashed
This commit is contained in:
parent
622daa498c
commit
937b5ecdb9
7 changed files with 129 additions and 0 deletions
|
|
@ -222,6 +222,21 @@ export function app(optionsArg: CompanionInitOptions) {
|
|||
controllers.sendToken,
|
||||
)
|
||||
|
||||
// Returns the decrypted Dropbox OAuth tokens for the current session so a client
|
||||
// can drive a server-side /dropbox/import Assembly. Deliberately weakens the
|
||||
// encrypted-token posture, so it is off unless explicitly enabled. The route keeps
|
||||
// the :providerName segment because verifyToken and the provider middleware are
|
||||
// keyed on it; the controller restricts the result to Dropbox.
|
||||
if (options.enableDropboxTokensEndpoint) {
|
||||
app.get(
|
||||
'/:providerName/bridge-tokens',
|
||||
middlewares.hasSessionAndProvider,
|
||||
middlewares.hasOAuthProvider,
|
||||
middlewares.verifyToken,
|
||||
controllers.bridgeTokens,
|
||||
)
|
||||
}
|
||||
|
||||
app.post(
|
||||
'/:providerName/simple-auth',
|
||||
express.json(),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export const defaultOptions = {
|
|||
},
|
||||
enableUrlEndpoint: false,
|
||||
enableGooglePickerEndpoint: false,
|
||||
enableDropboxTokensEndpoint: false,
|
||||
allowLocalUrls: false,
|
||||
periodicPingUrls: defaultPeriodicPingUrls,
|
||||
streamingUpload: true,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,12 @@ export interface CompanionInitOptions {
|
|||
sendSelfEndpoint?: string | undefined
|
||||
enableUrlEndpoint?: boolean | undefined
|
||||
enableGooglePickerEndpoint?: boolean | undefined
|
||||
/**
|
||||
* Exposes the opt decrypted Dropbox OAuth tokens for the current session via
|
||||
* `GET /dropbox/bridge-tokens`. This deliberately returns raw OAuth tokens to the
|
||||
* client.
|
||||
*/
|
||||
enableDropboxTokensEndpoint?: boolean | undefined
|
||||
metrics?: boolean | undefined
|
||||
periodicPingUrls?: string[] | undefined
|
||||
periodicPingInterval?: number | undefined
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import type { Request, Response } from 'express'
|
||||
|
||||
/**
|
||||
* Returns the decrypted Dropbox OAuth tokens for the current session.
|
||||
**/
|
||||
export default function bridgeTokens(req: Request, res: Response): void {
|
||||
// Responses on this route may carry OAuth tokens — never let them be cached.
|
||||
res.set('Cache-Control', 'no-store')
|
||||
|
||||
// The route is `/:providerName/bridge-tokens` because `verifyToken` and the
|
||||
// provider middleware are keyed on `:providerName`. A token minted for another
|
||||
// provider cannot be read here: `verifyToken` decrypts against this same param,
|
||||
// so a mismatched path is rejected by the middleware before reaching this point.
|
||||
if (req.params['providerName'] !== 'dropbox') {
|
||||
res
|
||||
.status(400)
|
||||
.json({ error: 'The bridge-tokens endpoint only supports Dropbox.' })
|
||||
return
|
||||
}
|
||||
|
||||
const session = req.companion.providerUserSession
|
||||
if (session?.accessToken == null) {
|
||||
// The auth token decrypted fine, but this session has no connected Dropbox
|
||||
// account — that is "not found", not an authentication failure.
|
||||
res.status(404).json({ error: 'No Dropbox tokens for this session.' })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({
|
||||
accessToken: session.accessToken,
|
||||
refreshToken: session.refreshToken ?? null,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as bridgeTokens } from './bridge-tokens.js'
|
||||
export { default as callback } from './callback.js'
|
||||
export { default as connect } from './connect.js'
|
||||
export { default as deauthorizationCallback } from './deauth-callback.js'
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ type StandaloneCompanionOptions = Pick<
|
|||
| 's3'
|
||||
| 'enableUrlEndpoint'
|
||||
| 'enableGooglePickerEndpoint'
|
||||
| 'enableDropboxTokensEndpoint'
|
||||
| 'periodicPingUrls'
|
||||
| 'periodicPingInterval'
|
||||
| 'periodicPingStaticPayload'
|
||||
|
|
@ -190,6 +191,8 @@ const getConfigFromEnv = (): StandaloneCompanionOptions => {
|
|||
enableUrlEndpoint: process.env['COMPANION_ENABLE_URL_ENDPOINT'] === 'true',
|
||||
enableGooglePickerEndpoint:
|
||||
process.env['COMPANION_ENABLE_GOOGLE_PICKER_ENDPOINT'] === 'true',
|
||||
enableDropboxTokensEndpoint:
|
||||
process.env['COMPANION_ENABLE_DROPBOX_TOKENS_ENDPOINT'] === 'true',
|
||||
periodicPingUrls: process.env['COMPANION_PERIODIC_PING_URLS']
|
||||
? process.env['COMPANION_PERIODIC_PING_URLS'].split(',')
|
||||
: [],
|
||||
|
|
|
|||
70
packages/@uppy/companion/test/bridge-tokens.test.ts
Normal file
70
packages/@uppy/companion/test/bridge-tokens.test.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import request from 'supertest'
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import * as tokenService from '../src/server/helpers/jwt.js'
|
||||
import { getServer } from './mockserver.js'
|
||||
|
||||
// getServer is called once per test; without this the prom-client metric
|
||||
// registered inside the metrics middleware collides on the second server.
|
||||
vi.mock('express-prom-bundle')
|
||||
|
||||
// Matches COMPANION_SECRET in test/mockserver.ts defaultEnv.
|
||||
const secret = 'secret'
|
||||
|
||||
const enabledEnv = { COMPANION_ENABLE_DROPBOX_TOKENS_ENDPOINT: 'true' }
|
||||
const disabledEnv = { COMPANION_ENABLE_DROPBOX_TOKENS_ENDPOINT: 'false' }
|
||||
|
||||
describe('GET /:providerName/bridge-tokens', () => {
|
||||
test('returns the dropbox tokens, marked no-store, for a valid token', async () => {
|
||||
const token = tokenService.generateEncryptedAuthToken(
|
||||
{ dropbox: { accessToken: 'acc-123', refreshToken: 'ref-456' } },
|
||||
secret,
|
||||
)
|
||||
return request(await getServer(enabledEnv))
|
||||
.get('/dropbox/bridge-tokens')
|
||||
.set('uppy-auth-token', token)
|
||||
.expect('Cache-Control', 'no-store')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).toEqual({
|
||||
accessToken: 'acc-123',
|
||||
refreshToken: 'ref-456',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('responds 404 when the Dropbox session has no access token', async () => {
|
||||
// The auth token decrypts fine, but holds no usable Dropbox access token.
|
||||
const token = tokenService.generateEncryptedAuthToken(
|
||||
{ dropbox: {} },
|
||||
secret,
|
||||
)
|
||||
return request(await getServer(enabledEnv))
|
||||
.get('/dropbox/bridge-tokens')
|
||||
.set('uppy-auth-token', token)
|
||||
.expect(404)
|
||||
})
|
||||
|
||||
test('rejects non-dropbox providers with 400', async () => {
|
||||
const token = tokenService.generateEncryptedAuthToken(
|
||||
{ drive: { accessToken: 'acc-123' } },
|
||||
secret,
|
||||
)
|
||||
return request(await getServer(enabledEnv))
|
||||
.get('/drive/bridge-tokens')
|
||||
.set('uppy-auth-token', token)
|
||||
.expect(400)
|
||||
})
|
||||
|
||||
test('responds 401 when no auth token is provided', async () => {
|
||||
return request(await getServer(enabledEnv))
|
||||
.get('/dropbox/bridge-tokens')
|
||||
.expect(401)
|
||||
})
|
||||
|
||||
test('responds 404 when the option is disabled', async () => {
|
||||
return request(await getServer(disabledEnv))
|
||||
.get('/dropbox/bridge-tokens')
|
||||
.set('uppy-auth-token', 'whatever')
|
||||
.expect(404)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue