From b4b48f3de194eea1af4696971c7e965a03d82c6d Mon Sep 17 00:00:00 2001 From: elangptra Date: Thu, 7 Nov 2024 09:18:27 +0700 Subject: [PATCH] feat: user email validation function --- .env.development | 1 + .env.developmentexample | 1 + .env.production | 1 + .env.productionexample | 1 + .envexample | 1 + controllers/auth/auth.js | 619 +++++++++++++++++- .../20241012202559-create-users.cjs | 5 + .../20241012193900-create-initial-user.cjs | 2 + index.js | 50 ++ models/usersModels/userModel.js | 5 + package-lock.json | 133 ++-- package.json | 1 + routes/auth/auth.js | 8 +- 13 files changed, 760 insertions(+), 68 deletions(-) diff --git a/.env.development b/.env.development index 1860359..8e7dc1d 100644 --- a/.env.development +++ b/.env.development @@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_dev ACCESS_TOKEN_SECRET = adamSacredToken REFRESH_TOKEN_SECRET = adamRefreshToken RESET_PASSWORD_SECRET = adamResetToken +VERIFY_TOKEN_SECRET = adamVerifyToken EMAIL_USER = 2141720057@student.polinema.ac.id EMAIL_PASS = bfcrcrtjbefcveuv \ No newline at end of file diff --git a/.env.developmentexample b/.env.developmentexample index 80ce7c3..b2baa21 100644 --- a/.env.developmentexample +++ b/.env.developmentexample @@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_dev ACCESS_TOKEN_SECRET = # secret code untuk generate access token REFRESH_TOKEN_SECRET = # secret code untuk generate refresh token RESET_PASSWORD_SECRET = # secret code reset password +VERIFY_TOKEN_SECRET = # secret code verify token EMAIL_USER = # alamat email yang mengirimkan token reset password EMAIL_PASS = # pass alamat email \ No newline at end of file diff --git a/.env.production b/.env.production index 99a7f98..8b7820c 100644 --- a/.env.production +++ b/.env.production @@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_prod ACCESS_TOKEN_SECRET = adamSacredToken REFRESH_TOKEN_SECRET = adamRefreshToken RESET_PASSWORD_SECRET = adamResetToken +VERIFY_TOKEN_SECRET = adamVerifyToken EMAIL_USER = 2141720057@student.polinema.ac.id EMAIL_PASS = bfcrcrtjbefcveuv \ No newline at end of file diff --git a/.env.productionexample b/.env.productionexample index 4bf9525..27589da 100644 --- a/.env.productionexample +++ b/.env.productionexample @@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_prod ACCESS_TOKEN_SECRET = # secret code untuk generate access token REFRESH_TOKEN_SECRET = # secret code untuk generate refresh token RESET_PASSWORD_SECRET = # secret code reset password +VERIFY_TOKEN_SECRET = # secret code verify token EMAIL_USER = # alamat email yang mengirimkan token reset password EMAIL_PASS = # pass alamat email \ No newline at end of file diff --git a/.envexample b/.envexample index f5f47b6..4391e10 100644 --- a/.envexample +++ b/.envexample @@ -10,6 +10,7 @@ DB_NAME = adaptive_learning ACCESS_TOKEN_SECRET = REFRESH_TOKEN_SECRET = RESET_PASSWORD_SECRET = +VERIFY_TOKEN_SECRET = EMAIL_USER = EMAIL_PASS = \ No newline at end of file diff --git a/controllers/auth/auth.js b/controllers/auth/auth.js index 7f80fad..7e44a69 100644 --- a/controllers/auth/auth.js +++ b/controllers/auth/auth.js @@ -104,6 +104,7 @@ export const registerTeacher = async (req, res) => { EMAIL: EMAIL, PASSWORD: hashedPassword, ROLE: "teacher", + IS_VALIDATED: 0, }, { transaction } ); @@ -118,15 +119,148 @@ export const registerTeacher = async (req, res) => { await transaction.commit(); - const teacherResponse = { - ID: newUser.ID, - NAME_USERS: newUser.NAME_USERS, - EMAIL: newUser.EMAIL, - NIP: NIP, - ROLE: newUser.ROLE, - }; + const token = jwt.sign( + { userId: newUser.ID }, + process.env.VERIFY_TOKEN_SECRET, + { expiresIn: "1h" } + ); - response(200, teacherResponse, "Teacher registration successful", res); + const validationLink = `${process.env.CLIENT_URL}/validate-email?token=${token}`; + await transporter.sendMail({ + from: process.env.EMAIL_USER, + to: EMAIL, + subject: "Email Verification", + html: ` + + + + + + Email Verification + + + +
+
+

SEALS

+
+ +
+

Hello, ${NAME_USERS}! 👋

+ +

Welcome to SEALS! We're excited to have you on board.

+ +

To get started, please verify your email address by clicking the button below:

+ +
+ Verify Email +
+ +
+

Important: This verification link will expire in 1 hour. If you don't complete the verification within this time, you'll need to register again.

+
+ +

If you didn't create an account with SEALS, please ignore this email.

+ +
+

Thank you for choosing SEALS!

+

The SEALS Team

+
+
+ + +
+ + + `, + }); + + response(200, null, "Teacher registered! Please verify your email.", res); } catch (error) { console.log(error); await transaction.rollback(); @@ -186,6 +320,322 @@ export const registerStudent = async (req, res) => { EMAIL: EMAIL, PASSWORD: hashedPassword, ROLE: "student", + IS_VALIDATED: 0, + }, + { transaction } + ); + + await models.Student.create( + { + ID: newUser.ID, + NISN: NISN, + }, + { transaction } + ); + + await transaction.commit(); + + const token = jwt.sign( + { userId: newUser.ID }, + process.env.VERIFY_TOKEN_SECRET, + { expiresIn: "1h" } + ); + + const validationLink = `${process.env.CLIENT_URL}/validate-email?token=${token}`; + await transporter.sendMail({ + from: process.env.EMAIL_USER, + to: EMAIL, + subject: "Email Verification", + html: ` + + + + + + Email Verification + + + +
+
+

SEALS

+
+ +
+

Hello, ${NAME_USERS}! 👋

+ +

Welcome to SEALS! We're excited to have you on board.

+ +

To get started, please verify your email address by clicking the button below:

+ +
+ Verify Email +
+ +
+

Important: This verification link will expire in 1 hour. If you don't complete the verification within this time, you'll need to register again.

+
+ +

If you didn't create an account with SEALS, please ignore this email.

+ +
+

Thank you for choosing SEALS!

+

The SEALS Team

+
+
+ + +
+ + + `, + }); + + response(200, null, "Student registered! Please verify your email.", res); + } catch (error) { + console.log(error); + await transaction.rollback(); + + if (error.name === "SequelizeUniqueConstraintError") { + const field = error.original.sqlMessage.match(/for key '(.+)'/)[1]; + + if (field === "student_unique_nisn") { + return response(400, null, "NISN already registered!", res); + } + + if (field === "user_unique_email") { + return response(400, null, "Email already registered!", res); + } + } + + response(500, null, "Internal Server Error", res); + } +}; + +export const validateEmail = async (req, res) => { + const { TOKEN } = req.body; + + try { + const decoded = jwt.verify(TOKEN, process.env.VERIFY_TOKEN_SECRET); + const userId = decoded.userId; + + await models.User.update({ IS_VALIDATED: 1 }, { where: { ID: userId } }); + + response(200, null, "Email successfully validated!", res); + } catch (error) { + response(400, null, "Invalid or expired token", res); + } +}; + +export const registerTeacherForAdmin = async (req, res) => { + const { NAME_USERS, EMAIL, NIP, PASSWORD, CONFIRM_PASSWORD } = req.body; + + if (!NAME_USERS) { + return response(400, null, "Name is required!", res); + } + + if (!EMAIL) { + return response(400, null, "Email is required!", res); + } + + if (!NIP) { + return response(400, null, "NIP is required for teachers!", res); + } + + if (!PASSWORD) { + return response(400, null, "Password is required!", res); + } + + if (!CONFIRM_PASSWORD) { + return response(400, null, "Confirm Password is required!", res); + } + + if (PASSWORD !== CONFIRM_PASSWORD) { + return response(400, null, "Passwords do not match!", res); + } + + const transaction = await models.db.transaction(); + + try { + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(PASSWORD, salt); + + const newUser = await models.User.create( + { + NAME_USERS: NAME_USERS, + EMAIL: EMAIL, + PASSWORD: hashedPassword, + ROLE: "teacher", + IS_VALIDATED: 1, + }, + { transaction } + ); + + await models.Teacher.create( + { + ID: newUser.ID, + NIP: NIP, + }, + { transaction } + ); + + await transaction.commit(); + + const teacherResponse = { + ID: newUser.ID, + NAME_USERS: newUser.NAME_USERS, + EMAIL: newUser.EMAIL, + NIP: NIP, + ROLE: newUser.ROLE, + IS_VALIDATED: newUser.IS_VALIDATED, + }; + + response(200, teacherResponse, "Teacher registration successful", res); + } catch (error) { + console.log(error); + await transaction.rollback(); + + if (error.name === "SequelizeUniqueConstraintError") { + const field = error.original.sqlMessage.match(/for key '(.+)'/)[1]; + + if (field === "teacher_unique_nip") { + return response(400, null, "NIP already registered!", res); + } + + if (field === "user_unique_email") { + return response(400, null, "Email already registered!", res); + } + } + + response(500, null, "Internal Server Error", res); + } +}; + +export const registerStudentForAdmin = async (req, res) => { + const { NAME_USERS, EMAIL, NISN, PASSWORD, CONFIRM_PASSWORD } = req.body; + + if (!NAME_USERS) { + return response(400, null, "Name is required!", res); + } + + if (!EMAIL) { + return response(400, null, "Email is required!", res); + } + + if (!NISN) { + return response(400, null, "NISN is required for students!", res); + } + + if (!PASSWORD) { + return response(400, null, "Password is required!", res); + } + + if (!CONFIRM_PASSWORD) { + return response(400, null, "Confirm Password is required!", res); + } + + if (PASSWORD !== CONFIRM_PASSWORD) { + return response(400, null, "Passwords do not match!", res); + } + + const transaction = await models.db.transaction(); + + try { + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(PASSWORD, salt); + + const newUser = await models.User.create( + { + NAME_USERS: NAME_USERS, + EMAIL: EMAIL, + PASSWORD: hashedPassword, + ROLE: "student", + IS_VALIDATED: 1, }, { transaction } ); @@ -206,6 +656,7 @@ export const registerStudent = async (req, res) => { EMAIL: newUser.EMAIL, NISN: NISN, ROLE: newUser.ROLE, + IS_VALIDATED: newUser.IS_VALIDATED, }; response(200, studentResponse, "Student registration successful", res); @@ -247,6 +698,10 @@ export const loginUser = async (req, res) => { return response(404, null, "User data not found!", res); } + if (user.IS_VALIDATED !== 1) { + return response(403, null, "User is not validated! Please verify your email first.", res); + } + const validPassword = await bcrypt.compare(PASSWORD, user.PASSWORD); if (!validPassword) { return response(401, null, "The password you entered is incorrect!", res); @@ -272,8 +727,7 @@ export const loginUser = async (req, res) => { res.cookie("refreshToken", refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", - // sameSite: "Strict", - maxAge: 7 * 24 * 60 * 60 * 1000 + maxAge: 7 * 24 * 60 * 60 * 1000, }); const userResponse = { @@ -347,8 +801,8 @@ export const refreshToken = async (req, res) => { res.cookie("refreshToken", newRefreshToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", - // sameSite: "Strict", - maxAge: 7 * 24 * 60 * 60 * 1000 + + maxAge: 7 * 24 * 60 * 60 * 1000, }); response( @@ -395,10 +849,143 @@ export const forgotPassword = async (req, res) => { from: process.env.EMAIL_USER, to: user.EMAIL, subject: "Password Reset", - text: `You are receiving this because you (or someone else) have requested the reset of the password for your account. - Please click on the following link, or paste this into your browser to complete the process: - ${resetLink} - If you did not request this, please ignore this email and your password will remain unchanged.`, + html: ` + + + + + + Reset Password + + + +
+
+

SEALS

+
+ +
+

Password Reset Request

+ +

Hello!

+ +

We received a request to reset the password for your SEALS account. To proceed with the password reset, please click the button below:

+ +
+ Reset Password +
+ +
+

Important: This reset password link will expire in 1 hour. Please reset your password as soon as possible to maintain access to your account.

+
+ +
+

⚠️ If you didn't request a password reset, please ignore this email or contact our support team if you have concerns about your account's security.

+
+ +
+

Thank you for using SEALS!

+

The SEALS Team

+
+
+ + +
+ + + `, }; await transporter.sendMail(mailOptions); diff --git a/database/migrations/20241012202559-create-users.cjs b/database/migrations/20241012202559-create-users.cjs index e4e5343..2797066 100644 --- a/database/migrations/20241012202559-create-users.cjs +++ b/database/migrations/20241012202559-create-users.cjs @@ -29,6 +29,11 @@ module.exports = { type: Sequelize.STRING(1024), allowNull: true, }, + IS_VALIDATED: { + type: Sequelize.TINYINT(1), + allowNull: true, + defaultValue: 0, + }, REFRESH_TOKEN: { type: Sequelize.STRING(256), allowNull: true, diff --git a/database/seeders/20241012193900-create-initial-user.cjs b/database/seeders/20241012193900-create-initial-user.cjs index 5140035..51e7af5 100644 --- a/database/seeders/20241012193900-create-initial-user.cjs +++ b/database/seeders/20241012193900-create-initial-user.cjs @@ -12,6 +12,7 @@ module.exports = { EMAIL: "adminseals@gmail.com", PASSWORD: adminHashedPassword, ROLE: "admin", + IS_VALIDATED: 1, TIME_USERS: new Date(), }, { @@ -20,6 +21,7 @@ module.exports = { EMAIL: "sealsteach@gmail.com", PASSWORD: teacherHashedPassword, ROLE: "teacher", + IS_VALIDATED: 1, TIME_USERS: new Date(), }, ]); diff --git a/index.js b/index.js index 2b4e42f..7fac4df 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,8 @@ import { testConnection } from "./database/db.js"; import router from "./routes/index.js"; import cookieParser from "cookie-parser"; import promBundle from "express-prom-bundle"; +import cron from "node-cron"; +import models from "./models/index.js"; dotenv.config(); const app = express(); @@ -35,6 +37,54 @@ app.use(express.urlencoded({ extended: true })); app.use(router); app.use(express.static("public")); +cron.schedule("0 0 * * *", async () => { + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); + const transaction = await models.db.transaction(); + + try { + const unvalidatedUsers = await models.User.findAll({ + attributes: ["ID"], + where: { + IS_VALIDATED: 0, + TIME_USERS: { [models.Sequelize.Op.lt]: oneDayAgo }, + }, + transaction, + }); + + const unvalidatedUserIDs = unvalidatedUsers.map((user) => user.ID); + + if (unvalidatedUserIDs.length > 0) { + await models.Student.destroy({ + where: { ID: { [models.Sequelize.Op.in]: unvalidatedUserIDs } }, + transaction, + }); + + await models.Teacher.destroy({ + where: { ID: { [models.Sequelize.Op.in]: unvalidatedUserIDs } }, + transaction, + }); + + await models.User.destroy({ + where: { + ID: { [models.Sequelize.Op.in]: unvalidatedUserIDs }, + }, + transaction, + }); + } + + await transaction.commit(); + console.log( + "Removed unvalidated users and their related data registered over 1 day ago." + ); + } catch (error) { + await transaction.rollback(); + console.error( + "Failed to delete unvalidated users and related data:", + error + ); + } +}); + app.listen(process.env.APP_PORT, () => { testConnection(); console.log(`Server running on port ${process.env.APP_PORT}`); diff --git a/models/usersModels/userModel.js b/models/usersModels/userModel.js index f314ce7..eaf02c6 100644 --- a/models/usersModels/userModel.js +++ b/models/usersModels/userModel.js @@ -41,6 +41,11 @@ const UserModel = (DataTypes) => { type: DataTypes.STRING(1024), allowNull: true, }, + IS_VALIDATED: { + type: DataTypes.TINYINT(1), + allowNull: true, + defaultValue: 0, + }, REFRESH_TOKEN: { type: DataTypes.STRING(256), allowNull: true, diff --git a/package-lock.json b/package-lock.json index 73c7f5d..fa92b99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "moment-timezone": "^0.5.45", "multer": "^1.4.5-lts.1", "mysql2": "^3.11.0", + "node-cron": "^3.0.3", "nodemailer": "^6.9.14", "nodemon": "^3.1.4", "prom-client": "^15.1.3", @@ -347,9 +348,9 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -359,7 +360,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -583,19 +584,19 @@ } }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "dependencies": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" }, "engines": { @@ -782,9 +783,9 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -901,36 +902,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -958,9 +959,9 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -985,12 +986,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -1585,9 +1586,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -1768,6 +1772,25 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-cron/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/nodemailer": { "version": "6.9.14", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", @@ -1909,9 +1932,9 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/pg-connection-string": { "version": "2.6.4", @@ -1969,11 +1992,11 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -2080,9 +2103,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -2102,6 +2125,14 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2232,14 +2263,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" diff --git a/package.json b/package.json index 0efca83..ab0f3aa 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "moment-timezone": "^0.5.45", "multer": "^1.4.5-lts.1", "mysql2": "^3.11.0", + "node-cron": "^3.0.3", "nodemailer": "^6.9.14", "nodemon": "^3.1.4", "prom-client": "^15.1.3", diff --git a/routes/auth/auth.js b/routes/auth/auth.js index aec5376..6ffea2f 100644 --- a/routes/auth/auth.js +++ b/routes/auth/auth.js @@ -1,5 +1,5 @@ import express from "express"; -import { registerTeacher, registerStudent, registerAdmin, loginUser, refreshToken, logoutUser, forgotPassword, resetPassword } from "../../controllers/auth/auth.js"; +import { registerTeacher, registerStudent, registerStudentForAdmin, registerTeacherForAdmin, registerAdmin, validateEmail, loginUser, refreshToken, logoutUser, forgotPassword, resetPassword } from "../../controllers/auth/auth.js"; import { verifyLoginUser, adminOnly } from "../../middlewares/User/authUser.js"; const router = express.Router(); @@ -8,8 +8,14 @@ router.post("/register/teacher", registerTeacher); router.post("/register/student", registerStudent); +router.post("/admin/register/teacher", verifyLoginUser, adminOnly, registerTeacherForAdmin); + +router.post("/admin/register/student", verifyLoginUser, adminOnly, registerStudentForAdmin); + router.post("/register/admin", verifyLoginUser, adminOnly, registerAdmin); +router.post("/validateEmail", validateEmail); + router.post("/login", loginUser); router.post("/refreshToken", refreshToken);