diff --git a/config.js b/config.js index 823ecdcb7..5f43b1c8d 100644 --- a/config.js +++ b/config.js @@ -7,8 +7,6 @@ // entrypoint for the entire applications configuration. require('env-rewrite').rewrite(); -const debug = require('debug')('talk:config'); - //============================================================================== // CONFIG INITIALIZATION //============================================================================== @@ -106,53 +104,12 @@ const CONFIG = { // JWT based configuration //------------------------------------------------------------------------------ -const jwt = require('./services/jwt'); - if (CONFIG.JWT_SECRETS) { CONFIG.JWT_SECRETS = JSON.parse(CONFIG.JWT_SECRETS); - if (!Array.isArray(CONFIG.JWT_SECRETS)) { - throw new Error('TALK_JWT_SECRETS must be a JSON array in the form [{"kid": kid, ["secret": secret | "private": private, "public": public]}, ...]'); - } - - if (CONFIG.JWT_SECRETS.length === 0) { - throw new Error('TALK_JWT_SECRETS must be a JSON array with non zero length'); - } - - // Wrap a multi-secret around the available secrets. - CONFIG.JWT_SECRET = new jwt.MultiSecret(CONFIG.JWT_SECRETS.map((secret) => { - if (!('kid' in secret)) { - throw new Error('when multiple keys are specified, kid\'s must be specified'); - } - - // HMAC secrets do not have public/private keys. - if (CONFIG.JWT_ALG.startsWith('HS')) { - return new jwt.SharedSecret(secret, CONFIG.JWT_ALG); - } - - if (!('public' in secret)) { - throw new Error('all symetric keys must provide a PEM encoded public key'); - } - - return new jwt.AsymmetricSecret(secret, CONFIG.JWT_ALG); - })); - - debug(`loaded ${CONFIG.JWT_SECRET.length} secrets`); -} else if (CONFIG.JWT_SECRET) { - if (CONFIG.JWT_ALG.startsWith('HS')) { - CONFIG.JWT_SECRET = new jwt.SharedSecret({ - secret: CONFIG.JWT_SECRET - }, CONFIG.JWT_ALG); - } else { - CONFIG.JWT_SECRET = new jwt.AsymmetricSecret(JSON.parse(CONFIG.JWT_SECRET), CONFIG.JWT_ALG); - } - - debug('loaded 1 secret'); } if (process.env.NODE_ENV === 'test' && !CONFIG.JWT_SECRET) { - CONFIG.JWT_SECRET = new jwt.SharedSecret({ - secret: 'keyboard cat' - }, CONFIG.JWT_ALG); + CONFIG.JWT_SECRET = 'keyboard cat'; } else if (!CONFIG.JWT_SECRET) { throw new Error( 'TALK_JWT_SECRET must be provided in the environment to sign/verify tokens' diff --git a/secrets.js b/secrets.js new file mode 100644 index 000000000..ee484a420 --- /dev/null +++ b/secrets.js @@ -0,0 +1,48 @@ +const { + JWT_SECRETS, + JWT_SECRET, + JWT_ALG +} = require('./config'); + +const debug = require('debug')('talk:secrets'); +const jwt = require('./services/jwt'); + +if (JWT_SECRETS) { + if (!Array.isArray(JWT_SECRETS)) { + throw new Error('TALK_JWT_SECRETS must be a JSON array in the form [{"kid": kid, ["secret": secret | "private": private, "public": public]}, ...]'); + } + + if (JWT_SECRETS.length === 0) { + throw new Error('TALK_JWT_SECRETS must be a JSON array with non zero length'); + } + + // Wrap a multi-secret around the available secrets. + module.exports.jwt = new jwt.MultiSecret(JWT_SECRETS.map((secret) => { + if (!('kid' in secret)) { + throw new Error('when multiple keys are specified, kid\'s must be specified'); + } + + // HMAC secrets do not have public/private keys. + if (JWT_ALG.startsWith('HS')) { + return new jwt.SharedSecret(secret, JWT_ALG); + } + + if (!('public' in secret)) { + throw new Error('all symetric keys must provide a PEM encoded public key'); + } + + return new jwt.AsymmetricSecret(secret, JWT_ALG); + })); + + debug(`loaded ${JWT_SECRET.length} secrets`); +} else if (JWT_SECRET) { + if (JWT_ALG.startsWith('HS')) { + module.exports.jwt = new jwt.SharedSecret({ + secret: JWT_SECRET + }, JWT_ALG); + } else { + module.exports.jwt = new jwt.AsymmetricSecret(JSON.parse(JWT_SECRET), JWT_ALG); + } + + debug('loaded 1 secret'); +} diff --git a/services/passport.js b/services/passport.js index 65fa1ac03..81338d75f 100644 --- a/services/passport.js +++ b/services/passport.js @@ -16,7 +16,6 @@ const {createClientFactory} = require('./redis'); const client = createClientFactory(); const { - JWT_SECRET, JWT_ISSUER, JWT_EXPIRY, JWT_AUDIENCE, @@ -25,6 +24,10 @@ const { RECAPTCHA_ENABLED } = require('../config'); +const { + jwt: JWT_SECRET +} = require('../secrets'); + // GenerateToken will sign a token to include all the authorization information // needed for the front end. const GenerateToken = (user) => { diff --git a/services/tokens.js b/services/tokens.js index 552f64e24..8184d47b5 100644 --- a/services/tokens.js +++ b/services/tokens.js @@ -3,11 +3,14 @@ const UserModel = require('../models/user'); const uuid = require('uuid'); const { - JWT_SECRET, JWT_ISSUER, JWT_AUDIENCE } = require('../config'); +const { + jwt: JWT_SECRET +} = require('../secrets'); + /** * TokenService manages Personal Access Tokens for users. These tokens are * persisted in the database and attached to the user. diff --git a/services/users.js b/services/users.js index 7e7134fbc..7ed55f712 100644 --- a/services/users.js +++ b/services/users.js @@ -4,10 +4,15 @@ const bcrypt = require('bcryptjs'); const url = require('url'); const Wordlist = require('./wordlist'); const errors = require('../errors'); + const { - JWT_SECRET, ROOT_URL } = require('../config'); + +const { + jwt: JWT_SECRET +} = require('../secrets'); + const debug = require('debug')('talk:services:users'); const UserModel = require('../models/user');