You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
4.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<boolean> {
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<typeof fail> }> {
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<typeof fail> }> {
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 },
},
});
}