T4: AES-GCM crypto + encrypted token store #4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "t4-crypto-token-store"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
T4: crypto + token store
Stacked on #3 (
t3-d1-drizzle) → #2 → #1. Merge those first.src/lib/crypto.ts: AES-256-GCMv1.<kid>.<iv>.<ct>(base64url).kid= first 8 bytes of SHA-256(key), so key IDs need no configuration.connections:<userId>:<provider>:<access|refresh>, so a ciphertext copied to another row or user fails to decrypt.TOKEN_ENCRYPTION_KEY(current) plus optionalTOKEN_ENCRYPTION_KEYS_PREVIOUS(comma-separated, decrypt-only). The token store re-encrypts stale envelopes with the current key when it reads them.src/lib/token-store.tssaveConnection(upsert, ciphertext only),getToken(user, provider)(auto-refresh),listConnections(UI-safe metadata, never tokens),deleteConnection.NotConnectedErrorandReauthRequiredError(src/lib/errors.ts, messages are safe to show to users and LLMs and include the connect URL), plus transientRefreshInProgressErrorandRefreshTimeoutError.refresh_locked_until+ randomrefresh_lease_id) with a conditional UPDATE before spending the refresh token. Other callers poll until the credential changes or the lease expires.AbortSignal+ race) against a 60 s lease. On timeout the lease is kept until it expires, so nobody replays the token while the aborted request could still be in flight.token_version. Connect and refresh bump the version; re-encryption never does, so key rotation can't discard a refresh.0001_refresh_lease.sqladdstoken_version,refresh_locked_untilandrefresh_lease_id.Verified
bun run lint && bun run typecheck && bun run test: 58 tests, 11 for crypto and 26 for the token store, covering tampering, AAD, rotation, refresh, dedupe, lease wait/takeover/timeout, stale-owner release, reconnect while polling, and rotation during refresh.bun run dev, a temporary route that encrypts and decrypts with the.dev.varskey, removed before committing).bun run db:migrateapplies 0000 and 0001.Astra review (gpt-6-astra), 3 rounds
Fixed:
refresh_token_enc) → CAS ontoken_version.refresh_lease_idowner check.fetch→AbortError) made a timeout look like a normal failure, which released the lease → track the timeout separately. Test added and confirmed to fail on the old code.Each fix has a regression test.
Known limitations (accepted): if an upstream request is processed after we abort it, we can't fully detect that. The 60 s quarantine only narrows the window. Same-isolate dedupe is per isolate, and D1 coordinates across isolates.
Also
TOKEN_ENCRYPTION_KEYandBETTER_AUTH_SECRETinto the git-ignored.dev.vars. They are not committed and were never printed.🤖 Generated with Claude Code