diff --git a/.env.example b/.env.example
index c00e1b2a9..364b7e3e0 100644
--- a/.env.example
+++ b/.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
diff --git a/.github/workflows/companion-deploy.yml b/.github/workflows/companion-deploy.yml
index 6b476f127..ddf98e2cb 100644
--- a/.github/workflows/companion-deploy.yml
+++ b/.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: .
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 82a25e934..a82cb4103 100644
--- a/.github/workflows/release.yml
+++ b/.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: .
diff --git a/README.md b/README.md
index 097e68652..fe9c81df9 100644
--- a/README.md
+++ b/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/)**
-
+
+
+
+
+
+
+
+
+
Uppy is being developed by the folks at [Transloadit](https://transloadit.com),
a versatile API to handle any file in your app.
-| Tests |  |  |  |
-| Deploys |  |  |  |
+| Tests |  |  |  |
+| Deploys |  |  |  |
## Example
diff --git a/docs/companion.md b/docs/companion.md
index 98344876b..22b112079 100644
--- a/docs/companion.md
+++ b/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.
diff --git a/docs/framework-integrations/angular.mdx b/docs/framework-integrations/angular.mdx
index e2f9ec3fb..6c6b74765 100644
--- a/docs/framework-integrations/angular.mdx
+++ b/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
diff --git a/e2e/start-companion-with-load-balancer.mjs b/e2e/start-companion-with-load-balancer.mjs
index d228df880..697c2865c 100755
--- a/e2e/start-companion-with-load-balancer.mjs
+++ b/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:
diff --git a/examples/aws-companion/server.cjs b/examples/aws-companion/server.cjs
index c64f9830f..d1a92dfeb 100644
--- a/examples/aws-companion/server.cjs
+++ b/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,
diff --git a/examples/aws-nodejs/index.js b/examples/aws-nodejs/index.js
index c0bf8744c..97accb384 100644
--- a/examples/aws-nodejs/index.js
+++ b/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
}
diff --git a/examples/digitalocean-spaces/server.cjs b/examples/digitalocean-spaces/server.cjs
index f98219648..5cbeefc25 100644
--- a/examples/digitalocean-spaces/server.cjs
+++ b/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,
diff --git a/packages/@uppy/companion-client/src/Provider.ts b/packages/@uppy/companion-client/src/Provider.ts
index b8e12dc27..2c9bc7b93 100644
--- a/packages/@uppy/companion-client/src/Provider.ts
+++ b/packages/@uppy/companion-client/src/Provider.ts
@@ -166,6 +166,7 @@ export default class Provider
}): 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 }),
})
diff --git a/packages/@uppy/companion/env_example b/packages/@uppy/companion/env_example
index 89e7b2298..08b3e0acf 100644
--- a/packages/@uppy/companion/env_example
+++ b/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=
diff --git a/packages/@uppy/companion/src/server/controllers/connect.js b/packages/@uppy/companion/src/server/controllers/connect.js
index b5d3b9c79..772f80fec 100644
--- a/packages/@uppy/companion/src/server/controllers/connect.js
+++ b/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) {
diff --git a/packages/@uppy/companion/src/server/s3-client.js b/packages/@uppy/companion/src/server/s3-client.js
index d5b5c48be..74717d238 100644
--- a/packages/@uppy/companion/src/server/s3-client.js
+++ b/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) {
diff --git a/packages/@uppy/companion/src/standalone/helper.js b/packages/@uppy/companion/src/standalone/helper.js
index 76662d1fc..9f85d51be 100644
--- a/packages/@uppy/companion/src/standalone/helper.js
+++ b/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,
}
}
diff --git a/packages/@uppy/companion/test/__tests__/uploader.js b/packages/@uppy/companion/test/__tests__/uploader.js
index 3e6d1398a..8cf3c2c93 100644
--- a/packages/@uppy/companion/test/__tests__/uploader.js
+++ b/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 = {}
diff --git a/packages/@uppy/companion/test/mockserver.js b/packages/@uppy/companion/test/mockserver.js
index b236e6505..6afe9cd11 100644
--- a/packages/@uppy/companion/test/mockserver.js
+++ b/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) {
diff --git a/packages/@uppy/core/src/Uppy.test.ts b/packages/@uppy/core/src/Uppy.test.ts
index 25f373611..c812cb450 100644
--- a/packages/@uppy/core/src/Uppy.test.ts
+++ b/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({
diff --git a/packages/@uppy/drag-drop/README.md b/packages/@uppy/drag-drop/README.md
index fa6619552..7566041fc 100644
--- a/packages/@uppy/drag-drop/README.md
+++ b/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