From 0b4ec46f7941fa862f0a5a32741e594a509bb5dd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 23 Jul 2025 12:12:22 -0700 Subject: [PATCH] add updated JSdoc --- server/controllers/authController.js | 283 +++++++++++++++++++++++++-- 1 file changed, 270 insertions(+), 13 deletions(-) diff --git a/server/controllers/authController.js b/server/controllers/authController.js index da0f2c8f7..12d2f77f4 100755 --- a/server/controllers/authController.js +++ b/server/controllers/authController.js @@ -10,13 +10,32 @@ import { editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, } from "../validation/joi.js"; -import jwt from "jsonwebtoken"; -import { getTokenFromHeaders } from "../utils/utils.js"; import { asyncHandler, createError } from "../utils/errorUtils.js"; const SERVICE_NAME = "authController"; +/** + * Authentication Controller + * + * Handles all authentication-related HTTP requests including user registration, + * login, password recovery, and user management operations. + * + * @class AuthController + * @description Manages user authentication and authorization operations + */ class AuthController { + /** + * Creates an instance of AuthController. + * + * @param {Object} dependencies - The dependencies required by the controller + * @param {Object} dependencies.db - Database service for data operations + * @param {Object} dependencies.settingsService - Service for application settings + * @param {Object} dependencies.emailService - Service for email operations + * @param {Object} dependencies.jobQueue - Service for job queue operations + * @param {Object} dependencies.stringService - Service for string/localization + * @param {Object} dependencies.logger - Logger service + * @param {Object} dependencies.userService - User business logic service + */ constructor({ db, settingsService, emailService, jobQueue, stringService, logger, userService }) { this.db = db; this.settingsService = settingsService; @@ -27,8 +46,47 @@ class AuthController { this.userService = userService; } + /** + * Registers a new user in the system. + * + * @async + * @function registerUser + * @param {Object} req - Express request object + * @param {Object} req.body - Request body containing user registration data + * @param {string} req.body.firstName - User's first name + * @param {string} req.body.lastName - User's last name + * @param {string} req.body.email - User's email address (will be converted to lowercase) + * @param {string} req.body.password - User's password + * @param {string} [req.body.inviteToken] - Invite token for registration (required if superadmin exists) + * @param {string} [req.body.teamId] - Team ID (auto-assigned if superadmin) + * @param {Array} [req.body.role] - User roles (auto-assigned if superadmin) + * @param {Object} [req.file] - Profile image file uploaded via multer + * @param {Object} res - Express response object + * @returns {Promise} Success response with user data and JWT token + * @throws {Error} 422 - Validation error if request body is invalid + * @throws {Error} 409 - Conflict if user already exists + * @example + * // Register first user (becomes superadmin) + * POST /auth/register + * { + * "firstName": "John", + * "lastName": "Doe", + * "email": "john@example.com", + * "password": "SecurePass123!" + * } + * + * // Register subsequent user (requires invite token) + * POST /auth/register + * { + * "firstName": "Jane", + * "lastName": "Smith", + * "email": "jane@example.com", + * "password": "SecurePass123!", + * "inviteToken": "abc123..." + * } + */ registerUser = asyncHandler( - async (req, res, next) => { + async (req, res) => { if (req.body?.email) { req.body.email = req.body.email?.toLowerCase(); } @@ -43,8 +101,28 @@ class AuthController { "registerUser" ); + /** + * Authenticates a user and returns a JWT token. + * + * @async + * @function loginUser + * @param {Object} req - Express request object + * @param {Object} req.body - Request body containing login credentials + * @param {string} req.body.email - User's email address (will be converted to lowercase) + * @param {string} req.body.password - User's password + * @param {Object} res - Express response object + * @returns {Promise} Success response with user data and JWT token + * @throws {Error} 422 - Validation error if request body is invalid + * @throws {Error} 401 - Unauthorized if credentials are incorrect + * @example + * POST /auth/login + * { + * "email": "john@example.com", + * "password": "SecurePass123!" + * } + */ loginUser = asyncHandler( - async (req, res, next) => { + async (req, res) => { if (req.body?.email) { req.body.email = req.body.email?.toLowerCase(); } @@ -63,8 +141,40 @@ class AuthController { "loginUser" ); + /** + * Updates the current user's profile information. + * + * @async + * @function editUser + * @param {Object} req - Express request object + * @param {Object} req.body - Request body containing user update data + * @param {string} [req.body.firstName] - Updated first name + * @param {string} [req.body.lastName] - Updated last name + * @param {string} [req.body.password] - Current password (required for password change) + * @param {string} [req.body.newPassword] - New password (required for password change) + * @param {boolean} [req.body.deleteProfileImage] - Flag to delete profile image + * @param {Object} [req.file] - New profile image file + * @param {Object} req.user - Current authenticated user (from JWT) + * @param {Object} res - Express response object + * @returns {Promise} Success response with updated user data + * @throws {Error} 422 - Validation error if request body is invalid + * @throws {Error} 403 - Forbidden if current password is incorrect + * @example + * PUT /auth/user + * { + * "firstName": "John Updated", + * "lastName": "Doe Updated" + * } + * + * // Change password + * PUT /auth/user + * { + * "password": "OldPass123!", + * "newPassword": "NewPass123!" + * } + */ editUser = asyncHandler( - async (req, res, next) => { + async (req, res) => { await editUserBodyValidation.validateAsync(req.body); const updatedUser = await this.userService.editUser(req.body, req.file, req.user); @@ -78,8 +188,20 @@ class AuthController { "editUser" ); + /** + * Checks if a superadmin account exists in the system. + * + * @async + * @function checkSuperadminExists + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @returns {Promise} Success response with boolean indicating superadmin existence + * @example + * GET /auth/users/superadmin + * // Response: { "data": true } or { "data": false } + */ checkSuperadminExists = asyncHandler( - async (req, res, next) => { + async (req, res) => { const superAdminExists = await this.userService.checkSuperadminExists(); return res.success({ msg: this.stringService.authAdminExists, @@ -90,8 +212,26 @@ class AuthController { "checkSuperadminExists" ); + /** + * Initiates password recovery process by sending a recovery email. + * + * @async + * @function requestRecovery + * @param {Object} req - Express request object + * @param {Object} req.body - Request body containing email + * @param {string} req.body.email - Email address for password recovery + * @param {Object} res - Express response object + * @returns {Promise} Success response with message ID + * @throws {Error} 422 - Validation error if email is invalid + * @throws {Error} 404 - Not found if user doesn't exist + * @example + * POST /auth/recovery/request + * { + * "email": "john@example.com" + * } + */ requestRecovery = asyncHandler( - async (req, res, next) => { + async (req, res) => { await recoveryValidation.validateAsync(req.body); const email = req?.body?.email; const msgId = await this.userService.requestRecovery(email); @@ -104,8 +244,26 @@ class AuthController { "requestRecovery" ); + /** + * Validates a password recovery token. + * + * @async + * @function validateRecovery + * @param {Object} req - Express request object + * @param {Object} req.body - Request body containing recovery token + * @param {string} req.body.recoveryToken - Recovery token to validate + * @param {Object} res - Express response object + * @returns {Promise} Success response if token is valid + * @throws {Error} 422 - Validation error if token format is invalid + * @throws {Error} 400 - Bad request if token is invalid or expired + * @example + * POST /auth/recovery/validate + * { + * "recoveryToken": "abc123..." + * } + */ validateRecovery = asyncHandler( - async (req, res, next) => { + async (req, res) => { await recoveryTokenBodyValidation.validateAsync(req.body); await this.userService.validateRecovery(req.body.recoveryToken); return res.success({ @@ -116,8 +274,28 @@ class AuthController { "validateRecovery" ); + /** + * Resets user password using a valid recovery token. + * + * @async + * @function resetPassword + * @param {Object} req - Express request object + * @param {Object} req.body - Request body containing new password and recovery token + * @param {string} req.body.password - New password + * @param {string} req.body.recoveryToken - Valid recovery token + * @param {Object} res - Express response object + * @returns {Promise} Success response with user data and JWT token + * @throws {Error} 422 - Validation error if password format is invalid + * @throws {Error} 400 - Bad request if token is invalid or expired + * @example + * POST /auth/recovery/reset + * { + * "password": "NewSecurePass123!", + * "recoveryToken": "abc123..." + * } + */ resetPassword = asyncHandler( - async (req, res, next) => { + async (req, res) => { await newPasswordValidation.validateAsync(req.body); const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken); return res.success({ @@ -129,8 +307,27 @@ class AuthController { "resetPassword" ); + /** + * Deletes the current user's account and associated data. + * + * @async + * @function deleteUser + * @param {Object} req - Express request object + * @param {Object} req.user - Current authenticated user (from JWT) + * @param {string} req.user._id - User ID + * @param {string} req.user.email - User email + * @param {string} req.user.teamId - User's team ID + * @param {Array} req.user.role - User roles + * @param {Object} res - Express response object + * @returns {Promise} Success response confirming user deletion + * @throws {Error} 400 - Bad request if user is demo user + * @throws {Error} 404 - Not found if user doesn't exist + * @example + * DELETE /auth/user + * // Requires JWT authentication + */ deleteUser = asyncHandler( - async (req, res, next) => { + async (req, res) => { await this.userService.deleteUser(req.user); return res.success({ msg: this.stringService.authDeleteUser, @@ -140,8 +337,21 @@ class AuthController { "deleteUser" ); + /** + * Retrieves all users in the system (admin/superadmin only). + * + * @async + * @function getAllUsers + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @returns {Promise} Success response with array of users + * @throws {Error} 403 - Forbidden if user doesn't have admin/superadmin role + * @example + * GET /auth/users + * // Requires JWT authentication with admin/superadmin role + */ getAllUsers = asyncHandler( - async (req, res, next) => { + async (req, res) => { const allUsers = await this.userService.getAllUsers(); return res.success({ msg: this.stringService.authGetAllUsers, @@ -152,8 +362,27 @@ class AuthController { "getAllUsers" ); + /** + * Retrieves a specific user by ID (superadmin only). + * + * @async + * @function getUserById + * @param {Object} req - Express request object + * @param {Object} req.params - URL parameters + * @param {string} req.params.userId - ID of the user to retrieve + * @param {Object} req.user - Current authenticated user (from JWT) + * @param {Array} req.user.role - Current user's roles + * @param {Object} res - Express response object + * @returns {Promise} Success response with user data + * @throws {Error} 422 - Validation error if userId is invalid + * @throws {Error} 403 - Forbidden if user doesn't have superadmin role + * @throws {Error} 404 - Not found if user doesn't exist + * @example + * GET /auth/users/507f1f77bcf86cd799439011 + * // Requires JWT authentication with superadmin role + */ getUserById = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getUserByIdParamValidation.validateAsync(req.params); const userId = req?.params?.userId; const roles = req?.user?.role; @@ -174,8 +403,36 @@ class AuthController { "getUserById" ); + /** + * Updates a specific user by ID (superadmin only). + * + * @async + * @function editUserById + * @param {Object} req - Express request object + * @param {Object} req.params - URL parameters + * @param {string} req.params.userId - ID of the user to update + * @param {Object} req.body - Request body containing user update data + * @param {string} [req.body.firstName] - Updated first name + * @param {string} [req.body.lastName] - Updated last name + * @param {Array} [req.body.role] - Updated user roles + * @param {Object} req.user - Current authenticated user (from JWT) + * @param {string} req.user._id - Current user's ID + * @param {Array} req.user.role - Current user's roles + * @param {Object} res - Express response object + * @returns {Promise} Success response confirming user update + * @throws {Error} 422 - Validation error if parameters or body are invalid + * @throws {Error} 403 - Forbidden if user doesn't have superadmin role + * @throws {Error} 404 - Not found if user doesn't exist + * @example + * PUT /auth/users/507f1f77bcf86cd799439011 + * { + * "firstName": "Updated Name", + * "role": ["admin"] + * } + * // Requires JWT authentication with superadmin role + */ editUserById = asyncHandler( - async (req, res, next) => { + async (req, res) => { const roles = req?.user?.role; if (!roles.includes("superadmin")) { throw createError("Unauthorized", 403);