diff --git a/API/src/app.js b/API/src/app.js
index 27077f2c..88c864d1 100644
--- a/API/src/app.js
+++ b/API/src/app.js
@@ -3,7 +3,6 @@ const morgan = require("morgan");
const cors = require('cors');
require('express-async-errors')
const cookieParser = require('cookie-parser')
-
const errorHandler = require("./middlewares/error_handler");
const app = express();
const session = require("express-session");
diff --git a/API/src/controllers/auth.controllers.js b/API/src/controllers/auth.controllers.js
index 00261fa8..e722a41a 100644
--- a/API/src/controllers/auth.controllers.js
+++ b/API/src/controllers/auth.controllers.js
@@ -2,7 +2,6 @@
* module: Controller
*/
-
/**
* @category Backend API
* @subcategory Controllers
@@ -12,7 +11,7 @@
* The following routes are handled by this module and their corresponding functions:
*
*
- *
+ *
* POST /auth/login
* POST /auth/signup
* POST /auth/addadmin
@@ -29,258 +28,249 @@
* GET /auth/github/callback
* POST /auth/google/callback
* GET /auth/verifyemail/:token
- *
+ *
*/
-
-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 UUID = require("uuid").v4;
+const jwt = require("jsonwebtoken");
+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');
+ 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
- },
- });
+ 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;
+
+ 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
- *
+ *
* 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
- *
+ *
* 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),
- });
+ 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 }
+ );
}
+
+ // 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'));
- }
- };
+ 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);
+ 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,
+ *
+ * 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.
- *
+ * 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';
+ 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")
+ );
+ }
+
+ // 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",
+ });
- // 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'));
+ await session.commitTransaction();
+ session.endSession();
+ });
- // Check if user already exists
- const existing_user = await User.findOne({ email }).populate('status')
- if (existing_user) return handleExistingUser(existing_user)(req, res, next);
+ // Check if request was made by a superadmin
+ if (req.user?.role == "SuperAdmin" && role != "SuperAdmin") {
+ // Activate and verify super admin user
+ new_user.status.isActive = true;
+ new_user.status.isVerified = true;
+ await new_user.status.save();
- 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 } });
- }
+ return res.status(200).json({ success: true, data: { user: new_user } });
+ }
- // Handle user verification
- await handleUnverifiedUser(new_user)(req);
+ // Handle user verification
+ await handleUnverifiedUser(new_user)(req);
- // Return access token
- return res.status(200).json({ success: true, data: { user: new_user } });
-}
+ // 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