|
|
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 },
|
|
|
},
|
|
|
});
|
|
|
}
|