ソースを参照

@uppy/companion: add `oauthOrigin` option (#5297)

Co-authored-by: Mikael Finstad <finstaden@gmail.com>
Antoine du Hamel 9 ヶ月 前
コミット
e849827824

+ 1 - 0
.env.example

@@ -11,6 +11,7 @@ COMPANION_PORT=3020
 COMPANION_CLIENT_ORIGINS=
 COMPANION_SECRET=development
 COMPANION_PREAUTH_SECRET=development2
+COMPANION_OAUTH_ORIGIN=*
 
 # NOTE: Only enable this in development. Enabling it in production is a security risk
 COMPANION_ALLOW_LOCAL_URLS=true

+ 15 - 0
docs/companion.md

@@ -343,6 +343,21 @@ which has only the secret, nothing else.
 
 :::
 
+### `oauthOrigin` `COMPANION_OAUTH_ORIGIN`
+
+:::caution
+
+Setting this option is strongly recommended. If left unset (or set to `'*'`),
+your app could be impersonated.
+
+:::
+
+An [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) specifying
+allowed origins, or an array of origins (comma-separated origins in
+`COMPANION_OAUTH_ORIGIN`). Any browser request from an origin that is not listed
+will not receive OAuth2 tokens, and the OAuth request won’t complete. Set it to
+`'*'` to allow all origins (not recommended). Default: `'*'`.
+
 #### `uploadUrls` `COMPANION_UPLOAD_URLS`
 
 An allowlist (array) of strings (exact URLs) or regular expressions. Companion

+ 1 - 0
e2e/start-companion-with-load-balancer.mjs

@@ -66,6 +66,7 @@ const startCompanion = ({ name, port }) => {
       COMPANION_PREAUTH_SECRET: 'development', // multi instance will not work without secret set
       COMPANION_ALLOW_LOCAL_URLS: 'true',
       COMPANION_LOGGER_PROCESS_NAME: name,
+      COMPANION_OAUTH_ORIGIN: '*',
     },
   })
   // Adding a `then` property so the return value is awaitable:

+ 1 - 0
packages/@uppy/companion-client/src/Provider.ts

@@ -171,6 +171,7 @@ export default class Provider<M extends Meta, B extends Body>
   }): string {
     const params = new URLSearchParams({
       ...query,
+      // This is only used for Companion instances configured to accept multiple origins.
       state: btoa(JSON.stringify({ origin: getOrigin() })),
       ...this.authQuery({ authFormData }),
     })

+ 16 - 3
packages/@uppy/companion/src/server/controllers/connect.js

@@ -13,12 +13,25 @@ const queryString = (params, prefix = '?') => {
  * @param {object} res
  */
 module.exports = function connect(req, res) {
-  const { secret } = req.companion.options
+  const { secret, oauthOrigin } = req.companion.options
   const stateObj = oAuthState.generateState()
 
-  if (req.query.state) {
+  // not sure if we need to store origin in the session state (e.g. we could've just gotten it directly inside send-token)
+  // but we're afraid to change the logic there
+  if (oauthOrigin && !Array.isArray(oauthOrigin)) {
+    // If the server only allows a single origin, we ignore the client-supplied
+    // origin from query because we don't need it.
+    stateObj.origin = oauthOrigin
+  } else if (oauthOrigin && oauthOrigin.length < 2) {
+    // eslint-disable-next-line prefer-destructuring
+    stateObj.origin = oauthOrigin[0]
+  } else {
+    // If we have multiple allowed origins, we need to check the client-supplied origin from query.
+    // If the client provides an untrusted origin,
+    // we want to send `undefined`. `undefined` means `/`, which is the same origin when passed to `postMessage`.
+    // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-window-postmessage-options-dev
     const { origin } = JSON.parse(atob(req.query.state))
-    stateObj.origin = origin
+    stateObj.origin = oauthOrigin ? oauthOrigin.find(o => o === origin) : origin
   }
 
   if (req.companion.options.server.oauthDomain) {

+ 3 - 0
packages/@uppy/companion/src/standalone/helper.js

@@ -183,6 +183,9 @@ const getConfigFromEnv = () => {
     corsOrigins: getCorsOrigins(),
     testDynamicOauthCredentials: process.env.COMPANION_TEST_DYNAMIC_OAUTH_CREDENTIALS === 'true',
     testDynamicOauthCredentialsSecret: process.env.COMPANION_TEST_DYNAMIC_OAUTH_CREDENTIALS_SECRET,
+    oauthOrigin: process.env.COMPANION_OAUTH_ORIGIN?.includes(',') ?
+      process.env.COMPANION_OAUTH_ORIGIN.split(',') :
+      process.env.COMPANION_OAUTH_ORIGIN,
   }
 }
 

+ 1 - 0
packages/@uppy/companion/test/__tests__/uploader.js

@@ -18,6 +18,7 @@ afterAll(() => {
 
 process.env.COMPANION_DATADIR = './test/output'
 process.env.COMPANION_DOMAIN = 'localhost:3020'
+process.env.COMPANION_OAUTH_ORIGIN = '*'
 const { companionOptions } = standalone()
 
 describe('uploader with tus protocol', () => {

+ 2 - 0
packages/@uppy/companion/test/mockserver.js

@@ -40,6 +40,8 @@ const defaultEnv = {
   COMPANION_PERIODIC_PING_URLS: '',
 
   COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '',
+
+  COMPANION_OAUTH_ORIGIN: '*',
 }
 
 function updateEnv (env) {