81 lines
2.0 KiB
TypeScript
81 lines
2.0 KiB
TypeScript
import type { Request, Response, NextFunction } from "express";
|
|
import jwt from "jsonwebtoken";
|
|
import type { AuthenticatedUser, Role } from "@fleetforge/shared";
|
|
|
|
const JWT_SECRET = process.env.JWT_SECRET || "dev-secret";
|
|
|
|
const ROLE_HIERARCHY: Record<Role, number> = {
|
|
owner: 4,
|
|
admin: 3,
|
|
operator: 2,
|
|
read_only: 1,
|
|
};
|
|
|
|
export function generateToken(user: AuthenticatedUser): string {
|
|
return jwt.sign(
|
|
{
|
|
userId: user.userId,
|
|
organizationId: user.organizationId,
|
|
role: user.role,
|
|
email: user.email,
|
|
},
|
|
JWT_SECRET,
|
|
{ expiresIn: (process.env.JWT_EXPIRES_IN || "24h") as any }
|
|
);
|
|
}
|
|
|
|
export function verifyToken(token: string): AuthenticatedUser {
|
|
return jwt.verify(token, JWT_SECRET) as AuthenticatedUser;
|
|
}
|
|
|
|
export function authenticate(req: Request, res: Response, next: NextFunction) {
|
|
const authHeader = req.headers.authorization;
|
|
if (!authHeader?.startsWith("Bearer ")) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: { code: "UNAUTHORIZED", message: "Missing or invalid token" },
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const token = authHeader.substring(7);
|
|
const user = verifyToken(token);
|
|
(req as any).user = user;
|
|
next();
|
|
} catch {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: { code: "UNAUTHORIZED", message: "Invalid or expired token" },
|
|
});
|
|
}
|
|
}
|
|
|
|
export function authorize(...allowedRoles: Role[]) {
|
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
const user = (req as any).user as AuthenticatedUser;
|
|
if (!user) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: { code: "UNAUTHORIZED", message: "Not authenticated" },
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (allowedRoles.length > 0) {
|
|
const hasRole = allowedRoles.some(
|
|
(role) => ROLE_HIERARCHY[user.role] >= ROLE_HIERARCHY[role]
|
|
);
|
|
if (!hasRole) {
|
|
res.status(403).json({
|
|
success: false,
|
|
error: { code: "FORBIDDEN", message: "Insufficient permissions" },
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
next();
|
|
};
|
|
}
|