Browse Source

Merge branch `main`

Antoine du Hamel 9 months ago
parent
commit
37ad27d5d1

+ 3 - 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
@@ -25,6 +26,8 @@ COMPANION_AWS_REGION="AWS REGION"
 COMPANION_AWS_PREFIX="OPTIONAL PREFIX"
 # to enable S3 Transfer Acceleration (default: false)
 # COMPANION_AWS_USE_ACCELERATE_ENDPOINT="false"
+# to enable S3 path style uploads (default: false), this is useful for localstack support
+# COMPANION_AWS_FORCE_PATH_STYLE="true"
 # to set X-Amz-Expires query param in presigned urls (in seconds, default: 800)
 # COMPANION_AWS_EXPIRES="800"
 # to set a canned ACL for uploaded objects: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl

+ 1 - 1
.github/workflows/companion-deploy.yml

@@ -63,7 +63,7 @@ jobs:
           username: ${{secrets.DOCKER_USERNAME}}
           password: ${{secrets.DOCKER_PASSWORD}}
       - name: Build and push
-        uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6.1.0
+        uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0
         with:
           push: true
           context: .

+ 1 - 1
.github/workflows/release.yml

@@ -161,7 +161,7 @@ jobs:
           username: ${{secrets.DOCKER_USERNAME}}
           password: ${{secrets.DOCKER_PASSWORD}}
       - name: Build and push
-        uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6.1.0
+        uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0
         with:
           push: true
           context: .

+ 11 - 3
README.md

@@ -16,14 +16,22 @@ about more important problems than building a file uploader.
 **[Read the docs](https://uppy.io/docs)** |
 **[Try Uppy](https://uppy.io/examples/dashboard/)**
 
-<a href="https://transloadit.com" target="_blank"><img width="185" src="https://github.com/transloadit/uppy/raw/main/assets/developed-by-transloadit.png"></a>
+<p>
+  <a href="https://transloadit.com" target="_blank">
+    <picture>
+      <source media="(prefers-color-scheme: dark)" srcset="https://github.com/transloadit/uppy/assets/375537/6651e57e-cb57-4336-8745-6473ae68d0bd">
+      <source media="(prefers-color-scheme: light)" srcset="https://github.com/transloadit/uppy/assets/375537/7f14421d-1e37-464e-8203-ade121216c88">
+      <img src="https://github.com/transloadit/uppy/assets/375537/7f14421d-1e37-464e-8203-ade121216c88" alt="Developed by Transloadit">
+    </picture>
+  </a>
+</p>
 
 Uppy is being developed by the folks at [Transloadit](https://transloadit.com),
 a versatile API to handle any file in your app.
 
 <table>
-<tr><th>Tests</th><td><img src="https://github.com/transloadit/uppy/workflows/Tests/badge.svg" alt="CI status for Uppy tests"></td><td><img src="https://github.com/transloadit/uppy/workflows/Companion/badge.svg" alt="CI status for Companion tests"></td><td><img src="https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg" alt="CI status for browser tests"></td></tr>
-<tr><th>Deploys</th><td><img src="https://github.com/transloadit/uppy/workflows/Release/badge.svg" alt="CI status for CDN deployment"></td><td><img src="https://github.com/transloadit/uppy/workflows/Companion%20Deploy/badge.svg" alt="CI status for Companion deployment"></td><td><img src="https://github.com/transloadit/uppy.io/workflows/Deploy%20to%20GitHub%20Pages/badge.svg" alt="CI status for website deployment"></td></tr>
+<tr><th>Tests</th><td><img src="https://github.com/transloadit/uppy/workflows/CI/badge.svg" alt="CI status for Uppy tests"></td><td><img src="https://github.com/transloadit/uppy/workflows/Companion/badge.svg" alt="CI status for Companion tests"></td><td><img src="https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg" alt="CI status for browser tests"></td></tr>
+<tr><th>Deploys</th><td><img src="https://github.com/transloadit/uppy/workflows/Release/badge.svg" alt="CI status for CDN deployment"></td><td><img src="https://github.com/transloadit/uppy/workflows/Companion%20Edge%20Deploy/badge.svg" alt="CI status for Companion deployment"></td><td><img src="https://github.com/transloadit/uppy.io/workflows/Deploy%20to%20GitHub%20Pages/badge.svg" alt="CI status for website deployment"></td></tr>
 </table>
 
 ## Example

+ 21 - 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
@@ -484,6 +499,12 @@ takes one argument which is an object with the following properties:
 - `req`, Express.js `Request` object. Do not use any Companion internals from
   the req object, as these might change in any minor version of Companion.
 
+#### `s3.forcePathStyle` `COMPANION_AWS_FORCE_PATH_STYLE`
+
+This adds support for setting the S3 client’s `forcePathStyle` option. That is
+necessary to use Uppy/Companion alongside localstack in development
+environments. **Default**: `false`.
+
 ##### `s3.region` `COMPANION_AWS_REGION`
 
 The datacenter region where the target bucket is located.

+ 1 - 1
docs/framework-integrations/angular.mdx

@@ -11,7 +11,7 @@ import TabItem from '@theme/TabItem';
 
 ## When should I use it?
 
-When you are using the Angular framework and you wouldl like to use on of the UI
+When you are using the Angular framework and you would like to use one of the UI
 components.
 
 ## Install

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

@@ -67,6 +67,7 @@ const startCompanion = ({ name, port }) => {
       COMPANION_ALLOW_LOCAL_URLS: 'true',
       COMPANION_ENABLE_URL_ENDPOINT: 'true',
       COMPANION_LOGGER_PROCESS_NAME: name,
+      COMPANION_OAUTH_ORIGIN: '*',
     },
   })
   // Adding a `then` property so the return value is awaitable:

+ 1 - 0
examples/aws-companion/server.cjs

@@ -35,6 +35,7 @@ const options = {
     bucket: process.env.COMPANION_AWS_BUCKET,
     region: process.env.COMPANION_AWS_REGION,
     endpoint: process.env.COMPANION_AWS_ENDPOINT,
+    forcePathStyle: process.env.COMPANION_AWS_FORCE_PATH_STYLE === 'true',
   },
   server: { host: 'localhost:3020' },
   filePath: DATA_DIR,

+ 1 - 0
examples/aws-nodejs/index.js

@@ -58,6 +58,7 @@ function getS3Client() {
       accessKeyId: process.env.COMPANION_AWS_KEY,
       secretAccessKey: process.env.COMPANION_AWS_SECRET,
     },
+    forcePathStyle: process.env.COMPANION_AWS_FORCE_PATH_STYLE === 'true',
   })
   return s3Client
 }

+ 2 - 0
examples/digitalocean-spaces/server.cjs

@@ -14,6 +14,7 @@ const companion = require('../../packages/@uppy/companion')
  *   - COMPANION_AWS_KEY - Your access key ID
  *   - COMPANION_AWS_SECRET - Your secret access key
  *   - COMPANION_AWS_BUCKET - Your space's name.
+ *   - COMPANION_AWS_FORCE_PATH_STYLE - Indicates if s3ForcePathStyle should be used rather than subdomain for S3 buckets.
  */
 
 if (!process.env.COMPANION_AWS_REGION) throw new Error('Missing Space region, please set the COMPANION_AWS_REGION environment variable (eg. "COMPANION_AWS_REGION=ams3")')
@@ -43,6 +44,7 @@ const { app: companionApp } = companion.app({
     secret: process.env.COMPANION_AWS_SECRET,
     bucket: process.env.COMPANION_AWS_BUCKET,
     region: process.env.COMPANION_AWS_REGION,
+    forcePathStyle: process.env.COMPANION_AWS_FORCE_PATH_STYLE === 'true',
   },
   server: { host },
   filePath: DATA_DIR,

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

@@ -166,6 +166,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 }),
     })

+ 1 - 0
packages/@uppy/companion/env_example

@@ -39,6 +39,7 @@ COMPANION_AWS_BUCKET=
 COMPANION_AWS_ENDPOINT=
 COMPANION_AWS_REGION=
 COMPANION_AWS_PREFIX=
+COMPANION_AWS_FORCE_PATH_STYLE="false"
 
 COMPANION_ZOOM_KEY=
 COMPANION_ZOOM_SECRET=

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

@@ -12,12 +12,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) {

+ 1 - 0
packages/@uppy/companion/src/server/s3-client.js

@@ -29,6 +29,7 @@ module.exports = (companionOptions, createPresignedPostMode = false) => {
     /** @type {import('@aws-sdk/client-s3').S3ClientConfig} */
     let s3ClientOptions = {
       region: s3.region,
+      forcePathStyle: Boolean(s3.forcePathStyle)
     }
 
     if (s3.useAccelerateEndpoint) {

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

@@ -133,6 +133,7 @@ const getConfigFromEnv = () => {
       process.env.COMPANION_AWS_USE_ACCELERATE_ENDPOINT === 'true',
       expires: parseInt(process.env.COMPANION_AWS_EXPIRES || '800', 10),
       acl: process.env.COMPANION_AWS_ACL,
+      forcePathStyle: process.env.COMPANION_AWS_FORCE_PATH_STYLE === 'true',
     },
     server: {
       host: process.env.COMPANION_DOMAIN,
@@ -182,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

@@ -20,6 +20,7 @@ afterAll(() => {
 
 process.env.COMPANION_DATADIR = './test/output'
 process.env.COMPANION_DOMAIN = 'localhost:3020'
+process.env.COMPANION_OAUTH_ORIGIN = '*'
 const { companionOptions } = standalone()
 
 const mockReq = {}

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

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

+ 51 - 0
packages/@uppy/core/src/Uppy.test.ts

@@ -1947,6 +1947,57 @@ describe('src/Core', () => {
     })
   })
 
+  describe('clearUploadedFiles', () => {
+    it('should reset state to default', () => {
+      const core = new Core()
+      core.addFile({
+        source: 'vi',
+        name: 'foo.jpg',
+        type: 'image/jpeg',
+        data: testImage,
+      })
+      core.addFile({
+        source: 'vi',
+        name: 'foo2.jpg',
+        type: 'image/jpeg',
+        data: testImage,
+      })
+      core.clearUploadedFiles()
+      expect(core.getState()).toMatchObject({
+        totalProgress: 0,
+        allowNewUpload: true,
+        error: null,
+        recoveredState: null,
+        files: {},
+      })
+    })
+
+    it('should throw error if plugin does not allow removing files during an upload', () => {
+      const core = new Core()
+      const newState = {
+        capabilities: {
+          individualCancellation: false,
+          uploadProgress: true,
+          resumableUploads: false,
+        },
+        currentUploads: {
+          upload1: {
+            fileIDs: [
+              'uppy-file1/jpg-1e-image/jpeg',
+              'uppy-file2/jpg-1e-image/jpeg',
+              'uppy-file3/jpg-1e-image/jpeg',
+            ],
+          },
+        },
+      }
+      // @ts-ignore
+      core.setState(newState)
+      expect(() => {
+        core.clearUploadedFiles()
+      }).toThrowError()
+    })
+  })
+
   describe('checkRestrictions', () => {
     it('should enforce the maxNumberOfFiles rule', () => {
       const core = new Core({

+ 2 - 3
packages/@uppy/drag-drop/README.md

@@ -9,8 +9,7 @@
 
 Droppable zone UI for Uppy. Drag and drop files into it to upload.
 
-**[Read the docs](https://uppy.io/docs/dragdrop)** |
-**[Try it](https://uppy.io/examples/dragdrop/)**
+**[Read the docs](https://uppy.io/docs/drag-drop/)**
 
 Uppy is being developed by the folks at [Transloadit](https://transloadit.com),
 a versatile file encoding service.
@@ -41,7 +40,7 @@ Transloadit’s CDN: Edgly. In that case `Uppy` will attach itself to the global
 ## Documentation
 
 Documentation for this plugin can be found on the
-[Uppy website](https://uppy.io/docs/dragdrop).
+[Uppy website](https://uppy.io/docs/drag-drop/).
 
 ## License