made register and login apis for host

This commit is contained in:
2025-11-12 16:03:57 +05:30
parent 5399c8b987
commit c0e58fe1ce
24 changed files with 2052 additions and 163 deletions

View File

@@ -8,7 +8,8 @@ import config from '../../../config/config';
const prisma = new PrismaClient();
interface DecodedToken {
id: number;
id?: number;
sub?: string | number;
role?: string;
iat: number;
exp: number;
@@ -26,7 +27,59 @@ declare module 'express-serve-static-core' {
}
/**
* Verifies JWT and validates Host user (role_xid = 3)
* Core authentication function - verifies JWT and validates Host user
* Can be used by both Express middleware and Lambda handlers
*/
export async function verifyHostToken(token: string): Promise<{ id: number; role?: string }> {
if (!token) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
try {
const decoded = jwt.verify(token, config.jwt.secret) as unknown as DecodedToken;
const userId = decoded.id ?? (decoded.sub ? Number(decoded.sub) : null);
if (!userId) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
}
// ✅ Fetch user from Prisma (Host user only)
const user = await prisma.user.findUnique({
where: { id: userId },
include: { role: true },
});
if (!user) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
}
// ✅ Check if user is active
if (user.isActive === false) {
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
}
// ✅ Check Host role (role_xid = 4)
if (user.roleXid !== 4) {
throw new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only');
}
return { id: user.id, role: user.role?.roleName };
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.');
}
if (error instanceof ApiError) {
throw error;
}
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
}
}
/**
* Verifies JWT and validates Host user (role_xid = 4)
*/
const verifyCallback = async (
req: Request,
@@ -35,62 +88,22 @@ const verifyCallback = async (
) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
if (!token) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
try {
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
if (!decoded?.id) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
}
// ✅ Fetch user from Prisma (Host user only)
const user = await prisma.user.findUnique({
where: { id: decoded.id },
include: { role: true },
});
if (!user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
}
// ✅ Check if user is active
if (!user.isActive) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
);
}
// ✅ Check Host role (role_xid = 3)
if (user.roleXid !== 3) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
);
}
const userInfo = await verifyHostToken(token);
// Attach user to request
req.user = { id: user.id.toString(), role: user.role?.roleName };
req.user = { id: userInfo.id.toString(), role: userInfo.role };
resolve();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return reject(
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
);
}
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
);
return reject(error as Error);
}
};
/**
* Express middleware — use as `auth()` in routes
*/
const auth =
const authForHost =
() =>
async (req: Request, res: Response, next: NextFunction) => {
return new Promise((resolve, reject) => {
@@ -100,4 +113,4 @@ const auth =
.catch((err) => next(err));
};
export default auth;
export default authForHost;

View File

@@ -26,24 +26,19 @@ declare module 'express-serve-static-core' {
}
/**
* Verifies JWT and validates Host user (role_xid = 3)
* Core authentication function - verifies JWT and validates Host user
* Can be used by both Express middleware and Lambda handlers
*/
const verifyCallback = async (
req: Request,
resolve: (value?: unknown) => void,
reject: (reason?: Error) => void
) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
export async function verifyHostToken(token: string): Promise<{ id: number; role?: string }> {
if (!token) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
try {
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
if (!decoded?.id) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
}
// ✅ Fetch user from Prisma (Host user only)
@@ -53,44 +48,59 @@ const verifyCallback = async (
});
if (!user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
}
// ✅ Check if user is active
if (!user.isActive) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
);
if (user.isActive === false) {
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
}
// ✅ Check Admin role (role_xid = 2)
if (user.roleXid !== 2) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
);
// ✅ Check Host role (role_xid = 1)
if (user.roleXid !== 1) {
throw new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only');
}
return { id: user.id, role: user.role?.roleName };
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.');
}
if (error instanceof ApiError) {
throw error;
}
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
}
}
/**
* Verifies JWT and validates Host user (role_xid = 4)
*/
const verifyCallback = async (
req: Request,
resolve: (value?: unknown) => void,
reject: (reason?: Error) => void
) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
try {
const userInfo = await verifyHostToken(token);
// Attach user to request
req.user = { id: user.id.toString(), role: user.role?.roleName };
req.user = { id: userInfo.id.toString(), role: userInfo.role };
resolve();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return reject(
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
);
}
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
);
return reject(error as Error);
}
};
/**
* Express middleware — use as `auth()` in routes
*/
const auth =
const authForHost =
() =>
async (req: Request, res: Response, next: NextFunction) => {
return new Promise((resolve, reject) => {
@@ -100,4 +110,4 @@ const auth =
.catch((err) => next(err));
};
export default auth;
export default authForHost;

View File

@@ -26,24 +26,19 @@ declare module 'express-serve-static-core' {
}
/**
* Verifies JWT and validates Host user (role_xid = 3)
* Core authentication function - verifies JWT and validates Host user
* Can be used by both Express middleware and Lambda handlers
*/
const verifyCallback = async (
req: Request,
resolve: (value?: unknown) => void,
reject: (reason?: Error) => void
) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
export async function verifyHostToken(token: string): Promise<{ id: number; role?: string }> {
if (!token) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
try {
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
if (!decoded?.id) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
}
// ✅ Fetch user from Prisma (Host user only)
@@ -53,44 +48,59 @@ const verifyCallback = async (
});
if (!user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
}
// ✅ Check if user is active
if (!user.isActive) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
);
if (user.isActive === false) {
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
}
// ✅ Check User role (role_xid = 1)
if (user.roleXid !== 1) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
);
// ✅ Check Host role (role_xid = 6)
if (user.roleXid !== 6) {
throw new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only');
}
return { id: user.id, role: user.role?.roleName };
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.');
}
if (error instanceof ApiError) {
throw error;
}
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
}
}
/**
* Verifies JWT and validates Host user (role_xid = 4)
*/
const verifyCallback = async (
req: Request,
resolve: (value?: unknown) => void,
reject: (reason?: Error) => void
) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
try {
const userInfo = await verifyHostToken(token);
// Attach user to request
req.user = { id: user.id.toString(), role: user.role?.roleName };
req.user = { id: userInfo.id.toString(), role: userInfo.role };
resolve();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return reject(
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
);
}
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
);
return reject(error as Error);
}
};
/**
* Express middleware — use as `auth()` in routes
*/
const auth =
const authForHost =
() =>
async (req: Request, res: Response, next: NextFunction) => {
return new Promise((resolve, reject) => {
@@ -100,4 +110,4 @@ const auth =
.catch((err) => next(err));
};
export default auth;
export default authForHost;

View File

@@ -0,0 +1,37 @@
import * as crypto from 'crypto';
const algorithm = 'aes-256-cbc';
const secretKey = crypto.scryptSync('your-secret-password', 'salt', 32);
const ivLength = 16;
// Encrypt function
export function encryptUserId(id: string): string {
const iv = crypto.randomBytes(ivLength);
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
let encrypted = cipher.update(id, 'utf8', 'hex');
encrypted += cipher.final('hex');
return `${iv.toString('hex')}:${encrypted}`;
}
// Decrypt function
export function decryptUserId(encryptedId: string): string | null {
try {
const parts = encryptedId.split(':');
if (parts.length !== 2) {
console.error('Invalid encryptedId format:', encryptedId);
return null;
}
const iv = Buffer.from(parts[0], 'hex');
const encryptedText = Buffer.from(parts[1], 'hex');
const decipher = crypto.createDecipheriv(algorithm, secretKey, iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString('utf8');
} catch (error) {
console.error('Decryption failed:', error);
return null;
}
}

View File

@@ -0,0 +1,18 @@
import config from '../../../config/config';
export class OtpGenerator {
static generateOtp(): string {
if (config.byPassOTP) {
return '1234';
}
return Math.floor(1000 + Math.random() * 9000).toString();
}
}
export class OtpGeneratorSixDigit {
static generateOtp(): string {
if (config.byPassOTP) {
return '123456';
}
return Math.floor(100000 + Math.random() * 900000).toString();
}
}

View File

@@ -0,0 +1,91 @@
import * as bcrypt from "bcryptjs";
import { OtpGenerator, OtpGeneratorSixDigit } from "./OtpGenerator";
import { encryptUserId } from "./CodeGenerator";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export interface OtpResult {
otp: string; // Plain OTP (for sending)
hashedOtp: string; // Hashed OTP (for DB storage)
expiry: Date; // Expiry timestamp
encryptedId: string; // Encrypted user ID
emailMessage: string; // Message body for email
emailSubject: string; // Subject line for email
}
/**
* Generate OTP, store it in DB, and send email.
* @param userId The users ID
* @param email Recipient email
* @param emailPurpose For which flow (e.g. "Register", "Login", "ForgotPassword")
* @param otpLength OTP length (4 or 6)
* @param expiryMinutes Expiry time in minutes
*/
export async function generateOtpHelper(
userId: number,
email: string,
emailPurpose: "Register" | "Login" | "ForgotPassword",
otpLength: 4 | 6 = 4,
expiryMinutes: number = 5
): Promise<OtpResult> {
// Generate OTP
const otp =
otpLength === 6
? OtpGeneratorSixDigit.generateOtp()
: OtpGenerator.generateOtp();
console.log("Generated OTP:", otp);
// Hash OTP
const hashedOtp = await bcrypt.hash(otp, 10);
// Expiry time
const expiry = new Date(Date.now() + expiryMinutes * 60 * 1000);
// Encrypt user ID
const encryptedId = encryptUserId(userId.toString());
// 🔹 First delete old OTPs for this user & purpose
await prisma.userOtp.deleteMany({
where: {
userXid: userId,
otpType: emailPurpose,
isActive: true,
},
});
// Save OTP into user_otps table
await prisma.userOtp.create({
data: {
userXid: userId,
otpType: emailPurpose,
otpCode: hashedOtp,
expiresOn: expiry,
isVerified: false,
isActive: true,
// sendOn will default to now()
// createdAt will default to now()
},
});
// Build email content
const emailSubject = `${emailPurpose} OTP Verification`;
const emailMessage = `Your OTP for ${emailPurpose} is ${otp}. It will expire in ${expiryMinutes} minutes.`;
// Send email
// await sendBulkEmailForOTP([
// {
// to: [{ email: email }],
// subject: emailSubject,
// htmlContent: emailMessage,
// },
// ]);
return {
otp,
hashedOtp,
expiry,
encryptedId,
emailMessage,
emailSubject,
};
}

View File

@@ -0,0 +1,36 @@
// validations/hostBankDetails.validation.ts
import { z } from "zod";
export const hostBankDetailsSchema = z.object({
accountNumber: z
.number()
.int({ message: "Account number must be an integer" })
.positive({ message: "Account number must be a positive number" }),
accountHolderName: z
.string()
.nonempty("Account holder name is required")
.min(2, { message: "Account holder name must be at least 2 characters" }),
ifscCode: z
.string()
.nonempty("IFSC code is required")
.regex(/^[A-Z]{4}0[A-Z0-9]{6}$/, { message: "Invalid IFSC code format" }),
bankXid: z
.number()
.int({ message: "Bank ID must be an integer" })
.positive({ message: "Bank ID must be a positive number" }),
hostXid: z
.number()
.int({ message: "Host ID must be an integer" })
.positive({ message: "Host ID must be a positive number" }),
bankBranchXid: z
.number()
.int({ message: "Bank branch ID must be an integer" })
.positive({ message: "Bank branch ID must be a positive number" }),
});
export type HostBankDetailsSchema = z.infer<typeof hostBankDetailsSchema>;

View File

@@ -0,0 +1,44 @@
import { z } from "zod";
// Allowed document types (must match your DocumentType master table IDs)
export const REQUIRED_DOC_TYPES = {
PAN: 1,
GST: 2,
REGISTRATION: 3,
AADHAAR: 4,
};
export const hostCompanyDetailsSchema = z.object({
companyName: z.string().min(1, "Company name is required"),
address1: z.string().min(1, "Address1 is required"),
address2: z.string().optional(),
hostRefNumber: z.string().min(1, "Host reference number is required"),
cityXid: z.number().min(1, "City is required"),
stateXid: z.number().min(1, "State is required"),
countryXid: z.number().min(1,"Country is required"),
pinCode: z.string().min(4, "Pincode/Zipcode is required"),
logoPath: z.string().optional(),
isSubsidairy: z.boolean(),
registrationNumber: z.string().min(1, "Registration number is required"),
panNumber: z.string().min(1, "PAN number is required"),
gstNumber: z.string().optional(),
formationDate: z.string().refine((val) => !isNaN(Date.parse(val)), {
message: "Formation date must be a valid date",
}),
companyType: z.string().min(1, "Company type is required"),
websiteUrl: z.url().optional(),
instagramUrl: z.url().optional(),
facebookUrl: z.url().optional(),
linkedinUrl: z.url().optional(),
twitterUrl: z.url().optional(),
currencyXid: z.number().min(1, "Currency is required"),
});
// Validation for documents
export const hostDocumentsSchema = z.array(
z.object({
documentTypeXid: z.number(),
documentName: z.string(),
filePath: z.string(),
})
);

View File

@@ -0,0 +1,20 @@
// validations/hostBankDetails.validation.ts
import { z } from "zod";
export const loginForHostSchema = z.object({
emailAddress : z
.string()
.nonempty("Email is required"),
userPassword : z
.string()
.nonempty("Password is required")
.min(8, { message: "Password must be at least 8 characters" }),
});
export type loginForHostSchema = z.infer<typeof loginForHostSchema>;