|
@@ -1,15 +1,29 @@
|
|
|
const jwt = require('jsonwebtoken')
|
|
|
const { encrypt, decrypt } = require('./utils')
|
|
|
|
|
|
-const EXPIRY = 60 * 60 * 24 // one day (24 hrs)
|
|
|
+// The Uppy auth token is a (JWT) container around provider OAuth access & refresh tokens.
|
|
|
+// Providers themselves will verify these inner tokens.
|
|
|
+// The expiry of the Uppy auth token should be higher than the expiry of the refresh token.
|
|
|
+// Because some refresh tokens never expire, we set the Uppy auth token expiry very high.
|
|
|
+// Chrome has a maximum cookie expiry of 400 days, so we'll use that (we also store the auth token in a cookie)
|
|
|
+//
|
|
|
+// If the Uppy auth token expiry were set too low (e.g. 24hr), we could risk this situation:
|
|
|
+// A user starts an upload, thus creating an auth token. They leave the upload running over night.
|
|
|
+// The upload finishes after a few hours, but with some failed files. Then the next day (say after 25hr)
|
|
|
+// they would retry the failed tiles, however now the Uppy auth token has expired and
|
|
|
+// even though the provider refresh token would still have been accepted and
|
|
|
+// there's no way for them to retry their failed files.
|
|
|
+// With 400 days, there's still a theoretical possibility but very low.
|
|
|
+const EXPIRY = 60 * 60 * 24 * 400
|
|
|
+const EXPIRY_MS = EXPIRY * 1000
|
|
|
|
|
|
/**
|
|
|
*
|
|
|
- * @param {*} payload
|
|
|
+ * @param {*} data
|
|
|
* @param {string} secret
|
|
|
*/
|
|
|
-module.exports.generateToken = (payload, secret) => {
|
|
|
- return jwt.sign({ data: payload }, secret, { expiresIn: EXPIRY })
|
|
|
+const generateToken = (data, secret) => {
|
|
|
+ return jwt.sign({ data }, secret, { expiresIn: EXPIRY })
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -17,13 +31,9 @@ module.exports.generateToken = (payload, secret) => {
|
|
|
* @param {string} token
|
|
|
* @param {string} secret
|
|
|
*/
|
|
|
-module.exports.verifyToken = (token, secret) => {
|
|
|
- try {
|
|
|
- // @ts-ignore
|
|
|
- return { payload: jwt.verify(token, secret, {}).data }
|
|
|
- } catch (err) {
|
|
|
- return { err }
|
|
|
- }
|
|
|
+const verifyToken = (token, secret) => {
|
|
|
+ // @ts-ignore
|
|
|
+ return jwt.verify(token, secret, {}).data
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -32,7 +42,17 @@ module.exports.verifyToken = (token, secret) => {
|
|
|
* @param {string} secret
|
|
|
*/
|
|
|
module.exports.generateEncryptedToken = (payload, secret) => {
|
|
|
- return encrypt(module.exports.generateToken(payload, secret), secret)
|
|
|
+ // return payload // for easier debugging
|
|
|
+ return encrypt(generateToken(payload, secret), secret)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @param {*} payload
|
|
|
+ * @param {string} secret
|
|
|
+ */
|
|
|
+module.exports.generateEncryptedAuthToken = (payload, secret) => {
|
|
|
+ return module.exports.generateEncryptedToken(JSON.stringify(payload), secret)
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -41,16 +61,26 @@ module.exports.generateEncryptedToken = (payload, secret) => {
|
|
|
* @param {string} secret
|
|
|
*/
|
|
|
module.exports.verifyEncryptedToken = (token, secret) => {
|
|
|
- try {
|
|
|
- return module.exports.verifyToken(decrypt(token, secret), secret)
|
|
|
- } catch (err) {
|
|
|
- return { err }
|
|
|
- }
|
|
|
+ const ret = verifyToken(decrypt(token, secret), secret)
|
|
|
+ if (!ret) throw new Error('No payload')
|
|
|
+ return ret
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @param {string} token
|
|
|
+ * @param {string} secret
|
|
|
+ */
|
|
|
+module.exports.verifyEncryptedAuthToken = (token, secret, providerName) => {
|
|
|
+ const json = module.exports.verifyEncryptedToken(token, secret)
|
|
|
+ const tokens = JSON.parse(json)
|
|
|
+ if (!tokens[providerName]) throw new Error(`Missing token payload for provider ${providerName}`)
|
|
|
+ return tokens
|
|
|
}
|
|
|
|
|
|
const addToCookies = (res, token, companionOptions, authProvider, prefix) => {
|
|
|
const cookieOptions = {
|
|
|
- maxAge: 1000 * EXPIRY, // would expire after one day (24 hrs)
|
|
|
+ maxAge: EXPIRY_MS,
|
|
|
httpOnly: true,
|
|
|
}
|
|
|
|
|
@@ -68,15 +98,11 @@ const addToCookies = (res, token, companionOptions, authProvider, prefix) => {
|
|
|
res.cookie(`${prefix}--${authProvider}`, token, cookieOptions)
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- *
|
|
|
- * @param {object} res
|
|
|
- * @param {string} token
|
|
|
- * @param {object} companionOptions
|
|
|
- * @param {string} authProvider
|
|
|
- */
|
|
|
-module.exports.addToCookies = (res, token, companionOptions, authProvider) => {
|
|
|
- addToCookies(res, token, companionOptions, authProvider, 'uppyAuthToken')
|
|
|
+module.exports.addToCookiesIfNeeded = (req, res, uppyAuthToken) => {
|
|
|
+ // some providers need the token in cookies for thumbnail/image requests
|
|
|
+ if (req.companion.provider.needsCookieAuth) {
|
|
|
+ addToCookies(res, uppyAuthToken, req.companion.options, req.companion.provider.authProvider, 'uppyAuthToken')
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -87,7 +113,7 @@ module.exports.addToCookies = (res, token, companionOptions, authProvider) => {
|
|
|
*/
|
|
|
module.exports.removeFromCookies = (res, companionOptions, authProvider) => {
|
|
|
const cookieOptions = {
|
|
|
- maxAge: 1000 * EXPIRY, // would expire after one day (24 hrs)
|
|
|
+ maxAge: EXPIRY_MS,
|
|
|
httpOnly: true,
|
|
|
}
|
|
|
|