18e915bcbb
- 文章管理:计数请求补 page=1 参数,命中分页接口返回正确的 total - 分类管理:编辑模式新增描述输入框,保存时一并提交 description - CSP:img-src 加入 https: 允许加载外部图片 - 关于页:数据存储描述从 JSON 文件更正为 SQLite 数据库 - Footer:添加 ICP 备案号
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { cookies } from "next/headers";
|
|
import { checkAuth, createSession, SESSION_KEY } from "@/lib/auth";
|
|
import { registerFailedAttempt, clearAttempts, isLocked } from "@/lib/rate-limit";
|
|
|
|
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "asui2026";
|
|
const SESSION_MAX_AGE = 60 * 60 * 24 * 7; // 7 天
|
|
|
|
/** 取客户端 IP 作为限流 key,兼容代理转发头。 */
|
|
function clientKey(request: NextRequest): string {
|
|
const fwd = request.headers.get("x-forwarded-for");
|
|
const ip = fwd ? fwd.split(",")[0].trim() : request.headers.get("x-real-ip") || "unknown";
|
|
return `login:${ip}`;
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const key = clientKey(request);
|
|
const lock = isLocked(key);
|
|
if (lock.locked) {
|
|
return NextResponse.json(
|
|
{ error: `尝试次数过多,请 ${Math.ceil(lock.retryAfterSec / 60)} 分钟后再试` },
|
|
{ status: 429 }
|
|
);
|
|
}
|
|
|
|
let body: { password?: string };
|
|
try {
|
|
body = await request.json();
|
|
} catch {
|
|
return NextResponse.json({ error: "请求格式错误" }, { status: 400 });
|
|
}
|
|
|
|
if (typeof body.password !== "string" || body.password !== ADMIN_PASSWORD) {
|
|
const result = registerFailedAttempt(key);
|
|
if (result.locked) {
|
|
return NextResponse.json(
|
|
{ error: "密码错误次数过多,账户已锁定 15 分钟" },
|
|
{ status: 429 }
|
|
);
|
|
}
|
|
return NextResponse.json({ error: "密码错误" }, { status: 401 });
|
|
}
|
|
|
|
clearAttempts(key);
|
|
const token = await createSession(SESSION_MAX_AGE);
|
|
const cookieStore = await cookies();
|
|
cookieStore.set(SESSION_KEY, token, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
maxAge: SESSION_MAX_AGE,
|
|
path: "/",
|
|
});
|
|
return NextResponse.json({ ok: true });
|
|
}
|
|
|
|
export async function DELETE() {
|
|
const cookieStore = await cookies();
|
|
cookieStore.delete(SESSION_KEY);
|
|
return NextResponse.json({ ok: true });
|
|
}
|
|
|
|
export async function GET() {
|
|
return NextResponse.json({ authenticated: await checkAuth() });
|
|
}
|