|
|
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<boolean> {
|
|
|
if (isKnownCustomer(customerId)) {
|
|
|
return true;
|
|
|
}
|
|
|
return isDbCustomerActive(customerId);
|
|
|
}
|
|
|
|
|
|
/** 从 Cookie 解析当前演示会话客户(越权由 sub 决定,禁止 body 指定 customer_id) */
|
|
|
async function resolveEmbedCustomer(): Promise<
|
|
|
{ customerId: string } | { error: ReturnType<typeof fail> }
|
|
|
> {
|
|
|
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);
|
|
|
}
|
|
|
}
|