aboutsummaryrefslogtreecommitdiffstats
path: root/packages/shared/signedTokens.test.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-06-21 11:32:42 +0000
committerMohamed Bassem <me@mbassem.com>2025-06-21 11:32:42 +0000
commit10d45e8d14cdc3672cc65dc7f5ae79e63fb2da1a (patch)
treec7718fa75a07a7aa166db6a1a1b2e93030732b1e /packages/shared/signedTokens.test.ts
parentf1f665f89cba21d4d448d27471d01a4d78a184ff (diff)
downloadkarakeep-10d45e8d14cdc3672cc65dc7f5ae79e63fb2da1a.tar.zst
fix: Change public image's signed tokens to be time aligned for better caching
Diffstat (limited to 'packages/shared/signedTokens.test.ts')
-rw-r--r--packages/shared/signedTokens.test.ts82
1 files changed, 82 insertions, 0 deletions
diff --git a/packages/shared/signedTokens.test.ts b/packages/shared/signedTokens.test.ts
new file mode 100644
index 00000000..35c3bbf3
--- /dev/null
+++ b/packages/shared/signedTokens.test.ts
@@ -0,0 +1,82 @@
+import { describe, expect, it } from "vitest";
+import { z } from "zod";
+
+import {
+ createSignedToken,
+ getAlignedExpiry,
+ SignedTokenPayload,
+ verifySignedToken,
+} from "./signedTokens";
+
+const SECRET = "secret";
+
+describe("getAlignedExpiry", () => {
+ it("should align to next interval when within grace period", () => {
+ const now = new Date("2023-01-01T12:29:30Z"); // 30 seconds before next interval
+ const expiry = getAlignedExpiry(1800, 60, now); // 30min interval, 60s grace
+ expect(expiry).toBe(new Date("2023-01-01T13:00:00Z").getTime());
+ });
+
+ it("should align to current interval when outside grace period", () => {
+ const now = new Date("2023-01-01T12:10:01Z");
+ const expiry = getAlignedExpiry(1800, 60, now); // 30min interval, 60s grace
+ expect(expiry).toBe(new Date("2023-01-01T12:30:00Z").getTime());
+ });
+
+ it("should handle exact interval boundary", () => {
+ const now = new Date("2023-01-01T12:00:00Z");
+ const expiry = getAlignedExpiry(1800, 60, now);
+ expect(expiry).toBe(new Date("2023-01-01T12:30:00Z").getTime());
+ });
+});
+
+describe("signed tokens", () => {
+ const testSchema = z
+ .object({
+ id: z.string(),
+ name: z.string(),
+ })
+ .strict();
+
+ const testPayload = {
+ id: "123",
+ name: "John",
+ };
+
+ it("should create and verify valid token", () => {
+ const token = createSignedToken(testPayload, SECRET);
+ const verified = verifySignedToken(token, SECRET, testSchema);
+ expect(verified).toEqual(testPayload);
+ });
+
+ it("should return null for expired token", () => {
+ const token = createSignedToken(testPayload, SECRET, Date.now() - 1000);
+ const verified = verifySignedToken(token, SECRET, testSchema);
+ expect(verified).toBeNull();
+ });
+
+ it("should return null for invalid signature", () => {
+ const token = createSignedToken(testPayload, SECRET);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ const decoded: SignedTokenPayload = JSON.parse(
+ Buffer.from(token, "base64").toString(),
+ );
+ decoded.signature = "tampered" + decoded.signature;
+ const tampered = Buffer.from(JSON.stringify(decoded)).toString("base64");
+ const verified = verifySignedToken(tampered, SECRET, testSchema);
+ expect(verified).toBeNull();
+ });
+
+ it("should return null if payload doesn't match schema", () => {
+ const invalidPayload = { ...testPayload, extra: "field" };
+ const token = createSignedToken(invalidPayload, SECRET);
+ const verified = verifySignedToken(token, SECRET, testSchema);
+ expect(verified).toBeNull();
+ });
+
+ it("should fail with different signing secrets", () => {
+ const token = createSignedToken(testPayload, "ONE SECRET");
+ const verified = verifySignedToken(token, "ANOTHER SECRET", testSchema);
+ expect(verified).toBeNull();
+ });
+});