-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathcompanion.js
144 lines (122 loc) · 5.74 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
const express = require('express')
const Grant = require('grant').express()
const merge = require('lodash.merge')
const cookieParser = require('cookie-parser')
const interceptor = require('express-interceptor')
const { randomUUID } = require('node:crypto')
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 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 }))
}
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 = randomUUID()
jobs.startPeriodicPingJob({
urls: options.periodicPingUrls,
interval: options.periodicPingInterval,
count: options.periodicPingCount,
staticPayload: options.periodicPingStaticPayload,
version,
processId,
})
return { app, emitter }
}