Files
HealthLog/src/lib/oauth/__tests__/signed-state.test.ts
T
Marc-André Bombeck 31abd307fd feat(integrations): add Polar AccessLink OAuth sync
Connect a Polar account over AccessLink v3: an authorization-code OAuth
flow (shared app from POLAR_CLIENT_ID / POLAR_CLIENT_SECRET) backed by a
stateless HMAC-signed state token doubling as an httpOnly cookie for CSRF.
The callback verifies the signature, expiry, provider binding, and the
byte-exact cookie match, then encrypts the access token plus member id onto
the user row.

An hourly poll-cohort job maps Nightly Recharge recovery, sleep stages,
and daily activity to canonical measurements tagged source POLAR, idempotent
on (userId, type, source, externalId). The recovery band feeds RECOVERY_SCORE
through the canonical ladder, which now ranks native readiness above the
computed proxy. Settings gains a connect / disconnect card. Tokens are never
logged or returned; every egress goes through safeFetch.
2026-06-14 20:08:50 +02:00

94 lines
3.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
OAUTH_STATE_TTL_MS,
mintSignedState,
oauthStateCookieName,
stateMatchesCookie,
verifySignedState,
} from "../signed-state";
const KEY = "x".repeat(48);
describe("OAuth signed state (Polar / Oura)", () => {
let prevKey: string | undefined;
beforeEach(() => {
prevKey = process.env.API_TOKEN_HMAC_KEY;
process.env.API_TOKEN_HMAC_KEY = KEY;
});
afterEach(() => {
if (prevKey === undefined) delete process.env.API_TOKEN_HMAC_KEY;
else process.env.API_TOKEN_HMAC_KEY = prevKey;
});
it("pins the cookie name per provider", () => {
expect(oauthStateCookieName("polar")).toBe("polar_oauth_state");
expect(oauthStateCookieName("oura")).toBe("oura_oauth_state");
});
it("round-trips a minted token back to the userId + provider", () => {
const now = 1_000_000;
const token = mintSignedState("polar", "user-abc", now);
const verified = verifySignedState("polar", token, now + 1000);
expect(verified).toEqual({
provider: "polar",
userId: "user-abc",
expiry: now + OAUTH_STATE_TTL_MS,
});
});
it("mints unique tokens for the same user (nonce entropy)", () => {
const a = mintSignedState("oura", "u1");
const b = mintSignedState("oura", "u1");
expect(a).not.toBe(b);
});
it("rejects a token whose provider does not match", () => {
const token = mintSignedState("polar", "u1");
expect(verifySignedState("oura", token)).toBeNull();
});
it("rejects a tampered userId (signature mismatch)", () => {
const now = 5_000;
const token = mintSignedState("polar", "victim", now);
const parts = token.split(".");
parts[1] = "attacker";
const forged = parts.join(".");
expect(verifySignedState("polar", forged, now + 1)).toBeNull();
});
it("rejects a tampered signature tag", () => {
const token = mintSignedState("oura", "u1");
const forged = token.slice(0, -3) + "AAA";
expect(verifySignedState("oura", forged)).toBeNull();
});
it("rejects an expired token even with a valid signature", () => {
const now = 10_000;
const token = mintSignedState("polar", "u1", now);
expect(verifySignedState("polar", token, now + OAUTH_STATE_TTL_MS + 1)).toBeNull();
});
it("rejects malformed shapes and empty input", () => {
expect(verifySignedState("polar", null)).toBeNull();
expect(verifySignedState("polar", "")).toBeNull();
expect(verifySignedState("polar", "a.b.c")).toBeNull();
expect(verifySignedState("polar", "polar.u1.notanumber.nonce.tag")).toBeNull();
});
it("a token minted under a different key fails verification", () => {
const token = mintSignedState("polar", "u1");
process.env.API_TOKEN_HMAC_KEY = "y".repeat(48);
expect(verifySignedState("polar", token)).toBeNull();
});
it("double-submit compare is byte-exact", () => {
const token = mintSignedState("oura", "u1");
expect(stateMatchesCookie(token, token)).toBe(true);
expect(stateMatchesCookie(token, token + "x")).toBe(false);
expect(stateMatchesCookie(token, null)).toBe(false);
expect(stateMatchesCookie(null, token)).toBe(false);
});
});