Source

API/src/utils/token.js

/**
 * @fileoverview Token utilities.
 * 
 * @category Backend API
 * @subcategory Utilities
 * 
 * @module Token Utility
 * 
 * @description This module contains functions for generating and verifying JWT tokens.
 *  
 * @requires ../models/user.models
 * @requires ../models/token.models
 * @requires ../utils/errors
 * @requires jsonwebtoken
 * @requires ../utils/config
 * @requires uuid
 *  
 */

const { User } = require('../models/user.models');
const { AuthCode } = require('../models/token.models');
const { NotFoundError } = require('./errors');
const jwt = require('jsonwebtoken');
const config = require('./config');
const { v4: UUID } = require('uuid');

/**
 * Gets the secret and expiry for the specified token type
 * 
 * @description This function returns the secret and expiry for the specified token type. <br>
 * The token type can be one of the following: <br>
 * - `access` - Access token <br>
 * - `refresh` - Refresh token <br>
 * - `password_reset` - Password reset token <br>
 * 
 * @param {string} type - Type of token to generate
 * @returns secret and expiry for the specified token type
 */
const getRequiredConfigVars = (type) => {
    switch (type) {
        case 'access':
            return {
                secret: config.JWT_ACCESS_SECRET,
                expiry: config.JWT_ACCESS_EXP,
            };

        case 'refresh':
            return {
                secret: config.JWT_REFRESH_SECRET,
                expiry: config.JWT_REFRESH_EXP,
            };

        case 'password_reset':
            return {
                secret: config.JWT_PASSWORDRESET_SECRET,
                expiry: config.JWT_PASSWORDRESET_EXP,
            };

        case 'verification':
            return {
                secret: config.JWT_EMAILVERIFICATION_SECRET,
                expiry: config.JWT_EMAILVERIFICATION_EXP,
            };

        case 'su_activation':
            return {
                secret: config.JWT_SUPERADMINACTIVATION_SECRET,
                expiry: config.JWT_SUPERADMINACTIVATION_EXP
            }

        case 'su_deactivation':
            return {
                secret: config.JWT_SUPERADMINACTIVATION_SECRET,
                expiry: config.JWT_SUPERADMINACTIVATION_EXP
            }
    }
};

/**
 * Generates a JWT token
 * 
 * @description This function generates a JWT token for the specified user. <br>
 * 
 * @param {ObjectId} user_id - ID of the user to generate token for
 * @param {string} token_type - Type of token to generate
 * @returns JWT token
 * 
 * @throws {NotFoundError} - If user does not exist
 * @throws {Error} - If any other error occurs
 *  */
const getAuthTokens = async (user_id, token_type = null) => {
    try {
        // Get user details
        const current_user = await User.findById(user_id).populate('status');
        if (!current_user) {
            throw new NotFoundError('User does not exist');
        }

        const data = {
            id: current_user.id,
            email: current_user.email,
            role: current_user.role,
            status: current_user.status,
        };

        // Set token type to access if not specified
        if (!token_type) token_type = 'access';

        // Get token secret and expiry
        let { secret, expiry } = getRequiredConfigVars(token_type);

        // Set token expiry to 6 hours if in development
        if (!process.env.NODE_ENV || process.env.NODE_ENV === 'dev') {
            expiry = '6h';
        }

        // Generate tokens
        const access_token = jwt.sign(data, secret, { expiresIn: expiry });
        const refresh_token = jwt.sign(data, config.JWT_REFRESH_SECRET, {
            expiresIn: config.JWT_REFRESH_EXP,
        });

        return { access_token, refresh_token };
    } catch (error) {
        throw error;
    }
};

/**
 * Generates Auth Codes
 * 
 * @description Generate authentication codes for user,
 * such as verification code, password reset code, activation code, deactivation code
 * 
 * @param {ObjectId} user_id
 * @param {string} code_type 
 * 
 * @returns verification_code, password_reset_code, 
 * @returns activation_code1, activation_code2, activation_code3
 */
const getAuthCodes = async (user_id, code_type) => {
    return new Promise(async (resolve, reject) => {
        try {
            let random_code = `${Math.floor(100000 + Math.random() * 900000)}`;
            let verification_code,
                password_reset_code,
                activation_code1,
                activation_code2,
                activation_code3,
                deactivation_code1,
                deactivation_code2,
                deactivation_code3;

            if (code_type == 'verification') {
                verification_code = random_code;
                await AuthCode.findOneAndUpdate(
                    { user: user_id },
                    { verification_code },
                    { new: true, upsert: true }
                );
                // console.log(autho);
            }

            if (code_type == 'password_reset') {
                password_reset_code = random_code;
                const autho = await AuthCode.findOneAndUpdate(
                    { user: user_id },
                    { password_reset_code },
                    { new: true, upsert: true }
                );

                // console.log(autho);
            }

            // If code_type is 'su_activation', generate 3 codes - SuperAdminAccountActivation
            if (code_type == 'su_activation') {
                activation_code1 = UUID(); // Will be sent to user
                activation_code2 = UUID(); // Will be sent to first admin
                activation_code3 = UUID(); // Will be sent to second admin

                const activation_code = `${activation_code1}-${activation_code2}-${activation_code3}`;

                const autho = await AuthCode.findOneAndUpdate(
                    { user: user_id },
                    { activation_code, expiresIn: 60 * 60 * 24 },
                    { new: true, upsert: true }
                );

                // console.log(autho);
            }

            // If code_type is 'su_deactivation', generate 3 codes - SuperAdminAccountDeactivation
            if (code_type == 'su_deactivation') {
                deactivation_code1 = UUID(); // Will be sent to user
                deactivation_code2 = UUID(); // Will be sent to first admin
                deactivation_code3 = UUID(); // Will be sent to second admin

                const deactivation_code = `${deactivation_code1}-${deactivation_code2}-${deactivation_code3}`;

                await AuthCode.findOneAndUpdate(
                    { user: user_id },
                    { deactivation_code },
                    { new: true, upsert: true }
                );
            }

            if (process.env.NODE_ENV === 'dev') {
                console.log(
                    'getAuthCodes',
                    verification_code,
                    password_reset_code,
                    activation_code1,
                    activation_code2,
                    activation_code3,
                    deactivation_code1,
                    deactivation_code2,
                    deactivation_code3
                );
            }

            resolve({
                verification_code,
                password_reset_code,
                activation_code1,
                activation_code2,
                activation_code3,
                deactivation_code1,
                deactivation_code2,
                deactivation_code3,
            });
        } catch (error) {
            reject(error);
        }
    });
};

/**
 * Decode JWT
 * 
 * @description Decodes a JWT token
 * 
 * @param {string} token 
 * @returns {Object} - Decoded token
 */
const decodeJWT = (token) => {
    try {
        const decoded = jwt.verify(token, config.JWT_ACCESS_SECRET);
        return decoded;
    } catch (error) {
        throw error;
    }
};

module.exports = { getAuthTokens, getAuthCodes, decodeJWT, getRequiredConfigVars };