@uppy/companion
Version:
OAuth helper and remote fetcher for Uppy's (https://uppy.io) extensible file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Dropbox and Google Drive, S3 and more :dog:
203 lines (184 loc) • 6.06 kB
JavaScript
/**
* @module provider
*/
import { getRedirectPath, getURLBuilder } from '../helpers/utils.js'
import * as logger from '../logger.js'
import box from './box/index.js'
import { getCredentialsResolver } from './credentials.js'
import dropbox from './dropbox/index.js'
import facebook from './facebook/index.js'
import { Drive } from './google/drive/index.js'
import instagram from './instagram/graph/index.js'
import onedrive from './onedrive/index.js'
// biome-ignore lint/correctness/noUnusedImports: It's used as a type
import Provider, { isOAuthProvider } from './Provider.js'
import unsplash from './unsplash/index.js'
import webdav from './webdav/index.js'
import zoom from './zoom/index.js'
/**
*
* @param {{server: object}} options
*/
const validOptions = (options) => {
return options.server.host && options.server.protocol
}
/**
* adds the desired provider module to the request object,
* based on the providerName parameter specified
*
* @param {Record<string, typeof Provider>} providers
*/
export function getProviderMiddleware(providers, grantConfig) {
/**
*
* @param {object} req
* @param {object} res
* @param {Function} next
* @param {string} providerName
*/
const middleware = (req, res, next, providerName) => {
const ProviderClass = providers[providerName]
if (ProviderClass && validOptions(req.companion.options)) {
const { allowLocalUrls, providerOptions } = req.companion.options
const { oauthProvider } = ProviderClass
let providerGrantConfig
if (isOAuthProvider(oauthProvider)) {
req.companion.getProviderCredentials = getCredentialsResolver(
providerName,
req.companion.options,
req,
)
providerGrantConfig = grantConfig[oauthProvider]
req.companion.providerGrantConfig = providerGrantConfig
}
const secret = providerOptions[providerName]?.secret
req.companion.provider = new ProviderClass({
secret,
providerName,
providerGrantConfig,
allowLocalUrls,
})
req.companion.providerName = providerName
req.companion.providerClass = ProviderClass
} else {
logger.warn(
'invalid provider options detected. Provider will not be loaded',
'provider.middleware.invalid',
req.id,
)
}
next()
}
return middleware
}
/**
* @returns {Record<string, typeof Provider>}
*/
export function getDefaultProviders() {
const providers = {
dropbox,
box,
drive: Drive,
facebook,
onedrive,
zoom,
instagram,
unsplash,
webdav,
}
return providers
}
/**
*
* @typedef {{'module': typeof Provider, config: Record<string,unknown>}} CustomProvider
*
* @param {Record<string, CustomProvider>} customProviders
* @param {Record<string, typeof Provider>} providers
* @param {object} grantConfig
*/
export function addCustomProviders(customProviders, providers, grantConfig) {
Object.keys(customProviders).forEach((providerName) => {
const customProvider = customProviders[providerName]
providers[providerName] = customProvider.module
const { oauthProvider } = customProvider.module
if (isOAuthProvider(oauthProvider)) {
grantConfig[oauthProvider] = {
...customProvider.config,
// todo: consider setting these options from a universal point also used
// by official providers. It'll prevent these from getting left out if the
// requirement changes.
callback: `/${providerName}/callback`,
transport: 'session',
}
}
})
}
/**
*
* @param {{server: object, providerOptions: object}} companionOptions
* @param {object} grantConfig
* @param {(a: string) => string} getOauthProvider
*/
export function addProviderOptions(
companionOptions,
grantConfig,
getOauthProvider,
) {
const { server, providerOptions } = companionOptions
if (!validOptions({ server })) {
logger.warn(
'invalid provider options detected. Providers will not be loaded',
'provider.options.invalid',
)
return
}
grantConfig.defaults = {
host: server.host,
protocol: server.protocol,
path: server.path,
}
const { oauthDomain } = server
const keys = Object.keys(providerOptions).filter((key) => key !== 'server')
keys.forEach((providerName) => {
const oauthProvider = getOauthProvider?.(providerName)
if (isOAuthProvider(oauthProvider) && grantConfig[oauthProvider]) {
// explicitly add providerOptions so users don't override other providerOptions.
grantConfig[oauthProvider].key = providerOptions[providerName].key
grantConfig[oauthProvider].secret = providerOptions[providerName].secret
if (providerOptions[providerName].credentialsURL) {
grantConfig[oauthProvider].dynamic = [
'key',
'secret',
'redirect_uri',
'origins',
]
}
const provider = getDefaultProviders()[providerName]
Object.assign(grantConfig[oauthProvider], provider.getExtraGrantConfig())
// override grant.js redirect uri with companion's custom redirect url
const isExternal = !!server.implicitPath
const redirectPath = getRedirectPath(providerName)
grantConfig[oauthProvider].redirect_uri = getURLBuilder(companionOptions)(
redirectPath,
isExternal,
)
if (oauthDomain) {
const fullRedirectPath = getURLBuilder(companionOptions)(
redirectPath,
isExternal,
true,
)
grantConfig[oauthProvider].redirect_uri =
`${server.protocol}://${oauthDomain}${fullRedirectPath}`
}
if (server.implicitPath) {
// no url builder is used for this because grant internally adds the path
grantConfig[oauthProvider].callback =
`${server.implicitPath}${grantConfig[oauthProvider].callback}`
} else if (server.path) {
grantConfig[oauthProvider].callback =
`${server.path}${grantConfig[oauthProvider].callback}`
}
}
})
}