1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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();
});
});
|