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
+49 -61
View File
@@ -1,103 +1,91 @@
// app/utils/emailVerification.util.js
const crypto = require("crypto");
const { sendMail } = require("../utils/mail.util");
const redis = require("../config/redisClient");
const EMAIL_VERIFICATION_TTL =
Number(
process.env.EMAIL_VERIFICATION_TTL_SECONDS
) || 1800;
Number(process.env.EMAIL_VERIFICATION_TTL_SECONDS) || 1800;
const generateEmailVerificationToken = () => {
return crypto
.randomBytes(32)
.toString("hex");
return crypto.randomBytes(32).toString("hex");
};
const hashEmailVerificationToken = (token) => {
return crypto
.createHash("sha256")
.update(token)
.digest("hex");
return crypto.createHash("sha256").update(token).digest("hex");
};
const createEmailVerification = async (userId) => {
// 1. Generate raw token
const token =
generateEmailVerificationToken();
const token = generateEmailVerificationToken();
// 2. Hash token
const tokenHash =
hashEmailVerificationToken(token);
const tokenHash = hashEmailVerificationToken(token);
// 3. Create Redis key
const redisKey =
`email-verification:${tokenHash}`;
const redisKey = `email-verification:${tokenHash}`;
await redis.set(
redisKey,
userId,
"EX",
EMAIL_VERIFICATION_TTL
);
await redis.set(redisKey, userId, "EX", EMAIL_VERIFICATION_TTL);
return token;
};
/**
* Check a verification token.
*/
const verifyEmailVerificationToken =
async (token) => {
const verifyEmailVerificationToken = async (token) => {
if (!token || typeof token !== "string") {
return null;
}
if (
!token ||
typeof token !== "string"
) {
return null;
}
// 1. Hash token received from user
const tokenHash = hashEmailVerificationToken(token);
// 2. Build same Redis key
const redisKey = `email-verification:${tokenHash}`;
// 1. Hash token received from user
const tokenHash =
hashEmailVerificationToken(token);
// 3. Search Redis
const userId = await redis.get(redisKey);
if (!userId) {
return null;
}
// 2. Build same Redis key
const redisKey =
`email-verification:${tokenHash}`;
// 3. Search Redis
const userId =
await redis.get(redisKey);
if (!userId) {
return null;
}
return {
userId,
redisKey,
};
return {
userId,
redisKey,
};
};
const deleteEmailVerification =
async (redisKey) => {
//send verification email to user
const sendVerificationEmail = async (email, firstName, verificationToken) => {
const confirmationLink = `${process.env.FRONTEND_URL}/verify-email?token=${verificationToken}`;
await redis.del(redisKey);
};
// Send email
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}`,
});
};
const deleteEmailVerification = async (redisKey) => {
await redis.del(redisKey);
};
module.exports = {
createEmailVerification,
verifyEmailVerificationToken,
sendVerificationEmail,
deleteEmailVerification,
hashEmailVerificationToken,
};
};
+10
View File
@@ -23,6 +23,14 @@ const generateUserId = () => {
return "usr_" + Math.random().toString(36).slice(2, 10);
};
const generateCustomerId = () => {
return "cust_" + Math.random().toString(36).slice(2, 10);
}
const generateBusinessCustomerId = () => {
return "b_cust_" + Math.random().toString(36).slice(2, 10);
}
const generateClientId = () => {
const prefix = "cli_";
return prefix + uuidv4();
@@ -141,6 +149,8 @@ const generateDocumentReferenceNo = async (type) => {
module.exports = {
generateUserId,
generateCustomerId,
generateBusinessCustomerId,
generateClientId,
generateInquId,
generateInquRefNo,
+12 -1
View File
@@ -15,6 +15,9 @@ require("dotenv").config();
const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_key";
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "1d"; // token validity
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || "your_refresh_token_secret_key";
const REFRESH_TOKEN_DAYS = process.env.REFRESH_TOKEN_DAYS || "7d"; // refresh token validity
/**
* Generate JWT token
* @param {Object} payload - usually { id, email, role }
@@ -33,4 +36,12 @@ const verifyToken = (token) => {
return jwt.verify(token, JWT_SECRET);
};
module.exports = { generateToken, verifyToken };
const generateRefreshToken = (payload) => {
return jwt.sign(payload, REFRESH_TOKEN_SECRET, { expiresIn: REFRESH_TOKEN_DAYS });
}
const verifyRefreshToken = (token) => {
return jwt.verify(token, REFRESH_TOKEN_SECRET);
}
module.exports = { generateToken, verifyToken, generateRefreshToken, verifyRefreshToken };
+3 -2
View File
@@ -17,8 +17,9 @@ function generateOTP(key){
return otp;
}
function saveOTP(key, otp, ttl = 5 * 60 * 1000) { // default TTL: 5 mins
const expiresAt = Date.now() + ttl;
function saveOTP(key, otp) {
ttl = parseInt(process.env.LOGIN_OTP_TTL_SECONDS || 300);
const expiresAt = Date.now() + ttl * 1000; // Convert seconds to milliseconds
otpCache.set(key, { otp, expiresAt });
}
+201
View File
@@ -0,0 +1,201 @@
// app/utils/refreshSession.util.js
const crypto = require("crypto");
const {
generateRefreshToken,
verifyRefreshToken,
} = require("./jwt.util");
const refreshSessions = new Map();
// Default = 7 days
const REFRESH_SESSION_TTL =
7 * 24 * 60 * 60 * 1000;
const hashRefreshToken = (token) => {
return crypto
.createHash("sha256")
.update(token)
.digest("hex");
};
const createRefreshSession = (userId) => {
// Unique session ID
const sessionId =
crypto.randomUUID();
// Create raw Refresh JWT
const refreshToken =
generateRefreshToken({
sub: userId,
sid: sessionId,
});
// Hash raw refresh token
const tokenHash =
hashRefreshToken(
refreshToken
);
// Expiration time
const expiresAt =
Date.now() +
REFRESH_SESSION_TTL;
// Save only HASH in RAM
refreshSessions.set(
sessionId,
{
userId,
tokenHash,
expiresAt,
}
);
return {
refreshToken,
sessionId,
};
};
const validateRefreshSession = (
refreshToken
) => {
if (!refreshToken) {
return null;
}
let decoded;
try {
decoded =
verifyRefreshToken(
refreshToken
);
} catch (error) {
return null;
}
const sessionId =
decoded.sid;
const userId =
decoded.sub;
if (!sessionId || !userId) {
return null;
}
// Get session from RAM
const session =
refreshSessions.get(
sessionId
);
if (!session) {
return null;
}
// Check session expiration
if (
Date.now() >
session.expiresAt
) {
refreshSessions.delete(
sessionId
);
return null;
}
// Hash received refresh token
const receivedHash =
hashRefreshToken(
refreshToken
);
// Compare stored hash
if (
receivedHash !==
session.tokenHash
) {
return null;
}
// Extra user check
if (
session.userId !==
userId
) {
return null;
}
return {
userId,
sessionId,
};
};
const deleteRefreshSession = (
sessionId
) => {
refreshSessions.delete(
sessionId
);
};
const deleteAllUserSessions = (
userId
) => {
for (
const [sessionId, session]
of refreshSessions.entries()
) {
if (
session.userId === userId
) {
refreshSessions.delete(
sessionId
);
}
}
};
module.exports = {
createRefreshSession,
validateRefreshSession,
deleteRefreshSession,
deleteAllUserSessions,
};
@@ -0,0 +1,86 @@
// app/utils/users/createBusinessCustomer.util.js
const db = require("../../models");
const {
generateBusinessCustomerId,
} = require("../idGen.util");
const BusinessCustomer = db.BusinessCustomer;
/**
* Create business-customer-specific details
*/
const createBusinessCustomerDetails = async (
userId,
businessData,
transaction
) => {
const {
businessName,
businessRegistrationNumber,
businessType,
contactName,
phoneNumber,
businessEmail,
expectedMonthlyVolume,
note,
} = businessData;
if (
!businessName ||
!businessRegistrationNumber ||
!businessType ||
!contactName ||
!phoneNumber ||
!businessEmail ||
!expectedMonthlyVolume
) {
throw new Error(
"Required business customer details are missing"
);
}
const businessCustomer =
await BusinessCustomer.create(
{
business_customer_id:
generateBusinessCustomerId(),
user_id:
userId,
businessName,
businessRegistrationNumber,
businessType,
contactName,
phoneNumber,
businessEmail,
expectedMonthlyVolume,
note:
note || null,
},
{
transaction,
}
);
return businessCustomer;
};
module.exports = {
createBusinessCustomerDetails,
};
@@ -0,0 +1,54 @@
//app/utils/users/createCustomerDetails.util.js
const db = require("../../models");
const { generateCustomerId } = require("../idGen.util");
const Customer = db.Customer;
const createCustomerDetails = async (
userId,
customerData,
transaction
) => {
const {
address,
phoneNumber,
} = customerData;
if (!address || !phoneNumber) {
throw new Error(
"Address and phone number are required for customer"
);
}
const customer = await Customer.create(
{
customer_id:
generateCustomerId(),
user_id:
userId,
address:
address,
phoneNumber:
phoneNumber,
},
{
transaction,
}
);
return customer;
};
module.exports = {
createCustomerDetails,
};