R81 补充企微回包字段取证
这个提交包含在:
@@ -214,10 +214,26 @@ async function exchangeWecomCode(config, code) {
|
||||
details: { wecom: "userinfo_exchange_failed", payload },
|
||||
});
|
||||
}
|
||||
const payloadKeys = payload && typeof payload === "object" ? Object.keys(payload).sort() : [];
|
||||
const hasStringValue = (value) => typeof value === "string" && value.trim().length > 0;
|
||||
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,
|
||||
diagnostics: {
|
||||
errcode: Number(payload.errcode ?? 0),
|
||||
errmsg: typeof payload.errmsg === "string" ? payload.errmsg : null,
|
||||
payloadKeys,
|
||||
fieldPresence: {
|
||||
UserId: hasStringValue(payload.UserId),
|
||||
userid: hasStringValue(payload.userid),
|
||||
OpenId: hasStringValue(payload.OpenId),
|
||||
openid: hasStringValue(payload.openid),
|
||||
external_userid: hasStringValue(payload.external_userid),
|
||||
user_ticket: hasStringValue(payload.user_ticket),
|
||||
corpid: hasStringValue(payload.corpid),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -370,6 +386,10 @@ async function wecomAuthRoutes(fastify) {
|
||||
hasUserId: false,
|
||||
hasOpenId: Boolean(profile.externalOpenUserId),
|
||||
hasDeviceId: Boolean(profile.deviceId),
|
||||
wecomErrCode: profile.diagnostics?.errcode ?? null,
|
||||
wecomErrMsg: profile.diagnostics?.errmsg ?? null,
|
||||
payloadKeys: profile.diagnostics?.payloadKeys ?? [],
|
||||
fieldPresence: profile.diagnostics?.fieldPresence ?? {},
|
||||
},
|
||||
"wecom callback missing internal user id",
|
||||
);
|
||||
|
||||
@@ -31,8 +31,8 @@ function buildPrisma(overrides = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
async function buildApp({ config = {}, prisma = buildPrisma() } = {}) {
|
||||
const app = Fastify();
|
||||
async function buildApp({ config = {}, prisma = buildPrisma(), fastifyOptions = {} } = {}) {
|
||||
const app = Fastify(fastifyOptions);
|
||||
await app.register(fastifyCookie);
|
||||
app.decorate("config", {
|
||||
appOrigin: "https://oa.hyinda.com",
|
||||
@@ -87,6 +87,18 @@ function mockWecomFetch({ userId = "wecom-user-1", openId = "wecom-open-1" } = {
|
||||
});
|
||||
}
|
||||
|
||||
function mockWecomFetchWithPayload(payload) {
|
||||
global.fetch = vi.fn()
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ errcode: 0, access_token: "access-token" }),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => payload,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
vi.restoreAllMocks();
|
||||
@@ -178,8 +190,26 @@ describe("wecom auth routes R27", () => {
|
||||
});
|
||||
|
||||
it("returns structured diagnostics when wecom callback has only open id", async () => {
|
||||
mockWecomFetch({ userId: "", openId: "wecom-open-only" });
|
||||
const app = await buildApp();
|
||||
const logs = [];
|
||||
mockWecomFetchWithPayload({
|
||||
errcode: 0,
|
||||
errmsg: "ok",
|
||||
openid: "lowercase-openid-value",
|
||||
user_ticket: "ticket-value",
|
||||
corpid: "corp-id-value",
|
||||
});
|
||||
const app = await buildApp({
|
||||
fastifyOptions: {
|
||||
logger: {
|
||||
level: "warn",
|
||||
stream: {
|
||||
write(message) {
|
||||
logs.push(JSON.parse(message));
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const state = signPayload({
|
||||
exp: Date.now() + 60_000,
|
||||
nonce: "nonce-openid-only",
|
||||
@@ -198,7 +228,40 @@ describe("wecom auth routes R27", () => {
|
||||
message: "企业微信认证失败",
|
||||
details: expect.objectContaining({
|
||||
wecom: "missing_user_id",
|
||||
hasOpenId: true,
|
||||
hasOpenId: false,
|
||||
mode: "login",
|
||||
}),
|
||||
}));
|
||||
expect(logs.some((entry) => (
|
||||
entry.msg === "wecom callback missing internal user id"
|
||||
&& entry.mode === "login"
|
||||
&& entry.hasUserId === false
|
||||
&& entry.hasOpenId === false
|
||||
&& entry.hasDeviceId === false
|
||||
&& entry.wecomErrCode === 0
|
||||
&& entry.wecomErrMsg === "ok"
|
||||
&& Array.isArray(entry.payloadKeys)
|
||||
&& entry.payloadKeys.join(",") === "corpid,errcode,errmsg,openid,user_ticket"
|
||||
&& entry.fieldPresence?.UserId === false
|
||||
&& entry.fieldPresence?.userid === false
|
||||
&& entry.fieldPresence?.OpenId === false
|
||||
&& entry.fieldPresence?.openid === true
|
||||
&& entry.fieldPresence?.external_userid === false
|
||||
&& entry.fieldPresence?.user_ticket === true
|
||||
&& entry.fieldPresence?.corpid === true
|
||||
))).toBe(true);
|
||||
|
||||
expect(logs.some((entry) => (
|
||||
entry.msg === "wecom callback missing internal user id"
|
||||
&& typeof entry.requestId === "string"
|
||||
))).toBe(true);
|
||||
|
||||
expect(response.json().error).toEqual(expect.objectContaining({
|
||||
code: "AUTH_003",
|
||||
message: "企业微信认证失败",
|
||||
details: expect.objectContaining({
|
||||
wecom: "missing_user_id",
|
||||
hasOpenId: false,
|
||||
mode: "login",
|
||||
}),
|
||||
}));
|
||||
|
||||
在新工单中引用
屏蔽一个用户