UNPKG

@underpostnet/underpost

Version:
236 lines (219 loc) 10.1 kB
/** * Module for managing identity and authorization * @module src/server/auth.js * @namespace Auth */ import dotenv from 'dotenv'; import jwt from 'jsonwebtoken'; import { loggerFactory } from './logger.js'; import crypto from 'crypto'; import { userRoleEnum } from '../api/user/user.model.js'; import { commonAdminGuard, commonModeratorGuard, validatePassword } from '../client/components/core/CommonJs.js'; dotenv.config(); const logger = loggerFactory(import.meta); /* The `const config` object is defining parameters related to the hashing process used for password security. Here's a breakdown of each property in the `config` object: */ const config = { hashBytes: 32, saltBytes: 16, iterations: 872791, digest: 'sha512', }; /** * @param {String} password - given password to hash * @returns {String} the hash corresponding to the password * @memberof Auth */ function hashPassword(password) { const { iterations, hashBytes, digest, saltBytes } = config; const salt = crypto.randomBytes(saltBytes).toString('hex'); const hash = crypto.pbkdf2Sync(password, salt, iterations, hashBytes, digest).toString('hex'); return [salt, hash].join('$'); } /** * @param {String} password - password to verify * @param {String} combined - a combined salt + hash returned by hashPassword function * @returns {Boolean} true if password correspond to the hash. False otherwise * @memberof Auth */ function verifyPassword(password, combined) { const { iterations, hashBytes, digest } = config; const [salt, originalHash] = combined.split('$'); const hash = crypto.pbkdf2Sync(password, salt, iterations, hashBytes, digest).toString('hex'); return hash === originalHash; } // jwt middleware /** * The hashJWT function generates a JSON Web Token (JWT) with a specified payload and expiration time. * @param payload - The `payload` parameter in the `hashJWT` function is the data that you want to * encode into the JSON Web Token (JWT). It typically contains information about the user or any other * relevant data that you want to securely transmit. * @param expire - The `expire` parameter in the `hashJWT` function is used to specify the expiration * time for the JSON Web Token (JWT) being generated. If a value is provided for `expire`, it will be * used as the expiration time. If `expire` is not provided (i.e., it * @memberof Auth */ const hashJWT = (payload, expire) => jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: expire !== undefined ? expire : `${process.env.EXPIRE}h` }); /** * The function `verifyJWT` is used to verify a JSON Web Token (JWT) using a secret key stored in the * environment variables. * @param token - The `token` parameter is a JSON Web Token (JWT) that is passed to the `verifyJWT` * function for verification. * @memberof Auth */ const verifyJWT = (token = '') => jwt.verify(token, process.env.JWT_SECRET); /** * The function `getBearerToken` extracts and returns the Bearer token from the Authorization header in * a request object. * @param req - The `req` parameter in the `getBearerToken` function is typically an object * representing the HTTP request. It is commonly used in Node.js applications with frameworks like * Express.js. The `req` object contains information about the incoming HTTP request, including * headers, body, parameters, and more. In * @returns {String} The function `getBearerToken` is returning the Bearer token extracted from the * Authorization header in the request object. If the Authorization header starts with 'Bearer ', it * will return the token portion of the header (excluding 'Bearer '). * @memberof Auth */ const getBearerToken = (req) => { const authHeader = String(req.headers['authorization'] || req.headers['Authorization'] || ''); if (authHeader.startsWith('Bearer ')) return authHeader.substring(7, authHeader.length); return ''; }; /** * The function `getPayloadJWT` extracts and verifies a JWT payload from a request using a bearer * token. * @param req - The `req` parameter is typically used in web development to represent the HTTP request * object. It contains information about the incoming request, such as headers, parameters, and body * data. In this context, it seems like the `getPayloadJWT` function is designed to extract and verify * a JWT token from * @returns {Object} The JWT payload from a request using a bearer * @memberof Auth */ const getPayloadJWT = (req) => verifyJWT(getBearerToken(req)); /** * The authMiddleware function checks and verifies the authorization token in the request headers * before allowing access to protected routes. * @param req - The `req` parameter in the `authMiddleware` function stands for the request object. It * contains information about the HTTP request made to the server, including headers, body, parameters, * and more. In this context, the function is extracting the authorization token from the request * headers to authenticate the user. * @param res - The `res` parameter in the `authMiddleware` function is the response object that * represents the HTTP response that an Express.js server sends when it receives an HTTP request. It is * used to send a response back to the client with status codes, headers, and data. * @param next - The `next` parameter in the `authMiddleware` function is a callback function that is * used to pass control to the next middleware function in the stack. When called, it invokes the next * middleware function in the chain. This is a common pattern in Express.js middleware functions to * move to the next middleware * @returns {Object} The `req.auth` included JWT payload in request authorization * @memberof Auth */ const authMiddleware = (req, res, next) => { try { const token = getBearerToken(req); if (token) { const payload = verifyJWT(token); req.auth = payload; return next(); } else return res.status(401).json({ status: 'error', message: 'unauthorized: invalid token', }); } catch (error) { logger.error(error, error.stack); return res.status(400).json({ status: 'error', message: error.message, }); } }; /** * The `adminGuard` function checks if the user has admin role permission and returns an error message * if not. * @param req - The `req` parameter typically represents the HTTP request object in Node.js. It * contains information about the incoming request such as the request headers, parameters, body, and * more. In the context of your `adminGuard` function, `req` is the request object that is being passed * to the middleware * @param res - The `res` parameter in the `adminGuard` function is the response object in Express.js. * It is used to send a response back to the client making the HTTP request. * @param next - The `next` parameter in the `adminGuard` function is a callback function that is used * to pass control to the next middleware function in the stack. When called, it executes the next * middleware function. If there are no more middleware functions in the stack, it will proceed to the * route handler. * @returns The `adminGuard` function is returning either a 403 status with an error message if the * user role is not 'admin', or it is calling the `next()` function to proceed to the next middleware * if the user role is 'admin'. If an error occurs during the process, it will log the error and return * a 400 status with the error message. * @memberof Auth */ const adminGuard = (req, res, next) => { try { if (!commonAdminGuard(req.auth.user.role)) return res.status(403).json({ status: 'error', message: 'Insufficient permission' }); return next(); } catch (error) { logger.error(error, error.stack); return res.status(400).json({ status: 'error', message: error.message, }); } }; /** * The function `moderatorGuard` checks if the user's role is at least a moderator and handles errors * accordingly. * @param req - The `req` parameter in the `moderatorGuard` function typically represents the HTTP * request object, which contains information about the incoming request such as headers, parameters, * body, etc. It is commonly used to access data sent from the client to the server. * @param res - The `res` parameter in the `moderatorGuard` function is the response object in * Express.js. It is used to send a response back to the client making the HTTP request. * @param next - The `next` parameter in the `moderatorGuard` function is a callback function that is * used to pass control to the next middleware function in the stack. When called, it will execute the * next middleware function. In the context of Express.js middleware, `next` is typically called to * move to * @returns In the `moderatorGuard` function, if the user's role is not a moderator or higher, a 403 * status with an error message "Insufficient permission" is returned. If there is an error during the * process, a 400 status with the error message is returned. If everything is successful, the `next()` * function is called to proceed to the next middleware in the chain. * @memberof Auth */ const moderatorGuard = (req, res, next) => { try { if (!commonModeratorGuard(req.auth.user.role)) return res.status(403).json({ status: 'error', message: 'Insufficient permission' }); return next(); } catch (error) { logger.error(error, error.stack); return res.status(400).json({ status: 'error', message: error.message, }); } }; const validatePasswordMiddleware = (req, password) => { let errors = []; if (req.body && 'password' in req.body) errors = validatePassword(req.body.password); if (errors.length > 0) return { status: 'error', message: 'Password, ' + errors.map((e, i) => (i > 0 ? ', ' : '') + (e[req.lang] ? e[req.lang] : e['en'])).join(''), }; else return { status: 'success', }; }; export { authMiddleware, hashPassword, verifyPassword, hashJWT, adminGuard, moderatorGuard, verifyJWT, validatePasswordMiddleware, getBearerToken, getPayloadJWT, };