实现企微首次免密自动绑定(07-28-all-account-login-readiness)
这个提交包含在:
+53
-15
@@ -11,6 +11,11 @@ const {
|
||||
fetchWecomOAuthOnboardingProfile,
|
||||
provisionWecomOAuthEmployee,
|
||||
} = require("../services/integrations/wecomOAuthOnboarding");
|
||||
const {
|
||||
WecomLoginAutoBindingError,
|
||||
autoBindExistingWecomEmployee,
|
||||
fetchWecomLoginIdentity,
|
||||
} = require("../services/integrations/wecomLoginAutoBinding");
|
||||
|
||||
const BIND_COOKIE_NAME = "wecom_bind";
|
||||
const PENDING_LOGIN_BIND_COOKIE_NAME = "wecom_login_bind";
|
||||
@@ -603,19 +608,51 @@ async function wecomAuthRoutes(fastify) {
|
||||
where: { external_user_id: profile.externalUserId },
|
||||
select: { employee_id: true, employee: { select: { id: true, status: true, token_version: true } } },
|
||||
});
|
||||
if (!binding?.employee || binding.employee.status !== "active") {
|
||||
const pendingBindToken = signPayload({
|
||||
exp: Date.now() + DATE_WINDOW_MS,
|
||||
externalUserId: profile.externalUserId,
|
||||
externalOpenUserId: profile.externalOpenUserId,
|
||||
returnTo,
|
||||
}, fastify.config.jwtSecret);
|
||||
reply.setCookie(
|
||||
PENDING_LOGIN_BIND_COOKIE_NAME,
|
||||
pendingBindToken,
|
||||
pendingLoginBindCookieOptions(fastify.config),
|
||||
let loginEmployee = binding?.employee ?? null;
|
||||
let bindingSource = "existing";
|
||||
if (loginEmployee && loginEmployee.status !== "active") {
|
||||
reply.clearCookie(PENDING_LOGIN_BIND_COOKIE_NAME, pendingLoginBindCookieOptions(fastify.config));
|
||||
return reply.redirect(
|
||||
`${origin}/login?redirect=${encodeURIComponent(returnTo)}&wecom=account_mapping_inactive`,
|
||||
);
|
||||
return reply.redirect(`${origin}/login?redirect=${encodeURIComponent(returnTo)}&wecom=unbound`);
|
||||
}
|
||||
if (!loginEmployee) {
|
||||
try {
|
||||
const identity = await fetchWecomLoginIdentity({
|
||||
accessToken: profile.accessToken,
|
||||
externalUserId: profile.externalUserId,
|
||||
});
|
||||
loginEmployee = await autoBindExistingWecomEmployee({
|
||||
prisma: fastify.prisma,
|
||||
request,
|
||||
identity,
|
||||
externalOpenUserId: profile.externalOpenUserId,
|
||||
});
|
||||
bindingSource = loginEmployee.autoBound ? "auto" : "concurrent_existing";
|
||||
} catch (error) {
|
||||
if (!(error instanceof WecomLoginAutoBindingError)) throw error;
|
||||
const conflictCodes = new Set([
|
||||
"oa_employee_conflict",
|
||||
"wecom_userid_conflict",
|
||||
"oa_employee_binding_conflict",
|
||||
"binding_race_conflict",
|
||||
"wecom_identity_mismatch",
|
||||
]);
|
||||
const inactiveCodes = new Set(["wecom_member_inactive", "oa_employee_inactive"]);
|
||||
const notice = conflictCodes.has(error.code)
|
||||
? "account_mapping_conflict"
|
||||
: inactiveCodes.has(error.code)
|
||||
? "account_mapping_inactive"
|
||||
: "account_mapping_required";
|
||||
request.log.warn(
|
||||
{ requestId: request.id, errorCode: error.code, notice },
|
||||
"wecom automatic login binding failed closed",
|
||||
);
|
||||
reply.clearCookie(PENDING_LOGIN_BIND_COOKIE_NAME, pendingLoginBindCookieOptions(fastify.config));
|
||||
return reply.redirect(
|
||||
`${origin}/login?redirect=${encodeURIComponent(returnTo)}&wecom=${notice}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const deviceFingerprint = resolveOAuthDeviceFingerprint(ctx.deviceFingerprint);
|
||||
@@ -623,13 +660,14 @@ async function wecomAuthRoutes(fastify) {
|
||||
prisma: fastify.prisma,
|
||||
request,
|
||||
config: fastify.config,
|
||||
employeeId: binding.employee.id,
|
||||
tokenVersion: binding.employee.token_version,
|
||||
employeeId: loginEmployee.id,
|
||||
tokenVersion: loginEmployee.token_version,
|
||||
deviceFingerprint,
|
||||
auditAction: "AUTH_WECOM_LOGIN_SUCCESS",
|
||||
extra: { externalUserId: profile.externalUserId },
|
||||
extra: { bindingSource },
|
||||
});
|
||||
|
||||
reply.clearCookie(PENDING_LOGIN_BIND_COOKIE_NAME, pendingLoginBindCookieOptions(fastify.config));
|
||||
reply.setCookie(fastify.config.refreshCookieName, session.refreshToken, refreshCookieOptions(fastify.config));
|
||||
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
|
||||
reply.setCookie(DEVICE_FINGERPRINT_COOKIE_NAME, deviceFingerprint, deviceFingerprintCookieOptions(fastify.config));
|
||||
|
||||
在新工单中引用
屏蔽一个用户