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} - 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 /** @@ -303,49 +293,55 @@ exports.addAdmin = async (req, res, next) => { * @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 - } - }); -} + 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. @@ -362,34 +358,43 @@ exports.login = async (req, res, next) => { * @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' }) -} + // 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. @@ -398,10 +403,10 @@ exports.verifyEmail = async (req, res, next) => { * * 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, + * 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. *
- * + * * 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. * @@ -414,58 +419,66 @@ exports.verifyEmail = async (req, res, next) => { * @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" - } - }) -} + 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 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. @@ -489,111 +502,132 @@ exports.requestSuperAdminAccountActivation = async (req, res, next) => { * @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" - } - }) -} + 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. + * @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" - } - }) -} + 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 @@ -619,65 +653,79 @@ exports.requestSuperAdminAccountDeactivation = async (req, res, next) => { * @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" - } - }) -} + 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 @@ -685,76 +733,82 @@ exports.deactivateSuperAdminAccount = async (req, res, next) => { * @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" - } - }) -} + 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" - } - }) -} + 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. @@ -767,49 +821,58 @@ exports.deactivateUserAccount = async (req, res, next) => { * @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 - } - }) -} + 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.
- * - * 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. + * + * 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. @@ -833,40 +896,45 @@ exports.forgetPassword = async (req, res, next) => { * @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", - } - }) -} + 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 /** @@ -886,89 +954,104 @@ exports.resetPassword = async (req, res, next) => { * @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'); + 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, + }; - // Exchange code for tokens - const { tokens } = await client.getToken(code) + 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(); + }); - // 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 - } + await returnAuthTokens(new_user, 200, res); + return; + } - console.log(existing_user.toObject()) + console.log(existing_user.toObject()); - await returnAuthTokens(existing_user, 200, res) + 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 - } - }) -} - - + // Check for valid authorization header + const user = await User.findById(req.user.id); + + return res.status(200).json({ + status: "success", + data: { + user, + }, + }); +}; diff --git a/API/src/controllers/certificate.controllers.js b/API/src/controllers/certificate.controllers.js index 03617613..533f39cb 100644 --- a/API/src/controllers/certificate.controllers.js +++ b/API/src/controllers/certificate.controllers.js @@ -1,302 +1,302 @@ -/** - * @category Backend API - * @subcategory Controllers - * @module Certificate Controller - * - * @description This module contains the controller methods for the certificate routes. - * - * @requires module:CertificateModel~certificateSchema - * @requires module:CourseReportModel~courseReportSchema - * @requires module:canvas - * - * The following routes are handled by this module:
- * - *
- * - * GET /api/certificate/verify/:sn
- * GET /api/certificate/course/:id
- * GET /api/certificate/:id
- * GET /api/certificate/
- * - */ - -const { Certificate } = require("../models/certificate.models") -const { CourseReport } = require("../models/course.models") - -const fs = require("fs") -const { createCanvas, loadImage } = require("canvas") -const { uploadToCloudinary } = require("../utils/cloudinary") -const { NotFoundError, BadRequestError, ForbiddenError } = require("../utils/errors") - -/** - * Verify Certificate - * - * @description Verify certificate by serial number - * - * @param {string} serial_number - Serial number of certificate to verify - * - * @throws {BadRequestError} if missing param in request body - * @throws {NotFoundError} if certificate not found - * - * @returns {Object} certificate - */ -exports.verifyCertificate = async (req, res, next) => { - const serial_number = req.params.sn - - // Check if missing params - if (!serial_number || serial_number === ":sn") { - return next(new BadRequestError("Missing required param in request body")) - } - - console.log(serial_number) - - // Check if certificate exists - const certificate = await Certificate.findOne({ serial_number: Number(serial_number) }).populate({ - path: "user course", - select: "title description _id firstname lastname email", - }) - if (!certificate) { - return next(new NotFoundError("Certificate not found")) - } - - return res.status(200).json({ - success: true, - data: { - message: "Certificate found", - certificate, - } - }) -} - -/** - * Get certificate for course - * - * @description Get certificate for course if it exists, else create it - * - * @param {string} course_id - Id of course to get certificate for - * - * @throws {BadRequestError} if missing param in request body - * @throws {NotFoundError} if course report not found - * @throws {ForbiddenError} if course not completed - * - * @returns {Object} certificate - */ -exports.getCertificateForCourse = async (req, res, next) => { - const course_id = req.params.id - - // Check if missing params - if (!course_id || course_id === ":id") { - return next(new BadRequestError("Missing required param in request body")) - } - - // Check if course exists - const student_course_report = await CourseReport.findOne({ course: course_id, user: req.user.id }) - if (!student_course_report) { - return next(new NotFoundError("User has not enrolled for course")) - } - - // Check if user has completed course - if (!student_course_report.isCompleted) { - // return next(new ForbiddenError("Course not completed")) - } - - // Check if certificate exists - const populate_conf = { - path: "user course", - select: "title description _id firstname lastname email", - } - if (student_course_report.certificate) { - return res.status(200).json({ - success: true, - message: "Certificate found", - certificate: await student_course_report.certificate.populate(populate_conf), - }) - } - - // Create certificate - const certificate = await this.issueCertificate(student_course_report) - - return res.status(200).json({ - success: true, - data: { - message: "Certificate issued", - certificate: await certificate.populate(populate_conf), - } - }) -} - -/** - * Get all certificates for user - * - * @description Get all users certificates - * - * @returns {Object} certificates - * - */ -exports.getAllUsersCertificates = async (req, res, next) => { - const populate_conf = { - path: "user course", - select: "title description _id firstname lastname email", - } - const certificates = await Certificate.find({ user: req.user.id }).populate(populate_conf) - - return res.status(200).json({ - success: true, - data: { - certificates, - } - }) -} - -/** - * Create certificate for student - * - * @description Create certificate for student after completing a course - * - * @param {string} student_course_report_id - Id of student course report - * - * @param {MongooseDocument} student_course_report - * @returns {MongooseDocument} certificate - */ -async function createCertificate(student_course_report) { - const certificate = new Certificate({ - user: student_course_report.user, - course: student_course_report.course, - course_report: student_course_report._id, - serial_number: Date.now(), - }); - - // Create certificate image - const sample_certificate_path = "src/assets/sample_certificate.png" - const image = await loadImage(sample_certificate_path) - - // Create canvas - const canvas = createCanvas(image.width, image.height) - const context = canvas.getContext("2d") - - // Draw image on canvas - context.drawImage(image, 0, 0, image.width, image.height) - - // Draw text on canvas - context.font = "italic 80px Arial" - context.fillStyle = "black" - context.textAlign = "center" - - // Add user's fullname - let users_firstname = student_course_report.user.firstname, - users_lastname = student_course_report.user.lastname - - // Capitalize first letter of firstname and lastname - users_firstname = users_firstname.charAt(0).toUpperCase() + users_firstname.slice(1) - users_lastname = users_lastname.charAt(0).toUpperCase() + users_lastname.slice(1) - - const users_fullmame = `${users_firstname} ${users_lastname}` - context.fillText(users_fullmame, 1000, 900) - - // Add course title - const course_title = student_course_report.course.title - context.fillText(course_title, 1000, 100) - - // Add certificate id - const certificate_id = certificate.serial_number.toString() - context.font = "italic 30px Arial" - context.fillText(certificate_id, 1000, image.height - 60) - - // Save image to file - const modified_image = canvas.toBuffer("image/png") - const image_path = "src/assets/certificate" + `_${certificate.serial_number.toString()}.png` - fs.writeFileSync(image_path, modified_image) - - return certificate -} - -/** - * Issue certificate to student - * - * @param {ObjectId} report_id - * @returns certificate - * - * @throws {Error} if missing Course report not found - * @throws {Error} if student has not completed course - */ -exports.issueCertificate = async (report_id) => { - const student_course_report = await CourseReport.findById(report_id).populate({ - path: "course", - select: "title description _id", - populate: { - path: "author", - select: "name email _id", - }, - }).populate('user certificate'); - - // Check if course report exists - if (!student_course_report) { - throw new BadRequestError("User has not enrolled for course"); - } - - // Check if student has already been issued a certificate - if (student_course_report.certificate) { - return student_course_report.certificate - } - - // Check if student completed course - if (!student_course_report.isCompleted) { - throw new BadRequestError("Course not completed"); - } - - let certificate = await createCertificate(student_course_report) - - // Upload file to cloudinary - const file_url = await uploadToCloudinary({ - path: "src/assets/certificate" + `_${certificate.serial_number.toString()}.png`, - file_name: `certificate_${certificate._id}`, - destination_path: `course_${certificate.course._id}/user_${certificate.user._id}`, - }); - - // Delete file from local storage - fs.unlinkSync("src/assets/certificate" + `_${certificate.serial_number.toString()}.png`) +// /** +// * @category Backend API +// * @subcategory Controllers +// * @module Certificate Controller +// * +// * @description This module contains the controller methods for the certificate routes. +// * +// * @requires module:CertificateModel~certificateSchema +// * @requires module:CourseReportModel~courseReportSchema +// * @requires module:canvas +// * +// * The following routes are handled by this module:
+// * +// *
+// * +// * GET /api/certificate/verify/:sn
+// * GET /api/certificate/course/:id
+// * GET /api/certificate/:id
+// * GET /api/certificate/
+// * +// */ + +// const { Certificate } = require("../models/certificate.models") +// const { CourseReport } = require("../models/course.models") + +// const fs = require("fs") +// const { createCanvas, loadImage } = require("canvas") +// const { uploadToCloudinary } = require("../utils/cloudinary") +// const { NotFoundError, BadRequestError, ForbiddenError } = require("../utils/errors") + +// /** +// * Verify Certificate +// * +// * @description Verify certificate by serial number +// * +// * @param {string} serial_number - Serial number of certificate to verify +// * +// * @throws {BadRequestError} if missing param in request body +// * @throws {NotFoundError} if certificate not found +// * +// * @returns {Object} certificate +// */ +// exports.verifyCertificate = async (req, res, next) => { +// const serial_number = req.params.sn + +// // Check if missing params +// if (!serial_number || serial_number === ":sn") { +// return next(new BadRequestError("Missing required param in request body")) +// } + +// console.log(serial_number) + +// // Check if certificate exists +// const certificate = await Certificate.findOne({ serial_number: Number(serial_number) }).populate({ +// path: "user course", +// select: "title description _id firstname lastname email", +// }) +// if (!certificate) { +// return next(new NotFoundError("Certificate not found")) +// } + +// return res.status(200).json({ +// success: true, +// data: { +// message: "Certificate found", +// certificate, +// } +// }) +// } + +// /** +// * Get certificate for course +// * +// * @description Get certificate for course if it exists, else create it +// * +// * @param {string} course_id - Id of course to get certificate for +// * +// * @throws {BadRequestError} if missing param in request body +// * @throws {NotFoundError} if course report not found +// * @throws {ForbiddenError} if course not completed +// * +// * @returns {Object} certificate +// */ +// exports.getCertificateForCourse = async (req, res, next) => { +// const course_id = req.params.id + +// // Check if missing params +// if (!course_id || course_id === ":id") { +// return next(new BadRequestError("Missing required param in request body")) +// } + +// // Check if course exists +// const student_course_report = await CourseReport.findOne({ course: course_id, user: req.user.id }) +// if (!student_course_report) { +// return next(new NotFoundError("User has not enrolled for course")) +// } + +// // Check if user has completed course +// if (!student_course_report.isCompleted) { +// // return next(new ForbiddenError("Course not completed")) +// } + +// // Check if certificate exists +// const populate_conf = { +// path: "user course", +// select: "title description _id firstname lastname email", +// } +// if (student_course_report.certificate) { +// return res.status(200).json({ +// success: true, +// message: "Certificate found", +// certificate: await student_course_report.certificate.populate(populate_conf), +// }) +// } + +// // Create certificate +// const certificate = await this.issueCertificate(student_course_report) + +// return res.status(200).json({ +// success: true, +// data: { +// message: "Certificate issued", +// certificate: await certificate.populate(populate_conf), +// } +// }) +// } + +// /** +// * Get all certificates for user +// * +// * @description Get all users certificates +// * +// * @returns {Object} certificates +// * +// */ +// exports.getAllUsersCertificates = async (req, res, next) => { +// const populate_conf = { +// path: "user course", +// select: "title description _id firstname lastname email", +// } +// const certificates = await Certificate.find({ user: req.user.id }).populate(populate_conf) + +// return res.status(200).json({ +// success: true, +// data: { +// certificates, +// } +// }) +// } + +// /** +// * Create certificate for student +// * +// * @description Create certificate for student after completing a course +// * +// * @param {string} student_course_report_id - Id of student course report +// * +// * @param {MongooseDocument} student_course_report +// * @returns {MongooseDocument} certificate +// */ +// async function createCertificate(student_course_report) { +// const certificate = new Certificate({ +// user: student_course_report.user, +// course: student_course_report.course, +// course_report: student_course_report._id, +// serial_number: Date.now(), +// }); + +// // Create certificate image +// const sample_certificate_path = "src/assets/sample_certificate.png" +// const image = await loadImage(sample_certificate_path) + +// // Create canvas +// const canvas = createCanvas(image.width, image.height) +// const context = canvas.getContext("2d") + +// // Draw image on canvas +// context.drawImage(image, 0, 0, image.width, image.height) + +// // Draw text on canvas +// context.font = "italic 80px Arial" +// context.fillStyle = "black" +// context.textAlign = "center" + +// // Add user's fullname +// let users_firstname = student_course_report.user.firstname, +// users_lastname = student_course_report.user.lastname + +// // Capitalize first letter of firstname and lastname +// users_firstname = users_firstname.charAt(0).toUpperCase() + users_firstname.slice(1) +// users_lastname = users_lastname.charAt(0).toUpperCase() + users_lastname.slice(1) + +// const users_fullmame = `${users_firstname} ${users_lastname}` +// context.fillText(users_fullmame, 1000, 900) + +// // Add course title +// const course_title = student_course_report.course.title +// context.fillText(course_title, 1000, 100) + +// // Add certificate id +// const certificate_id = certificate.serial_number.toString() +// context.font = "italic 30px Arial" +// context.fillText(certificate_id, 1000, image.height - 60) + +// // Save image to file +// const modified_image = canvas.toBuffer("image/png") +// const image_path = "src/assets/certificate" + `_${certificate.serial_number.toString()}.png` +// fs.writeFileSync(image_path, modified_image) + +// return certificate +// } + +// /** +// * Issue certificate to student +// * +// * @param {ObjectId} report_id +// * @returns certificate +// * +// * @throws {Error} if missing Course report not found +// * @throws {Error} if student has not completed course +// */ +// exports.issueCertificate = async (report_id) => { +// const student_course_report = await CourseReport.findById(report_id).populate({ +// path: "course", +// select: "title description _id", +// populate: { +// path: "author", +// select: "name email _id", +// }, +// }).populate('user certificate'); + +// // Check if course report exists +// if (!student_course_report) { +// throw new BadRequestError("User has not enrolled for course"); +// } + +// // Check if student has already been issued a certificate +// if (student_course_report.certificate) { +// return student_course_report.certificate +// } + +// // Check if student completed course +// if (!student_course_report.isCompleted) { +// throw new BadRequestError("Course not completed"); +// } + +// let certificate = await createCertificate(student_course_report) + +// // Upload file to cloudinary +// const file_url = await uploadToCloudinary({ +// path: "src/assets/certificate" + `_${certificate.serial_number.toString()}.png`, +// file_name: `certificate_${certificate._id}`, +// destination_path: `course_${certificate.course._id}/user_${certificate.user._id}`, +// }); + +// // Delete file from local storage +// fs.unlinkSync("src/assets/certificate" + `_${certificate.serial_number.toString()}.png`) - // Save file url to database - certificate.certificate_url = file_url - certificate = await certificate.save() - - return certificate -} - -/** - * Get certificate data - * - * @description Get certificate data - * - * @param {string} certificate_id - Id of certificate - * - * @returns {Object} certificate - * - * @throws {BadRequestError} if missing certificate_id - * @throws {NotfoundError} if certificate not found - */ -exports.getCertificateData = async (req, res, next) => { - const certificate_id = req.params.id - - // Check if missing params - if (!certificate_id || certificate_id === ":id") { - return next(new BadRequestError("Missing required param in request body")) - } - - // Check if certificate exists - const certificate = await Certificate.findById(certificate_id).populate({ - path: "course", - select: "title description _id", - }).populate('user'); - - if (!certificate) { - return next(new NotFoundError('Certificate not found')) - } - - return res.status(200).json({ - success: true, - data: { - certificate, - } - }) -} \ No newline at end of file +// // Save file url to database +// certificate.certificate_url = file_url +// certificate = await certificate.save() + +// return certificate +// } + +// /** +// * Get certificate data +// * +// * @description Get certificate data +// * +// * @param {string} certificate_id - Id of certificate +// * +// * @returns {Object} certificate +// * +// * @throws {BadRequestError} if missing certificate_id +// * @throws {NotfoundError} if certificate not found +// */ +// exports.getCertificateData = async (req, res, next) => { +// const certificate_id = req.params.id + +// // Check if missing params +// if (!certificate_id || certificate_id === ":id") { +// return next(new BadRequestError("Missing required param in request body")) +// } + +// // Check if certificate exists +// const certificate = await Certificate.findById(certificate_id).populate({ +// path: "course", +// select: "title description _id", +// }).populate('user'); + +// if (!certificate) { +// return next(new NotFoundError('Certificate not found')) +// } + +// return res.status(200).json({ +// success: true, +// data: { +// certificate, +// } +// }) +// } \ No newline at end of file diff --git a/API/src/routes/auth.routes.js b/API/src/routes/auth.routes.js index 98145ab6..d0a6f5ab 100644 --- a/API/src/routes/auth.routes.js +++ b/API/src/routes/auth.routes.js @@ -21,8 +21,6 @@ passport.deserializeUser(function (id, done) { router .post('/signup', authController.signup) - .post('/addadmin', rbac('SuperAdmin'), authController.addAdmin) - // SuperAdmin Account Activation/Deactivation .get('/superadmin/reqactivation/:email', authController.requestSuperAdminAccountActivation) .post('/superadmin/activate', basicAuth('su_activation'), authController.activateSuperAdminAccount) diff --git a/API/src/routes/certificate.routes.js b/API/src/routes/certificate.routes.js index 9e366699..845e1296 100644 --- a/API/src/routes/certificate.routes.js +++ b/API/src/routes/certificate.routes.js @@ -1,20 +1,20 @@ -const express = require('express') -const router = express.Router() +// const express = require('express') +// const router = express.Router() -const { - verifyCertificate, getCertificateForCourse, - getAllUsersCertificates, getCertificateData -} = require('../controllers/certificate.controllers') +// const { +// verifyCertificate, getCertificateForCourse, +// getAllUsersCertificates, getCertificateData +// } = require('../controllers/certificate.controllers') -const { basicAuth } = require('../middlewares/auth') -const permit = require('../middlewares/permission_handler') +// const { basicAuth } = require('../middlewares/auth') +// const permit = require('../middlewares/permission_handler') -router.use(basicAuth(), permit('Admin SuperAdmin EndUser')) +// router.use(basicAuth(), permit('Admin SuperAdmin EndUser')) -router - .post('/verify/:sn', verifyCertificate) - .get('/course/:id', getCertificateForCourse) - .get('/:id', getCertificateData) - .get('/', getAllUsersCertificates) +// router +// .post('/verify/:sn', verifyCertificate) +// .get('/course/:id', getCertificateForCourse) +// .get('/:id', getCertificateData) +// .get('/', getAllUsersCertificates) -module.exports = router \ No newline at end of file +// module.exports = router \ No newline at end of file diff --git a/API/src/routes/routes_handler.js b/API/src/routes/routes_handler.js index 53c638fc..2beff604 100644 --- a/API/src/routes/routes_handler.js +++ b/API/src/routes/routes_handler.js @@ -4,7 +4,7 @@ const courseSectionRoute = require('./coursesection.routes') const exerciseRoute = require("./exercise.routes") const questionRoute = require("./question.routes") const textmaterialRoute = require("./textmaterial.routes") -const certificateRoute = require("./certificate.routes") +// const certificateRoute = require("./certificate.routes") // Route path format should start with /api/v1/ module.exports = function (app) { @@ -14,6 +14,6 @@ module.exports = function (app) { app.use('/api/v1/exercise', exerciseRoute) app.use('/api/v1/question', questionRoute) app.use('/api/v1/textmaterial', textmaterialRoute) - app.use('/api/v1/certificate', certificateRoute) + // app.use('/api/v1/certificate', certificateRoute) // app.use('/api/v1/course', courseRoute) } \ No newline at end of file diff --git a/API/src/seeders/index.js b/API/src/seeders/index.js index 4f0a3651..0c9c5678 100644 --- a/API/src/seeders/index.js +++ b/API/src/seeders/index.js @@ -1,193 +1,265 @@ -require('dotenv').config({ path: `${__dirname}/../.env.dev` }); - -const mongoose = require('mongoose'); -const { Course, CourseSection, Question, Video, Exercise, TextMaterial } = require('../models/course.models'); -const { videos, exercises, text_materials, course_sections, course, questions } = require('./settings'); -const { User, Status } = require('../models/user.models'); -const Password = require('../models/password.models'); - - -mongoose.set('strictQuery', false); +require("dotenv").config({ path: `${__dirname}/../.env.dev` }); + +const mongoose = require("mongoose"); +const { + Course, + CourseSection, + Question, + Video, + Exercise, + TextMaterial, +} = require("../models/course.models"); +const { + videos, + exercises, + text_materials, + course_sections, + course, + questions, +} = require("./settings"); +const { User, Status } = require("../models/user.models"); +const Password = require("../models/password.models"); + +mongoose.set("strictQuery", false); // Function to connect to the database async function connectToDatabase() { - try { - const MONGO_URL = process.env.MONGO_URI_DEV + try { + const MONGO_URL = process.env.MONGO_URI_DEV; - console.log("Connecting to local database..."); + console.log("Connecting to local database..."); - await mongoose.connect(MONGO_URL); - - console.log("Connected to local database successfully"); - } catch (error) { - console.log("'[Error] - Error connecting to local database"); - process.exit(1); - } + await mongoose.connect(MONGO_URL); + + console.log("Connected to local database successfully"); + } catch (error) { + console.log("'[Error] - Error connecting to local database"); + process.exit(1); + } } async function createTestUser() { - // Get randdom email - const random_email = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + '@gmail.com'; - - const user_data = { - email: random_email, - password: 'testpassword', - firstname: 'Test', - lastname: 'User', - role: 'EndUser', - } + // Get randdom email + const random_email = + Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15) + + "@gmail.com"; - const user = await User.create(user_data); + const user_data = { + email: random_email, + password: "testpassword", + firstname: "Test", + lastname: "User", + role: "EndUser", + }; - await Password.create({ user: user._id, password: user_data.password }); + const user = await User.create(user_data); - await Status.create({ user: user._id, isVerified: true, isActive: true }); + await Password.create({ user: user._id, password: user_data.password }); - return user_data + await Status.create({ user: user._id, isVerified: true, isActive: true }); + + return user_data; } +exports.createSuperAdmin = async () => { + try { + let userCount = await User.estimatedDocumentCount(); + if (userCount === 0) { + const user_data = { + email: process.env.ADMIN_EMAIL, + password: process.env.ADMIN_PASSWORD, + firstname: "Admin-Mooc", + lastname: "Admin", + role: "SuperAdmin", + }; + const user = await User.create(user_data); + await Password.create({ user: user._id, password: user_data.password }); + await Status.create({ user: user._id, isVerified: true, isActive: true }); + console.log("Super Admin created"); + return user_data; + } + } catch (error) { + console.log("Error creating default admin", error); + } +}; + // Function to enroll a user for a course async function enrollUserForCourse(user, course) { - await Course.updateOne( - { _id: course._id }, - { $addToSet: { enrolled_users: user._id } } - ); + await Course.updateOne( + { _id: course._id }, + { $addToSet: { enrolled_users: user._id } } + ); } // Function to enroll all users for all courses // so that all users can access all courses async function enrollAllUsersForAllCourses() { - const users = await User.find({ role: 'EndUser' }); - const courses = await Course.find(); + const users = await User.find({ role: "EndUser" }); + const courses = await Course.find(); - for (let i = 0; i < users.length; i++) { - for (let j = 0; j < courses.length; j++) { - await enrollUserForCourse(users[i], courses[j]); - } + for (let i = 0; i < users.length; i++) { + for (let j = 0; j < courses.length; j++) { + await enrollUserForCourse(users[i], courses[j]); } + } } // Function to create a new course async function createCourse() { - // Convert preview image path to absolute path - course.preview_image = 'https://res.cloudinary.com/dipyrsqvy/image/upload/v1679875257/courses/preview_images/course_preview_6420dcd1283f2c65f97b674c.jpg'; + // Convert preview image path to absolute path + course.preview_image = + "https://res.cloudinary.com/dipyrsqvy/image/upload/v1679875257/courses/preview_images/course_preview_6420dcd1283f2c65f97b674c.jpg"; - const new_course = await Course.create(course); - console.log('Course created successfully'); - return new_course; + const new_course = await Course.create(course); + console.log("Course created successfully"); + return new_course; } // Function to create course sections for a given course async function createCourseSections(new_course) { - for (let i = 0; i < course_sections.length; i++) { - console.log('creating new course section ' + i) + for (let i = 0; i < course_sections.length; i++) { + console.log("creating new course section " + i); - const new_course_section = await CourseSection.create({ - ...course_sections[i], - course: new_course._id, - }); + const new_course_section = await CourseSection.create({ + ...course_sections[i], + course: new_course._id, + }); - await createVideos(new_course, new_course_section, i); - await createExercises(new_course, new_course_section, i); - await createTextMaterials(new_course, new_course_section, i); + await createVideos(new_course, new_course_section, i); + await createExercises(new_course, new_course_section, i); + await createTextMaterials(new_course, new_course_section, i); - console.log('[OK] - course section ' + i + ' created successfully') - } + console.log("[OK] - course section " + i + " created successfully"); + } } // Function to create videos for a given course section -async function createVideos(new_course, new_course_section, course_section_index) { - for (let j = 0; j < videos.length; j++) { - if (videos[j].course_section === course_section_index) { - console.log('creating new video ' + j + ' for course section ' + course_section_index + '') - await Video.create({ - ...videos[j], - course_section: new_course_section._id, - course: new_course._id, - }); - console.log('[OK] - video ' + j + ' created successfully') - } +async function createVideos( + new_course, + new_course_section, + course_section_index +) { + for (let j = 0; j < videos.length; j++) { + if (videos[j].course_section === course_section_index) { + console.log( + "creating new video " + + j + + " for course section " + + course_section_index + + "" + ); + await Video.create({ + ...videos[j], + course_section: new_course_section._id, + course: new_course._id, + }); + console.log("[OK] - video " + j + " created successfully"); } + } } // Function to create exercises for a given course section -async function createExercises(new_course, new_course_section, course_section_index) { - for (let j = 0; j < exercises.length; j++) { - let new_exercise; - - // Create exercise if it belongs to the current course section - if (exercises[j].course_section === course_section_index) { - console.log('creating new exercise ' + j + ' for course section ' + course_section_index + '') - - new_exercise = await Exercise.create({ - ...exercises[j], - course_section: new_course_section._id, - course: new_course._id, - }); - - await createQuestions(new_course, new_exercise, j); - - console.log('[OK] - exercise ' + j + ' created successfully') - } +async function createExercises( + new_course, + new_course_section, + course_section_index +) { + for (let j = 0; j < exercises.length; j++) { + let new_exercise; + + // Create exercise if it belongs to the current course section + if (exercises[j].course_section === course_section_index) { + console.log( + "creating new exercise " + + j + + " for course section " + + course_section_index + + "" + ); + + new_exercise = await Exercise.create({ + ...exercises[j], + course_section: new_course_section._id, + course: new_course._id, + }); + + await createQuestions(new_course, new_exercise, j); + + console.log("[OK] - exercise " + j + " created successfully"); } + } } // Function to create questions for a given exercise async function createQuestions(new_course, new_exercise, exercise_index) { - for (let k = 0; k < questions.length; k++) { - console.log('creating new question ' + k + ' for exercise ' + exercise_index + '') - if (questions[k].exercise === exercise_index) { - await Question.create({ - ...questions[k], - exercise: new_exercise._id, - course: new_course._id, - }); - } - console.log('[OK] - question ' + k + ' created successfully') + for (let k = 0; k < questions.length; k++) { + console.log( + "creating new question " + k + " for exercise " + exercise_index + "" + ); + if (questions[k].exercise === exercise_index) { + await Question.create({ + ...questions[k], + exercise: new_exercise._id, + course: new_course._id, + }); } + console.log("[OK] - question " + k + " created successfully"); + } } // This function creates text materials for each course section in the database -async function createTextMaterials(new_course, new_course_section, course_section_index) { - for (let j = 0; j < text_materials.length; j++) { - // Check if the text material is for the current course section being created - if (text_materials[j].course_section === course_section_index) { - // Set the file URL for the text material - const file_url = "https://res.cloudinary.com/dipyrsqvy/image/upload/v1679254945/course_6411dbb7d07a77d6c06a44f3/coursesection_6411dc27d07a77d6c06a454a/textmaterial_641765bcf7e6c01b997c0b42_resume%20test.pdf.pdf" - - console.log('creating new text material ' + j + ' for course section ' + course_section_index + '') - - // Create the text material in the database - await TextMaterial.create({ - ...text_materials[j], - course_section: new_course_section._id, - course: new_course._id, - file_url - }); - - console.log('[OK] - text material ' + j + ' created successfully') - } +async function createTextMaterials( + new_course, + new_course_section, + course_section_index +) { + for (let j = 0; j < text_materials.length; j++) { + // Check if the text material is for the current course section being created + if (text_materials[j].course_section === course_section_index) { + // Set the file URL for the text material + const file_url = + "https://res.cloudinary.com/dipyrsqvy/image/upload/v1679254945/course_6411dbb7d07a77d6c06a44f3/coursesection_6411dc27d07a77d6c06a454a/textmaterial_641765bcf7e6c01b997c0b42_resume%20test.pdf.pdf"; + + console.log( + "creating new text material " + + j + + " for course section " + + course_section_index + + "" + ); + + // Create the text material in the database + await TextMaterial.create({ + ...text_materials[j], + course_section: new_course_section._id, + course: new_course._id, + file_url, + }); + + console.log("[OK] - text material " + j + " created successfully"); } + } } // This function seeds the database with courses, course sections, and text materials async function seedDatabase() { - try { - // Connect to the database - await connectToDatabase(); - // Create a new course in the database - const new_course = await createCourse(); - // Create course sections for the new course - await createCourseSections(new_course); + try { + // Connect to the database + await connectToDatabase(); + // Create a new course in the database + const new_course = await createCourse(); + // Create course sections for the new course + await createCourseSections(new_course); - const new_user = await createTestUser(); + const new_user = await createTestUser(); - await enrollAllUsersForAllCourses(); + await enrollAllUsersForAllCourses(); - console.log('Database seeded successfully'); + console.log("Database seeded successfully"); - console.log( - ` + console.log( + ` User created successfully Email: ${new_user.email} @@ -195,19 +267,16 @@ async function seedDatabase() { Use the above credentials to log into the client app ` - ) - - } catch (error) { - console.log(error); - } finally { - mongoose.disconnect(); - process.exit(0); - } + ); + } catch (error) { + console.log(error); + } finally { + mongoose.disconnect(); + process.exit(0); + } } -seedDatabase(); - // Handle any unhandled promise rejections by logging them -process.on('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); -}); \ No newline at end of file +process.on("unhandledRejection", (reason, p) => { + console.log("Unhandled Rejection at: Promise", p, "reason:", reason); +}); diff --git a/API/src/server.js b/API/src/server.js index 561c2c33..397abb64 100644 --- a/API/src/server.js +++ b/API/src/server.js @@ -1,43 +1,35 @@ -const environments = ['dev', 'test', 'prod'] -const NODE_ENV = process.env.NODE_ENV +const environments = ["dev", "test", "prod"]; +const NODE_ENV = process.env.NODE_ENV; if (environments.includes(NODE_ENV)) { - require('dotenv').config({ path: `${__dirname}/.env.${NODE_ENV}` }); + require("dotenv").config({ path: `${__dirname}/.env.${NODE_ENV}` }); } else { - require('dotenv').config({ path: `${__dirname}/.env` }); + require("dotenv").config({ path: `${__dirname}/.env` }); } // Project config variables -const config = require('./utils/config'); - -const connectDatabase = require('./db/connectDB'); +const config = require("./utils/config"); +const connectDatabase = require("./db/connectDB"); +const { createSuperAdmin } = require("./seeders"); function getMongoURI() { - return config['MONGO_URI' + (environments.includes(NODE_ENV) ? `_${NODE_ENV.toUpperCase()}` : '')]; + return config[ + "MONGO_URI" + + (environments.includes(NODE_ENV) ? `_${NODE_ENV.toUpperCase()}` : "") + ]; } -const app = require('./app'); -const { default: axios } = require('axios'); +const app = require("./app"); +const { default: axios } = require("axios"); const PORT = config.PORT; async function start() { - try { - await connectDatabase(getMongoURI()); - - app.listen(PORT, function () { - console.log(`Server is running on port ${PORT}....`); - }); - - // const service_data = { - // port: parseInt(PORT), - // name: 'moocs', - // version: '1' - // } - // await axios - // .post('http://localhost:5500/host/registry/service/register', service_data) - // .then(res => res) - // .catch(err => err) - - } catch (error) { - console.log(error); - } + try { + await connectDatabase(getMongoURI()); + await createSuperAdmin(); + app.listen(PORT, function () { + console.log(`Server is running on port ${PORT}....`); + }); + } catch (error) { + console.log(error); + } } start(); diff --git a/API/src/utils/config.js b/API/src/utils/config.js index 6fa569d3..7864a1be 100644 --- a/API/src/utils/config.js +++ b/API/src/utils/config.js @@ -1,109 +1,113 @@ const MONGO_URI = process.env.MONGO_URI, - MONGO_URI_TEST = process.env.MONGO_URI_TEST, - MONGO_URI_DEV = process.env.MONGO_URI_DEV, - MONGO_URI_PROD = process.env.MONGO_URI_PROD; + MONGO_URI_TEST = process.env.MONGO_URI_TEST, + MONGO_URI_DEV = process.env.MONGO_URI_DEV, + MONGO_URI_PROD = process.env.MONGO_URI_PROD; -const PORT = process.env.PORT || 5555; +const PORT = process.env.PORT; /* JWT TOKENS */ const JWT_SECRET = process.env.JWT_ACCESS_SECRET, - JWT_ACCESS_SECRET = process.env.JWT_ACCESS_SECRET, - JWT_SECRET_EXP = process.env.JWT_ACCESS_EXP, - JWT_ACCESS_EXP = process.env.JWT_ACCESS_EXP, - JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET, - JWT_REFRESH_EXP = process.env.JWT_REFRESH_EXP, - JWT_PASSWORDRESET_SECRET = process.env.JWT_PASSWORDRESET_SECRET, - JWT_PASSWORDRESET_EXP = process.env.JWT_PASSWORDRESET_EXP, - JWT_EMAILVERIFICATION_SECRET = process.env.JWT_EMAILVERIFICATION_SECRET, - JWT_EMAILVERIFICATION_EXP = process.env.JWT_EMAILVERIFICATION_EXP, - JWT_SUPERADMINACTIVATION_SECRET = process.env.JWT_SUPERADMINACTIVATION_SECRET, - JWT_SUPERADMINACTIVATION_EXP = process.env.JWT_SUPERADMINACTIVATION_EXP; + JWT_ACCESS_SECRET = process.env.JWT_ACCESS_SECRET, + JWT_SECRET_EXP = process.env.JWT_ACCESS_EXP, + JWT_ACCESS_EXP = process.env.JWT_ACCESS_EXP, + JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET, + JWT_REFRESH_EXP = process.env.JWT_REFRESH_EXP, + JWT_PASSWORDRESET_SECRET = process.env.JWT_PASSWORDRESET_SECRET, + JWT_PASSWORDRESET_EXP = process.env.JWT_PASSWORDRESET_EXP, + JWT_EMAILVERIFICATION_SECRET = process.env.JWT_EMAILVERIFICATION_SECRET, + JWT_EMAILVERIFICATION_EXP = process.env.JWT_EMAILVERIFICATION_EXP, + JWT_SUPERADMINACTIVATION_SECRET = process.env.JWT_SUPERADMINACTIVATION_SECRET, + JWT_SUPERADMINACTIVATION_EXP = process.env.JWT_SUPERADMINACTIVATION_EXP; /* EMAIL and OAUTH2*/ const EMAIL_HOST = process.env.EMAIL_HOST, - EMAIL_PORT = process.env.EMAIL_PORT, - EMAIL_HOST_ADDRESS = process.env.EMAIL_HOST_ADDRESS, - OAUTH_CLIENT_ID = process.env.OAUTH_CLIENT_ID, - OAUTH_CLIENT_SECRET = process.env.OAUTH_CLIENT_SECRET, - OAUTH_REFRESH_TOKEN = process.env.OAUTH_REFRESH_TOKEN, - OAUTH_ACCESS_TOKEN = process.env.OAUTH_ACCESS_TOKEN, - GOOGLE_SIGNIN_CLIENT_ID = process.env.GOOGLE_SIGNIN_CLIENT_ID, - HOST_ADMIN_EMAIL1 = process.env.HOST_ADMIN_EMAIL1, - HOST_ADMIN_EMAIL2 = process.env.HOST_ADMIN_EMAIL2; + EMAIL_PORT = process.env.EMAIL_PORT, + EMAIL_PASS = process.env.EMAIL_PASS, + EMAIL_HOST_ADDRESS = process.env.EMAIL_HOST_ADDRESS, + OAUTH_CLIENT_ID = process.env.OAUTH_CLIENT_ID, + OAUTH_CLIENT_SECRET = process.env.OAUTH_CLIENT_SECRET, + OAUTH_REFRESH_TOKEN = process.env.OAUTH_REFRESH_TOKEN, + OAUTH_ACCESS_TOKEN = process.env.OAUTH_ACCESS_TOKEN, + GOOGLE_SIGNIN_CLIENT_ID = process.env.GOOGLE_SIGNIN_CLIENT_ID, + HOST_ADMIN_EMAIL1 = process.env.HOST_ADMIN_EMAIL1, + HOST_ADMIN_EMAIL2 = process.env.HOST_ADMIN_EMAIL2, + ADMIN_EMAIL = process.env.ADMIN_EMAIL, + ADMIN_PASSWORD = process.env.ADMIN_PASSWORD; /* Server */ const SERVER_URL = process.env.SERVER_URL, - CLIENT_APP_URL = process.env.CLIENT_APP_URL; + CLIENT_APP_URL = process.env.CLIENT_APP_URL; /* Github */ const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID, - GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET; + GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET; /* Cloudinary */ const CLOUDINARY_CLOUD_NAME = process.env.CLOUDINARY_CLOUD_NAME, - CLOUDINARY_API_KEY = process.env.CLOUDINARY_API_KEY, - CLOUDINARY_API_SECRET = process.env.CLOUDINARY_API_SECRET; + CLOUDINARY_API_KEY = process.env.CLOUDINARY_API_KEY, + CLOUDINARY_API_SECRET = process.env.CLOUDINARY_API_SECRET; /* CROWDIN */ const CROWDIN_API_KEY = process.env.CROWDIN_API_KEY, - CROWDIN_PROJECT_ID = process.env.CROWDIN_PROJECT_ID, - CROWDIN_API = process.env.CROWDIN_API, - CROWDIN_MTS_ID = process.env.CROWDIN_MTS_ID; - + CROWDIN_PROJECT_ID = process.env.CROWDIN_PROJECT_ID, + CROWDIN_API = process.env.CROWDIN_API, + CROWDIN_MTS_ID = process.env.CROWDIN_MTS_ID; module.exports = { - // MongoDB URI - MONGO_URI, - MONGO_URI_TEST, - MONGO_URI_DEV, - MONGO_URI_PROD, - - // Server Port - PORT, - - // JWT Tokens - JWT_SECRET, - JWT_SECRET_EXP, - JWT_ACCESS_SECRET, - JWT_REFRESH_SECRET, - JWT_ACCESS_EXP, - JWT_REFRESH_EXP, - JWT_PASSWORDRESET_SECRET, - JWT_PASSWORDRESET_EXP, - JWT_EMAILVERIFICATION_SECRET, - JWT_EMAILVERIFICATION_EXP, - JWT_SUPERADMINACTIVATION_SECRET, - JWT_SUPERADMINACTIVATION_EXP, - - - // Email - EMAIL_HOST, - EMAIL_PORT, - EMAIL_HOST_ADDRESS, - - // OAUTH2 - OAUTH_CLIENT_ID, - OAUTH_CLIENT_SECRET, - OAUTH_REFRESH_TOKEN, - OAUTH_ACCESS_TOKEN, - GOOGLE_SIGNIN_CLIENT_ID, - GITHUB_CLIENT_ID, - GITHUB_CLIENT_SECRET, - HOST_ADMIN_EMAIL1, - HOST_ADMIN_EMAIL2, - - // Server - SERVER_URL, - CLIENT_APP_URL, - - // Cloudinary - CLOUDINARY_CLOUD_NAME, - CLOUDINARY_API_KEY, - CLOUDINARY_API_SECRET, - - // CROWDIN - CROWDIN_API_KEY, - CROWDIN_PROJECT_ID, - CROWDIN_API, - CROWDIN_MTS_ID + // MongoDB URI + MONGO_URI, + MONGO_URI_TEST, + MONGO_URI_DEV, + MONGO_URI_PROD, + + // Server Port + PORT, + + // JWT Tokens + JWT_SECRET, + JWT_SECRET_EXP, + JWT_ACCESS_SECRET, + JWT_REFRESH_SECRET, + JWT_ACCESS_EXP, + JWT_REFRESH_EXP, + JWT_PASSWORDRESET_SECRET, + JWT_PASSWORDRESET_EXP, + JWT_EMAILVERIFICATION_SECRET, + JWT_EMAILVERIFICATION_EXP, + JWT_SUPERADMINACTIVATION_SECRET, + JWT_SUPERADMINACTIVATION_EXP, + + // Email + EMAIL_HOST, + EMAIL_PORT, + EMAIL_HOST_ADDRESS, + EMAIL_PASS, + ADMIN_EMAIL, + ADMIN_PASSWORD, + + // OAUTH2 + OAUTH_CLIENT_ID, + OAUTH_CLIENT_SECRET, + OAUTH_REFRESH_TOKEN, + OAUTH_ACCESS_TOKEN, + GOOGLE_SIGNIN_CLIENT_ID, + GITHUB_CLIENT_ID, + GITHUB_CLIENT_SECRET, + HOST_ADMIN_EMAIL1, + HOST_ADMIN_EMAIL2, + + // Server + SERVER_URL, + CLIENT_APP_URL, + + // Cloudinary + CLOUDINARY_CLOUD_NAME, + CLOUDINARY_API_KEY, + CLOUDINARY_API_SECRET, + + // CROWDIN + CROWDIN_API_KEY, + CROWDIN_PROJECT_ID, + CROWDIN_API, + CROWDIN_MTS_ID, }; diff --git a/API/src/utils/email/email.js b/API/src/utils/email/email.js index 2e3adbb8..453e4d5d 100644 --- a/API/src/utils/email/email.js +++ b/API/src/utils/email/email.js @@ -1,31 +1,32 @@ /** * @fileoverview Email utilities. - * + * * @category Backend API * @subcategory Utilities - * + * * @module Email Utilities - * + * * @description This module contains functions for sending emails. - * + * * @requires nodemailer */ -const nodemailer = require('nodemailer') -const config = require('../config') +const nodemailer = require("nodemailer"); +const config = require("../config"); const { - password_reset_template, - password_reset_template_ar, - email_verification_template, - email_verification_template_ar } = require('./templates') + password_reset_template, + password_reset_template_ar, + email_verification_template, + email_verification_template_ar, +} = require("./templates"); // 3. Send email to user /** * Send Email - * + * * @description This function sends an email to the specified email address. - * + * * @param {string} options.email - Email address to send email to * @param {string} options.subject - Subject of the email * @param {string} options.message - Message to send in the email @@ -37,56 +38,53 @@ const { * @memberof module:Email Utilities * @name sendEmail */ -const sendEmail = async (options) => { - try { - //1. Create the transporter - const transporter = nodemailer.createTransport({ - host: process.env.EMAIL_HOST, - port: process.env.EMAIL_PORT, - secure: true, - auth: { - type: "OAuth2", - user: config.EMAIL_HOST_ADDRESS, - clientId: config.OAUTH_CLIENT_ID, - clientSecret: config.OAUTH_CLIENT_SECRET, - refreshToken: config.OAUTH_REFRESH_TOKEN, - accessToken: config.OAUTH_ACCESS_TOKEN - } - }) - //2. Define Email Options - const mailOptions = { - from: 'MOOCs platform', - to: options.email, - subject: options.subject, - text: options.message, - html: options.html - } - // actually send message - await transporter.sendMail(mailOptions) +const sendEmail = async (options) => { + try { + //1. Create the transporter + const transporter = nodemailer.createTransport({ + service: "gmail", + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + user: config.EMAIL_HOST_ADDRESS, + pass: config.EMAIL_PASS, + }, + }); - } catch (error) { - console.log(error) - return error - } -} + //2. Define Email Options + const mailOptions = { + from: "MOOCs platform", + to: options.email, + subject: options.subject, + text: options.message, + html: options.html, + }; + // actually send message + await transporter.sendMail(mailOptions); + } catch (error) { + console.log(error); + return error; + } +}; class EmailMessage { - constructor(lang = 'en') { - this.lang = lang - } + constructor(lang = "en") { + this.lang = lang; + } - passwordReset(name, reset_code, lang = this.lang) { - return lang != 'en' - ? password_reset_template_ar(name, reset_code) - : password_reset_template(name, reset_code) - } + passwordReset(name, reset_code, lang = this.lang) { + return lang != "en" + ? password_reset_template_ar(name, reset_code) + : password_reset_template(name, reset_code); + } - emailVerification(name, verification_link, lang = this.lang) { - return lang != 'en' - ? email_verification_template_ar(name, verification_link) - : email_verification_template(name, verification_link) - } + emailVerification(name, verification_link, lang = this.lang) { + return lang != "en" + ? email_verification_template_ar(name, verification_link) + : email_verification_template(name, verification_link); + } } -module.exports = { sendEmail, EmailMessage } +module.exports = { sendEmail, EmailMessage }; diff --git a/API/src/utils/token.js b/API/src/utils/token.js index 0913c7c5..dcb50217 100644 --- a/API/src/utils/token.js +++ b/API/src/utils/token.js @@ -92,6 +92,7 @@ const getAuthTokens = async (user_id, token_type = null) => { try { // Get user details const current_user = await User.findById(user_id).populate("status"); + console.log(current_user) if (!current_user) { throw new NotFoundError("User does not exist"); }