const errors = require('../errors'); const UserModel = require('../models/user'); const JWT = require('jsonwebtoken'); const uuid = require('uuid'); const { JWT_SECRET, JWT_ISSUER, JWT_AUDIENCE } = require('../config'); /** * TokenService manages Personal Access Tokens for users. These tokens are * persisted in the database and attached to the user. */ module.exports = class TokenService { /** * Creates a token for a user with a given name. * * @param {String} userID the id of the user owning the token * @param {String} tokenName the name of the token to be created */ static async create(userID, tokenName) { // Create the token. const payload = { jti: uuid.v4(), iss: JWT_ISSUER, aud: JWT_AUDIENCE, sub: userID, pat: true }; // Sign the payload. const jwt = JWT.sign(payload, JWT_SECRET, {}); // Create the PAT. let pat = { id: payload.jti, name: tokenName, active: true }; // Wait to update the user model with the new PAT. await UserModel.update({id: userID}, { $push: { tokens: pat } }); return {payload, jwt, pat}; } /** * Revokes a token and prevents the token from being used. Once a token has * been revoked, it cannot be re-enabled. * * @param {String} userID the id of the user owning the token * @param {String} tokenID the jti of the token to revoke */ static async revoke(userID, tokenID) { let query = { tokens: { $elemMatch: { id: tokenID } } }; if (userID) { query.id = userID; } // Revoke the token id. await UserModel.update(query, { $set: { 'tokens.$.active': false } }); } /** * Validate that a given Token is valid. * * @param {String} userID the user's id that owns the token * @param {String} tokenID the id of the token */ static async validate(userID, tokenID) { // Find the user. let user = await UserModel.findOne({ id: userID }).select('tokens'); if (!user || !user.tokens) { throw new errors.ErrAuthentication('user does not exist'); } // Extract the token from the user. let token = user.tokens.find(({id}) => id === tokenID); if (!token) { throw new errors.ErrAuthentication('token does not exist'); } // Check to see if it is active. if (!token.active) { throw new errors.ErrAuthentication('token is not active'); } } /** * Lists the tokens owned by the user. * * @param {String} userID the id of the user owning the token */ static async list(userID) { // Get the user specified by the id. let user = await UserModel.findOne({id: userID}).select('tokens'); if (!user || !user.tokens) { return []; } return user.tokens; } };