R18-1/R18-3: 接入企业微信入口绑定与免登回退

这个提交包含在:
2026-03-22 08:33:53 +08:00
父节点 2a1ffe66f3
当前提交 215e3967bc
共修改 15 个文件,包含 1394 行新增和 26 行删除
+82 -4
查看文件
@@ -29,6 +29,85 @@ function getRequestOrigin(request) {
}
}
function normalizeOriginValue(value) {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
if (!trimmed) {
return null;
}
try {
return new URL(trimmed).origin.replace(/\/+$/, "");
} catch {
return null;
}
}
function parseAllowedOriginContext(config, request) {
const allowedOrigins = new Set();
const allowedHosts = new Set();
for (const item of String(config.appOrigin || "").split(",").map((value) => value.trim()).filter(Boolean)) {
const normalized = normalizeOriginValue(item);
if (!normalized) {
continue;
}
allowedOrigins.add(normalized);
try {
allowedHosts.add(new URL(normalized).host);
} catch {
// ignore invalid app origin host
}
}
for (const item of String(config.wecomAllowedReturnHosts || "").split(",").map((value) => value.trim()).filter(Boolean)) {
allowedHosts.add(item);
}
if (!allowedOrigins.size) {
const fallbackOrigin = normalizeOriginValue(resolvePrimaryAppOrigin(config, request));
if (fallbackOrigin) {
allowedOrigins.add(fallbackOrigin);
try {
allowedHosts.add(new URL(fallbackOrigin).host);
} catch {
// ignore
}
}
}
return { allowedOrigins, allowedHosts };
}
function isRequestOriginAllowed(config, request) {
const requestOrigin = getRequestOrigin(request);
if (!requestOrigin) {
return { allowed: true, requestOrigin: null, expectedOrigins: [] };
}
const normalizedRequestOrigin = normalizeOriginValue(requestOrigin);
if (!normalizedRequestOrigin) {
return { allowed: false, requestOrigin, expectedOrigins: [] };
}
const { allowedOrigins, allowedHosts } = parseAllowedOriginContext(config, request);
if (allowedOrigins.has(normalizedRequestOrigin)) {
return { allowed: true, requestOrigin: normalizedRequestOrigin, expectedOrigins: Array.from(allowedOrigins) };
}
try {
const host = new URL(normalizedRequestOrigin).host;
if (allowedHosts.has(host)) {
return { allowed: true, requestOrigin: normalizedRequestOrigin, expectedOrigins: Array.from(allowedOrigins) };
}
} catch {
// ignore invalid host
}
return { allowed: false, requestOrigin: normalizedRequestOrigin, expectedOrigins: Array.from(allowedOrigins) };
}
function refreshCookieOptions(config) {
return {
httpOnly: true,
@@ -361,16 +440,15 @@ async function authRoutes(fastify) {
});
}
const expectedOrigin = fastify.config.appOrigin;
const requestOrigin = getRequestOrigin(request);
if (expectedOrigin && requestOrigin && requestOrigin !== expectedOrigin) {
const originCheck = isRequestOriginAllowed(fastify.config, request);
if (!originCheck.allowed) {
await fastify.prisma.auditLog.create({
data: buildAuditLogData(request, {
actorId: session.employee_id,
action: "AUTH_REFRESH_ORIGIN_MISMATCH",
targetType: "employee",
targetId: session.employee_id,
extra: { expectedOrigin, requestOrigin },
extra: { expectedOrigins: originCheck.expectedOrigins, requestOrigin: originCheck.requestOrigin },
}),
});
throw new AppError({