Просмотр исходного кода

Merge pull request #937 from transloadit/broader-server-pattern

refactor: allow Array, RegExp, and String for serverPattern
Ifedapo .A. Olarewaju 6 лет назад
Родитель
Сommit
c4f7d33c69

+ 1 - 12
packages/@uppy/dropbox/src/index.js

@@ -7,8 +7,8 @@ const { h } = require('preact')
 module.exports = class Dropbox extends Plugin {
   constructor (uppy, opts) {
     super(uppy, opts)
-    this.type = 'acquirer'
     this.id = this.opts.id || 'Dropbox'
+    Provider.initPlugin(this, opts)
     this.title = 'Dropbox'
     this.icon = () => (
       <svg class="UppyIcon" width="128" height="118" viewBox="0 0 128 118">
@@ -18,25 +18,14 @@ module.exports = class Dropbox extends Plugin {
       </svg>
     )
 
-    // writing out the key explicitly for readability the key used to store
-    // the provider instance must be equal to this.id.
     this[this.id] = new Provider(uppy, {
       serverUrl: this.opts.serverUrl,
       serverHeaders: this.opts.serverHeaders,
       provider: 'dropbox'
     })
 
-    this.files = []
-
     this.onAuth = this.onAuth.bind(this)
     this.render = this.render.bind(this)
-
-    // set default options
-    const defaultOptions = {}
-
-    // merge default options with the ones set by user
-    this.opts = Object.assign({}, defaultOptions, opts)
-    this.opts.serverPattern = opts.serverPattern || opts.serverUrl
   }
 
   install () {

+ 3 - 1
packages/@uppy/dropbox/types/index.d.ts

@@ -1,7 +1,9 @@
 import { Plugin, PluginOptions, Uppy } from '@uppy/core';
+import { ProviderOptions } from '@uppy/server-utils';
 
-export interface DropboxOptions extends PluginOptions {
+export interface DropboxOptions extends PluginOptions, ProviderOptions {
   serverUrl: string;
+  serverPattern: string | RegExp | Array<string | RegExp>;
 }
 
 export default class Dropbox extends Plugin {

+ 1 - 10
packages/@uppy/google-drive/src/index.js

@@ -6,8 +6,8 @@ const { h } = require('preact')
 module.exports = class GoogleDrive extends Plugin {
   constructor (uppy, opts) {
     super(uppy, opts)
-    this.type = 'acquirer'
     this.id = this.opts.id || 'GoogleDrive'
+    Provider.initPlugin(this, opts)
     this.title = 'Google Drive'
     this.icon = () =>
       <svg aria-hidden="true" class="UppyIcon UppyModalTab-icon" width="28" height="28" viewBox="0 0 16 16">
@@ -21,17 +21,8 @@ module.exports = class GoogleDrive extends Plugin {
       authProvider: 'google'
     })
 
-    this.files = []
-
     this.onAuth = this.onAuth.bind(this)
     this.render = this.render.bind(this)
-
-    // set default options
-    const defaultOptions = {}
-
-    // merge default options with the ones set by user
-    this.opts = Object.assign({}, defaultOptions, opts)
-    this.opts.serverPattern = opts.serverPattern || opts.serverUrl
   }
 
   install () {

+ 3 - 2
packages/@uppy/google-drive/types/index.d.ts

@@ -1,8 +1,9 @@
 import { Plugin, PluginOptions, Uppy } from '@uppy/core';
+import { ProviderOptions } from '@uppy/server-utils';
 
-export interface GoogleDriveOptions extends PluginOptions {
+export interface GoogleDriveOptions extends PluginOptions, ProviderOptions {
   serverUrl: string;
-  // TODO inherit from ProviderOptions
+  serverPattern: string | RegExp | Array<string | RegExp>;
 }
 
 export default class GoogleDrive extends Plugin {

+ 1 - 10
packages/@uppy/instagram/src/index.js

@@ -6,8 +6,8 @@ const { h } = require('preact')
 module.exports = class Instagram extends Plugin {
   constructor (uppy, opts) {
     super(uppy, opts)
-    this.type = 'acquirer'
     this.id = this.opts.id || 'Instagram'
+    Provider.initPlugin(this, opts)
     this.title = 'Instagram'
     this.icon = () => (
       <svg aria-hidden="true" class="UppyIcon" width="28" height="28" viewBox="0 0 512 512">
@@ -24,17 +24,8 @@ module.exports = class Instagram extends Plugin {
       authProvider: 'instagram'
     })
 
-    this.files = []
-
     this.onAuth = this.onAuth.bind(this)
     this.render = this.render.bind(this)
-
-    // set default options
-    const defaultOptions = {}
-
-    // merge default options with the ones set by user
-    this.opts = Object.assign({}, defaultOptions, opts)
-    this.opts.serverPattern = opts.serverPattern || opts.serverUrl
   }
 
   install () {

+ 3 - 2
packages/@uppy/instagram/types/index.d.ts

@@ -1,8 +1,9 @@
 import { Plugin, PluginOptions, Uppy } from '@uppy/core';
+import { ProviderOptions } from '@uppy/server-utils';
 
-export interface InstagramOptions extends PluginOptions {
+export interface InstagramOptions extends PluginOptions, ProviderOptions {
   serverUrl: string;
-  // TODO inherit from ProviderOptions
+  serverPattern: string | RegExp | Array<string | RegExp>;
 }
 
 export default class Instagram extends Plugin {

+ 16 - 3
packages/@uppy/provider-views/src/index.js

@@ -453,10 +453,8 @@ module.exports = class ProviderView {
     const link = `${this.Provider.authUrl()}?state=${authState}`
 
     const authWindow = window.open(link, '_blank')
-    const noProtocol = (url) => url.replace(/^(https?:|)\/\//, '')
     const handleToken = (e) => {
-      const allowedOrigin = new RegExp(noProtocol(this.plugin.opts.serverPattern))
-      if (!allowedOrigin.test(noProtocol(e.origin)) || e.source !== authWindow) {
+      if (!this._isOriginAllowed(e.origin, this.plugin.opts.serverPattern) || e.source !== authWindow) {
         this.plugin.uppy.log(`rejecting event from ${e.origin} vs allowed pattern ${this.plugin.opts.serverPattern}`)
         return
       }
@@ -468,6 +466,21 @@ module.exports = class ProviderView {
     window.addEventListener('message', handleToken)
   }
 
+  _isOriginAllowed (origin, allowedOrigin) {
+    const getRegex = (value) => {
+      if (typeof value === 'string') {
+        return new RegExp(`^${value}$`)
+      } else if (value instanceof RegExp) {
+        return value
+      }
+    }
+
+    const patterns = Array.isArray(allowedOrigin) ? allowedOrigin.map(getRegex) : [getRegex(allowedOrigin)]
+    return patterns
+      .filter((pattern) => pattern !== null)
+      .some((pattern) => pattern.test(origin))
+  }
+
   handleError (error) {
     const uppy = this.plugin.uppy
     const message = uppy.i18n('uppyServerError')

+ 23 - 0
packages/@uppy/server-utils/src/Provider.js

@@ -52,4 +52,27 @@ module.exports = class Provider extends RequestClient {
         return res
       })
   }
+
+  static initPlugin (plugin, opts, defaultOpts) {
+    plugin.type = 'acquirer'
+    plugin.files = []
+    if (defaultOpts) {
+      plugin.opts = Object.assign({}, defaultOpts, opts)
+    }
+    if (opts.serverPattern) {
+      const pattern = opts.serverPattern
+      // validate serverPattern param
+      if (typeof pattern !== 'string' && !Array.isArray(pattern) && !(pattern instanceof RegExp)) {
+        throw new TypeError(`${plugin.id}: the option "serverPattern" must be one of string, Array, RegExp`)
+      }
+      plugin.opts.serverPattern = pattern
+    } else {
+      // does not start with https://
+      if (/^(?!https?:\/\/).*$/.test(opts.serverUrl)) {
+        plugin.opts.serverPattern = `${location.protocol}//${opts.serverUrl.replace(/^\/\//, '')}`
+      } else {
+        plugin.opts.serverPattern = opts.serverUrl
+      }
+    }
+  }
 }

+ 2 - 1
packages/@uppy/server-utils/types/index.d.ts

@@ -1,4 +1,4 @@
-import { Uppy } from '@uppy/core';
+import { Uppy, Plugin } from '@uppy/core';
 
 export interface RequestClientOptions {
   serverUrl: string;
@@ -25,6 +25,7 @@ export class Provider extends RequestClient {
   fileUrl (id: string): string;
   list (directory: string): Promise<any>;
   logout (redirect?: string): Promise<any>;
+  static initPlugin(plugin: Plugin, opts: object, defaultOpts?: object): void;
 }
 
 export interface SocketOptions {

+ 13 - 1
website/src/docs/dropbox.md

@@ -53,7 +53,19 @@ DOM element, CSS selector, or plugin to mount the Dropbox provider into. This sh
 
 ### `serverUrl: null`
 
-URL to an Uppy Server instance.
+URL to an [Uppy Server](/docs/server) instance.
+
+### `serverHeaders: {}`
+
+Custom headers that should be sent along to [Uppy Server](/docs/server) on every request.
+
+### `serverPattern: serverUrl`
+
+The valid and authorised URL(s) from which OAuth responses should be accepted.
+
+This value can be a `String`, a `Regex` pattern, or an `Array` of both.
+
+This is useful when you have your [Uppy Server](/docs/server) running on multiple hosts. Otherwise the default value should do just fine.
 
 ### `locale: {}`
 

+ 13 - 1
website/src/docs/google-drive.md

@@ -53,7 +53,19 @@ DOM element, CSS selector, or plugin to mount the Google Drive provider into. Th
 
 ### `serverUrl: null`
 
-URL to an Uppy Server instance.
+URL to an [Uppy Server](/docs/server) instance.
+
+### `serverHeaders: {}`
+
+Custom headers that should be sent along to [Uppy Server](/docs/server) on every request.
+
+### `serverPattern: serverUrl`
+
+The valid and authorised URL(s) from which OAuth responses should be accepted.
+
+This value can be a `String`, a `Regex` pattern, or an `Array` of both.
+
+This is useful when you have your [Uppy Server](/docs/server) running on multiple hosts. Otherwise the default value should be good enough.
 
 ### `locale: {}`
 

+ 13 - 1
website/src/docs/instagram.md

@@ -55,7 +55,19 @@ DOM element, CSS selector, or plugin to mount the Instagram provider into. This
 
 ### `serverUrl: null`
 
-URL to an Uppy Server instance.
+URL to an [Uppy Server](/docs/server) instance.
+
+### `serverHeaders: {}`
+
+Custom headers that should be sent along to [Uppy Server](/docs/server) on every request.
+
+### `serverPattern: serverUrl`
+
+The valid and authorised URL(s) from which OAuth responses should be accepted.
+
+This value can be a `String`, a `Regex` pattern, or an `Array` of both.
+
+This is useful when you have your [Uppy Server](/docs/server) running on multiple hosts. Otherwise the default value should be good enough.
 
 ### `locale: {}`