R82 修复企微 userid 兼容登录
这个提交包含在:
@@ -216,10 +216,18 @@ async function exchangeWecomCode(config, code) {
|
||||
}
|
||||
const payloadKeys = payload && typeof payload === "object" ? Object.keys(payload).sort() : [];
|
||||
const hasStringValue = (value) => typeof value === "string" && value.trim().length > 0;
|
||||
const pickCandidate = (...values) => {
|
||||
for (const value of values) {
|
||||
if (hasStringValue(value)) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return {
|
||||
externalUserId: typeof payload.UserId === "string" && payload.UserId.trim() ? payload.UserId.trim() : null,
|
||||
externalOpenUserId: typeof payload.OpenId === "string" && payload.OpenId.trim() ? payload.OpenId.trim() : null,
|
||||
deviceId: typeof payload.DeviceId === "string" ? payload.DeviceId : null,
|
||||
externalUserId: pickCandidate(payload.UserId, payload.userid),
|
||||
externalOpenUserId: pickCandidate(payload.OpenId, payload.openid),
|
||||
deviceId: pickCandidate(payload.DeviceId, payload.deviceid),
|
||||
diagnostics: {
|
||||
errcode: Number(payload.errcode ?? 0),
|
||||
errmsg: typeof payload.errmsg === "string" ? payload.errmsg : null,
|
||||
@@ -229,6 +237,8 @@ async function exchangeWecomCode(config, code) {
|
||||
userid: hasStringValue(payload.userid),
|
||||
OpenId: hasStringValue(payload.OpenId),
|
||||
openid: hasStringValue(payload.openid),
|
||||
DeviceId: hasStringValue(payload.DeviceId),
|
||||
deviceid: hasStringValue(payload.deviceid),
|
||||
external_userid: hasStringValue(payload.external_userid),
|
||||
user_ticket: hasStringValue(payload.user_ticket),
|
||||
corpid: hasStringValue(payload.corpid),
|
||||
|
||||
@@ -189,6 +189,40 @@ describe("wecom auth routes R27", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("accepts lowercase userid and redirects unbound users back to login", async () => {
|
||||
mockWecomFetchWithPayload({
|
||||
errcode: 0,
|
||||
errmsg: "ok",
|
||||
userid: "wecom-unbound-lowercase",
|
||||
});
|
||||
const prisma = buildPrisma({
|
||||
wecomIdentityBinding: {
|
||||
findUnique: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
});
|
||||
const app = await buildApp({ prisma });
|
||||
const state = signPayload({
|
||||
exp: Date.now() + 60_000,
|
||||
nonce: "nonce-login-lowercase-userid",
|
||||
returnTo: "/portal",
|
||||
mode: "login",
|
||||
}, JWT_SECRET);
|
||||
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: `/api/auth/wecom/callback?state=${encodeURIComponent(state)}&code=test-code`,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(302);
|
||||
expect(response.headers.location).toBe("https://oa.hyinda.com/login?redirect=%2Fportal&wecom=unbound");
|
||||
expect(prisma.wecomIdentityBinding.findUnique).toHaveBeenCalledWith({
|
||||
where: { external_user_id: "wecom-unbound-lowercase" },
|
||||
select: { employee_id: true, employee: { select: { id: true, status: true, token_version: true } } },
|
||||
});
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("returns structured diagnostics when wecom callback has only open id", async () => {
|
||||
const logs = [];
|
||||
mockWecomFetchWithPayload({
|
||||
@@ -228,7 +262,7 @@ describe("wecom auth routes R27", () => {
|
||||
message: "企业微信认证失败",
|
||||
details: expect.objectContaining({
|
||||
wecom: "missing_user_id",
|
||||
hasOpenId: false,
|
||||
hasOpenId: true,
|
||||
mode: "login",
|
||||
}),
|
||||
}));
|
||||
@@ -236,7 +270,7 @@ describe("wecom auth routes R27", () => {
|
||||
entry.msg === "wecom callback missing internal user id"
|
||||
&& entry.mode === "login"
|
||||
&& entry.hasUserId === false
|
||||
&& entry.hasOpenId === false
|
||||
&& entry.hasOpenId === true
|
||||
&& entry.hasDeviceId === false
|
||||
&& entry.wecomErrCode === 0
|
||||
&& entry.wecomErrMsg === "ok"
|
||||
@@ -261,7 +295,7 @@ describe("wecom auth routes R27", () => {
|
||||
message: "企业微信认证失败",
|
||||
details: expect.objectContaining({
|
||||
wecom: "missing_user_id",
|
||||
hasOpenId: false,
|
||||
hasOpenId: true,
|
||||
mode: "login",
|
||||
}),
|
||||
}));
|
||||
|
||||
在新工单中引用
屏蔽一个用户