132 lines
3.0 KiB
TypeScript
132 lines
3.0 KiB
TypeScript
/**
|
|
* Auth Middleware - JWT verification for Express/NestJS
|
|
*
|
|
* @module @erp-suite/core/middleware
|
|
*/
|
|
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import jwt from 'jsonwebtoken';
|
|
|
|
/**
|
|
* JWT payload structure
|
|
*/
|
|
export interface JwtPayload {
|
|
userId: string;
|
|
tenantId: string;
|
|
email: string;
|
|
roles: string[];
|
|
iat?: number;
|
|
exp?: number;
|
|
}
|
|
|
|
/**
|
|
* Extended Express Request with auth context
|
|
*/
|
|
export interface AuthRequest extends Request {
|
|
user?: JwtPayload;
|
|
}
|
|
|
|
/**
|
|
* Auth middleware configuration
|
|
*/
|
|
export interface AuthMiddlewareConfig {
|
|
jwtSecret: string;
|
|
skipPaths?: string[];
|
|
}
|
|
|
|
/**
|
|
* Creates an auth middleware that verifies JWT tokens
|
|
*
|
|
* @param config - Middleware configuration
|
|
* @returns Express middleware function
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* import { createAuthMiddleware } from '@erp-suite/core/middleware';
|
|
*
|
|
* const authMiddleware = createAuthMiddleware({
|
|
* jwtSecret: process.env.JWT_SECRET,
|
|
* skipPaths: ['/health', '/login'],
|
|
* });
|
|
*
|
|
* app.use(authMiddleware);
|
|
* ```
|
|
*/
|
|
export function createAuthMiddleware(config: AuthMiddlewareConfig) {
|
|
return (req: AuthRequest, res: Response, next: NextFunction): void => {
|
|
// Skip authentication for certain paths
|
|
if (config.skipPaths?.some((path) => req.path.startsWith(path))) {
|
|
return next();
|
|
}
|
|
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (!authHeader) {
|
|
res.status(401).json({ error: 'No authorization header' });
|
|
return;
|
|
}
|
|
|
|
const parts = authHeader.split(' ');
|
|
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
|
res.status(401).json({ error: 'Invalid authorization header format' });
|
|
return;
|
|
}
|
|
|
|
const token = parts[1];
|
|
|
|
try {
|
|
const payload = jwt.verify(token, config.jwtSecret) as JwtPayload;
|
|
req.user = payload;
|
|
next();
|
|
} catch (error) {
|
|
if (error instanceof jwt.TokenExpiredError) {
|
|
res.status(401).json({ error: 'Token expired' });
|
|
return;
|
|
}
|
|
res.status(401).json({ error: 'Invalid token' });
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* NestJS Guard for JWT authentication
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* import { AuthGuard } from '@erp-suite/core/middleware';
|
|
*
|
|
* @Controller('api')
|
|
* @UseGuards(AuthGuard)
|
|
* export class ApiController {
|
|
* // Protected routes
|
|
* }
|
|
* ```
|
|
*/
|
|
export class AuthGuard {
|
|
constructor(private readonly jwtSecret: string) {}
|
|
|
|
canActivate(context: any): boolean {
|
|
const request = context.switchToHttp().getRequest();
|
|
const authHeader = request.headers.authorization;
|
|
|
|
if (!authHeader) {
|
|
return false;
|
|
}
|
|
|
|
const parts = authHeader.split(' ');
|
|
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
|
return false;
|
|
}
|
|
|
|
const token = parts[1];
|
|
|
|
try {
|
|
const payload = jwt.verify(token, this.jwtSecret) as JwtPayload;
|
|
request.user = payload;
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
}
|