128 lines
3.2 KiB
TypeScript
128 lines
3.2 KiB
TypeScript
import jwt from 'jsonwebtoken';
|
|
import httpStatus from 'http-status';
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import ApiError from '../../utils/helper/ApiError';
|
|
import config from '../../../config/config';
|
|
import { ROLE } from '@/common/utils/constants/common.constant';
|
|
|
|
import { prisma } from '../../database/prisma.client';
|
|
|
|
interface DecodedToken {
|
|
id?: number;
|
|
sub?: string | number;
|
|
role?: string;
|
|
iat: number;
|
|
exp: number;
|
|
}
|
|
|
|
interface UserPayload {
|
|
id: string;
|
|
role?: string;
|
|
}
|
|
|
|
declare module 'express-serve-static-core' {
|
|
interface Request {
|
|
user?: UserPayload;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Core authentication function - verifies JWT and validates Host user
|
|
* Can be used by both Express middleware and Lambda handlers
|
|
*/
|
|
export async function verifyUserToken(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 },
|
|
});
|
|
|
|
const latestToken = await prisma.token.findFirst({
|
|
where: {
|
|
userXid: userId
|
|
},
|
|
orderBy: { id: 'desc' }
|
|
})
|
|
|
|
if (latestToken.isBlackListed == true) {
|
|
throw new ApiError(401, "This session is expired. Please login.")
|
|
}
|
|
|
|
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 = 6)
|
|
if (user.roleXid !== ROLE.USER) {
|
|
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
|
|
}
|
|
|
|
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 verifyUserToken(token);
|
|
|
|
// Attach user to request
|
|
req.user = { id: userInfo.id.toString(), role: userInfo.role };
|
|
|
|
resolve();
|
|
} catch (error) {
|
|
return reject(error as Error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Express middleware — use as `auth()` in routes
|
|
*/
|
|
const authForHost =
|
|
() =>
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
return new Promise((resolve, reject) => {
|
|
verifyCallback(req, resolve, reject);
|
|
})
|
|
.then(() => next())
|
|
.catch((err) => next(err));
|
|
};
|
|
|
|
export default authForHost;
|