import { NextRequest, NextResponse } from 'next/server' import jwt from 'jsonwebtoken' import bcrypt from 'bcryptjs' import crypto from 'crypto' import connectToDatabase from './mongodb' import { User } from './models' import type { AuthUser, SessionPayload } from '@/types/user' // ─── Password Hashing ──────────────────────────────────────────── export async function hashPassword(password: string): Promise { return bcrypt.hash(password, 12) } export async function verifyPassword(password: string, hashedPassword: string): Promise { return bcrypt.compare(password, hashedPassword) } // ─── JWT Tokens ────────────────────────────────────────────────── export function generateAccessToken(payload: SessionPayload): string { return jwt.sign(payload, process.env.JWT_ACCESS_SECRET!, { expiresIn: '1h' }) } export function generateRefreshToken(payload: SessionPayload): string { return jwt.sign(payload, process.env.JWT_REFRESH_SECRET!, { expiresIn: '7d' }) } export function verifyAccessToken(token: string): SessionPayload | null { try { return jwt.verify(token, process.env.JWT_ACCESS_SECRET!) as SessionPayload } catch { return null } } export function verifyRefreshToken(token: string): SessionPayload | null { try { return jwt.verify(token, process.env.JWT_REFRESH_SECRET!) as SessionPayload } catch { return null } } // ─── 2FA Code ──────────────────────────────────────────────────── export function generate2FACode(): string { return crypto.randomInt(100000, 999999).toString() } export function hash2FACode(code: string): string { const hmac = crypto.createHmac('sha256', process.env.TWO_FACTOR_SECRET!) hmac.update(code) return hmac.digest('hex') } export function verify2FACode(inputCode: string, hashedCode: string): boolean { const inputHash = hash2FACode(inputCode.trim()) try { return crypto.timingSafeEqual( Buffer.from(inputHash, 'hex'), Buffer.from(hashedCode, 'hex') ) } catch { return false } } // ─── Session Validation ────────────────────────────────────────── export async function validateSession(request: NextRequest): Promise { const token = request.cookies.get('session-token')?.value if (!token) return null const payload = verifyAccessToken(token) if (!payload) return null try { await connectToDatabase() const user = await User.findById(payload.userId) .populate('roles') .lean() if (!user || user.status !== 'active') return null const permissions = new Set() const roleNames: string[] = [] if (Array.isArray(user.roles)) { for (const role of user.roles) { if (typeof role === 'object' && role !== null) { const r = role as unknown as { name: string; permissions: { resource: string; actions: string[] }[] } roleNames.push(r.name) for (const perm of r.permissions) { for (const action of perm.actions) { permissions.add(`${perm.resource}:${action}`) } } } } } return { _id: user._id.toString(), id: user._id.toString(), username: user.username, email: user.email, permissions: Array.from(permissions), roles: roleNames, } } catch { return null } } // ─── Cookie Helpers ────────────────────────────────────────────── export function setAuthCookies(response: NextResponse, accessToken: string, refreshToken: string): void { const isProduction = process.env.NODE_ENV === 'production' response.cookies.set('session-token', accessToken, { httpOnly: true, secure: isProduction, sameSite: 'strict', maxAge: 3600, // 1 hour path: '/', }) response.cookies.set('refresh-token', refreshToken, { httpOnly: true, secure: isProduction, sameSite: 'strict', maxAge: 7 * 24 * 3600, // 7 days path: '/', }) } export function clearAuthCookies(response: NextResponse): void { response.cookies.set('session-token', '', { maxAge: 0, path: '/' }) response.cookies.set('refresh-token', '', { maxAge: 0, path: '/' }) } // ─── Permission Check ──────────────────────────────────────────── export function hasPermission(user: AuthUser, permission: string): boolean { if (user.permissions.includes('*:*')) return true return user.permissions.includes(permission) }