const fs = require('fs') const path = require('path') const crypto = require('crypto') const jwt = require('jsonwebtoken') const invariant = require('invariant') const forge = require('node-forge') const db = require('./RedisClient') let keys if (process.env.NODE_ENV === 'production') { keys = { public: fs.readFileSync(path.resolve(__dirname, '../public.key'), 'utf8'), private: process.env.PRIVATE_KEY } invariant(keys.private, 'Missing $PRIVATE_KEY environment variable') } else { // Generate a random keypair for dev/testing. // See https://gist.github.com/sebadoom/2b70969e70db5da9a203bebd9cff099f const keypair = forge.rsa.generateKeyPair({ bits: 2048 }) keys = { public: forge.pki.publicKeyToPem(keypair.publicKey, 72), private: forge.pki.privateKeyToPem(keypair.privateKey, 72) } } function getCurrentSeconds() { return Math.floor(Date.now() / 1000) } function createTokenId() { return crypto.randomBytes(16).toString('hex') } function createToken(scopes = {}) { return new Promise((resolve, reject) => { const payload = { jti: createTokenId(), iss: 'https://unpkg.com', iat: getCurrentSeconds(), scopes } jwt.sign(payload, keys.private, { algorithm: 'RS256' }, (error, token) => { if (error) { reject(error) } else { resolve(token) } }) }) } const RevokedTokensSet = 'revoked-tokens' function verifyToken(token) { return new Promise((resolve, reject) => { const options = { algorithms: ['RS256'] } jwt.verify(token, keys.public, options, (error, payload) => { if (error) { reject(error) } else { if (payload.jti) { db.sismember(RevokedTokensSet, payload.jti, (error, value) => { if (error) { reject(error) } else { resolve(value === 0 ? payload : null) } }) } else { resolve(null) } } }) }) } function revokeToken(token) { return verifyToken(token).then(payload => { if (payload) { return new Promise((resolve, reject) => { db.sadd(RevokedTokensSet, payload.jti, error => { if (error) { reject(error) } else { resolve() } }) }) } }) } function removeAllRevokedTokens() { return new Promise((resolve, reject) => { db.del(RevokedTokensSet, error => { if (error) { reject(error) } else { resolve() } }) }) } function getPublicKey() { return keys.public } module.exports = { createToken, verifyToken, revokeToken, removeAllRevokedTokens, getPublicKey }