240 lines
6.4 KiB
JavaScript
240 lines
6.4 KiB
JavaScript
/**
|
|
* Copyright (c) 2026 Niolla
|
|
* All rights reserved.
|
|
*
|
|
* This source code is proprietary and confidential.
|
|
* Unauthorized copying, modification, distribution, or use
|
|
* of this file, via any medium, is strictly prohibited.
|
|
*/
|
|
|
|
// app/controllers/auth.controller.js
|
|
|
|
const { checkPassword } = require("../utils/hashPassword.util");
|
|
const { sendMail } = require("../utils/mail.util");
|
|
const { generateOTP, validateOTP } = require("../utils/otp.util");
|
|
const { getCachedUser, clearUserCache } = require("../utils/cache.util");
|
|
const { generateToken } = require("../utils/jwt.util");
|
|
const {
|
|
createRefreshSession,
|
|
validateRefreshSession,
|
|
deleteRefreshSession,
|
|
} = require("../utils/refreshSession.util");
|
|
const db = require("../models");
|
|
const { log } = require("../utils/consoleLog.utill");
|
|
|
|
const appName = process.env.APP_NAME || "Niolla";
|
|
|
|
const User = db.User;
|
|
|
|
// Login Step 1: Request OTP
|
|
exports.loginReq = async (req, res) => {
|
|
try {
|
|
const { email, password } = req.body;
|
|
|
|
const user = await getCachedUser(email);
|
|
if (!user) {
|
|
return res
|
|
.status(404)
|
|
.send({ success: false, message: "User Not Found" });
|
|
}
|
|
|
|
const passwordIsValid = await checkPassword(password, user.password);
|
|
if (!passwordIsValid) {
|
|
return res
|
|
.status(401)
|
|
.send({ success: false, message: "Invalid Password" });
|
|
}
|
|
|
|
const otp = generateOTP(email);
|
|
|
|
await sendMail({
|
|
to: email,
|
|
subject: `OTP for Your ${appName} Account`,
|
|
templateName: "otp",
|
|
templateVars: {
|
|
firstName: user.firstName,
|
|
otp: otp,
|
|
},
|
|
text: `Hello ${user.firstName}, your otp is ${otp}`,
|
|
});
|
|
|
|
log(`OTP for ${email}: ${otp}`);
|
|
log(`OTP sent to ${email} successfully.`);
|
|
|
|
res.status(201).send({ success: true, message: "OTP Sent Successfully" });
|
|
} catch (error) {
|
|
log("Error occurred while sending OTP:", error);
|
|
res.status(500).send({ success: false, message: error.message });
|
|
}
|
|
};
|
|
|
|
// Login Step 2: Verify OTP and issue JWT
|
|
exports.login = async (req, res) => {
|
|
try {
|
|
const { email, otp } = req.body;
|
|
|
|
if (!email || !otp) {
|
|
return res
|
|
.status(400)
|
|
.send({ success: false, message: "Email and OTP are required" });
|
|
}
|
|
|
|
const user = await getCachedUser(email);
|
|
|
|
if (!user) {
|
|
return res
|
|
.status(404)
|
|
.send({ success: false, message: "User Not Found" });
|
|
}
|
|
|
|
if (user.accountStatus !== "ACTIVE") {
|
|
return res.status(403).send({
|
|
success: false,
|
|
message: "Account is not active",
|
|
});
|
|
}
|
|
|
|
const isValidOTP = validateOTP(email, String(otp));
|
|
|
|
if (!isValidOTP) {
|
|
return res
|
|
.status(401)
|
|
.send({ success: false, message: "Invalid or Expired OTP" });
|
|
}
|
|
|
|
// Generate JWT token
|
|
const token = generateToken({
|
|
id: user.id,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
email: user.email,
|
|
accountType: user.accountType,
|
|
});
|
|
|
|
const { refreshToken } = createRefreshSession(user.id);
|
|
|
|
// 3. Set JWT as HttpOnly cookie
|
|
res.cookie("access_token", token, {
|
|
httpOnly: true, // JS cannot access
|
|
secure: process.env.NODE_ENV === "production", // HTTPS only in prod
|
|
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
|
maxAge: 15 * 60 * 1000, // 1 day
|
|
});
|
|
|
|
res.cookie("refresh_token", refreshToken, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
|
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
});
|
|
|
|
log(`JWT issued for ${email}`);
|
|
clearUserCache(email);
|
|
|
|
res.status(200).send({
|
|
success: true,
|
|
message: "Login Successful",
|
|
data: {
|
|
id: user.id,
|
|
email: user.email,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
role: user.role,
|
|
accountType: user.accountType,
|
|
accessToken: token,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
log("Error occurred during login:", error);
|
|
res.status(500).send({ success: false, message: error.message });
|
|
}
|
|
};
|
|
|
|
exports.refreshToken = async (req, res) => {
|
|
try {
|
|
const refreshToken = req.cookies?.refresh_token;
|
|
|
|
if (!refreshToken) {
|
|
return res.status(401).send({
|
|
success: false,
|
|
message: "Refresh token is required",
|
|
});
|
|
}
|
|
|
|
const session = validateRefreshSession(refreshToken);
|
|
|
|
if (!session) {
|
|
res.clearCookie("refresh_token");
|
|
|
|
return res.status(401).send({
|
|
success: false,
|
|
message: "Invalid or expired session. Please login again.",
|
|
});
|
|
}
|
|
|
|
const user = await User.findByPk(session.userId);
|
|
|
|
if (!user || user.accountStatus !== "ACTIVE") {
|
|
deleteRefreshSession(session.sessionId);
|
|
|
|
return res.status(401).send({
|
|
success: false,
|
|
message: "Session is no longer valid",
|
|
});
|
|
}
|
|
|
|
// Generate new Access Token
|
|
const token = generateToken({
|
|
id: user.id,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
email: user.email,
|
|
accountType: user.accountType,
|
|
});
|
|
|
|
// Generate new Refresh Token
|
|
const { refreshToken: newRefreshToken } = createRefreshSession(user.id);
|
|
|
|
deleteRefreshSession(session.sessionId);
|
|
|
|
// Replace access cookie
|
|
res.cookie("access_token", token, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
|
maxAge: 15 * 60 * 1000,
|
|
});
|
|
|
|
// Replace refresh cookie
|
|
res.cookie("refresh_token", newRefreshToken, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
|
maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
});
|
|
|
|
return res.status(200).send({
|
|
success: true,
|
|
message: "Session refreshed successfully",
|
|
});
|
|
} catch (error) {
|
|
console.error("REFRESH ERROR:", error);
|
|
|
|
return res.status(401).send({
|
|
success: false,
|
|
message: "Invalid or expired session. Please login again.",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Logout: Clear the JWT cookie
|
|
exports.logout = (req, res) => {
|
|
res.clearCookie("access_token", {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
|
});
|
|
|
|
res.json({ success: true, message: "Logged out successfully" });
|
|
};
|