/
companion.js
156 lines (134 loc) · 6.17 KB
/
companion.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
const express = require('express')
// @ts-ignore
const Grant = require('grant').express()
const merge = require('lodash.merge')
const cookieParser = require('cookie-parser')
const interceptor = require('express-interceptor')
const uuid = require('uuid')
const grantConfig = require('./config/grant')()
const providerManager = require('./server/provider')
const controllers = require('./server/controllers')
const s3 = require('./server/controllers/s3')
const url = require('./server/controllers/url')
const createEmitter = require('./server/emitter')
const redis = require('./server/redis')
const { getURLBuilder } = require('./server/helpers/utils')
const jobs = require('./server/jobs')
const logger = require('./server/logger')
const middlewares = require('./server/middlewares')
const { getMaskableSecrets, defaultOptions, validateConfig } = require('./config/companion')
const { ProviderApiError, ProviderAuthError } = require('./server/provider/error')
const { getCredentialsOverrideMiddleware } = require('./server/provider/credentials')
// @ts-ignore
const { version } = require('../package.json')
// intercepts grantJS' default response error when something goes
// wrong during oauth process.
const interceptGrantErrorResponse = interceptor((req, res) => {
return {
isInterceptable: () => {
// match grant.js' callback url
return /^\/connect\/\w+\/callback/.test(req.path)
},
intercept: (body, send) => {
const unwantedBody = 'error=Grant%3A%20missing%20session%20or%20misconfigured%20provider'
if (body === unwantedBody) {
logger.error(`grant.js responded with error: ${body}`, 'grant.oauth.error', req.id)
res.set('Content-Type', 'text/plain')
const reqHint = req.id ? `Request ID: ${req.id}` : ''
send([
'Companion was unable to complete the OAuth process :(',
'Error: User session is missing or the Provider was misconfigured',
reqHint,
].join('\n'))
} else {
send(body)
}
},
}
})
// make the errors available publicly for custom providers
module.exports.errors = { ProviderApiError, ProviderAuthError }
module.exports.socket = require('./server/socket')
/**
* Entry point into initializing the Companion app.
*
* @param {object} optionsArg
* @returns {{ app: import('express').Express, emitter: any }}}
*/
module.exports.app = (optionsArg = {}) => {
validateConfig(optionsArg)
const options = merge({}, defaultOptions, optionsArg)
const providers = providerManager.getDefaultProviders()
const searchProviders = providerManager.getSearchProviders()
providerManager.addProviderOptions(options, grantConfig)
const { customProviders } = options
if (customProviders) {
providerManager.addCustomProviders(customProviders, providers, grantConfig)
}
// mask provider secrets from log messages
logger.setMaskables(getMaskableSecrets(options))
// create singleton redis client
if (options.redisUrl) {
redis.client(options)
}
const emitter = createEmitter(options.redisUrl, options.redisPubSubScope)
const app = express()
if (options.metrics) {
app.use(middlewares.metrics({ path: options.server.path }))
// backward compatibility
// TODO remove in next major semver
if (options.server.path) {
const buildUrl = getURLBuilder(options)
app.get('/metrics', (req, res) => {
process.emitWarning('/metrics is deprecated when specifying a path to companion')
const metricsUrl = buildUrl('/metrics', true)
res.redirect(metricsUrl)
})
}
}
app.use(cookieParser()) // server tokens are added to cookies
app.use(interceptGrantErrorResponse)
// override provider credentials at request time
app.use('/connect/:authProvider/:override?', getCredentialsOverrideMiddleware(providers, options))
app.use(Grant(grantConfig))
app.use((req, res, next) => {
if (options.sendSelfEndpoint) {
const { protocol } = options.server
res.header('i-am', `${protocol}://${options.sendSelfEndpoint}`)
}
next()
})
app.use(middlewares.cors(options))
// add uppy options to the request object so it can be accessed by subsequent handlers.
app.use('*', middlewares.getCompanionMiddleware(options))
app.use('/s3', s3(options.s3))
app.use('/url', url())
app.post('/:providerName/preauth', middlewares.hasSessionAndProvider, controllers.preauth)
app.get('/:providerName/connect', middlewares.hasSessionAndProvider, controllers.connect)
app.get('/:providerName/redirect', middlewares.hasSessionAndProvider, controllers.redirect)
app.get('/:providerName/callback', middlewares.hasSessionAndProvider, controllers.callback)
app.post('/:providerName/deauthorization/callback', middlewares.hasSessionAndProvider, controllers.deauthorizationCallback)
app.get('/:providerName/logout', middlewares.hasSessionAndProvider, middlewares.gentleVerifyToken, controllers.logout)
app.get('/:providerName/send-token', middlewares.hasSessionAndProvider, middlewares.verifyToken, controllers.sendToken)
app.get('/:providerName/list/:id?', middlewares.hasSessionAndProvider, middlewares.verifyToken, controllers.list)
app.post('/:providerName/get/:id', middlewares.hasSessionAndProvider, middlewares.verifyToken, controllers.get)
app.get('/:providerName/thumbnail/:id', middlewares.hasSessionAndProvider, middlewares.cookieAuthToken, middlewares.verifyToken, controllers.thumbnail)
// @ts-ignore Type instantiation is excessively deep and possibly infinite.
app.get('/search/:searchProviderName/list', middlewares.hasSearchQuery, middlewares.loadSearchProviderToken, controllers.list)
app.post('/search/:searchProviderName/get/:id', middlewares.loadSearchProviderToken, controllers.get)
app.param('providerName', providerManager.getProviderMiddleware(providers, true))
app.param('searchProviderName', providerManager.getProviderMiddleware(searchProviders))
if (app.get('env') !== 'test') {
jobs.startCleanUpJob(options.filePath)
}
const processId = uuid.v4()
jobs.startPeriodicPingJob({
urls: options.periodicPingUrls,
interval: options.periodicPingInterval,
count: options.periodicPingCount,
staticPayload: options.periodicPingStaticPayload,
version,
processId,
})
return { app, emitter }
}