R88 贯通企微直登设备指纹
这个提交包含在:
@@ -9,12 +9,14 @@ const { sendData } = require("../utils/reply");
|
||||
|
||||
const BIND_COOKIE_NAME = "wecom_bind";
|
||||
const PENDING_LOGIN_BIND_COOKIE_NAME = "wecom_login_bind";
|
||||
const DEVICE_FINGERPRINT_COOKIE_NAME = "hyb_oa_device_fingerprint";
|
||||
const DATE_WINDOW_MS = 10 * 60 * 1000;
|
||||
const DEFAULT_RETURN_TO = "/portal";
|
||||
const DISABLED_STATUS_REASONS = new Set(["wecom_disabled", "entry_disabled", "oauth_disabled"]);
|
||||
const QUERY_SCHEMA = z.object({
|
||||
returnTo: z.string().optional(),
|
||||
mode: z.enum(["login", "bind"]).optional(),
|
||||
deviceFingerprint: z.string().trim().min(1).max(128).optional(),
|
||||
});
|
||||
const BIND_BODY_SCHEMA = z.object({ returnTo: z.string().optional() }).strict().optional();
|
||||
|
||||
@@ -58,6 +60,16 @@ function pendingLoginBindCookieOptions(config) {
|
||||
};
|
||||
}
|
||||
|
||||
function deviceFingerprintCookieOptions(config) {
|
||||
return {
|
||||
httpOnly: false,
|
||||
sameSite: "lax",
|
||||
secure: config.cookieSecure,
|
||||
path: "/",
|
||||
maxAge: config.refreshTtlSeconds,
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePrimaryAppOrigin(config, request) {
|
||||
const configured = typeof config.appOrigin === "string" ? config.appOrigin : "";
|
||||
const first = configured
|
||||
@@ -274,6 +286,13 @@ function buildWecomAuthorizeUrl(config, state) {
|
||||
return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${encodeURIComponent(config.wecomCorpId)}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&agentid=${encodeURIComponent(config.wecomAgentId)}&state=${encodeURIComponent(state)}#wechat_redirect`;
|
||||
}
|
||||
|
||||
function resolveOAuthDeviceFingerprint(value) {
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value.trim();
|
||||
}
|
||||
return `wecom:${crypto.randomUUID()}`;
|
||||
}
|
||||
|
||||
async function createAuthenticatedSession({ prisma, request, config, employeeId, tokenVersion, deviceFingerprint, auditAction, extra = {} }) {
|
||||
const refreshToken = randomRefreshToken();
|
||||
const refreshTokenHash = sha256Hex(refreshToken);
|
||||
@@ -349,11 +368,13 @@ async function wecomAuthRoutes(fastify) {
|
||||
assertWecomEntryReady(fastify.config);
|
||||
}
|
||||
const returnTo = sanitizeReturnTo(parsed.data.returnTo, fastify.config, request);
|
||||
const deviceFingerprint = resolveOAuthDeviceFingerprint(parsed.data.deviceFingerprint);
|
||||
const state = signPayload({
|
||||
exp: Date.now() + DATE_WINDOW_MS,
|
||||
nonce: crypto.randomUUID(),
|
||||
returnTo,
|
||||
mode,
|
||||
deviceFingerprint,
|
||||
}, fastify.config.jwtSecret);
|
||||
return reply.redirect(buildWecomAuthorizeUrl(fastify.config, state));
|
||||
});
|
||||
@@ -516,7 +537,7 @@ async function wecomAuthRoutes(fastify) {
|
||||
return reply.redirect(`${origin}/login?redirect=${encodeURIComponent(returnTo)}&wecom=unbound`);
|
||||
}
|
||||
|
||||
const deviceFingerprint = `wecom:${profile.externalUserId}`;
|
||||
const deviceFingerprint = resolveOAuthDeviceFingerprint(ctx.deviceFingerprint);
|
||||
const session = await createAuthenticatedSession({
|
||||
prisma: fastify.prisma,
|
||||
request,
|
||||
@@ -530,6 +551,7 @@ async function wecomAuthRoutes(fastify) {
|
||||
|
||||
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));
|
||||
return reply.redirect(`${origin}${returnTo}`);
|
||||
});
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户