Source

API/src/controllers/auth.controllers.js

/**
 * module: Controller
 */


/**
 * @category Backend API
 * @subcategory Controllers
 * @module Auth Controller
 * @description This module contains the controllers for handling user authentication, including login, signup, password reset, super admin activation, and deactivation routes.
 *
 * The following routes are handled by this module and their corresponding functions: </br>
 *
 * </br>
 * 
 * <b>POST</b> /auth/login </br>
 * <b>POST</b> /auth/signup </br>
 * <b>POST</b> /auth/addadmin  </br>
 * <b>GET</b> /auth/superadmin/reqactivation/:email </br>
 * <b>POST</b> /auth/superadmin/activate  </br>
 * <b>GET</b> /auth/superadmin/reqdeactivation/:email  </br>
 * <b>POST</b> /auth/superadmin/deactivate </br>
 * <b>POST</b> /auth/user/activate/:email </br>
 * <b>POST</b> /auth/user/deactivate/:email l </br>
 * <b>POST</b> /auth/forgotpassword </br>
 * <b>POST</b> /auth/resetpassword </br>
 * <b>POST</b> /auth/googlesignin </br>
 * <b>GET</b> /auth/github </br>
 * <b>GET</b> /auth/github/callback </br>
 * <b>POST</b> /auth/google/callback </br>
 * <b>GET</b> /auth/verifyemail/:token </br>
 *  
 */


const UUID = require('uuid').v4;
const jwt = require('jsonwebtoken');
const crypto = require('crypto');

const config = require('../utils/config');
const { sendEmail, EmailMessage } = require('../utils/email/email');
const {
    CustomAPIError,
    BadRequestError,
    UnauthorizedError,
    ForbiddenError,
} = require('../utils/errors');
const { getAuthCodes, getAuthTokens } = require('../utils/token.js');

const { OAuth2Client } = require('google-auth-library');
const { User, Status } = require('../models/user.models');
const { BlacklistedToken, AuthCode, TestAuthToken } = require('../models/token.models');
const Password = require('../models/password.models');
const { default: mongoose } = require('mongoose');

/**
 * Create and send JWT tokens to the client.
 * 
 * @description This function creates a JWT access token and a refresh token and sends them to the client.
 * The access token contains the user's data which will be required for the client to make authorized requests to the API.
 * 
 * @param {MongooseDocument} user - The user object.
 * @param {number} statusCode - The HTTP response status code.
 * @param {ExpressResponseObject} res - The Express response object.
 * @memberof module:Controllers/AuthController
 * @returns {void}
 * 
 * @throws {Error} If error occurs
 */
const returnAuthTokens = async (user, statusCode, res) => {
    console.log(user)
    if (!user.status) user = await User.findById(user._id).populate('status');
    const { access_token, refresh_token } = await getAuthTokens(user.toObject());

    // Remove sensitive data from user object
    user.password = undefined;
    user.passwordConfirm = undefined;
    user.emailVerificationToken = undefined;
    user.passwordResetToken = undefined;
    user.isVerified = undefined;
    user.auth_code = undefined;

    console.log(access_token)

    res.status(statusCode).json({
        success: true,
        data: {
            user,
            access_token,
            refresh_token
        },
    });
};

/**
 * Handle existing unverified user.
 * 
 * @description Ssends new verification email to user if the existing user is unverified</br>
 * 
 * Inside the email, there is a link that the user can click to verify their email address,
 * this link contains a JWT token that is used to verify the user's email address, the token has an expiry date of 1 hour </br>
 * 
 * The token is generated using the getAuthTokens function
 * 
 * @param {MongooseObject} user - Mongoose user object
 * @returns {string} access_token, refresh_token - JWT tokens
 */
const handleUnverifiedUser = function (user) {
    return async function (req) {
        // Generate email verification link 
        const { access_token } = await getAuthTokens(user, 'verification');

        const verification_url = `${config.CLIENT_APP_URL}/api/v1/auth/verifyemail/${access_token}`;

        if (process.env.NODE_ENV == 'test') {
            await TestAuthToken.findOneAndUpdate(
                { user: user._id },
                { access_token },
                { upsert: true }
            )
        }

        //console.log(verification_url)

        // Send verification email
        const message = new EmailMessage(req.query.lang)
        await sendEmail({
            email: user.email,
            subject: 'Verify your email address',
            html: message.emailVerification(user.firstname, verification_url),
        });
    }
};

/**
 * Handle existing user
 * 
 * @description It sends new verification email to user if the existing user is unverified
 * 
 * @param {MongooseObject} user - Mongoose user object
 * @returns {function} - Express middleware function
 * @throws {BadRequestError} - If user is already verified
 *
 */
const handleExistingUser = function (user) {
    return async function (req, res, next) {
        const existing_user = user.toObject();

        // If user is not verified - send verification email
        if (!existing_user.status.isVerified) {
            await handleUnverifiedUser(existing_user)(req);

            // Return access token
            res.status(400).json({
                success: true,
                message: 'User account exists already, verification mail sent to user',
                data: {
                    user: {
                        _id: existing_user._id,
                        firstname: existing_user.firstname,
                        lastname: existing_user.lastname,
                        email: existing_user.email,
                    }
                }
            });
        } else {
            return next(new BadRequestError('User already exists'));
        }
    };
};

exports.passportOauthCallback = function (req, res) {
    returnAuthTokens(req.user, 200, res);
};

/**
 * Signup a new user
 * 
 * @description This function creates a new user and sends a verification email to the user.
 * 
 * The user is created using the User model, and the user's password is hashed using the bcrypt library. 
 * The user is created with the status of unverified, which means that the user cannot login until their email address is verified. 
 * 
 * Each user account has a status document linked to it, 
 * which holds two data fields: isVerified and isActive. By default, isVerified is set to false, which means that the user cannot login until their email address is verified.
 * isActive is set to true for EndUsers, which means that the user account is active. 
 * 
 * For Superadmin accounts, it has to be activated using the superadmin activation route.
 * @see {@link module:controllers/auth~activateSuperAdmin}
 * 
 * 
 * @param {string} role - User role (EndUser, Admin, SuperAdmin)
 * @param {string} email - User email
 * @param {string} password - User password
 * @param {string} passwordConfirm - User password confirmation
 * @param {string} firstname - User firstname
 * @param {string} lastname - User lastname
 * 
 * @returns {object} Object containing the new user object, JWT token, and the status of the request
 * 
 * 
 * // TODO: Add super admin signup
 * 
 * @throws {BadRequestError} if passwordConfirm is not provided
 * @throws {Error} if an error occurs
 */
exports.signup = async (req, res, next) => {
    let { firstname, lastname, email, role, password, passwordConfirm, preferred_language } = req.body;

    // NOTE: Will be handled by mongoose schema validation
    // Check if all required fields are provided
    // if (!firstname || !lastname || !email || !role || !password || !passwordConfirm) {
    //     return next(new BadRequestError('Please provide all required fields'));
    // }

    if (!passwordConfirm) { return next(new BadRequestError('Path `passwordConfirm` is required., Try again')) }
    if (!role) role = 'EndUser';

    // Check if superAdmin tries to create another superadmin from - addAdmin route
    if (role === 'SuperAdmin' && req.user?.role == 'SuperAdmin')
        return next(new BadRequestError('You cannot create a superadmin account'));

    // Check if user already exists
    const existing_user = await User.findOne({ email }).populate('status')
    if (existing_user) return handleExistingUser(existing_user)(req, res, next);

    let new_user;
    const session = await mongoose.startSession();
    await session.withTransaction(async () => {
        await User.create([{ firstname, lastname, email, role, preferred_language }], { session, context: 'query' }).then((user) => { new_user = user[0] });
        await Password.create([{ user: new_user._id, password }], { session, context: 'query' });
        await Status.create([{ user: new_user._id }], { session, context: 'query' })
        await AuthCode.create([{ user: new_user._id }], { session, context: 'query' })

        await session.commitTransaction()
        session.endSession()
    })

    // Check if request was made by a superadmin
    if (req.user?.role == 'SuperAdmin' && role != 'SuperAdmin') {
        // Activate and verify user
        new_user.status.isActive = true;
        new_user.status.isVerified = true;
        await new_user.status.save();

        return res.status(200).json({ success: true, data: { user: new_user } });
    }

    // Handle user verification
    await handleUnverifiedUser(new_user)(req);

    // Return access token
    return res.status(200).json({ success: true, data: { user: new_user } });
}

/**
 * Create a new admin account and send a verification email to the user.
 *
 * This function is only accessible to the superadmin to create a new admin account.
 *
 * @param {Object} req - Express request object
 * @param {Object} req.body - Request body containing user details
 * @param {string} req.body.email - User email
 * @param {string} req.body.password - User password
 * @param {string} req.body.passwordConfirm - User password confirmation
 * @param {string} req.body.firstname - User firstname
 * @param {string} req.body.lastname - User lastname
 *
 * @param {Object} res - Express response object
 * @param {function} next - Express next middleware function
 *
 * @returns {Promise<Object>} - Returns a promise that resolves to an object with the following properties:
 * - user: Mongoose user object
 * - token: JWT token
 * - status: Status of the request
 *
 * @throws {BadRequestError} If email or password is not provided or incorrect.
 * @throws {Error} If an error occurs while creating the user or sending the verification email.
 */
exports.addAdmin = async (req, res, next) => {
    req.body.role = 'Admin';
    this.signup(req, res, next);
}

// Login a user
/**
 * Login a user and return a JWT token.
 *
 * This function logs in a user with their email and password, and returns two JWT tokens:
 * an access token and a refresh token. The access token is used to authenticate the user
 * on subsequent requests, and expires after 15 minutes. The refresh token is used to
 * refresh the access token, and expires after 2 days.
 *
 * @param {object} req - The HTTP request object.
 * @param {object} res - The HTTP response object.
 * @param {function} next - The next middleware function.
 * @param {string} req.body.email - The user's email address.
 * @param {string} req.body.password - The user's password.
 * @returns {object} - The HTTP response object containing a success flag, the user object,
 *                     an access token, and a refresh token.
 * @throws {CustomAPIError} - If the email or password is missing or incorrect, or if the
 *                            email is not verified or the account is not activated.
 * @throws {Error} - If an error occurs.
 */
exports.login = async (req, res, next) => {
    const { email, password } = req.body

    //Check if fields are provided
    if (!email || !password) {
        return next(new BadRequestError('Please provide email and password'))
    }
    //check if email exists
    const currentUser = await User.findOne({ email }).populate('password status')
    //console.log(currentUser);

    //Check if email and password matches
    if (
        !currentUser ||
        !(await currentUser.password.comparePassword(password, currentUser.password))
    ) {
        return next(new BadRequestError('Incorrect email or password'));
    }

    // Check if user is verified
    if (!currentUser.status.isVerified) {
        return next(new BadRequestError('Please verify your email'));
    }

    // Check if user account in acivted
    if (!currentUser.status.isActive) {
        return next(new BadRequestError('Please activate your account'));
    }

    // Get access and refresh token
    const { access_token, refresh_token } = await getAuthTokens(currentUser, 'access');

    currentUser.enrolled_courses = undefined

    // Return access token
    return res.status(200).json({
        success: true,
        data: {
            user: currentUser,
            access_token,
            refresh_token
        }
    });
}

/**
 * Verify a user's email.
 *
 * This function verifies a user's email using a JWT token. If the token is valid and
 * not blacklisted, the user's email is marked as verified in the database. Otherwise,
 * an error is returned.
 *
 * @param {object} req - The HTTP request object.
 * @param {object} res - The HTTP response object.
 * @param {function} next - The next middleware function.
 * @returns {object} - The HTTP response object containing a success flag and a message.
 * @throws {CustomAPIError} - If the token is invalid or expired.
 * @throws {Error} - If an error occurs.
 */
exports.verifyEmail = async (req, res, next) => {
    //  Get token from url
    const { token } = req.params;

    if (!token) {
        return next(BadRequestError('No authentication token provided'))
    }

    //  Verify token
    const payload = jwt.verify(token, config.JWT_EMAILVERIFICATION_SECRET);

    //  Check if token is blacklisted
    const blacklisted_token = await BlacklistedToken.findOne({ token });
    if (blacklisted_token) return next(new UnauthorizedError('Token Invalid or Token Expired, Request for a new verification token'))

    //  Get user from token
    const user = await User.findById(payload.id).populate('status');

    if (!user) {
        return next(new BadRequestError('Token Invalid or Token Expired, Request for a new verification token'))
    }

    user.status.isVerified = true;
    await user.status.save();

    await BlacklistedToken.create({ token });

    return res.status(200).send({ success: true, message: 'Email verified' })
}

/**
 * Request activation for the super admin account.
 *
 * @description This function allows the super admin to request for account activation.
 *
 * The super admin account is not activated by default for security reasons.
 * This function generates two activation codes - one for the super admin and one for the project hosts.
 * The new super admin uses the first activation code to activate the account, 
 * and the project hosts use the second activation code to activate the account.
 * </br>
 * 
 * Once the activation codes are generated, they are sent to the super admin and the project hosts via email.
 * The activation codes will be required to complete the account activation process.
 *
 * @param {string} email - Super admin email.
 *
 * @returns {Object} The status of the request and access token if successful.
 *
 * @throws {CustomAPIError} If the super admin account does not exist.
 * @throws {CustomAPIError} If the super admin account is already active.
 * @throws {Error} If an error occurs during the request.
 */
exports.requestSuperAdminAccountActivation = async (req, res, next) => {
    const email = req.params.email

    // Check if a super admin account exists, and it's not active
    const super_admin = await User.findOne({ email, role: 'SuperAdmin' }).populate('status')
    if (!super_admin) return next(new BadRequestError('Superadmin account does not exist'))
    //console.log(super_admin)

    // Check if account is active 
    if (super_admin.status.isActive) return next(new BadRequestError('Account is already active'))

    // Generate activation codes
    const { activation_code1, activation_code2, activation_code3 } = await getAuthCodes(super_admin._id, 'su_activation')

    // Send activation codes to HOSTs
    sendEmail({
        email: config.HOST_ADMIN_EMAIL1,
        subject: `New super admin activation request for ${super_admin.email}`,
        message: `This is your part of the required activation code ${activation_code1}`
    })
    sendEmail({
        email: config.HOST_ADMIN_EMAIL2,
        subject: `New super admin activation request for ${super_admin.email}`,
        message: `This is your part of the required activation code ${activation_code2}`
    })

    // Send activation code to user
    sendEmail({
        email: super_admin.email,
        subject: `New super admin activation request for ${super_admin.email}`,
        message: `This is your part of the required activation code ${activation_code3}`
    })

    // Get activation access token
    const { access_token } = await getAuthTokens(super_admin._id, 'su_activation')

    // Send response to client
    return res.status(200)
        .send({
            success: true,
            data: {
                access_token,
                message: "Activation codes sent to users email"
            }
        })
}

/**
 * Activates a super admin account.
 *
 * This function activates a super admin account on the MOOCs platform. 
 * The function requires three activation codes to complete the account activation process. 
 * These activation codes are generated when the super admin requests for account activation. 
 * This function is used by the super admin to activate the account.
 *
 * @see {@link module:controllers/auth~requestSuperAdminAccountActivation} for generating the activation codes.
 *
 * @param {Object} req - The request object.
 * @param {Object} res - The response object.
 * @param {function} next - The next middleware function.
 * @param {string} req.body.activation_code1 - The first activation code sent to the new super admin.
 * @param {string} req.body.activation_code2 - The second activation code sent to the project hosts.
 * @param {string} req.body.activation_code3 - The third activation code sent to the project hosts.
 *
 * @returns {Object} The response object containing a `status` field and a success message.
 * @returns {boolean} response.success - The status of the request, which is always `true`.
 * @returns {Object} response.data - The data returned by the function.
 * @returns {string} response.data.message - The success message, which is "Super admin account activated".
 *
 * @throws {BadRequestError} If any of the activation codes are missing.
 * @throws {BadRequestError} If the user making the request is not a super admin.
 * @throws {BadRequestError} If the activation code is invalid.
 * @throws {BadRequestError} If the activation code has expired.
 * @throws {Error} If any other error occurs.
 */
exports.activateSuperAdminAccount = async (req, res, next) => {
    const { activation_code1, activation_code2, activation_code3 } = req.body

    // Check if all activation codes are provided
    if (!activation_code1 || !activation_code2 || !activation_code3) {
        return next(new BadRequestError('Missing required parameter in request body'));
    }

    const admin = await User.findOne({ _id: req.user.id, role: 'SuperAdmin' }).populate('status')
    //console.log(admin)

    // Check if user exists
    if (!admin) {
        throw new BadRequestError('Token Invalid or Token Expired, Request for a new activation token');
    }

    // Find activation code document
    const activation_code = `${activation_code1}-${activation_code2}-${activation_code3}`
    const auth_code = await AuthCode.findOne({ user: admin._id, activation_code })

    // Check if activation code exists
    if (!auth_code) { throw new BadRequestError('Invalid activation code') }

    // Check if activation code has expired
    if (auth_code.expiresIn < Date.now()) {
        throw new BadRequestError('Activation code has expired, request for a new activation code')
    }

    // Activate user
    admin.status.isActive = true;
    await admin.status.save()

    // Blacklist token
    // await BlacklistedToken.create({ token: req.token })

    // Send response to client
    return res.status(200)
        .send({
            success: true,
            data: {
                message: "Super admin account activated"
            }
        })
}

/**
 * Request for super admin account deactivation
 * 
 * @description If a super admin account is deactivated, all project hosts will be notified. 
 * This function generates three deactivation codes and sends them to the super admin and two project hosts via email. 
 *
 * @see {@link module:controllers/auth~deactivateSuperAdminAccount} for deactivating the super admin account. 
 * @param {string} email   - Super admin email
 * 
 * @returns {string} status - Status of the request
 * 
 * @throws {CustomAPIError} if super admin account does not exist
 * @throws {CustomAPIError} if super admin account is already active
 * @throws {Error} if error occurs
 */
exports.requestSuperAdminAccountDeactivation = async (req, res, next) => {
    const email = req.params.email

    // Check if a super admin account exists, and it's not active
    const super_admin = await User.findOne({ email, role: 'SuperAdmin' }).populate('status')
    if (!super_admin) return next(new BadRequestError('Superadmin account does not exist'))

    //console.log(super_admin)
    // Check if account is active 
    if (!super_admin.status.isActive) return next(new BadRequestError('Account is already inactive'))

    // Generate activation codes
    const { deactivation_code1, deactivation_code2, deactivation_code3 } = await getAuthCodes(super_admin._id, 'su_deactivation')

    // Send activation codes to HOSTs
    sendEmail({
        email: config.HOST_ADMIN_EMAIL1,
        subject: `New super admin deactivation request for ${super_admin.email}`,
        message: `This is your part of the required deactivation activation code ${deactivation_code1}`
    })
    sendEmail({
        email: config.HOST_ADMIN_EMAIL2,
        subject: `New super admin activation request for ${super_admin.email}`,
        message: `This is your part of the required deactivation code ${deactivation_code2}`
    })

    // Send activation code to user
    sendEmail({
        email: super_admin.email,
        subject: `New super admin deactivation request for ${super_admin.email}`,
        message: `This is your part of the required deactivation code ${deactivation_code3}`
    })

    // Get activation access token
    const { access_token } = await getAuthTokens(super_admin._id, 'su_deactivation')

    // Send response to client
    return res.status(200)
        .send({
            success: true,
            data: {
                access_token,
                message: "Deactivation codes sent to users email"
            }
        })
}

/**
 * Deactivate super admin account
 * 
 * @description Deactivates super admin account if all activation codes are correct <br>
 * 
 * @param {string} deactivation_code1 - deactivation code 1 sent to new super admin
 * @param {string} deactivation_code2 - deactivation code 2 sent to project hosts
 * @param {string} deactivation_code3 - deactivation code 3 sent to project hosts
 * 
 * @returns {string} status - Status of the request
 * 
 * @throws {BadRequestError} if activation codes are not provided
 * @throws {BadRequestError} if token is invalid or token has expired
 * @throws {BadRequestError} if token is blacklisted
 * @throws {Error} if error occurs

 * Note: To obtain the deactivation code,
 * a request must first be made to the `requestSuperAdminAccountDeactivation()` route. 
 * This route sends an email with the codes to the new 
 * super admin and two other emails with parts of the code to the project hosts.
 * 
 * @see {@link module:controllers/auth~requestSuperAdminAccountDeactivation} for requesting for a deactivation code.
 */
exports.deactivateSuperAdminAccount = async (req, res, next) => {
    const { deactivation_code1, deactivation_code2, deactivation_code3 } = req.body

    // Check if all activation codes are provided
    if (!deactivation_code1 || !deactivation_code2 || !deactivation_code3) {
        return next(new BadRequestError('Missing required parameter in request body'));
    }

    const admin = await User.findOne({ _id: req.user.id, role: 'SuperAdmin' }).populate('status')

    // Check if user exists
    if (!admin) {
        throw new BadRequestError('Token Invalid or Token Expired, Request for a new deactivation token');
    }

    // Find activation code document
    const deactivation_code = `${deactivation_code1}-${deactivation_code2}-${deactivation_code3}`
    const auth_code = await AuthCode.findOne({ user: admin._id, deactivation_code: deactivation_code })

    // Check if activation code exists
    if (!auth_code) { throw new BadRequestError('Invalid deactivation code') }

    // Check if activation code has expired
    if (auth_code.expiresIn < Date.now()) {
        throw new BadRequestError('Deactivation code has expired, request for a new deactivation code')
    }

    // Activate user
    admin.status.isActive = false;
    await admin.status.save();

    // Blacklist token
    await BlacklistedToken.create({ token: req.token })

    // Send response to client
    return res.status(200)
        .send({
            success: true,
            data: {
                message: "Super admin account Deactivated"
            }
        })
}

/**
 * Activate user account
 * 
 * @description Activates a user account if the account exists and it's not already active.
 * 
 * @param {object} req - The HTTP request object
 * @param {object} req.params - The request parameters object
 * @param {string} req.params.email - The email address of the user to activate
 * @param {object} res - The HTTP response object
 * @param {function} next - The next middleware function
 * 
 * @returns {object} - The HTTP response object
 * @returns {boolean} success - Indicates if the request was successful
 * @returns {object} data - An object containing the success message
 * @returns {string} data.message - A message indicating the user account was activated
 * 
 * @throws {BadRequestError} - If the email parameter is missing or invalid
 * @throws {BadRequestError} - If the user account does not exist
 * @throws {BadRequestError} - If the user account is already active
 * @throws {ForbiddenError} - If the user is a super admin (super admin accounts cannot be activated)
 * @throws {Error} - If an unexpected error occurs
 */
exports.activateUserAccount = async (req, res, next) => {
    const email = req.params.email

    // Check if a user account exists, and it's not active
    const user = await User.findOne({ email }).populate('status')
    if (!user) return next(new BadRequestError('User account does not exist'))

    // Check if account is active
    if (user.status.isActive) return next(new BadRequestError('Account is already active'))

    // Check if user is a super admin
    if (user.role === 'superadmin') return next(new ForbiddenError('You cannot activate a super admin account'))

    // Activate user
    user.status.isActive = true;
    await user.status.save();

    // Send response to client
    return res.status(200)
        .send({
            success: true,
            data: {
                message: "User account activated"
            }
        })
}

/**
 * Deactivate user account
 * 
 * @description Deactivates user account if user account exists and it's active
 * 
 * @param {string} email - User email
 * 
 * @returns {Object} response - The HTTP response
 * @returns {boolean} response.success - Indicates if the request was successful
 * @returns {Object} response.data - The response data
 * @returns {string} response.data.message - A message indicating the status of the request
 * 
 * @throws {BadRequestError} If user account does not exist or is already deactivated
 * @throws {ForbiddenError} If user is a SuperAdmin account
 * @throws {Error} If an unexpected error occurs
 */
exports.deactivateUserAccount = async (req, res, next) => {
    const email = req.params.email

    // Check if a user account exists, and it's not active
    const user = await User.findOne({ email }).populate('status')

    // Check if user exists
    if (!user) return next(new BadRequestError('User account does not exist'))

    // Check if users role is SuperAdmin
    if (user.role === 'SuperAdmin') return next(new ForbiddenError('You cannot deactivate a SuperAdmin account'))

    // Check if account is active
    if (!user.status.isActive) return next(new BadRequestError('Account is already deactivated'))

    // Deactivate user
    user.status.isActive = false;
    await user.status.save();

    // Send response to client
    return res.status(200)
        .send({
            success: true,
            data: {
                message: "User account deactivated"
            }
        })
}

/**
 * Sends a password reset code to a user's email.
 *
 * @param {string} email - User's email address.
 *
 * @returns {Object} An object containing a success message and an access token.
 *
 * @throws {BadRequestError} If the required parameter is missing in the request body.
 * @throws {BadRequestError} If the user does not exist.
 */
exports.forgetPassword = async (req, res, next) => {
    const { email } = req.body

    //  Check for missing required field in request body
    if (!email) return next(new BadRequestError('Missing required parameter in request body'));

    const current_user = await User.findOne({ email })
    //console.log(current_user);

    //  Check if user exists
    if (!current_user) return next(new BadRequestError('User does not exist'));

    //  Get password reset code
    const { password_reset_code } = await getAuthCodes(current_user.id, 'password_reset')

    //  Send password reset code to user
    const message = new EmailMessage(req.query.lang)
    sendEmail({
        email: current_user.email,
        subject: 'Password reset for user',
        html: message.passwordReset(current_user.firstname, password_reset_code)
    })

    //  Get access token
    const { access_token } = await getAuthTokens(current_user._id, 'password_reset')

    return res.status(200).send({
        success: true,
        data: {
            message: "Successful, Password reset code sent to users email",
            access_token
        }
    })
}

// Reset Password
/**
 * Reset a user's password.
 *
 * @description Resets the password of the authenticated user if the provided password reset code is valid.<br>
 * 
 * Note: A request has to be made to the {@link module:controllers/AuthController~forgetPassword forgetPassword} 
 * endpoint to get a password reset code. Then the password reset code 
 * is sent to the user's email address. 
 * This endpoint is used to reset the password using the password reset code.
 *
 * @see {@link module:controllers/AuthController~forgetPassword forgetPassword} for more information on how to get a password reset code.
 * @param {Object} req - Express request object.
 * @param {Object} req.body - Request body.
 * @param {string} req.body.new_password - New password.
 * @param {string} req.body.password_reset_code - Password reset code.
 * @param {Object} req.user - Authenticated user object.
 * @param {string} req.user.id - User ID.
 *
 * @returns {Object} Response object.
 * @returns {boolean} Response.success - Indicates if the request was successful.
 * @returns {Object} Response.data - Response data.
 * @returns {string} Response.data.message - Success message.
 *
 * @throws {BadRequestError} If the required parameters are missing in the request body.
 * @throws {BadRequestError} If the user does not exist.
 * @throws {BadRequestError} If the password reset code is invalid.
 * @throws {BadRequestError} If the password reset code has expired.
 * @throws {UnauthorizedError} If the access token has expired or is invalid.
 * @throws {Error} If any other error occurs.
 */
exports.resetPassword = async (req, res, next) => {
    const { new_password, password_reset_code } = req.body

    // Check if new password and password reset code are provided
    if (!new_password || !password_reset_code)
        return next(new BadRequestError('Missing required parameter in request body'));

    // Check if user exists
    const current_user = await (
        await User.findOne({ _id: req.user.id })
    ).populate('auth_codes password');

    if (!current_user) {
        throw new BadRequestError('User does not exist');
    }

    // Check if password reset code is valid
    if (password_reset_code !== current_user.auth_codes.password_reset_code) {
        throw new BadRequestError('Invalid password reset code');
    }

    // Change password
    await current_user.password.updatePassword(new_password, current_user.password);

    // Delete auth code, blacklist jwt token
    BlacklistedToken.create({ token: req.token });
    // BlacklistToken.create({ token: jwtToken })

    return res.status(200).send({
        success: true,
        data: {
            message: "Successfully reset password",
        }
    })
}

// Google Signin
/**
 * Sign in a user using their Google account.
 *
 * @param {Object} req - The request object.
 * @param {string} req.headers.authorization - The authorization header containing the Google auth code.
 *
 * @returns {Object} An object containing the status of the request, the access token, and the refresh token.
 * @returns {string} status - The status of the request.
 * @returns {string} access_token - The access token.
 * @returns {string} refresh_token - The refresh token.
 *
 * @throws {Error} If an error occurs during the sign-in process.
 * @throws {BadRequestError} If the required parameter is missing from the request body.
 * @throws {BadRequestError} If the user does not exist.
 * @throws {BadRequestError} If the user is not verified.
 */
exports.googleSignin = async (req, res, next) => {
    const authorization = req.headers.authorization;
    const code = authorization.split(' ')[1];

    if (!code) {
        return next(new BadRequestError('Missing required params in request body'))
    }

    const client = new OAuth2Client(config.OAUTH_CLIENT_ID, config.OAUTH_CLIENT_SECRET, 'postmessage');

    // Exchange code for tokens
    const { tokens } = await client.getToken(code)

    // Verify id token
    const ticket = await client.verifyIdToken({
        idToken: tokens.id_token,
        audience: config.OAUTH_CLIENT_ID,
    }),
        payload = ticket.getPayload(),
        existing_user = await User.findOne({ email: payload.email }).populate('status')

    // Create new user in db
    const random_str = UUID(); // Random unique str as password, won't be needed for authentication
    if (!existing_user) {
        const user_data = {
            firstname: payload.given_name,
            lastname: payload.family_name,
            email: payload.email,
            role: 'EndUser',
            password: random_str,
            passwordConfirm: random_str,
            googleId: payload.sub,
        };

        const session = await mongoose.startSession();
        let new_user;
        await session.withTransaction(async () => {
            await User.create(
                [{ ...user_data }], { session, context: 'query' }
            ).then((user) => { new_user = user[0] });

            await Password.create([{ user: new_user._id, password: user_data.password }], { session, context: 'query' });
            await Status.create([{ user: new_user._id }], { session, context: 'query' })
            await AuthCode.create([{ user: new_user._id }], { session, context: 'query' })

            await session.commitTransaction()
            session.endSession()
        })

        await returnAuthTokens(new_user, 200, res);
        return
    }

    console.log(existing_user.toObject())

    await returnAuthTokens(existing_user, 200, res)
};

// Get details of logged in user
/**
 * Get data for the currently logged in user.
 * 
 * @param {Object} req - The request object.
 * @param {Object} req.user - The user object set by the `authenticateToken` middleware.
 * @param {string} req.user.id - The ID of the currently logged in user.
 * @param {Object} res - The response object.
 * 
 * @returns {Object} - The response object containing the user data.
 * @returns {string} .status - The status of the response, either "success" or "error".
 * @returns {Object} .data - The data returned by the response.
 * @returns {Object} .data.user - The user object containing the data for the currently logged in user.
 * 
 * @throws {Error} if an error occurs while fetching the user data.
 */
exports.getLoggedInUser = async (req, res, next) => {
    // Check for valid authorization header
    const user = await User.findById(req.user.id);

    return res.status(200).json({
        status: 'success',
        data: {
            user
        }
    })
}