import { cookies } from "next/headers"; import { fail, ok } from "@/lib/response"; import { EMBED_DEMO_COOKIE, verifyEmbedDemoToken, } from "@/modules/embed-demo/auth"; import { isKnownCustomer } from "@/modules/auth/service-token-env"; import { isDbCustomerActive } from "@/modules/customer/customer-service"; import { writeAudit } from "@/modules/audit/service"; import { isProviderCredentialProvider, listCustomerProviderCredentialsForAdmin, serializeProviderCredentialAdminView, upsertCustomerProviderCredential, } from "@/modules/customer/provider-credentials"; import { prisma } from "@/lib/prisma"; async function isEmbedCustomerAllowed(customerId: string): Promise { if (isKnownCustomer(customerId)) { return true; } return isDbCustomerActive(customerId); } /** 从 Cookie 解析当前演示会话客户(越权由 sub 决定,禁止 body 指定 customer_id) */ async function resolveEmbedCustomer(): Promise< { customerId: string } | { error: ReturnType } > { const cookieStore = await cookies(); const raw = cookieStore.get(EMBED_DEMO_COOKIE)?.value; if (!raw) { return { error: fail("UNAUTHORIZED", "未登录", 401) }; } let customerId: string; try { const payload = await verifyEmbedDemoToken(raw); customerId = payload.sub; } catch { return { error: fail("UNAUTHORIZED", "演示会话无效或已过期", 401) }; } if (!(await isEmbedCustomerAllowed(customerId))) { return { error: fail("FORBIDDEN", "客户已停用", 403) }; } return { customerId }; } /** GET /api/embed-demo/provider-credentials — 明文回传邮箱+密码,便于用户核对准确性 */ export async function GET() { const resolved = await resolveEmbedCustomer(); if ("error" in resolved) { return resolved.error; } const list = await listCustomerProviderCredentialsForAdmin(resolved.customerId); await writeAudit( "embed:provider_credential_view", `embed:${resolved.customerId}`, resolved.customerId, { providers: list.filter((x) => x.has_password).map((x) => x.provider) }, ); return ok({ customer_id: resolved.customerId, list }); } /** PUT /api/embed-demo/provider-credentials — 保存/修改当前客户承运商账密 */ export async function PUT(request: Request) { const resolved = await resolveEmbedCustomer(); if ("error" in resolved) { return resolved.error; } const { customerId } = resolved; let body: unknown; try { body = await request.json(); } catch { return fail("VALIDATION_FAILED", "请求体格式无效", 400); } const payload = body as { provider?: unknown; email?: unknown; password?: unknown; }; if (!isProviderCredentialProvider(payload.provider)) { return fail("VALIDATION_FAILED", "provider 须为 mothership 或 flock", 400); } if (typeof payload.email !== "string" || !payload.email.trim()) { return fail("VALIDATION_FAILED", "请填写登录邮箱", 400); } const password = typeof payload.password === "string" ? payload.password : undefined; try { await upsertCustomerProviderCredential(customerId, payload.provider, { email: payload.email, password, updatedBy: `embed:${customerId}`, }); const row = await prisma.customerProviderCredential.findUnique({ where: { customerId_provider: { customerId, provider: payload.provider, }, }, }); if (!row) { return fail("INTERNAL_ERROR", "保存后读取账密失败", 500); } const saved = serializeProviderCredentialAdminView(row); await writeAudit( "embed:provider_credential_upsert", `embed:${customerId}`, customerId, { provider: payload.provider, email: saved.email, password_updated: Boolean(password?.trim()), }, ); return ok(saved); } catch (error) { const message = error instanceof Error ? error.message : "保存失败"; return fail("VALIDATION_FAILED", message, 400); } }