import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' // API Key 验证:检查 ALLOWED_API_KEYS 环境变量(逗号分隔明文 key) // 注意:middleware 运行在 Edge Runtime,不能使用 better-sqlite3 等 Node.js 原生模块 // DB 级别的 key 验证在 route handler 中进行(auth.ts verifyApiKey) function verifyApiKey(key: string): boolean { if (!key.startsWith('ak_')) return false const allowedKeys = process.env.ALLOWED_API_KEYS || '' if (!allowedKeys) return false return allowedKeys.split(',').map(k => k.trim()).includes(key) } 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)) } export function middleware(request: NextRequest) { const { pathname } = request.nextUrl // 登录/退出路径 + 内部 API 放行(自有 key 认证) if (pathname === '/login' || pathname.startsWith('/api/auth/login') || pathname === '/api/auth/logout' || pathname.startsWith('/api/internal/')) { return NextResponse.next() } // API 路由:检查 Bearer API Key 或 session cookie if (pathname.startsWith('/api/')) { const authHeader = request.headers.get('authorization') if (authHeader?.startsWith('Bearer ak_')) { if (verifyApiKey(authHeader.slice(7))) return NextResponse.next() return NextResponse.json({ error: '未授权' }, { status: 401 }) } const sharedToken = request.cookies.get('tlyq_session')?.value const sharedPayload = sharedToken ? decodeJwtPayload(sharedToken) : null if (isValidPayload(sharedPayload)) return NextResponse.next() const localToken = request.cookies.get('session_assets')?.value const localPayload = localToken ? decodeJwtPayload(localToken) : null if (isValidPayload(localPayload)) return NextResponse.next() return NextResponse.json({ error: '未授权' }, { status: 401 }) } // 页面路由:优先检查 tlyq_session(共享 JWT),回退 session_assets(本地 JWT) const sharedToken = request.cookies.get('tlyq_session')?.value const sharedPayload = sharedToken ? decodeJwtPayload(sharedToken) : null if (isValidPayload(sharedPayload)) { const response = NextResponse.next() response.cookies.set('session', JSON.stringify({ username: sharedPayload.username }), { httpOnly: true, sameSite: 'lax', path: '/', }) return response } const localToken = request.cookies.get('session_assets')?.value const localPayload = localToken ? decodeJwtPayload(localToken) : null if (isValidPayload(localPayload)) { const response = NextResponse.next() response.cookies.set('session', JSON.stringify({ username: localPayload.username }), { httpOnly: true, sameSite: 'lax', path: '/', }) return response } // 未认证 → 重定向登录页 const loginUrl = new URL('/login', request.url) const dest = pathname + (request.nextUrl.search || '') loginUrl.searchParams.set('redirect', dest) const response = NextResponse.redirect(loginUrl) if (sharedToken) response.cookies.delete('tlyq_session') if (localToken) response.cookies.delete('session_assets') return response } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico|public).*)'] }