修复 R7E 真机反馈回流与移动入口 WF8-11A R7E-16A R7E-16B BIZ8-12A

这个提交包含在:
2026-05-14 21:53:20 +08:00
父节点 45900e0673
当前提交 5491e922e6
共修改 26 个文件,包含 281 行新增和 56 行删除
@@ -99,6 +99,90 @@ async function buildApp(passwordHash) {
}
describe("auth login route", () => {
it("keeps signed deeplink usable after legacy exp and redirects normal browsers to target", async () => {
const passwordHash = await hashPassword("AdminPassword1234");
const { app } = await buildApp(passwordHash);
const token = signPayload({
targetPath: "/mobile/approvals?taskId=task-1",
exp: Math.floor(Date.now() / 1000) - 60,
}, JWT_SECRET);
const response = await app.inject({
method: "GET",
url: `/api/auth/deeplink?token=${encodeURIComponent(token)}`,
});
expect(response.statusCode).toBe(302);
expect(response.headers.location).toBe("http://127.0.0.1:3000/mobile/approvals?taskId=task-1");
await app.close();
});
it("routes signed deeplink through wecom login inside Enterprise WeChat", async () => {
const passwordHash = await hashPassword("AdminPassword1234");
const { app } = await buildApp(passwordHash);
const token = signPayload({
targetPath: "/mobile/approvals?taskId=task-1",
exp: Math.floor(Date.now() / 1000) - 60,
}, JWT_SECRET);
const response = await app.inject({
method: "GET",
url: `/api/auth/deeplink?token=${encodeURIComponent(token)}`,
headers: {
"user-agent": "Mozilla/5.0 wxwork/4.1.20 MicroMessenger/8.0.1",
},
});
expect(response.statusCode).toBe(302);
expect(response.headers.location).toBe(
"http://127.0.0.1:3000/api/auth/wecom/login?returnTo=%2Fmobile%2Fapprovals%3FtaskId%3Dtask-1",
);
await app.close();
});
it("rejects deeplink targets outside internal OA paths", async () => {
const passwordHash = await hashPassword("AdminPassword1234");
const { app } = await buildApp(passwordHash);
const token = signPayload({ targetPath: "//evil.example.com/mobile" }, JWT_SECRET);
const response = await app.inject({
method: "GET",
url: `/api/auth/deeplink?token=${encodeURIComponent(token)}`,
});
expect(response.statusCode).toBe(400);
expect(response.json().error.details).toEqual({ targetPath: "invalid_target_path" });
await app.close();
});
it("rejects tampered deeplink signatures and absolute URL targets", async () => {
const passwordHash = await hashPassword("AdminPassword1234");
const { app } = await buildApp(passwordHash);
const validToken = signPayload({ targetPath: "/mobile" }, JWT_SECRET);
const tamperedResponse = await app.inject({
method: "GET",
url: `/api/auth/deeplink?token=${encodeURIComponent(`${validToken}x`)}`,
});
expect(tamperedResponse.statusCode).toBe(401);
expect(tamperedResponse.json().error.details).toEqual({ token: "invalid_signature" });
const absoluteUrlToken = signPayload({ targetPath: "https://evil.example.com/mobile" }, JWT_SECRET);
const absoluteUrlResponse = await app.inject({
method: "GET",
url: `/api/auth/deeplink?token=${encodeURIComponent(absoluteUrlToken)}`,
});
expect(absoluteUrlResponse.statusCode).toBe(400);
expect(absoluteUrlResponse.json().error.details).toEqual({ targetPath: "invalid_target_path" });
await app.close();
});
it("auto-binds pending wecom identity after password login", async () => {
const passwordHash = await hashPassword("AdminPassword1234");
const { app, wecomFindFirst, wecomUpsert } = await buildApp(passwordHash);
+7 -1
查看文件
@@ -4,12 +4,18 @@ const {
enqueueImNotifications,
resolveRecipientRoleCodes,
} = require("../../../src/services/notifications/imEnqueue");
const { verifyToken } = require("../../../src/utils/signedUrl");
describe("IM enqueue", () => {
it("builds detail url from APP_ORIGIN", () => {
expect(buildDetailUrl({ appOrigin: "https://app.example.com", jwtSecret: "test-secret" }, "/workflow/tasks/1")).toContain(
const detailUrl = buildDetailUrl({ appOrigin: "https://app.example.com", jwtSecret: "test-secret" }, "/workflow/tasks/1");
expect(detailUrl).toContain(
"https://app.example.com/api/auth/deeplink?token=",
);
const token = new URL(detailUrl).searchParams.get("token");
const verified = verifyToken(token, "test-secret");
expect(verified.ok).toBe(true);
expect(verified.payload).toEqual({ targetPath: "/workflow/tasks/1" });
expect(buildDetailUrl({ appOrigin: "" }, "/workflow/tasks/1")).toBeNull();
});