/* eslint-disable no-console,@typescript-eslint/no-explicit-any */ import { NextRequest, NextResponse } from 'next/server'; import { getConfig } from '@/lib/config'; import { db } from '@/lib/db'; export const runtime = 'nodejs'; // 读取存储类型环境变量,默认 localstorage const STORAGE_TYPE = (process.env.NEXT_PUBLIC_STORAGE_TYPE as | 'localstorage' | 'redis' | 'upstash' | 'kvrocks' | undefined) || 'localstorage'; // 生成签名 async function generateSignature( data: string, secret: string ): Promise { const encoder = new TextEncoder(); const keyData = encoder.encode(secret); const messageData = encoder.encode(data); // 导入密钥 const key = await crypto.subtle.importKey( 'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ); // 生成签名 const signature = await crypto.subtle.sign('HMAC', key, messageData); // 转换为十六进制字符串 return Array.from(new Uint8Array(signature)) .map((b) => b.toString(16).padStart(2, '0')) .join(''); } // 生成认证Cookie(带签名) async function generateAuthCookie( username?: string, password?: string, role?: 'owner' | 'admin' | 'user', includePassword = false ): Promise { const authData: any = { role: role || 'user' }; // 只在需要时包含 password if (includePassword && password) { authData.password = password; } if (username && process.env.PASSWORD) { authData.username = username; // 使用密码作为密钥对用户名进行签名 const signature = await generateSignature(username, process.env.PASSWORD); authData.signature = signature; authData.timestamp = Date.now(); // 添加时间戳防重放攻击 } return encodeURIComponent(JSON.stringify(authData)); } export async function POST(req: NextRequest) { try { // 本地 / localStorage 模式——仅校验固定密码 if (STORAGE_TYPE === 'localstorage') { const envPassword = process.env.PASSWORD; // 未配置 PASSWORD 时直接放行 if (!envPassword) { const response = NextResponse.json({ ok: true }); // 清除可能存在的认证cookie response.cookies.set('auth', '', { path: '/', expires: new Date(0), sameSite: 'lax', // 改为 lax 以支持 PWA httpOnly: false, // PWA 需要客户端可访问 secure: false, // 根据协议自动设置 }); return response; } const { password } = await req.json(); if (typeof password !== 'string') { return NextResponse.json({ error: '密码不能为空' }, { status: 400 }); } if (password !== envPassword) { return NextResponse.json( { ok: false, error: '密码错误' }, { status: 401 } ); } // 验证成功,设置认证cookie const response = NextResponse.json({ ok: true }); const cookieValue = await generateAuthCookie( undefined, password, 'user', true ); // localstorage 模式包含 password const expires = new Date(); expires.setDate(expires.getDate() + 7); // 7天过期 response.cookies.set('auth', cookieValue, { path: '/', expires, sameSite: 'lax', // 改为 lax 以支持 PWA httpOnly: false, // PWA 需要客户端可访问 secure: false, // 根据协议自动设置 }); return response; } // 数据库 / redis 模式——校验用户名并尝试连接数据库 const { username, password, machineCode } = await req.json(); if (!username || typeof username !== 'string') { return NextResponse.json({ error: '用户名不能为空' }, { status: 400 }); } if (!password || typeof password !== 'string') { return NextResponse.json({ error: '密码不能为空' }, { status: 400 }); } // 可能是站长,直接读环境变量 if ( username === process.env.USERNAME && password === process.env.PASSWORD ) { // 验证成功,设置认证cookie const response = NextResponse.json({ ok: true }); const cookieValue = await generateAuthCookie( username, password, 'owner', false ); // 数据库模式不包含 password const expires = new Date(); expires.setDate(expires.getDate() + 7); // 7天过期 response.cookies.set('auth', cookieValue, { path: '/', expires, sameSite: 'lax', // 改为 lax 以支持 PWA httpOnly: false, // PWA 需要客户端可访问 secure: false, // 根据协议自动设置 }); return response; } else if (username === process.env.USERNAME) { return NextResponse.json({ error: '用户名或密码错误' }, { status: 401 }); } const config = await getConfig(); const user = config.UserConfig.Users.find((u) => u.username === username); if (user && user.banned) { return NextResponse.json({ error: '用户被封禁' }, { status: 401 }); } // 校验用户密码 try { const pass = await db.verifyUser(username, password); if (!pass) { return NextResponse.json( { error: '用户名或密码错误' }, { status: 401 } ); } // 检查机器码绑定 const boundMachineCode = await db.getUserMachineCode(username); if (boundMachineCode) { // 用户已绑定机器码,需要验证 if (!machineCode) { return NextResponse.json({ error: '该账户已绑定设备,请提供机器码', requireMachineCode: true }, { status: 403 }); } if (machineCode.toUpperCase() !== boundMachineCode.toUpperCase()) { return NextResponse.json({ error: '机器码不匹配,此账户只能在绑定的设备上使用', machineCodeMismatch: true }, { status: 403 }); } } else if (machineCode) { // 用户未绑定机器码,但提供了机器码,检查是否被其他用户绑定 const codeOwner = await db.isMachineCodeBound(machineCode); if (codeOwner && codeOwner !== username) { return NextResponse.json({ error: `该机器码已被用户 ${codeOwner} 绑定`, machineCodeTaken: true }, { status: 409 }); } } // 验证成功,设置认证cookie const response = NextResponse.json({ ok: true, machineCodeBound: !!boundMachineCode, username: username }); const cookieValue = await generateAuthCookie( username, password, user?.role || 'user', false ); // 数据库模式不包含 password const expires = new Date(); expires.setDate(expires.getDate() + 7); // 7天过期 response.cookies.set('auth', cookieValue, { path: '/', expires, sameSite: 'lax', // 改为 lax 以支持 PWA httpOnly: false, // PWA 需要客户端可访问 secure: false, // 根据协议自动设置 }); return response; } catch (err) { console.error('数据库验证失败', err); return NextResponse.json({ error: '数据库错误' }, { status: 500 }); } } catch (error) { console.error('登录接口异常', error); return NextResponse.json({ error: '服务器错误' }, { status: 500 }); } }