Parcourir la source

Default allow headers (#3167)

* refactor cors middleware to avoid duplicates

* Make more ACAH headers default

Add the following headers to access-control-allow-headers by default:
Authorization, Origin, Content-Type, Accept
They are needed for basic operation. See https://github.com/transloadit/uppy/issues/3021
therefore also remove custom middlewares in examples and standalone

* Update outdated readme for S3

AWS now requires JSON instead of XML format

* Update packages/@uppy/companion/src/server/middlewares.js

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>

* Update packages/@uppy/companion/src/server/middlewares.js

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>

* fix review comments

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
Mikael Finstad il y a 3 ans
Parent
commit
12705e769f

+ 0 - 12
examples/custom-provider/server/index.js

@@ -14,18 +14,6 @@ app.use(session({
   saveUninitialized: true,
 }))
 
-app.use((req, res, next) => {
-  res.setHeader(
-    'Access-Control-Allow-Methods',
-    'GET, POST, OPTIONS, PUT, PATCH, DELETE'
-  )
-  res.setHeader(
-    'Access-Control-Allow-Headers',
-    'Authorization, Origin, Content-Type, Accept'
-  )
-  next()
-})
-
 // Routes
 app.get('/', (req, res) => {
   res.setHeader('Content-Type', 'text/plain')

+ 0 - 8
examples/uppy-with-companion/server/index.js

@@ -14,14 +14,6 @@ app.use(session({
 
 app.use((req, res, next) => {
   res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*')
-  res.setHeader(
-    'Access-Control-Allow-Methods',
-    'GET, POST, OPTIONS, PUT, PATCH, DELETE'
-  )
-  res.setHeader(
-    'Access-Control-Allow-Headers',
-    'Authorization, Origin, Content-Type, Accept'
-  )
   next()
 })
 

+ 30 - 16
packages/@uppy/companion/src/server/middlewares.js

@@ -79,25 +79,39 @@ exports.loadSearchProviderToken = (req, res, next) => {
 }
 
 exports.cors = (options = {}) => (req, res, next) => {
-  const exposedHeaders = [
-    // exposed so it can be accessed for our custom uppy client preflight
-    'Access-Control-Allow-Headers',
-  ]
-  if (options.sendSelfEndpoint) exposedHeaders.push('i-am')
-  if (res.get('Access-Control-Expose-Headers')) exposedHeaders.push(res.get('Access-Control-Expose-Headers'))
+  // HTTP headers are not case sensitive, and express always handles them in lower case, so that's why we lower case them.
+  // I believe that HTTP verbs are case sensitive, and should be uppercase.
+
+  // TODO: Move to optional chaining when we drop Node.js v12.x support
+  const existingExposeHeaders = res.get('Access-Control-Expose-Headers')
+  const exposeHeadersSet = new Set(existingExposeHeaders && existingExposeHeaders.split(',').map(method => method.trim().toLowerCase()))
 
+  // exposed so it can be accessed for our custom uppy client preflight
+  exposeHeadersSet.add('access-control-allow-headers')
+  if (options.sendSelfEndpoint) exposeHeadersSet.add('i-am')
+
+  // Needed for basic operation: https://github.com/transloadit/uppy/issues/3021
   const allowedHeaders = [
     'uppy-auth-token',
     'uppy-versions',
     'uppy-credentials-params',
+    'authorization',
+    'origin',
+    'content-type',
+    'accept',
   ]
-  if (res.get('Access-Control-Allow-Headers')) allowedHeaders.push(res.get('Access-Control-Allow-Headers'))
-
-  // TODO: Move to optional chaining when we drop Node.js v12.x support
-  const ACAMHeader = res.get('Access-Control-Allow-Methods')
-  const existingAllowMethodsHeader = new Set(ACAMHeader && ACAMHeader.split(',').map(method => method.trim().toUpperCase()))
-  existingAllowMethodsHeader.add('GET').add('POST').add('OPTIONS').add('DELETE')
-  const methods = Array.from(existingAllowMethodsHeader)
+  const existingAllowHeaders = res.get('Access-Control-Allow-Headers')
+  const allowHeadersSet = new Set(existingAllowHeaders
+    ? existingAllowHeaders
+      .split(',')
+      .map((method) => method.trim().toLowerCase())
+      .concat(allowedHeaders)
+    : allowedHeaders)
+
+  const existingAllowMethods = res.get('Access-Control-Allow-Methods')
+  const allowMethodsSet = new Set(existingAllowMethods && existingAllowMethods.split(',').map(method => method.trim().toUpperCase()))
+  // Needed for basic operation:
+  allowMethodsSet.add('GET').add('POST').add('OPTIONS').add('DELETE')
 
   // If endpoint urls are specified, then we only allow those endpoints.
   // Otherwise, we allow any client url to access companion.
@@ -110,9 +124,9 @@ exports.cors = (options = {}) => (req, res, next) => {
   return cors({
     credentials: true,
     origin,
-    methods,
-    allowedHeaders: allowedHeaders.join(','),
-    exposedHeaders: exposedHeaders.join(','),
+    methods: Array.from(allowMethodsSet),
+    allowedHeaders: Array.from(allowHeadersSet).join(','),
+    exposedHeaders: Array.from(exposeHeadersSet).join(','),
   })(req, res, next)
 }
 

+ 0 - 8
packages/@uppy/companion/src/standalone/index.js

@@ -133,14 +133,6 @@ module.exports = function server (inputCompanionOptions = {}) {
 
   app.use(session(sessionOptions))
 
-  app.use((req, res, next) => {
-    res.setHeader(
-      'Access-Control-Allow-Headers',
-      'Authorization, Origin, Content-Type, Accept'
-    )
-    next()
-  })
-
   // Routes
   if (process.env.COMPANION_HIDE_WELCOME !== 'true') {
     app.get('/', (req, res) => {

+ 8 - 8
packages/@uppy/companion/test/__tests__/cors.js

@@ -41,8 +41,8 @@ describe('cors', () => {
       ['Vary', 'Origin'],
       ['Access-Control-Allow-Credentials', 'true'],
       ['Access-Control-Allow-Methods', 'PATCH,OPTIONS,POST,GET,DELETE'],
-      ['Access-Control-Allow-Headers', 'uppy-auth-token,uppy-versions,uppy-credentials-params,test-allow-header'],
-      ['Access-Control-Expose-Headers', 'Access-Control-Allow-Headers,i-am,test'],
+      ['Access-Control-Allow-Headers', 'test-allow-header,uppy-auth-token,uppy-versions,uppy-credentials-params,authorization,origin,content-type,accept'],
+      ['Access-Control-Expose-Headers', 'test,access-control-allow-headers,i-am'],
       ['Content-Length', '0'],
     ])
     // expect(next).toHaveBeenCalled()
@@ -55,8 +55,8 @@ describe('cors', () => {
       ['Vary', 'Origin'],
       ['Access-Control-Allow-Credentials', 'true'],
       ['Access-Control-Allow-Methods', 'GET,POST,OPTIONS,DELETE'],
-      ['Access-Control-Allow-Headers', 'uppy-auth-token,uppy-versions,uppy-credentials-params'],
-      ['Access-Control-Expose-Headers', 'Access-Control-Allow-Headers'],
+      ['Access-Control-Allow-Headers', 'uppy-auth-token,uppy-versions,uppy-credentials-params,authorization,origin,content-type,accept'],
+      ['Access-Control-Expose-Headers', 'access-control-allow-headers'],
       ['Content-Length', '0'],
     ])
   })
@@ -72,8 +72,8 @@ describe('cors', () => {
       ['Vary', 'Origin'],
       ['Access-Control-Allow-Credentials', 'true'],
       ['Access-Control-Allow-Methods', 'GET,POST,OPTIONS,DELETE'],
-      ['Access-Control-Allow-Headers', 'uppy-auth-token,uppy-versions,uppy-credentials-params'],
-      ['Access-Control-Expose-Headers', 'Access-Control-Allow-Headers'],
+      ['Access-Control-Allow-Headers', 'uppy-auth-token,uppy-versions,uppy-credentials-params,authorization,origin,content-type,accept'],
+      ['Access-Control-Expose-Headers', 'access-control-allow-headers'],
       ['Content-Length', '0'],
     ])
   })
@@ -85,8 +85,8 @@ describe('cors', () => {
       ['Vary', 'Origin'],
       ['Access-Control-Allow-Credentials', 'true'],
       ['Access-Control-Allow-Methods', 'GET,POST,OPTIONS,DELETE'],
-      ['Access-Control-Allow-Headers', 'uppy-auth-token,uppy-versions,uppy-credentials-params'],
-      ['Access-Control-Expose-Headers', 'Access-Control-Allow-Headers'],
+      ['Access-Control-Allow-Headers', 'uppy-auth-token,uppy-versions,uppy-credentials-params,authorization,origin,content-type,accept'],
+      ['Access-Control-Expose-Headers', 'access-control-allow-headers'],
       ['Content-Length', '0'],
     ])
   })

+ 24 - 16
website/src/docs/aws-s3-multipart.md

@@ -150,20 +150,28 @@ The default implementation calls out to Companion's S3 signing endpoints.
 
 ## S3 Bucket Configuration
 
-S3 buckets do not allow public uploads by default.  In order to allow Uppy to upload to a bucket directly, its CORS permissions need to be configured.
-
-This process is described in the [AwsS3 documentation](/docs/aws-s3/#S3-Bucket-configuration).
-
-While the Uppy AWS S3 plugin uses `POST` requests while uploading files to an S3 bucket, the AWS S3 Multipart plugin uses `PUT` requests when uploading file parts. Additionally, the `ETag` header must also be whitelisted:
-
-```xml
-<CORSRule>
-  <!-- Change from POST to PUT if you followed the docs for the AWS S3 plugin ... -->
-  <AllowedMethod>PUT</AllowedMethod>
-
-  <!-- ... keep the existing MaxAgeSeconds and AllowedHeader lines and your other stuff ... -->
-
-  <!-- ... and don't forget to add this tag. -->
-  <ExposeHeader>ETag</ExposeHeader>
-</CORSRule>
+S3 buckets do not allow public uploads by default.  In order to allow Uppy to upload to a bucket directly, its CORS permissions need to be configured. This process is described in the [AwsS3 documentation](/docs/aws-s3/#S3-Bucket-configuration).
+
+While the Uppy AWS S3 plugin uses `POST` requests when uploading files to an S3 bucket, the AWS S3 Multipart plugin uses `PUT` requests when uploading file parts. Additionally, the `ETag` header must also be exposed (in the response):
+
+```json
+[
+  {
+    "AllowedOrigins": ["https://my-app.com"],
+    "AllowedMethods": ["GET", "PUT"],
+    "MaxAgeSeconds": 3000,
+    "AllowedHeaders": [
+      "Authorization",
+      "x-amz-date",
+      "x-amz-content-sha256",
+      "content-type"
+    ]
+    "ExposedHeaders": ["ETag"]
+  },
+  {
+    "AllowedOrigins": ["*"],
+    "AllowedMethods": ["GET"],
+    "MaxAgeSeconds": 3000
+  }
+]
 ```

+ 50 - 42
website/src/docs/aws-s3.md

@@ -135,71 +135,79 @@ In order to allow Uppy to upload directly to a bucket, at least its CORS permiss
 
 CORS permissions can be found in the [S3 Management Console](https://console.aws.amazon.com/s3/home).
 Click the bucket that will receive the uploads, then go into the "Permissions" tab and select the "CORS configuration" button.
-An XML document will be shown that contains the CORS configuration.
+A JSON document will be shown that contains the CORS configuration. (AWS used to use XML but now only allow JSON). More information about the [S3 CORS format here](https://docs.amazonaws.cn/en_us/AmazonS3/latest/userguide/ManageCorsUsing.html).
 
 It is good practice to use two CORS rules: one for viewing the uploaded files, and one for uploading files.
 
 Depending on which settings were enabled during bucket creation, AWS S3 may have defined a CORS rule that allows public reading already.
 This rule looks like:
 
-```xml
-<CORSRule>
-  <AllowedOrigin>*</AllowedOrigin>
-  <AllowedMethod>GET</AllowedMethod>
-  <MaxAgeSeconds>3000</MaxAgeSeconds>
-</CORSRule>
+```json
+{
+  "AllowedOrigins": ["*"],
+  "AllowedMethods": ["GET"],
+  "MaxAgeSeconds": 3000
+}
 ```
 
 If uploaded files should be publically viewable, but a rule like this is not present, add it.
 
-A different `<CORSRule>` is necessary to allow uploading.
+A different rule is necessary to allow uploading.
 This rule should come _before_ the existing rule, because S3 only uses the first rule that matches the origin of the request.
 
 At minimum, the domain from which the uploads will happen must be whitelisted, and the definitions from the previous rule must be added:
 
-```xml
-<AllowedOrigin>https://my-app.com</AllowedOrigin>
-<AllowedMethod>GET</AllowedMethod>
-<MaxAgeSeconds>3000</MaxAgeSeconds>
+```json
+{
+  "AllowedOrigins": ["https://my-app.com"],
+  "AllowedMethods": ["GET"],
+  "MaxAgeSeconds": 3000
+}
 ```
 
 When using Companion, which generates a POST policy document, the following permissions must be granted:
 
-```xml
-<AllowedMethod>POST</AllowedMethod>
-<AllowedHeader>Authorization</AllowedHeader>
-<AllowedHeader>x-amz-date</AllowedHeader>
-<AllowedHeader>x-amz-content-sha256</AllowedHeader>
-<AllowedHeader>content-type</AllowedHeader>
+```json
+{
+  "AllowedMethods": ["POST"],
+  "AllowedHeaders": [
+    "Authorization",
+    "x-amz-date",
+    "x-amz-content-sha256",
+    "content-type"
+  ]
+}
 ```
 
 When using a presigned upload URL, the following permissions must be granted:
 
-```xml
-<AllowedMethod>PUT</AllowedMethod>
+```json
+{
+  "AllowedMethods": ["PUT"],
+}
 ```
 
-The final configuration should look something like this:
-
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
-  <CORSRule>
-    <AllowedOrigin>https://my-app.com</AllowedOrigin>
-    <AllowedMethod>GET</AllowedMethod>
-    <AllowedMethod>POST</AllowedMethod>
-    <MaxAgeSeconds>3000</MaxAgeSeconds>
-    <AllowedHeader>Authorization</AllowedHeader>
-    <AllowedHeader>x-amz-date</AllowedHeader>
-    <AllowedHeader>x-amz-content-sha256</AllowedHeader>
-    <AllowedHeader>content-type</AllowedHeader>
-  </CORSRule>
-  <CORSRule>
-    <AllowedOrigin>*</AllowedOrigin>
-    <AllowedMethod>GET</AllowedMethod>
-    <MaxAgeSeconds>3000</MaxAgeSeconds>
-  </CORSRule>
-</CORSConfiguration>
+The final configuration should look something like this (note that it contains two rules in an array `[]`):
+
+```json
+[
+  {
+    "AllowedOrigins": ["https://my-app.com"],
+    "AllowedMethods": ["GET", "POST"],
+    "MaxAgeSeconds": 3000,
+    "AllowedHeaders": [
+      "Authorization",
+      "x-amz-date",
+      "x-amz-content-sha256",
+      "content-type"
+    ]
+  },
+  {
+    "AllowedOrigins": ["*"],
+    "AllowedMethods": ["GET"],
+    "MaxAgeSeconds": 3000
+  }
+]
 ```
 
 Even with these CORS rules in place, you browser might still encounter HTTP status 403 responses with `AccessDenied` in the response body when it tries to `POST` to your bucket. In this case, within the "Permissions" tab of the [S3 Management Console](https://console.aws.amazon.com/s3/home), choose "Public access settings".
@@ -258,7 +266,7 @@ You do not need to configure the region with GCS.
 
 You also need to configure CORS differently. Unlike Amazon, Google does not offer a UI for CORS configurations. Instead, an HTTP API must be used. If you haven't done this already, see [Configuring CORS on a Bucket](https://cloud.google.com/storage/docs/configuring-cors#Configuring-CORS-on-a-Bucket) in the GCS documentation, or follow the steps below to do it using Google's API playground.
 
-GCS has multiple CORS formats, both XML and JSON. Unfortunately, their XML format is different from Amazon's, so we can't simply use the one from the [S3 Bucket configuration](#S3-Bucket-configuration) section. Google appears to favour the JSON format, so we will use that.
+GCS has multiple CORS formats, both XML and JSON. Unfortunately, their formats are different from Amazon's, so we can't simply use the one from the [S3 Bucket configuration](#S3-Bucket-configuration) section. Google appears to favour the JSON format, so we will use that.
 
 #### JSON CORS configuration