456 lines
11 KiB
JavaScript
456 lines
11 KiB
JavaScript
import response from "../../response.js";
|
|
import bcrypt from "bcryptjs";
|
|
import jwt from "jsonwebtoken";
|
|
import nodemailer from "nodemailer";
|
|
import models from "../../models/index.js";
|
|
|
|
const transporter = nodemailer.createTransport({
|
|
service: "gmail",
|
|
auth: {
|
|
user: process.env.EMAIL_USER,
|
|
pass: process.env.EMAIL_PASS,
|
|
},
|
|
});
|
|
|
|
export const registerAdmin = async (req, res) => {
|
|
const { NAME_USERS, EMAIL, 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 (!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);
|
|
}
|
|
|
|
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: "admin",
|
|
});
|
|
|
|
const adminResponse = {
|
|
ID: newUser.ID,
|
|
NAME_USERS: newUser.NAME_USERS,
|
|
EMAIL: newUser.EMAIL,
|
|
ROLE: newUser.ROLE,
|
|
};
|
|
|
|
response(200, adminResponse, "Admin registration successful", res);
|
|
} catch (error) {
|
|
console.log(error);
|
|
|
|
if (error.name === "SequelizeUniqueConstraintError") {
|
|
return response(400, null, "Email already registered!", res);
|
|
}
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
}
|
|
};
|
|
|
|
export const registerTeacher = 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",
|
|
},
|
|
{ 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,
|
|
};
|
|
|
|
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 registerStudent = 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",
|
|
},
|
|
{ transaction }
|
|
);
|
|
|
|
await models.Student.create(
|
|
{
|
|
ID: newUser.ID,
|
|
NISN: NISN,
|
|
},
|
|
{ transaction }
|
|
);
|
|
|
|
await transaction.commit();
|
|
|
|
const studentResponse = {
|
|
ID: newUser.ID,
|
|
NAME_USERS: newUser.NAME_USERS,
|
|
EMAIL: newUser.EMAIL,
|
|
NISN: NISN,
|
|
ROLE: newUser.ROLE,
|
|
};
|
|
|
|
response(200, studentResponse, "Student 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 === "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 loginUser = async (req, res) => {
|
|
const { EMAIL, PASSWORD } = req.body;
|
|
|
|
if (!EMAIL) {
|
|
return response(400, null, "Email is required!", res);
|
|
}
|
|
|
|
if (!PASSWORD) {
|
|
return response(400, null, "Password is required!", res);
|
|
}
|
|
|
|
try {
|
|
const user = await models.User.findOne({ where: { EMAIL } });
|
|
|
|
if (!user) {
|
|
return response(404, null, "User data not found!", res);
|
|
}
|
|
|
|
const validPassword = await bcrypt.compare(PASSWORD, user.PASSWORD);
|
|
if (!validPassword) {
|
|
return response(401, null, "The password you entered is incorrect!", res);
|
|
}
|
|
|
|
const accessToken = jwt.sign(
|
|
{ ID: user.ID, ROLE: user.ROLE },
|
|
process.env.ACCESS_TOKEN_SECRET,
|
|
{ expiresIn: "3h" }
|
|
);
|
|
|
|
const refreshToken = jwt.sign(
|
|
{ ID: user.ID, ROLE: user.ROLE },
|
|
process.env.REFRESH_TOKEN_SECRET,
|
|
{ expiresIn: "7d" }
|
|
);
|
|
|
|
await models.User.update(
|
|
{ REFRESH_TOKEN: refreshToken },
|
|
{ where: { ID: user.ID } }
|
|
);
|
|
|
|
res.cookie("refreshToken", refreshToken, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
// sameSite: "Strict",
|
|
maxAge: 7 * 24 * 60 * 60 * 1000
|
|
});
|
|
|
|
const userResponse = {
|
|
ID: user.ID,
|
|
NAME_USERS: user.NAME_USERS,
|
|
EMAIL: user.EMAIL,
|
|
ROLE: user.ROLE,
|
|
TOKEN: `Bearer ${accessToken}`,
|
|
REFRESH_TOKEN: refreshToken,
|
|
};
|
|
|
|
response(200, userResponse, "Login successful", res);
|
|
} catch (error) {
|
|
console.log(error);
|
|
response(500, null, "Internal Server Error", res);
|
|
}
|
|
};
|
|
|
|
export const refreshToken = async (req, res) => {
|
|
const refreshToken = req.cookies?.refreshToken || req.body?.REFRESH_TOKEN;
|
|
|
|
if (!refreshToken) {
|
|
return response(400, null, "Refresh token is required!", res);
|
|
}
|
|
|
|
try {
|
|
const user = await models.User.findOne({
|
|
where: { REFRESH_TOKEN: refreshToken },
|
|
});
|
|
|
|
if (!user) {
|
|
return response(403, null, "Invalid refresh token!", res);
|
|
}
|
|
|
|
let decoded;
|
|
try {
|
|
decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
|
|
} catch (err) {
|
|
if (err.name === "TokenExpiredError") {
|
|
return response(
|
|
401,
|
|
null,
|
|
"Refresh token expired. Please login again.",
|
|
res
|
|
);
|
|
}
|
|
return response(403, null, "Invalid refresh token!", res);
|
|
}
|
|
|
|
if (decoded.ID !== user.ID) {
|
|
return response(403, null, "Invalid refresh token data!", res);
|
|
}
|
|
|
|
const newAccessToken = jwt.sign(
|
|
{ ID: user.ID, ROLE: user.ROLE },
|
|
process.env.ACCESS_TOKEN_SECRET,
|
|
{ expiresIn: "3h" }
|
|
);
|
|
|
|
const newRefreshToken = jwt.sign(
|
|
{ ID: user.ID, ROLE: user.ROLE },
|
|
process.env.REFRESH_TOKEN_SECRET,
|
|
{ expiresIn: "7d" }
|
|
);
|
|
|
|
await models.User.update(
|
|
{ REFRESH_TOKEN: newRefreshToken },
|
|
{ where: { ID: user.ID } }
|
|
);
|
|
|
|
res.cookie("refreshToken", newRefreshToken, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
// sameSite: "Strict",
|
|
maxAge: 7 * 24 * 60 * 60 * 1000
|
|
});
|
|
|
|
response(
|
|
200,
|
|
{ TOKEN: `Bearer ${newAccessToken}`, REFRESH_TOKEN: newRefreshToken },
|
|
"Token refreshed successfully",
|
|
res
|
|
);
|
|
} catch (error) {
|
|
console.log(error);
|
|
response(500, null, "Internal Server Error", res);
|
|
}
|
|
};
|
|
|
|
export const logoutUser = (req, res) => {
|
|
response(200, null, "You have successfully logged out.", res);
|
|
};
|
|
|
|
export const forgotPassword = async (req, res) => {
|
|
const { EMAIL } = req.body;
|
|
|
|
if (!EMAIL) {
|
|
return response(400, null, "Email is required!", res);
|
|
}
|
|
|
|
try {
|
|
const user = await models.User.findOne({ where: { EMAIL: EMAIL } });
|
|
|
|
if (!user) {
|
|
return response(404, null, "Email is not registered!", res);
|
|
}
|
|
|
|
const resetToken = jwt.sign(
|
|
{ id: user.ID },
|
|
process.env.RESET_PASSWORD_SECRET,
|
|
{
|
|
expiresIn: "1h",
|
|
}
|
|
);
|
|
|
|
const resetLink = `${process.env.CLIENT_URL}/resetPassword/${resetToken}`;
|
|
|
|
const mailOptions = {
|
|
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.`,
|
|
};
|
|
|
|
await transporter.sendMail(mailOptions);
|
|
|
|
response(200, null, "Password reset email sent successfully!", res);
|
|
} catch (error) {
|
|
console.log(error);
|
|
response(500, null, "Internal Server Error", res);
|
|
}
|
|
};
|
|
|
|
export const resetPassword = async (req, res) => {
|
|
const { TOKEN, NEW_PASSWORD, CONFIRM_NEW_PASSWORD } = req.body;
|
|
|
|
if (!TOKEN) {
|
|
return response(400, null, "Token is required!", res);
|
|
}
|
|
|
|
if (!NEW_PASSWORD) {
|
|
return response(400, null, "New password is required!", res);
|
|
}
|
|
|
|
if (!CONFIRM_NEW_PASSWORD) {
|
|
return response(400, null, "Confirm new password is required!", res);
|
|
}
|
|
|
|
if (NEW_PASSWORD !== CONFIRM_NEW_PASSWORD) {
|
|
return response(400, null, "Passwords do not match!", res);
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(TOKEN, process.env.RESET_PASSWORD_SECRET);
|
|
const user = await models.User.findOne({ where: { ID: decoded.id } });
|
|
|
|
if (!user) {
|
|
return response(404, null, "User data not found!", res);
|
|
}
|
|
|
|
const salt = await bcrypt.genSalt(10);
|
|
const hashedPassword = await bcrypt.hash(NEW_PASSWORD, salt);
|
|
|
|
user.PASSWORD = hashedPassword;
|
|
await user.save();
|
|
|
|
response(200, null, "Password has been reset successfully!", res);
|
|
} catch (error) {
|
|
console.log(error);
|
|
if (error.name === "TokenExpiredError") {
|
|
return response(400, null, "Reset token has expired!", res);
|
|
} else {
|
|
return response(500, null, "Internal Server Error", res);
|
|
}
|
|
}
|
|
};
|