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.
83 lines
2.4 KiB
83 lines
2.4 KiB
import type { Permission } from "@/lib/constants/auth";
|
|
import { forbidden } from "@/modules/auth/errors";
|
|
import type { AuthContext } from "@/modules/auth/rbac";
|
|
import { verifyServiceTokenAsync } from "@/modules/auth/service-token";
|
|
|
|
export type ServiceAuthContext = Extract<AuthContext, { type: "service" }>;
|
|
|
|
function parsePermissionsHeader(raw: string): Permission[] {
|
|
return raw
|
|
.split(",")
|
|
.map((p) => p.trim())
|
|
.filter(Boolean) as Permission[];
|
|
}
|
|
|
|
function parseVerifiedServiceAuth(request: Request): ServiceAuthContext | null {
|
|
const authType = request.headers.get("x-auth-type");
|
|
if (authType !== "service") {
|
|
return null;
|
|
}
|
|
|
|
const customerId = request.headers.get("x-customer-id");
|
|
if (!customerId) {
|
|
throw forbidden("缺少客户标识");
|
|
}
|
|
|
|
const verified = request.headers.get("x-service-verified");
|
|
const permissionsRaw = request.headers.get("x-permissions") ?? "";
|
|
if (verified === "true" || permissionsRaw.length > 0) {
|
|
return {
|
|
type: "service",
|
|
customerId,
|
|
permissions: parsePermissionsHeader(permissionsRaw),
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function extractBearerFromRequest(request: Request): string | null {
|
|
const forwarded = request.headers.get("x-bearer-token")?.trim();
|
|
if (forwarded) {
|
|
return forwarded;
|
|
}
|
|
const authHeader = request.headers.get("authorization");
|
|
if (authHeader?.startsWith("Bearer ")) {
|
|
return authHeader.slice(7).trim();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** 从 middleware 注入的请求头解析宿主鉴权上下文(含 DB Token 二次校验) */
|
|
export async function parseServiceAuth(
|
|
request: Request,
|
|
): Promise<ServiceAuthContext> {
|
|
const fastPath = parseVerifiedServiceAuth(request);
|
|
if (fastPath) {
|
|
return fastPath;
|
|
}
|
|
|
|
const bearerToken = extractBearerFromRequest(request);
|
|
if (!bearerToken) {
|
|
throw forbidden("需要宿主 Service Token");
|
|
}
|
|
|
|
const customerId = request.headers.get("x-customer-id")?.trim() || undefined;
|
|
const verified = await verifyServiceTokenAsync(bearerToken, customerId);
|
|
return {
|
|
type: "service",
|
|
customerId: verified.customerId,
|
|
permissions: verified.permissions,
|
|
};
|
|
}
|
|
|
|
/** 校验请求体 customer_id 与 token 租户一致 */
|
|
export function assertCustomerMatch(
|
|
auth: ServiceAuthContext,
|
|
bodyCustomerId: string,
|
|
): void {
|
|
if (auth.customerId !== bodyCustomerId) {
|
|
throw forbidden("customer_id 与令牌租户不匹配");
|
|
}
|
|
}
|