Merge branch 'testing' into 'master'

feat: user email validation function

See merge request profile-image/kedaireka/polinema-adapative-learning/backend-adaptive-learning!5
This commit is contained in:
elangptra 2024-11-07 03:22:33 +00:00
commit 20069433eb
13 changed files with 760 additions and 68 deletions

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_dev
ACCESS_TOKEN_SECRET = adamSacredToken ACCESS_TOKEN_SECRET = adamSacredToken
REFRESH_TOKEN_SECRET = adamRefreshToken REFRESH_TOKEN_SECRET = adamRefreshToken
RESET_PASSWORD_SECRET = adamResetToken RESET_PASSWORD_SECRET = adamResetToken
VERIFY_TOKEN_SECRET = adamVerifyToken
EMAIL_USER = 2141720057@student.polinema.ac.id EMAIL_USER = 2141720057@student.polinema.ac.id
EMAIL_PASS = bfcrcrtjbefcveuv EMAIL_PASS = bfcrcrtjbefcveuv

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_dev
ACCESS_TOKEN_SECRET = # secret code untuk generate access token ACCESS_TOKEN_SECRET = # secret code untuk generate access token
REFRESH_TOKEN_SECRET = # secret code untuk generate refresh token REFRESH_TOKEN_SECRET = # secret code untuk generate refresh token
RESET_PASSWORD_SECRET = # secret code reset password RESET_PASSWORD_SECRET = # secret code reset password
VERIFY_TOKEN_SECRET = # secret code verify token
EMAIL_USER = # alamat email yang mengirimkan token reset password EMAIL_USER = # alamat email yang mengirimkan token reset password
EMAIL_PASS = # pass alamat email EMAIL_PASS = # pass alamat email

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_prod
ACCESS_TOKEN_SECRET = adamSacredToken ACCESS_TOKEN_SECRET = adamSacredToken
REFRESH_TOKEN_SECRET = adamRefreshToken REFRESH_TOKEN_SECRET = adamRefreshToken
RESET_PASSWORD_SECRET = adamResetToken RESET_PASSWORD_SECRET = adamResetToken
VERIFY_TOKEN_SECRET = adamVerifyToken
EMAIL_USER = 2141720057@student.polinema.ac.id EMAIL_USER = 2141720057@student.polinema.ac.id
EMAIL_PASS = bfcrcrtjbefcveuv EMAIL_PASS = bfcrcrtjbefcveuv

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_prod
ACCESS_TOKEN_SECRET = # secret code untuk generate access token ACCESS_TOKEN_SECRET = # secret code untuk generate access token
REFRESH_TOKEN_SECRET = # secret code untuk generate refresh token REFRESH_TOKEN_SECRET = # secret code untuk generate refresh token
RESET_PASSWORD_SECRET = # secret code reset password RESET_PASSWORD_SECRET = # secret code reset password
VERIFY_TOKEN_SECRET = # secret code verify token
EMAIL_USER = # alamat email yang mengirimkan token reset password EMAIL_USER = # alamat email yang mengirimkan token reset password
EMAIL_PASS = # pass alamat email EMAIL_PASS = # pass alamat email

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning
ACCESS_TOKEN_SECRET = ACCESS_TOKEN_SECRET =
REFRESH_TOKEN_SECRET = REFRESH_TOKEN_SECRET =
RESET_PASSWORD_SECRET = RESET_PASSWORD_SECRET =
VERIFY_TOKEN_SECRET =
EMAIL_USER = EMAIL_USER =
EMAIL_PASS = EMAIL_PASS =

View File

@ -104,6 +104,7 @@ export const registerTeacher = async (req, res) => {
EMAIL: EMAIL, EMAIL: EMAIL,
PASSWORD: hashedPassword, PASSWORD: hashedPassword,
ROLE: "teacher", ROLE: "teacher",
IS_VALIDATED: 0,
}, },
{ transaction } { transaction }
); );
@ -118,15 +119,148 @@ export const registerTeacher = async (req, res) => {
await transaction.commit(); await transaction.commit();
const teacherResponse = { const token = jwt.sign(
ID: newUser.ID, { userId: newUser.ID },
NAME_USERS: newUser.NAME_USERS, process.env.VERIFY_TOKEN_SECRET,
EMAIL: newUser.EMAIL, { expiresIn: "1h" }
NIP: NIP, );
ROLE: newUser.ROLE,
};
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: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Verification</title>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Arial, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 0;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
background-color: #0090FF;
padding: 25px 20px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.header h1 {
color: #ffffff;
margin: 0;
font-size: 32px;
letter-spacing: 2px;
font-weight: bold;
}
.content {
padding: 40px 30px;
background-color: #ffffff;
color: #333333;
}
.content h2 {
color: #0090FF;
margin-top: 0;
font-size: 24px;
margin-bottom: 20px;
}
.content p {
margin-bottom: 15px;
font-size: 16px;
line-height: 1.7;
}
.button {
display: inline-block;
padding: 12px 35px;
background-color: #0090FF;
color: #ffffff !important;
text-decoration: none;
border-radius: 5px;
margin: 25px 0;
font-size: 16px;
font-weight: bold;
text-align: center;
transition: background-color 0.3s ease;
box-shadow: 0 2px 5px rgba(0,144,255,0.3);
}
.button:hover {
background-color: #007acc;
}
.footer {
background-color: #0090FF;
color: #ffffff;
padding: 25px 20px;
text-align: center;
border-radius: 0 0 10px 10px;
font-size: 14px;
}
.signature {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-weight: bold;
color: #0090FF;
}
.important-note {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #0090FF;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>SEALS</h1>
</div>
<div class="content">
<h2>Hello, ${NAME_USERS}! 👋</h2>
<p>Welcome to SEALS! We're excited to have you on board.</p>
<p>To get started, please verify your email address by clicking the button below:</p>
<div style="text-align: center;">
<a href="${validationLink}" class="button">Verify Email</a>
</div>
<div class="important-note">
<p style="margin: 0;"><strong>Important:</strong> This verification link will expire in 1 hour. If you don't complete the verification within this time, you'll need to register again.</p>
</div>
<p>If you didn't create an account with SEALS, please ignore this email.</p>
<div class="signature">
<p>Thank you for choosing SEALS!</p>
<p style="margin: 5px 0 0 0;">The SEALS Team</p>
</div>
</div>
<div class="footer">
<p style="margin: 0;">&copy; 2024 SEALS. All rights reserved.</p>
</div>
</div>
</body>
</html>
`,
});
response(200, null, "Teacher registered! Please verify your email.", res);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
await transaction.rollback(); await transaction.rollback();
@ -186,6 +320,322 @@ export const registerStudent = async (req, res) => {
EMAIL: EMAIL, EMAIL: EMAIL,
PASSWORD: hashedPassword, PASSWORD: hashedPassword,
ROLE: "student", 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: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Verification</title>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Arial, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 0;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
background-color: #0090FF;
padding: 25px 20px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.header h1 {
color: #ffffff;
margin: 0;
font-size: 32px;
letter-spacing: 2px;
font-weight: bold;
}
.content {
padding: 40px 30px;
background-color: #ffffff;
color: #333333;
}
.content h2 {
color: #0090FF;
margin-top: 0;
font-size: 24px;
margin-bottom: 20px;
}
.content p {
margin-bottom: 15px;
font-size: 16px;
line-height: 1.7;
}
.button {
display: inline-block;
padding: 12px 35px;
background-color: #0090FF;
color: #ffffff !important;
text-decoration: none;
border-radius: 5px;
margin: 25px 0;
font-size: 16px;
font-weight: bold;
text-align: center;
transition: background-color 0.3s ease;
box-shadow: 0 2px 5px rgba(0,144,255,0.3);
}
.button:hover {
background-color: #007acc;
}
.footer {
background-color: #0090FF;
color: #ffffff;
padding: 25px 20px;
text-align: center;
border-radius: 0 0 10px 10px;
font-size: 14px;
}
.signature {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-weight: bold;
color: #0090FF;
}
.important-note {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #0090FF;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>SEALS</h1>
</div>
<div class="content">
<h2>Hello, ${NAME_USERS}! 👋</h2>
<p>Welcome to SEALS! We're excited to have you on board.</p>
<p>To get started, please verify your email address by clicking the button below:</p>
<div style="text-align: center;">
<a href="${validationLink}" class="button">Verify Email</a>
</div>
<div class="important-note">
<p style="margin: 0;"><strong>Important:</strong> This verification link will expire in 1 hour. If you don't complete the verification within this time, you'll need to register again.</p>
</div>
<p>If you didn't create an account with SEALS, please ignore this email.</p>
<div class="signature">
<p>Thank you for choosing SEALS!</p>
<p style="margin: 5px 0 0 0;">The SEALS Team</p>
</div>
</div>
<div class="footer">
<p style="margin: 0;">&copy; 2024 SEALS. All rights reserved.</p>
</div>
</div>
</body>
</html>
`,
});
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 } { transaction }
); );
@ -206,6 +656,7 @@ export const registerStudent = async (req, res) => {
EMAIL: newUser.EMAIL, EMAIL: newUser.EMAIL,
NISN: NISN, NISN: NISN,
ROLE: newUser.ROLE, ROLE: newUser.ROLE,
IS_VALIDATED: newUser.IS_VALIDATED,
}; };
response(200, studentResponse, "Student registration successful", res); 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); 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); const validPassword = await bcrypt.compare(PASSWORD, user.PASSWORD);
if (!validPassword) { if (!validPassword) {
return response(401, null, "The password you entered is incorrect!", res); 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, { res.cookie("refreshToken", refreshToken, {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === "production", secure: process.env.NODE_ENV === "production",
// sameSite: "Strict", maxAge: 7 * 24 * 60 * 60 * 1000,
maxAge: 7 * 24 * 60 * 60 * 1000
}); });
const userResponse = { const userResponse = {
@ -347,8 +801,8 @@ export const refreshToken = async (req, res) => {
res.cookie("refreshToken", newRefreshToken, { res.cookie("refreshToken", newRefreshToken, {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === "production", secure: process.env.NODE_ENV === "production",
// sameSite: "Strict",
maxAge: 7 * 24 * 60 * 60 * 1000 maxAge: 7 * 24 * 60 * 60 * 1000,
}); });
response( response(
@ -395,10 +849,143 @@ export const forgotPassword = async (req, res) => {
from: process.env.EMAIL_USER, from: process.env.EMAIL_USER,
to: user.EMAIL, to: user.EMAIL,
subject: "Password Reset", subject: "Password Reset",
text: `You are receiving this because you (or someone else) have requested the reset of the password for your account. html: `
Please click on the following link, or paste this into your browser to complete the process: <!DOCTYPE html>
${resetLink} <html>
If you did not request this, please ignore this email and your password will remain unchanged.`, <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Password</title>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Arial, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 0;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
background-color: #0090FF;
padding: 25px 20px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.header h1 {
color: #ffffff;
margin: 0;
font-size: 32px;
letter-spacing: 2px;
font-weight: bold;
}
.content {
padding: 40px 30px;
background-color: #ffffff;
color: #333333;
}
.content h2 {
color: #0090FF;
margin-top: 0;
font-size: 24px;
margin-bottom: 20px;
}
.content p {
margin-bottom: 15px;
font-size: 16px;
line-height: 1.7;
}
.button {
display: inline-block;
padding: 12px 35px;
background-color: #0090FF;
color: #ffffff !important;
text-decoration: none;
border-radius: 5px;
margin: 25px 0;
font-size: 16px;
font-weight: bold;
text-align: center;
transition: background-color 0.3s ease;
box-shadow: 0 2px 5px rgba(0,144,255,0.3);
}
.button:hover {
background-color: #007acc;
}
.footer {
background-color: #0090FF;
color: #ffffff;
padding: 25px 20px;
text-align: center;
border-radius: 0 0 10px 10px;
font-size: 14px;
}
.signature {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-weight: bold;
color: #0090FF;
}
.important-note {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #0090FF;
margin: 20px 0;
}
.security-warning {
background-color: #fff3cd;
padding: 15px;
border-left: 4px solid #ffc107;
margin: 20px 0;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>SEALS</h1>
</div>
<div class="content">
<h2>Password Reset Request</h2>
<p>Hello!</p>
<p>We received a request to reset the password for your SEALS account. To proceed with the password reset, please click the button below:</p>
<div style="text-align: center;">
<a href="${resetLink}" class="button">Reset Password</a>
</div>
<div class="important-note">
<p style="margin: 0;"><strong>Important:</strong> This reset password link will expire in 1 hour. Please reset your password as soon as possible to maintain access to your account.</p>
</div>
<div class="security-warning">
<p style="margin: 0;"> 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.</p>
</div>
<div class="signature">
<p>Thank you for using SEALS!</p>
<p style="margin: 5px 0 0 0;">The SEALS Team</p>
</div>
</div>
<div class="footer">
<p style="margin: 0;">&copy; 2024 SEALS. All rights reserved.</p>
</div>
</div>
</body>
</html>
`,
}; };
await transporter.sendMail(mailOptions); await transporter.sendMail(mailOptions);

View File

@ -29,6 +29,11 @@ module.exports = {
type: Sequelize.STRING(1024), type: Sequelize.STRING(1024),
allowNull: true, allowNull: true,
}, },
IS_VALIDATED: {
type: Sequelize.TINYINT(1),
allowNull: true,
defaultValue: 0,
},
REFRESH_TOKEN: { REFRESH_TOKEN: {
type: Sequelize.STRING(256), type: Sequelize.STRING(256),
allowNull: true, allowNull: true,

View File

@ -12,6 +12,7 @@ module.exports = {
EMAIL: "adminseals@gmail.com", EMAIL: "adminseals@gmail.com",
PASSWORD: adminHashedPassword, PASSWORD: adminHashedPassword,
ROLE: "admin", ROLE: "admin",
IS_VALIDATED: 1,
TIME_USERS: new Date(), TIME_USERS: new Date(),
}, },
{ {
@ -20,6 +21,7 @@ module.exports = {
EMAIL: "sealsteach@gmail.com", EMAIL: "sealsteach@gmail.com",
PASSWORD: teacherHashedPassword, PASSWORD: teacherHashedPassword,
ROLE: "teacher", ROLE: "teacher",
IS_VALIDATED: 1,
TIME_USERS: new Date(), TIME_USERS: new Date(),
}, },
]); ]);

View File

@ -5,6 +5,8 @@ import { testConnection } from "./database/db.js";
import router from "./routes/index.js"; import router from "./routes/index.js";
import cookieParser from "cookie-parser"; import cookieParser from "cookie-parser";
import promBundle from "express-prom-bundle"; import promBundle from "express-prom-bundle";
import cron from "node-cron";
import models from "./models/index.js";
dotenv.config(); dotenv.config();
const app = express(); const app = express();
@ -35,6 +37,54 @@ app.use(express.urlencoded({ extended: true }));
app.use(router); app.use(router);
app.use(express.static("public")); 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, () => { app.listen(process.env.APP_PORT, () => {
testConnection(); testConnection();
console.log(`Server running on port ${process.env.APP_PORT}`); console.log(`Server running on port ${process.env.APP_PORT}`);

View File

@ -41,6 +41,11 @@ const UserModel = (DataTypes) => {
type: DataTypes.STRING(1024), type: DataTypes.STRING(1024),
allowNull: true, allowNull: true,
}, },
IS_VALIDATED: {
type: DataTypes.TINYINT(1),
allowNull: true,
defaultValue: 0,
},
REFRESH_TOKEN: { REFRESH_TOKEN: {
type: DataTypes.STRING(256), type: DataTypes.STRING(256),
allowNull: true, allowNull: true,

133
package-lock.json generated
View File

@ -20,6 +20,7 @@
"moment-timezone": "^0.5.45", "moment-timezone": "^0.5.45",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"mysql2": "^3.11.0", "mysql2": "^3.11.0",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.14", "nodemailer": "^6.9.14",
"nodemon": "^3.1.4", "nodemon": "^3.1.4",
"prom-client": "^15.1.3", "prom-client": "^15.1.3",
@ -347,9 +348,9 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.2", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"content-type": "~1.0.5", "content-type": "~1.0.5",
@ -359,7 +360,7 @@
"http-errors": "2.0.0", "http-errors": "2.0.0",
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.13.0",
"raw-body": "2.5.2", "raw-body": "2.5.2",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
@ -583,19 +584,19 @@
} }
}, },
"node_modules/cookie": { "node_modules/cookie": {
"version": "0.4.1", "version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/cookie-parser": { "node_modules/cookie-parser": {
"version": "1.4.6", "version": "1.4.7",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
"dependencies": { "dependencies": {
"cookie": "0.4.1", "cookie": "0.7.2",
"cookie-signature": "1.0.6" "cookie-signature": "1.0.6"
}, },
"engines": { "engines": {
@ -782,9 +783,9 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
}, },
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "1.0.2", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@ -901,36 +902,36 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.19.2", "version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.2", "body-parser": "1.20.3",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.6.0", "cookie": "0.7.1",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"etag": "~1.8.1", "etag": "~1.8.1",
"finalhandler": "1.2.0", "finalhandler": "1.3.1",
"fresh": "0.5.2", "fresh": "0.5.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"merge-descriptors": "1.0.1", "merge-descriptors": "1.0.3",
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7", "path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.11.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "0.18.0", "send": "0.19.0",
"serve-static": "1.15.0", "serve-static": "1.16.2",
"setprototypeof": "1.2.0", "setprototypeof": "1.2.0",
"statuses": "2.0.1", "statuses": "2.0.1",
"type-is": "~1.6.18", "type-is": "~1.6.18",
@ -958,9 +959,9 @@
} }
}, },
"node_modules/express/node_modules/cookie": { "node_modules/express/node_modules/cookie": {
"version": "0.6.0", "version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@ -985,12 +986,12 @@
} }
}, },
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "1.2.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
@ -1585,9 +1586,12 @@
} }
}, },
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}, },
"node_modules/methods": { "node_modules/methods": {
"version": "1.1.2", "version": "1.1.2",
@ -1768,6 +1772,25 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" "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": { "node_modules/nodemailer": {
"version": "6.9.14", "version": "6.9.14",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz",
@ -1909,9 +1932,9 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.7", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
}, },
"node_modules/pg-connection-string": { "node_modules/pg-connection-string": {
"version": "2.6.4", "version": "2.6.4",
@ -1969,11 +1992,11 @@
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.11.0", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": { "dependencies": {
"side-channel": "^1.0.4" "side-channel": "^1.0.6"
}, },
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
@ -2080,9 +2103,9 @@
} }
}, },
"node_modules/send": { "node_modules/send": {
"version": "0.18.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
@ -2102,6 +2125,14 @@
"node": ">= 0.8.0" "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": { "node_modules/send/node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -2232,14 +2263,14 @@
} }
}, },
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.15.0", "version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": { "dependencies": {
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"send": "0.18.0" "send": "0.19.0"
}, },
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"

View File

@ -32,6 +32,7 @@
"moment-timezone": "^0.5.45", "moment-timezone": "^0.5.45",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"mysql2": "^3.11.0", "mysql2": "^3.11.0",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.14", "nodemailer": "^6.9.14",
"nodemon": "^3.1.4", "nodemon": "^3.1.4",
"prom-client": "^15.1.3", "prom-client": "^15.1.3",

View File

@ -1,5 +1,5 @@
import express from "express"; 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"; import { verifyLoginUser, adminOnly } from "../../middlewares/User/authUser.js";
const router = express.Router(); const router = express.Router();
@ -8,8 +8,14 @@ router.post("/register/teacher", registerTeacher);
router.post("/register/student", registerStudent); 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("/register/admin", verifyLoginUser, adminOnly, registerAdmin);
router.post("/validateEmail", validateEmail);
router.post("/login", loginUser); router.post("/login", loginUser);
router.post("/refreshToken", refreshToken); router.post("/refreshToken", refreshToken);