/** * 后台管理员登录鉴权:生成/校验签名 Cookie,不暴露账号密码 * 账号密码从环境变量读取,默认 admin / key123456(与 .cursorrules 一致) */ import { createHmac, timingSafeEqual } from 'crypto' const COOKIE_NAME = 'admin_session' const MAX_AGE_SEC = 7 * 24 * 3600 // 7 天 const SECRET = process.env.ADMIN_SESSION_SECRET || 'soul-admin-secret-change-in-prod' export function getAdminCredentials() { return { username: process.env.ADMIN_USERNAME || 'admin', password: process.env.ADMIN_PASSWORD || 'key123456', } } export function verifyAdminCredentials(username: string, password: string): boolean { const { username: u, password: p } = getAdminCredentials() return username === u && password === p } function sign(payload: string): string { return createHmac('sha256', SECRET).update(payload).digest('base64url') } /** 生成签名 token,写入 Cookie 用 */ export function createAdminToken(): string { const exp = Math.floor(Date.now() / 1000) + MAX_AGE_SEC const payload = `${exp}` const sig = sign(payload) return `${payload}.${sig}` } /** 校验 Cookie 中的 token */ export function verifyAdminToken(token: string | null | undefined): boolean { if (!token || typeof token !== 'string') return false const dot = token.indexOf('.') if (dot === -1) return false const payload = token.slice(0, dot) const sig = token.slice(dot + 1) const exp = parseInt(payload, 10) if (Number.isNaN(exp) || exp < Math.floor(Date.now() / 1000)) return false const expected = sign(payload) if (typeof expected !== 'string' || typeof sig !== 'string') return false if (sig.length !== expected.length) return false try { return timingSafeEqual(Buffer.from(sig, 'base64url'), Buffer.from(expected, 'base64url')) } catch { return false } } export function getAdminCookieName() { return COOKIE_NAME } export function getAdminCookieOptions() { return { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' as const, maxAge: MAX_AGE_SEC, path: '/', } } /** 从请求中读取 admin cookie 并校验,未通过时返回 null */ export function getAdminTokenFromRequest(request: Request): string | null { const cookieHeader = request.headers.get('cookie') if (!cookieHeader) return null const name = COOKIE_NAME + '=' const start = cookieHeader.indexOf(name) if (start === -1) return null const valueStart = start + name.length const end = cookieHeader.indexOf(';', valueStart) const value = end === -1 ? cookieHeader.slice(valueStart) : cookieHeader.slice(valueStart, end) return value.trim() || null } /** 若未登录则返回 401 Response,供各 admin API 使用 */ export function requireAdminResponse(request: Request): Response | null { const token = getAdminTokenFromRequest(request) if (!verifyAdminToken(token)) { return new Response(JSON.stringify({ error: '未授权访问,请先登录' }), { status: 401, headers: { 'Content-Type': 'application/json' }, }) } return null }