Quellcode durchsuchen

companion,companion-client: send uppy-versions header to companion

Ifedapo Olarewaju vor 5 Jahren
Ursprung
Commit
d77180adeb

Datei-Diff unterdrückt, da er zu groß ist
+ 121 - 121
package-lock.json


+ 49 - 5
packages/@uppy/companion-client/src/RequestClient.js

@@ -14,6 +14,8 @@ module.exports = class RequestClient {
     this.uppy = uppy
     this.opts = opts
     this.onReceiveResponse = this.onReceiveResponse.bind(this)
+    this.allowedHeaders = []
+    this.preflightDone = false
   }
 
   get hostname () {
@@ -25,12 +27,15 @@ module.exports = class RequestClient {
   get defaultHeaders () {
     return {
       'Accept': 'application/json',
-      'Content-Type': 'application/json'
+      'Content-Type': 'application/json',
+      'Uppy-Versions': '@uppy/companion-client=1.0.3'
     }
   }
 
   headers () {
-    return Promise.resolve(Object.assign({}, this.defaultHeaders, this.opts.serverHeaders || {}))
+    return Promise.resolve(
+      Object.assign({}, this.defaultHeaders, this.opts.serverHeaders || {})
+    )
   }
 
   _getPostResponseFunc (skip) {
@@ -77,9 +82,48 @@ module.exports = class RequestClient {
     return res.json()
   }
 
+  preflight (path) {
+    return new Promise((resolve, reject) => {
+      if (this.preflightDone) {
+        return resolve(this.allowedHeaders.slice())
+      }
+
+      fetch(this._getUrl(path), {
+        method: 'OPTIONS'
+      })
+        .then((response) => {
+          if (response.headers.has('access-control-allow-headers')) {
+            const allowedHeaders = response.headers.get('access-control-allow-headers')
+              .split(',').map((headerName) => headerName.trim().toLowerCase())
+            this.allowedHeaders = this.allowedHeaders.concat(allowedHeaders)
+          }
+          this.preflightDone = true
+          resolve(this.allowedHeaders.slice())
+        })
+        .catch(reject)
+    })
+  }
+
+  preflightAndHeaders (path) {
+    return new Promise((resolve, reject) => {
+      this.preflight(path).then((allowedHeaders) => {
+        this.headers().then((headers) => {
+          // filter to keep only allowed Headers
+          Object.keys(headers).forEach((header) => {
+            if (allowedHeaders.indexOf(header.toLowerCase()) === -1) {
+              delete headers[header]
+            }
+          })
+
+          resolve(headers)
+        })
+      }).catch(reject)
+    })
+  }
+
   get (path, skipPostResponse) {
     return new Promise((resolve, reject) => {
-      this.headers().then((headers) => {
+      this.preflightAndHeaders(path).then((headers) => {
         fetch(this._getUrl(path), {
           method: 'get',
           headers: headers,
@@ -97,7 +141,7 @@ module.exports = class RequestClient {
 
   post (path, data, skipPostResponse) {
     return new Promise((resolve, reject) => {
-      this.headers().then((headers) => {
+      this.preflightAndHeaders(path).then((headers) => {
         fetch(this._getUrl(path), {
           method: 'post',
           headers: headers,
@@ -116,7 +160,7 @@ module.exports = class RequestClient {
 
   delete (path, data, skipPostResponse) {
     return new Promise((resolve, reject) => {
-      this.headers().then((headers) => {
+      this.preflightAndHeaders(path).then((headers) => {
         fetch(`${this.hostname}/${path}`, {
           method: 'delete',
           headers: headers,

+ 5 - 0
packages/@uppy/companion/package-lock.json

@@ -52,6 +52,11 @@
 			"requires": {
 				"has-flag": "^3.0.0"
 			}
+		},
+		"semver": {
+			"version": "6.1.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz",
+			"integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ=="
 		}
 	}
 }

+ 1 - 0
packages/@uppy/companion/package.json

@@ -56,6 +56,7 @@
     "purest": "3.0.0",
     "redis": "2.8.0",
     "request": "2.85.0",
+    "semver": "6.1.1",
     "serialize-error": "^2.1.0",
     "tus-js-client": "^1.8.0-0",
     "uuid": "2.0.2",

+ 3 - 2
packages/@uppy/companion/src/server/controllers/send-token.js

@@ -6,6 +6,7 @@ const tokenService = require('../helpers/jwt')
 const parseUrl = require('url').parse // eslint-disable-line node/no-deprecated-api
 const { hasMatch, sanitizeHtml } = require('../helpers/utils')
 const oAuthState = require('../helpers/oauth-state')
+const versionCmp = require('../helpers/version')
 
 /**
  *
@@ -29,8 +30,8 @@ module.exports = function sendToken (req, res, next) {
     const allowedClients = req.uppy.options.clients
     // if no preset clients then allow any client
     if (!allowedClients || hasMatch(origin, allowedClients) || hasMatch(parseUrl(origin).host, allowedClients)) {
-      // @todo do a more secure client version check, see https://www.npmjs.com/package/semver
-      return res.send(clientVersion ? htmlContent(uppyAuthToken, origin) : oldHtmlContent(uppyAuthToken, origin))
+      const allowsStringMessage = versionCmp.gte(clientVersion, '1.0.2')
+      return res.send(allowsStringMessage ? htmlContent(uppyAuthToken, origin) : oldHtmlContent(uppyAuthToken, origin))
     }
   }
   next()

+ 14 - 0
packages/@uppy/companion/src/server/helpers/version.js

@@ -0,0 +1,14 @@
+const semver = require('semver')
+
+/**
+ * checks if a version is greater than or equal to
+ * @param {string} v1 the LHS version
+ * @param {string} v2 the RHS version
+ * @returns {boolean}
+ */
+exports.gte = (v1, v2) => {
+  v1 = semver.coerce(v1).version
+  v2 = semver.coerce(v2).version
+
+  return semver.gte(v1, v2)
+}

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

@@ -21,6 +21,7 @@ const promInterval = collectDefaultMetrics({ register: promClient.register, time
 
 // Add version as a prometheus gauge
 const versionGauge = new promClient.Gauge({ name: 'companion_version', help: 'npm version as an integer' })
+// @ts-ignore
 const numberVersion = version.replace(/\D/g, '') * 1
 versionGauge.set(numberVersion)
 

+ 25 - 10
packages/@uppy/companion/src/uppy.js

@@ -63,19 +63,34 @@ module.exports.app = (options = {}) => {
   app.use((req, res, next) => {
     res.header(
       'Access-Control-Allow-Headers',
-      [res.get('Access-Control-Allow-Headers'), 'uppy-auth-token', 'uppy-client'].join(', ')
+      [
+        'uppy-auth-token',
+        'uppy-versions',
+        res.get('Access-Control-Allow-Headers')
+      ].join(',')
     )
-    next()
-  })
-  if (options.sendSelfEndpoint) {
-    app.use('*', (req, res, next) => {
+
+    const exposedHeaders = [
+      // exposed so it can be accessed for our custom uppy preflight
+      'Access-Control-Allow-Headers'
+    ]
+
+    if (options.sendSelfEndpoint) {
+      // add it to the exposed headers.
+      exposedHeaders.push('i-am')
+
       const { protocol } = options.server
       res.header('i-am', `${protocol}://${options.sendSelfEndpoint}`)
-      // add it to the exposed custom headers.
-      res.header('Access-Control-Expose-Headers', [res.get('Access-Control-Expose-Headers'), 'i-am'].join(', '))
-      next()
-    })
-  }
+    }
+
+    if (res.get('Access-Control-Expose-Headers')) {
+      // if the header had been previously set, the values should be added too
+      exposedHeaders.push(res.get('Access-Control-Expose-Headers'))
+    }
+
+    res.header('Access-Control-Expose-Headers', exposedHeaders.join(','))
+    next()
+  })
 
   // add uppy options to the request object so it can be accessed by subsequent handlers.
   app.use('*', getOptionsMiddleware(options))

+ 39 - 4
packages/@uppy/companion/test/__tests__/companion.js

@@ -6,8 +6,21 @@ jest.mock('../../src/server/helpers/oauth-state', () => {
   return {
     generateState: () => 'some-cool-nice-encrytpion',
     addToState: () => 'some-cool-nice-encrytpion',
-    getFromState: (state) => {
-      return state === 'state-with-invalid-instance-url' ? 'http://localhost:3452' : 'http://localhost:3020'
+    getFromState: (state, key) => {
+      console.log('mummy', state, key)
+      if (state === 'state-with-invalid-instance-url') {
+        return 'http://localhost:3452'
+      }
+
+      if (state === 'state-with-older-version' && key === 'clientVersion') {
+        return '@uppy/companion-client:1.0.1'
+      }
+
+      if (state === 'state-with-newer-version' && key === 'clientVersion') {
+        return '@uppy/companion-client:1.0.3'
+      }
+
+      return 'http://localhost:3020'
     }
   }
 })
@@ -76,13 +89,13 @@ describe('test authentication', () => {
   })
 
   test('the token gets sent via cookie and html', () => {
+    // see mock ../../src/server/helpers/oauth-state above for state values
     return request(authServer)
-      .get(`/drive/send-token?uppyAuthToken=${token}`)
+      .get(`/drive/send-token?uppyAuthToken=${token}&state=state-with-newer-version`)
       .expect(200)
       .expect((res) => {
         const authToken = res.header['set-cookie'][0].split(';')[0].split('uppyAuthToken--google=')[1]
         expect(authToken).toEqual(token)
-        // see mock ../../src/server/helpers/oauth-state above for http://localhost:3020
         const body = `
     <!DOCTYPE html>
     <html>
@@ -99,6 +112,28 @@ describe('test authentication', () => {
       })
   })
 
+  test('the token gets sent to html based on version', () => {
+    // see mock ../../src/server/helpers/oauth-state above for state values
+    return request(authServer)
+      .get(`/drive/send-token?uppyAuthToken=${token}&state=state-with-older-version`)
+      .expect(200)
+      .expect((res) => {
+        const body = `
+    <!DOCTYPE html>
+    <html>
+    <head>
+        <meta charset="utf-8" />
+        <script>
+          window.opener.postMessage({token: "${token}"}, "http://localhost:3020")
+          window.close()
+        </script>
+    </head>
+    <body></body>
+    </html>`
+        expect(res.text).toBe(body)
+      })
+  })
+
   test('logout provider', () => {
     return request(authServer)
       .get('/drive/logout/')

+ 1 - 1
packages/@uppy/companion/test/mockserver.js

@@ -13,7 +13,7 @@ authServer.all('*/callback', (req, res, next) => {
 })
 authServer.all('/drive/send-token', (req, res, next) => {
   req.session.grant = {
-    state: 'non-empty-value' }
+    state: req.query.state || 'non-empty-value' }
   next()
 })
 

+ 3 - 1
packages/@uppy/companion/tsconfig.json

@@ -7,7 +7,9 @@
     "sourceMap": false,
     "allowJs": true,
     "checkJs": true,
-    "noEmitOnError": true
+    "noEmitOnError": true,
+    "resolveJsonModule": true,
+    "esModuleInterop": true
   },
   "include": [
     "src/**/*"

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.