add create user and login user parts

This commit is contained in:
Isuru Bimsara
2026-08-21 02:20:57 +05:30
parent 621d348eb1
commit b2c5e198a1
15 changed files with 842 additions and 336 deletions
+112 -5
View File
@@ -12,12 +12,20 @@
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 { 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 {
@@ -47,7 +55,7 @@ exports.loginReq = async (req, res) => {
firstName: user.firstName,
otp: otp,
},
text: `Hello ${user.firstName}, your otp is ${otp}`,
text: `Hello ${user.firstName}, your otp is ${otp}`,
});
log(`OTP for ${email}: ${otp}`);
@@ -65,6 +73,12 @@ 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) {
@@ -73,7 +87,14 @@ exports.login = async (req, res) => {
.send({ success: false, message: "User Not Found" });
}
const isValidOTP = validateOTP(email, otp);
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
@@ -87,16 +108,24 @@ exports.login = async (req, res) => {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
role: user.role,
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: 24 * 60 * 60 * 1000, // 1 day
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}`);
@@ -112,6 +141,7 @@ exports.login = async (req, res) => {
lastName: user.lastName,
role: user.role,
accountType: user.accountType,
accessToken: token,
},
});
} catch (error) {
@@ -120,6 +150,83 @@ exports.login = async (req, res) => {
}
};
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", {
+183 -260
View File
@@ -9,19 +9,29 @@
// app/controllers/user.controller.js
// const { Op } = require("sequelize");
const db = require("../models");
const { hashPassword } = require("../utils/hashPassword.util");
const { validatePassword } = require("../utils/validation/validatePassword.util");
const {
validatePassword,
} = require("../utils/validation/validatePassword.util");
const { validateEmail } = require("../utils/validation/validateEmail.util");
const { generateUserId, generateId } = require("../utils/idGen.util");
const {
createCustomerDetails,
} = require("../utils/users/createCustomerDetails.util");
const {
createBusinessCustomerDetails,
} = require("../utils/users/createBusinessCustomer.util");
const { logActivity } = require("../services/activity.service");
const { sendMail } = require("../utils/mail.util");
const {
createEmailVerification, verifyEmailVerificationToken, deleteEmailVerification} = require("../utils/emailVerification.util");;
createEmailVerification,
verifyEmailVerificationToken,
sendVerificationEmail,
deleteEmailVerification,
} = require("../utils/emailVerification.util");
const User = db.User;
const Profile = db.Profile;
// const EmailVerification = db.EmailVerification;
// Create a new user
exports.createNewUser = async (req, res) => {
@@ -33,28 +43,48 @@ exports.createNewUser = async (req, res) => {
lastName,
email,
password,
accountType,
address,
phoneNumber,
businessName,
businessRegistrationNumber,
businessType,
contactName,
businessEmail,
expectedMonthlyVolume,
note,
} = req.body;
if (!firstName || !lastName || !email || !password) {
await transaction.rollback();
if (!firstName || !lastName || !email || !password || !accountType) {
await transaction.rollback();
return res.status(400).send({
success: false,
message:
"First name, last name, email and password are required",
});
}
return res.status(400).send({
success: false,
message:
"First name, last name, email, password and account type are required",
});
}
const emailValid = validateEmail(email);
if (accountType !== "customer" && accountType !== "business_customer") {
await transaction.rollback();
if (!emailValid) {
await transaction.rollback();
return res.status(400).send({
success: false,
message:
"Invalid account type. Must be either 'customer' or 'business_customer'",
});
}
return res.status(400).send({
success: false,
message: "Invalid email address",
});
}
const emailValid = validateEmail(email);
if (!emailValid) {
await transaction.rollback();
return res.status(400).send({
success: false,
message: "Invalid email address",
});
}
const userExists = await User.findOne({ where: { email } });
if (userExists) {
@@ -66,14 +96,6 @@ if (!emailValid) {
});
}
// let pass;
// if(accountType === "admin" || accountType === "superadmin" || accountType === "manager" || accountType === "support_agent") {
// pass = process.env.DEFAULT_PASSWORD;
// }else{
// pass = password;
// }
const validatePasswordResult = validatePassword(password);
if (!validatePasswordResult) {
@@ -95,13 +117,12 @@ if (!emailValid) {
lastName,
email,
password: hashedPassword,
// accountType,
accountType,
accountStatus: "PENDING_VERIFICATION",
emailVerifiedAt: null,
emailVerifiedAt: null,
},
{ transaction },
);
const newProfile = await Profile.create(
{
@@ -117,78 +138,52 @@ if (!emailValid) {
{ transaction },
);
//create customer and business customer
if (accountType === "customer") {
const customerData = {
address,phoneNumber,
};
await createCustomerDetails(
newUser.id,
customerData,
transaction,
);
} else if (accountType === "business_customer") {
const businessData = {
businessName,
businessRegistrationNumber,
businessType,
contactName,
phoneNumber,
businessEmail,
expectedMonthlyVolume,
note,
};
await createBusinessCustomerDetails(
newUser.id,
businessData,
transaction,
);
}
await transaction.commit();
const verificationToken = await createEmailVerification(newUser.id);
const confirmationLink =
`${process.env.FRONTEND_URL}/verify-email?token=${verificationToken}`;
try {
await sendMail({
to:
email,
subject:
"Confirm Your ZUMRI Account",
templateName:
"emailVerification",
templateVars: {
customer_name:
firstName,
confirmation_link:
confirmationLink,
},
text:
`Hello ${firstName}, please verify your ZUMRI account using this link: ${confirmationLink}`,
});
} catch (mailError) {
console.error(
"VERIFICATION EMAIL ERROR:",
mailError
await sendVerificationEmail(
newUser.email,
newUser.firstName,
verificationToken,
);
return res.status(201).send({
success: true,
message:
"Account created, but verification email could not be sent. Please request a new verification email.",
data: {
id:
newUser.id,
firstName:
newUser.firstName,
lastName:
newUser.lastName,
email:
newUser.email,
accountType:
newUser.accountType,
accountStatus:
newUser.accountStatus,
},
});
} catch (error) {
console.error("Error sending verification email:", error);
}
await logActivity({
user: req.user,
user: newUser,
description: `Created New User with ID: ${newUser.id}`,
type: "CREATE_USER",
module: "User Management",
@@ -202,20 +197,16 @@ if (!emailValid) {
firstName: newUser.firstName,
lastName: newUser.lastName,
email: newUser.email,
// accountType: newUser.accountType,
// role: newUser.role,
// department: newUser.department,
accountType: newUser.accountType,
},
});
} catch (error) {
if (!transaction.finished) {
await transaction.rollback();
}
if (!transaction.finished) {
await transaction.rollback();
}
console.error(
"CREATE USER ERROR:",
error
);
console.error("CREATE USER ERROR:", error);
res.status(500).send({
success: false,
@@ -226,168 +217,100 @@ if (!emailValid) {
};
// Verify customer email
exports.verifyEmail =
async (req, res) => {
exports.verifyEmail = async (req, res) => {
const transaction = await db.sequelize.transaction();
const transaction =
await db.sequelize.transaction();
try {
const { token } = req.body;
if (!token) {
await transaction.rollback();
try {
const {
token,
} = req.body;
return res.status(400).send({
success: false,
if (!token) {
await transaction.rollback();
return res.status(400).send({
success:
false,
message:
"Verification token is required",
});
}
const verification =
await verifyEmailVerificationToken(
token
);
if (!verification) {
await transaction.rollback();
return res.status(400).send({
success:
false,
message:
"Verification token is invalid or expired",
});
}
const {
userId,
redisKey,
} =
verification;
const user =
await User.findOne({
where: {
id:
userId,
},
transaction,
});
if (!user) {
await transaction.rollback();
await deleteEmailVerification(
redisKey
);
return res.status(404).send({
success:
false,
message:
"User not found",
});
}
if (
user.accountStatus ===
"ACTIVE" &&
user.emailVerifiedAt
) {
await transaction.rollback();
// Token is no longer needed
await deleteEmailVerification(
redisKey
);
return res.status(400).send({
success:
false,
message:
"Email is already verified",
});
}
user.accountStatus =
"ACTIVE";
user.emailVerifiedAt =
new Date();
await user.save({
transaction,
});
await transaction.commit();
await deleteEmailVerification(
redisKey
);
return res.status(200).send({
success:
true,
message:
"Email verified successfully. Your account is now active.",
});
} catch (error) {
if (!transaction.finished) {
await transaction.rollback();
}
console.error(
"VERIFY EMAIL ERROR:",
error
);
return res.status(500).send({
success:
false,
message:
"Failed to verify email",
message: "Verification token is required",
});
}
};
const verification = await verifyEmailVerificationToken(token);
if (!verification) {
await transaction.rollback();
return res.status(400).send({
success: false,
message: "Verification token is invalid or expired",
});
}
const { userId, redisKey } = verification;
const user = await User.findOne({
where: {
id: userId,
},
transaction,
});
if (!user) {
await transaction.rollback();
await deleteEmailVerification(redisKey);
return res.status(404).send({
success: false,
message: "User not found",
});
}
if (user.accountStatus === "ACTIVE" && user.emailVerifiedAt) {
await transaction.rollback();
// Token is no longer needed
await deleteEmailVerification(redisKey);
return res.status(400).send({
success: false,
message: "Email is already verified",
});
}
user.accountStatus = "ACTIVE";
user.emailVerifiedAt = new Date();
await user.save({
transaction,
});
await transaction.commit();
await deleteEmailVerification(redisKey);
return res.status(200).send({
success: true,
message: "Email verified successfully. Your account is now active.",
});
} catch (error) {
if (!transaction.finished) {
await transaction.rollback();
}
console.error("VERIFY EMAIL ERROR:", error);
return res.status(500).send({
success: false,
message: "Failed to verify email",
});
}
};
// Get users with pagination (20 per page)
exports.getAllUsers = async (req, res) => {