// shared/lib/auth/middleware.ts — 通用 middleware 工厂(Edge Runtime 兼容) // 不使用 Node.js crypto,仅用 atob 解码 JWT payload import { NextResponse, type NextRequest } from 'next/server' export interface MiddlewareConfig { /** 公开路径(不需要认证),默认 ['/login', '/api/auth', '/api/health', '/_next', '/favicon.ico'] */ publicPaths?: string[] /** 需要 admin 权限的路径前缀,默认 ['/admin'] */ adminPaths?: string[] /** 本地 cookie 名(如 'session_issue'),用于回退验证 */ localCookieName?: string /** 启用 API Key 认证(读取 ALLOWED_API_KEYS 环境变量),默认 false */ enableApiKey?: boolean /** 管理员角色名,默认 'admin' */ adminRole?: string } // Edge Runtime 兼容:用 atob 解码 JWT payload,不验证签名 function decodeJwtPayload(token: string): Record | null { try { const parts = token.split('.') if (parts.length !== 3) return null let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/') while (payload.length % 4) payload += '=' return JSON.parse(atob(payload)) } catch { return null } } function isValidPayload(payload: Record | null): boolean { if (!payload) return false return !(payload.exp && (payload.exp as number) < Math.floor(Date.now() / 1000)) } function noCache(response: NextResponse): NextResponse { response.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate') return response } /** * 创建通用 middleware * * @example * ```ts * // src/middleware.ts — 使用默认配置 * export { middleware, config } from '@shared/lib/auth/middleware' * ``` * * @example * ```ts * // src/middleware.ts — 自定义配置 * import { createMiddleware } from '@shared/lib/auth/middleware' * export const middleware = createMiddleware({ * localCookieName: 'session_issue', * adminPaths: ['/settings'], * enableApiKey: true, * }) * export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] } * ``` */ export function createMiddleware(cfg: MiddlewareConfig = {}) { const publicPaths = cfg.publicPaths || ['/login', '/api/auth', '/api/health', '/_next', '/favicon.ico'] const adminPaths = cfg.adminPaths || ['/admin'] const localCookieName = cfg.localCookieName const enableApiKey = cfg.enableApiKey || false const adminRole = cfg.adminRole || 'admin' return function middleware(request: NextRequest) { const { pathname } = request.nextUrl // 公开路径放行 if (publicPaths.some(p => pathname.startsWith(p))) { // 登录页:已登录用户自动跳转首页 if (pathname === '/login') { const token = request.cookies.get('tlyq_session')?.value || (localCookieName ? request.cookies.get(localCookieName)?.value : undefined) const payload = token ? decodeJwtPayload(token) : null if (isValidPayload(payload)) { return NextResponse.redirect(new URL('/', request.url)) } } return NextResponse.next() } // API Key 认证(可选) if (enableApiKey) { const authHeader = request.headers.get('authorization') if (authHeader?.startsWith('Bearer ak_')) { const key = authHeader.slice(7) const allowedKeys = process.env.ALLOWED_API_KEYS || '' if (allowedKeys && allowedKeys.split(',').map(k => k.trim()).includes(key)) { return NextResponse.next() } if (pathname.startsWith('/api/')) { return NextResponse.json({ error: '无效的 API Key' }, { status: 401 }) } } } // 验证 tlyq_session(共享 JWT) const sharedToken = request.cookies.get('tlyq_session')?.value const sharedPayload = sharedToken ? decodeJwtPayload(sharedToken) : null if (isValidPayload(sharedPayload)) { // 管理路径检查 admin 权限 if (adminPaths.some(p => pathname.startsWith(p))) { const role = sharedPayload?.role as string if (role !== adminRole && role !== 'admin') { return new NextResponse('Forbidden', { status: 403 }) } } // 设置 session cookie(兼容旧代码) if (sharedPayload) { const response = pathname.startsWith('/api/') ? NextResponse.next() : noCache(NextResponse.next()) response.cookies.set('session', JSON.stringify({ username: sharedPayload.username }), { httpOnly: true, sameSite: 'lax', path: '/', }) return response } } // 回退本地 cookie(可选) if (localCookieName) { const localToken = request.cookies.get(localCookieName)?.value const localPayload = localToken ? decodeJwtPayload(localToken) : null if (isValidPayload(localPayload)) { // 管理路径检查 admin 权限 if (adminPaths.some(p => pathname.startsWith(p))) { const role = localPayload?.role as string if (role !== adminRole && role !== 'admin') { return new NextResponse('Forbidden', { status: 403 }) } } if (pathname.startsWith('/api/')) { return NextResponse.next() } if (localPayload) { const response = noCache(NextResponse.next()) response.cookies.set('session', JSON.stringify({ username: localPayload.username }), { httpOnly: true, sameSite: 'lax', path: '/', }) return response } } // 清除无效的本地 cookie if (localToken) { const loginUrl = new URL('/login', request.url) const response = NextResponse.redirect(loginUrl) response.cookies.delete(localCookieName) if (sharedToken) response.cookies.delete('tlyq_session') return response } } // 未认证:API 返回 401,页面重定向登录 if (pathname.startsWith('/api/')) { return NextResponse.json({ error: '未登录' }, { status: 401 }) } return noCache(NextResponse.redirect(new URL('/login', request.url))) } } // 默认导出(无配置,适用于简单站点) export const middleware = createMiddleware() export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] }