


















@@ -1,7 +1,7 @@
11import { mkdtemp, rm } from "node:fs/promises";
22import os from "node:os";
33import path from "node:path";
4-import { afterEach, describe, expect, it } from "vitest";
4+import { afterEach, describe, expect, it, vi } from "vitest";
55import {
66looksLikeNextcloudTalkTargetId,
77normalizeNextcloudTalkMessagingTarget,
@@ -131,6 +131,99 @@ describe("nextcloud talk core", () => {
131131).toBeNull();
132132});
133133134+it("rejects tampered bodies, wrong secrets, and tampered signatures", () => {
135+const body = JSON.stringify({ hello: "world" });
136+const generated = generateNextcloudTalkSignature({
137+ body,
138+secret: "secret-123",
139+});
140+141+expect(
142+verifyNextcloudTalkSignature({
143+signature: generated.signature,
144+random: generated.random,
145+body: JSON.stringify({ hello: "tampered" }),
146+secret: "secret-123",
147+}),
148+).toBe(false);
149+expect(
150+verifyNextcloudTalkSignature({
151+signature: generated.signature,
152+random: generated.random,
153+ body,
154+secret: "wrong-secret",
155+}),
156+).toBe(false);
157+expect(
158+verifyNextcloudTalkSignature({
159+signature: "a".repeat(generated.signature.length),
160+random: generated.random,
161+ body,
162+secret: "secret-123",
163+}),
164+).toBe(false);
165+});
166+167+it("takes the first value from array-backed headers", () => {
168+expect(
169+extractNextcloudTalkHeaders({
170+"x-nextcloud-talk-signature": ["sig1", "sig2"],
171+"x-nextcloud-talk-random": ["rand1", "rand2"],
172+"x-nextcloud-talk-backend": ["backend1", "backend2"],
173+}),
174+).toEqual({
175+signature: "sig1",
176+random: "rand1",
177+backend: "backend1",
178+});
179+});
180+181+it("still runs timingSafeEqual when the supplied signature length mismatches", async () => {
182+const timingSafeEqualMock = vi.fn();
183+184+vi.resetModules();
185+vi.doMock("node:crypto", async (importOriginal) => {
186+const actual = await importOriginal<typeof import("node:crypto")>();
187+return {
188+ ...actual,
189+timingSafeEqual: vi.fn((left: NodeJS.ArrayBufferView, right: NodeJS.ArrayBufferView) => {
190+timingSafeEqualMock(left, right);
191+return actual.timingSafeEqual(left, right);
192+}),
193+};
194+});
195+196+const { generateNextcloudTalkSignature, verifyNextcloudTalkSignature } =
197+await import("./signature.js");
198+const body = JSON.stringify({ hello: "world" });
199+const generated = generateNextcloudTalkSignature({
200+ body,
201+secret: "secret-123",
202+});
203+const shortSignature = generated.signature.slice(0, 12);
204+205+expect(
206+verifyNextcloudTalkSignature({
207+signature: shortSignature,
208+random: generated.random,
209+ body,
210+secret: "secret-123",
211+}),
212+).toBe(false);
213+214+expect(timingSafeEqualMock).toHaveBeenCalledOnce();
215+const [leftBuffer, rightBuffer] = timingSafeEqualMock.mock.calls[0] ?? [];
216+expect(Buffer.isBuffer(leftBuffer)).toBe(true);
217+expect(Buffer.isBuffer(rightBuffer)).toBe(true);
218+if (!Buffer.isBuffer(leftBuffer) || !Buffer.isBuffer(rightBuffer)) {
219+throw new TypeError("Expected timingSafeEqual to receive Buffer arguments");
220+}
221+expect(leftBuffer).toHaveLength(rightBuffer.length);
222+223+vi.doUnmock("node:crypto");
224+vi.resetModules();
225+});
226+134227it("persists replay decisions across guard instances and scopes account namespaces", async () => {
135228const stateDir = await makeTempDir();
136229此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。