返回 CodeWhale
route.ts
根目录 / web / app / api / admin / login / route.ts
1 import { NextResponse } from "next/server";
2 import { getAgentEnv, safeEqual, createSession } from "@/lib/community-agent";
3 import { FormBodyError, readBoundedUrlEncodedForm } from "@/lib/bounded-form";
4
5 export const dynamic = "force-dynamic";
6
7 const ALLOWED_LOCALES = new Set(["en", "zh"]);
8 const MAX_LOGIN_BODY_BYTES = 4_096;
9 const MAX_TOKEN_CHARS = 512;
10
11 function pickLocale(value: string | null | undefined): string {
12 if (!value) return "en";
13 return ALLOWED_LOCALES.has(value) ? value : "en";
14 }
15
16 export async function POST(req: Request) {
17 const env = await getAgentEnv();
18 const url = new URL(req.url);
19 const localeFromQuery = pickLocale(url.searchParams.get("locale"));
20
21 if (!env.MAINTAINER_TOKEN) {
22 return new NextResponse("Not configured", {
23 status: 503,
24 headers: { "Cache-Control": "no-store" },
25 });
26 }
27
28 let form: URLSearchParams;
29 try {
30 form = await readBoundedUrlEncodedForm(req, MAX_LOGIN_BODY_BYTES);
31 } catch (error) {
32 if (error instanceof FormBodyError) {
33 return new NextResponse(error.message, {
34 status: error.status,
35 headers: { "Cache-Control": "no-store" },
36 });
37 }
38 throw error;
39 }
40 const submitted = form.get("token") ?? "";
41 const locale = pickLocale(form.get("locale") ?? localeFromQuery);
42 if (submitted.length > MAX_TOKEN_CHARS) {
43 return new NextResponse("Token too long", {
44 status: 413,
45 headers: { "Cache-Control": "no-store" },
46 });
47 }
48
49 const valid = await safeEqual(submitted, env.MAINTAINER_TOKEN);
50 if (!valid) {
51 return NextResponse.redirect(new URL(`/${locale}/admin?err=1`, req.url), {
52 status: 303,
53 headers: { "Cache-Control": "no-store" },
54 });
55 }
56
57 const sid = await createSession(env.CURATED_KV);
58 if (!sid) {
59 return new NextResponse("Session storage unavailable", {
60 status: 503,
61 headers: { "Cache-Control": "no-store" },
62 });
63 }
64
65 const res = NextResponse.redirect(new URL(`/${locale}/admin`, req.url), {
66 status: 303,
67 headers: { "Cache-Control": "no-store" },
68 });
69 res.cookies.set("mt_sid", sid, {
70 path: "/",
71 httpOnly: true,
72 secure: true,
73 sameSite: "strict",
74 maxAge: 60 * 60 * 24,
75 });
76 return res;
77 }
78
78 lines TYPESCRIPT