aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Utility/feedToken.test.ts
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-06-02 12:46:03 +0000
committerFuwn <[email protected]>2026-06-02 12:59:04 +0000
commitf0398c1b8835022ce0489f0cfb5283d4178c48f6 (patch)
tree103aa593ceca5728ece9b12d3ef54c71cc50b738 /src/lib/Utility/feedToken.test.ts
parentperf(badges): skip off-screen cells via IntersectionObserver (diff)
downloaddue.moe-f0398c1b8835022ce0489f0cfb5283d4178c48f6.tar.xz
due.moe-f0398c1b8835022ce0489f0cfb5283d4178c48f6.zip
feat(security): add AES-GCM feed-token helper (M5)
Diffstat (limited to 'src/lib/Utility/feedToken.test.ts')
-rw-r--r--src/lib/Utility/feedToken.test.ts44
1 files changed, 44 insertions, 0 deletions
diff --git a/src/lib/Utility/feedToken.test.ts b/src/lib/Utility/feedToken.test.ts
new file mode 100644
index 00000000..ffcb5fd7
--- /dev/null
+++ b/src/lib/Utility/feedToken.test.ts
@@ -0,0 +1,44 @@
+import { describe, expect, it } from "vitest";
+import { decryptFeedToken, encryptFeedToken } from "./feedToken";
+
+// Fixed 32-byte keys so the cipher is exercised without the FEED_TOKEN_KEY env
+// var (production omits the key argument and reads it from env).
+const key = new Uint8Array(32).fill(7);
+const otherKey = new Uint8Array(32).fill(9);
+
+describe("feed token", () => {
+ // Behaviour gate: the feed must still resolve to the same refresh token it
+ // was minted from, and the cleartext must never appear in the URL blob.
+ it("round-trips the refresh token without leaking it", async () => {
+ const refreshToken = "anilist-refresh-token-abc123";
+ const sealed = await encryptFeedToken(refreshToken, key);
+
+ expect(sealed).not.toContain(refreshToken);
+ expect(await decryptFeedToken(sealed, key)).toBe(refreshToken);
+ });
+
+ it("produces a fresh ciphertext each time (random IV)", async () => {
+ expect(await encryptFeedToken("same-token", key)).not.toBe(
+ await encryptFeedToken("same-token", key),
+ );
+ });
+
+ it("rejects a token sealed with a different key", async () => {
+ const sealed = await encryptFeedToken("secret", otherKey);
+
+ expect(await decryptFeedToken(sealed, key)).toBeNull();
+ });
+
+ it("rejects a tampered ciphertext", async () => {
+ const sealed = await encryptFeedToken("secret", key);
+ const tampered = `${sealed[0] === "A" ? "B" : "A"}${sealed.slice(1)}`;
+
+ expect(await decryptFeedToken(tampered, key)).toBeNull();
+ });
+
+ it("rejects malformed input", async () => {
+ expect(await decryptFeedToken("", key)).toBeNull();
+ expect(await decryptFeedToken("short", key)).toBeNull();
+ expect(await decryptFeedToken("not valid base64url!!", key)).toBeNull();
+ });
+});