Source: route-auth/controller.js

/**
 * @module  auth-route-controller
 * @desc    The leaderboard-api authentication controller module.
 * @version 1.0.0
 * @author  Essam A. El-Sherif
 */

/* Import node.js common modules */
import { createHash } from 'node:crypto';

/* Import package dependencies */
import jwt from 'jsonwebtoken';

/* Import local dependencies */
import { Users } from './model.js';
import { LeaderBoard } from './model.js';

/**
 * @func
 * @async
 * @static
 * @param  {object} req - The request object.
 * @param  {object} res - The response object.
 * @return {Promise}
 * @desc   Router-level middleware function to get all users.
 * @requires module:redis-users.Users.getUsers
 */
export async function getUsersMiddleware(req, res){

	res.setHeader('Server', req.app.get('serverName'));

	const usersObj = new Users(req.app.get('clientRd'), req.app.get('redisKeyUser'));

	try{
		let users = await usersObj.getUsers();
		res.status(200).send(users.map((u) => u.username).join(','));
	}
	catch(err){
		res.status(401).send(`Authentication Error: ${err.message}`);
	}
}

/**
 * @func
 * @async
 * @static
 * @param  {object} req - The request object.
 * @param  {object} res - The response object.
 * @return {Promise}
 * @desc   Router-level middleware function to create a nonexistent user and responds with JWT token.
 * @requires module:redis-users.Users.createUser
 */
export async function createUserMiddleware(req, res){

	if(!verifyCredentials(req, res)) return;

	const usersObj = new Users(req.app.get('clientRd'), req.app.get('redisKeyUser'));

	const user = {
		username: req.body.username.toLowerCase(),
		password: createHash('md5').update(req.body.password).digest('hex')
	};

	try{
		await usersObj.createUser(user.username, user.password);

		const payload = { ...user };
		delete payload.password;

		// https://www.npmjs.com/package/jsonwebtoken?activeTab=readme#jwtsignpayload-secretorprivatekey-options-callback
		// jwt.sign(payload, secretOrPrivateKey, [options, callback])
		const token = jwt.sign(payload, req.app.get('secretKey'), {algorithm: 'HS256'});

		res.status(201).json({ token });
	}
	catch(err){
		res.status(401).send(`Authentication Error: ${err.message}`);
	}
}

/**
 * @func
 * @async
 * @static
 * @param  {object} req - The request object.
 * @param  {object} res - The response object.
 * @return {Promise}
 * @desc   Router-level middleware function to update an existing user and responds with JWT token.
 * @requires module:redis-users.Users.updateUser
 */
export async function updateUserMiddleware(req, res){

	if(!verifyCredentials(req, res)) return;

	const usersObj = new Users(req.app.get('clientRd'), req.app.get('redisKeyUser'));

	const user = {
		username: req.body.username.toLowerCase(),
		password: createHash('md5').update(req.body.password).digest('hex')
	};

	if('newpassword' in req.body && req.body.newpassword){
		user.newpassword = createHash('md5').update(req.body.newpassword).digest('hex');
	}

	try{
		if('newpassword' in req.body){
			await usersObj.updateUser(user.username, user.password, user.newpassword);
		}
		else{
			await usersObj.updateUser(user.username, user.password);
		}

		const payload = { ...user };
		delete payload.password;
		if(payload.newpassword) delete payload.newpassword;

		// https://www.npmjs.com/package/jsonwebtoken?activeTab=readme#jwtsignpayload-secretorprivatekey-options-callback
		// jwt.sign(payload, secretOrPrivateKey, [options, callback])
		const token = jwt.sign(payload, req.app.get('secretKey'), {algorithm: 'HS256'});

		res.status(201).json({ token });
	}
	catch(err){
		res.status(401).send(`Authentication Error: ${err.message}`);
	}
}

/**
 * @func
 * @async
 * @static
 * @param  {object} req - The request object.
 * @param  {object} res - The response object.
 * @return {Promise}
 * @desc   Router-level middleware function to delete an existing user.
 * @requires module:redis-users.Users.deleteUser
 * @requires module:redis-leaderboard.LeaderBoard.removeUserScore
 */
export async function deleteUserMiddleware(req, res){

	if(!verifyCredentials(req, res)) return;

	const redisKeyA = req.app.get('redisKeyActivity');
	const redisKeyT = req.app.get('redisKeyTimestamp');

    const leaderboardObj = new LeaderBoard(req.app.get('clientRd'), redisKeyT, redisKeyA);
	const usersObj = new Users(req.app.get('clientRd'), req.app.get('redisKeyUser'));

	const user = {
		username: req.body.username.toLowerCase(),
		password: createHash('md5').update(req.body.password).digest('hex')
	};

	try{
		await usersObj.deleteUser(user.username, user.password);
		await leaderboardObj.removeUserScore(user.username);

		res.status(204).send();
	}
	catch(err){
		res.status(401).send(`Authentication Error: ${err.message}`);
	}
}

/**
 * @func
 * @param  {object} req - The request object.
 * @param  {object} res - The response object.
 * @return {boolean} True when user credentials are verified, false otherwise.
 * @desc   Verifies the user credentials and sends non OK response otherwise.
 */
function verifyCredentials(req, res){

	res.setHeader('Server', req.app.get('serverName'));

	if(Object.keys(req.body).length === 0){
		res.status(400).send('Authentication Error: none or invalid request payload');
		return;
	}

	if(!('username' in req.body || 'password' in req.body)){
		res.status(400).send('Authentication Error: no username/password given');
		return;
	}

	if(!('username' in req.body) || !req.body.username){
		res.status(400).send('Authentication Error: no username given');
		return;
	}

	if(!('password' in req.body) || !req.body.password){
		res.status(400).send('Authentication Error: no password given');
		return;
	}

	return true;
}