/
credentials.js
178 lines (159 loc) · 6.15 KB
/
credentials.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
const request = require('request')
const atob = require('atob')
const { htmlEscape } = require('escape-goat')
const logger = require('../logger')
const oAuthState = require('../helpers/oauth-state')
const tokenService = require('../helpers/jwt')
// eslint-disable-next-line
const Provider = require('./Provider')
/**
* @param {string} url
* @param {string} providerName
* @param {object|null} credentialRequestParams - null asks for default credentials.
*/
function fetchKeys (url, providerName, credentialRequestParams) {
return new Promise((resolve, reject) => {
const options = {
body: {
provider: providerName,
parameters: credentialRequestParams,
},
json: true,
}
request.post(url, options, (requestErr, resp, body) => {
if (requestErr) {
logger.error(requestErr, 'credentials.fetch.fail')
return reject(requestErr)
}
if (resp.statusCode !== 200 || !body.credentials) {
const err = new Error(`received status: ${resp.statusCode} with no credentials`)
logger.error(err, 'credentials.fetch.fail')
return reject(err)
}
return resolve(body.credentials)
})
})
}
/**
* Fetches for a providers OAuth credentials. If the config for thtat provider allows fetching
* of the credentials via http, and the `credentialRequestParams` argument is provided, the oauth
* credentials will be fetched via http. Otherwise, the credentials provided via companion options
* will be used instead.
*
* @param {string} providerName the name of the provider whose oauth keys we want to fetch (e.g onedrive)
* @param {object} companionOptions the companion options object
* @param {object} credentialRequestParams the params that should be sent if an http request is required.
*/
async function fetchProviderKeys (providerName, companionOptions, credentialRequestParams) {
let providerConfig = companionOptions.providerOptions[providerName]
if (!providerConfig) {
providerConfig = (companionOptions.customProviders[providerName] || {}).config
}
if (!providerConfig) {
return null
}
if (!providerConfig.credentialsURL) {
return providerConfig
}
// If a default key is configured, do not ask the credentials endpoint for it.
// In a future version we could make this an XOR thing, providing either an endpoint or global keys,
// but not both.
if (!credentialRequestParams && providerConfig.key) {
return providerConfig
}
return fetchKeys(providerConfig.credentialsURL, providerName, credentialRequestParams || null)
}
/**
* Returns a request middleware function that can be used to pre-fetch a provider's
* Oauth credentials before the request is passed to the Oauth handler (https://github.com/simov/grant in this case).
*
* @param {Record<string, typeof Provider>} providers provider classes enabled for this server
* @param {object} companionOptions companion options object
* @returns {import('express').RequestHandler}
*/
exports.getCredentialsOverrideMiddleware = (providers, companionOptions) => {
return (req, res, next) => {
const { authProvider, override } = req.params
const [providerName] = Object.keys(providers).filter((name) => providers[name].authProvider === authProvider)
if (!providerName) {
next()
return
}
if (!companionOptions.providerOptions[providerName].credentialsURL) {
next()
return
}
const dynamic = oAuthState.getDynamicStateFromRequest(req)
// only use state via session object if user isn't making intial "connect" request.
// override param indicates subsequent requests from the oauth flow
const state = override ? dynamic : req.query.state
if (!state) {
next()
return
}
const preAuthToken = oAuthState.getFromState(state, 'preAuthToken', companionOptions.secret)
if (!preAuthToken) {
next()
return
}
const { err, payload } = tokenService.verifyEncryptedToken(preAuthToken, companionOptions.preAuthSecret)
if (err || !payload) {
next()
return
}
fetchProviderKeys(providerName, companionOptions, payload).then((credentials) => {
res.locals.grant = {
dynamic: {
key: credentials.key,
secret: credentials.secret,
},
}
if (credentials.redirect_uri) {
res.locals.grant.dynamic.redirect_uri = credentials.redirect_uri
}
next()
}).catch((keyErr) => {
// TODO we should return an html page here that can communicate the error
// back to the Uppy client, just like /send-token does
res.send(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Could not fetch credentials</h1>
<p>
This is probably an Uppy configuration issue. Check that your Transloadit key is correct, and that the configured <code>credentialsName</code> for this remote provider matches the name you gave it in the Template Credentials setup on the Transloadit side.
</p>
<p>Internal error message: ${htmlEscape(keyErr.message)}</p>
</body>
</html>
`)
})
}
}
/**
* Returns a request scoped function that can be used to get a provider's oauth credentials
* through out the lifetime of the request.
*
* @param {string} providerName the name of the provider attached to the scope of the request
* @param {object} companionOptions the companion options object
* @param {object} req the express request object for the said request
* @returns {(providerName: string, companionOptions: object, credentialRequestParams?: object) => Promise}
*/
module.exports.getCredentialsResolver = (providerName, companionOptions, req) => {
const credentialsResolver = () => {
const encodedCredentialsParams = req.header('uppy-credentials-params')
let credentialRequestParams = null
if (encodedCredentialsParams) {
try {
credentialRequestParams = JSON.parse(atob(encodedCredentialsParams)).params
} catch (error) {
logger.error(error, 'credentials.resolve.fail', req.id)
}
}
return fetchProviderKeys(providerName, companionOptions, credentialRequestParams)
}
return credentialsResolver
}