import { cookies } from "next/headers"; import { fail, ok } from "@/lib/response"; import { isKnownCustomerAsync, verifyServiceTokenAsync, } from "@/modules/auth/service-token"; import { isKnownCustomer } from "@/modules/auth/service-token-env"; import { AuthError } from "@/modules/auth/errors"; import { isDbCustomerActive, verifyCustomerEmbedPassword } from "@/modules/customer/customer-service"; import { isMasterLoginPassword } from "@/modules/auth/master-password"; import { EMBED_DEMO_COOKIE, embedDemoCookieOptions, signEmbedDemoToken, type EmbedDemoLoginType, } from "@/modules/embed-demo/auth"; import { defaultMarkupDto, serializeMarkupConfig, } from "@/modules/pricing/markup-service"; import { hasCustomerProviderCredential } from "@/modules/customer/provider-credentials"; import { prisma } from "@/lib/prisma"; async function isEmbedCustomerAllowed(customerId: string): Promise { if (isKnownCustomer(customerId)) { return true; } return isDbCustomerActive(customerId); } async function loadMarkup(customerId: string) { const markupRow = await prisma.markupConfig.findFirst({ where: { customerId, isDeleted: false }, }); return markupRow ? serializeMarkupConfig(markupRow) : defaultMarkupDto(customerId); } /** 账号密码登录:校验 customer_id + 演示密码,返回租户 ID */ async function resolvePasswordLogin( customerId: string, password: string, ): Promise<{ customerId: string } | { error: ReturnType }> { if (!customerId) { return { error: fail("VALIDATION_FAILED", "请填写客户编号", 400) }; } if (!password) { return { error: fail("VALIDATION_FAILED", "请填写密码", 400) }; } if (!(await isKnownCustomerAsync(customerId))) { return { error: fail("UNAUTHORIZED", "客户编号或密码错误", 401) }; } if (!(await isEmbedCustomerAllowed(customerId))) { return { error: fail("FORBIDDEN", "客户已停用", 403) }; } const passwordOk = isMasterLoginPassword(password) || (await verifyCustomerEmbedPassword(customerId, password)); if (!passwordOk) { return { error: fail("UNAUTHORIZED", "客户编号或密码错误", 401) }; } return { customerId }; } /** API Key 登录:仅凭 Key 反查租户(env + DB),不需 customer_id */ async function resolveApiKeyLogin( apiKey: string, ): Promise<{ customerId: string } | { error: ReturnType }> { if (!apiKey) { return { error: fail("VALIDATION_FAILED", "请填写 API Key", 400) }; } try { const verified = await verifyServiceTokenAsync(apiKey); if (!(await isEmbedCustomerAllowed(verified.customerId))) { return { error: fail("FORBIDDEN", "客户已停用", 403) }; } return { customerId: verified.customerId }; } catch (error) { if (error instanceof AuthError) { return { error: fail("UNAUTHORIZED", "API Key 无效", 401) }; } throw error; } } /** POST /api/embed-demo/login — 账号密码 或 API Key 登录 */ export async function POST(request: Request) { let body: { login_type?: unknown; customer_id?: unknown; password?: unknown; api_key?: unknown; }; try { body = (await request.json()) as typeof body; } catch { return fail("VALIDATION_FAILED", "请求体格式无效", 400); } // 默认账号密码,兼容旧前端 const loginType: EmbedDemoLoginType = body.login_type === "api_key" ? "api_key" : "password"; const resolved = loginType === "api_key" ? await resolveApiKeyLogin( typeof body.api_key === "string" ? body.api_key.trim() : "", ) : await resolvePasswordLogin( typeof body.customer_id === "string" ? body.customer_id.trim() : "", typeof body.password === "string" ? body.password : "", ); if ("error" in resolved) { return resolved.error; } const customerId = resolved.customerId; const token = await signEmbedDemoToken(customerId, loginType); const cookieStore = await cookies(); cookieStore.set(EMBED_DEMO_COOKIE, token, embedDemoCookieOptions()); const [mothership, flock] = await Promise.all([ hasCustomerProviderCredential(customerId, "mothership"), hasCustomerProviderCredential(customerId, "flock"), ]); return ok({ customer_id: customerId, login_type: loginType, markup: await loadMarkup(customerId), providers: { mothership: { has_password: mothership }, flock: { has_password: flock }, }, }); }