```typescript
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
import { Request, Response, NextFunction } from "express";
// ============================================
// Configuration
// ============================================
const JWT_SECRET =
process.env.JWT_SECRET || "your-super-secret-key-change-in-production";
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "24h";
const BCRYPT_ROUNDS = 12;
// ============================================
// Types
// ============================================
export interface TokenPayload {
userId: string;
email: string;
role: "user" | "admin";
}
export interface AuthRequest extends Request {
user?: TokenPayload;
}
// ============================================
// Password Hashing
// ============================================
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, BCRYPT_ROUNDS);
}
export async function verifyPassword(
password: string,
hash: string,
): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// ============================================
// JWT Token Functions
// ============================================
export function generateToken(payload: TokenPayload): string {
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
}
export function verifyToken(token: string): TokenPayload | null {
try {
return jwt.verify(token, JWT_SECRET) as TokenPayload;
} catch {
return null;
}
}
export function decodeToken(token: string): TokenPayload | null {
try {
return jwt.decode(token) as TokenPayload;
} catch {
return null;
}
}
// ============================================
// Middleware
// ============================================
export function authMiddleware(
req: AuthRequest,
res: Response,
next: NextFunction,
) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "No token provided" });
}
const token = authHeader.split(" ")[1];
const payload = verifyToken(token);
if (!payload) {
return res.status(401).json({ error: "Invalid or expired token" });
}
req.user = payload;
next();
}
export function requireRole(...roles: string[]) {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: "Authentication required" });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
}
export function optionalAuth(
req: AuthRequest,
res: Response,
next: NextFunction,
) {
const authHeader = req.headers.authorization;
if (authHeader?.startsWith("Bearer ")) {
const token = authHeader.split(" ")[1];
const payload = verifyToken(token);
if (payload) {
req.user = payload;
}
}
next();
}
// ============================================
// Refresh Token (In-Memory - Use Redis in production)
// ============================================
const refreshTokens = new Map<string, { userId: string; expiresAt: Date }>();
export function generateRefreshToken(userId: string): string {
const token = Buffer.from(
`${userId}-${Date.now()}-${Math.random()}`,
).toString("base64");
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
refreshTokens.set(token, { userId, expiresAt });
return token;
}
export function validateRefreshToken(token: string): string | null {
const data = refreshTokens.get(token);
if (!data) return null;
if (data.expiresAt < new Date()) {
refreshTokens.delete(token);
return null;
}
return data.userId;
}
export function revokeRefreshToken(token: string): void {
refreshTokens.delete(token);
}
// ============================================
// Rate Limiting (Simple In-Memory)
// ============================================
const requestCounts = new Map<string, { count: number; resetAt: Date }>();
export function rateLimiter(maxRequests: number, windowMs: number) {
return (req: Request, res: Response, next: NextFunction) => {
const ip = req.ip || req.socket.remoteAddress || "unknown";
const now = new Date();
const record = requestCounts.get(ip);
if (!record || record.resetAt < now) {
requestCounts.set(ip, {
count: 1,
resetAt: new Date(now.getTime() + windowMs),
});
return next();
}
if (record.count >= maxRequests) {
const retryAfter = Math.ceil(
(record.resetAt.getTime() - now.getTime()) / 1000,
);
res.set("Retry-After", retryAfter.toString());
return res.status(429).json({
error: "Too many requests",
retryAfter,
});
}
record.count++;
next();
};
}
// ============================================
// CSRF Protection
// ============================================
const csrfTokens = new Map<string, Date>();
export function generateCsrfToken(): string {
const token = Buffer.from(`${Date.now()}-${Math.random()}`).toString(
"base64",
);
csrfTokens.set(token, new Date(Date.now() + 3600000)); // 1 hour
return token;
}
export function csrfProtection(
req: Request,
res: Response,
next: NextFunction,
) {
if (["GET", "HEAD", "OPTIONS"].includes(req.method)) {
return next();
}
const token = req.headers["x-csrf-token"] as string;
if (!token || !csrfTokens.has(token)) {
return res.status(403).json({ error: "Invalid CSRF token" });
}
const expiry = csrfTokens.get(token)!;
if (expiry < new Date()) {
csrfTokens.delete(token);
return res.status(403).json({ error: "CSRF token expired" });
}
next();
}
// ============================================
// Security Headers Middleware
// ============================================
export function securityHeaders(
req: Request,
res: Response,
next: NextFunction,
) {
res.set({
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'self'",
});
next();
}
```