[WSAPI-3] Port Wealthsimple authentication & session management #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/WSAPI-3"
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?
Ticket
WSAPI-3 Port Wealthsimple authentication & session management
Summary
into wealthsimple-api's own Mongo-backed session store (
lib/config,lib/mongo,lib/session,lib/wealthsimple/{auth,discovery,totp,request,errors,login-cli}).invest.read trade.read tax.read) — the stored credential can nevermove money.
lib/session/import-cli's zod schema now exact-matches this scope, rejecting ahand-edited or corrupted export that claims write access.
and every later call halts until a human re-bootstraps via
bun run ws:login.bun run ws:login— the bootstrap/recovery CLI for the rare case a fresh login is genuinely needed.bun run session:import <file>— new to this service, the one-time migration mechanism: imports amongoexport --jsonArrayfile into the new store, so the cutover doesn't require a fresh login (afresh login is a materially more detectable event against a live financial institution than a
routine token refresh). Validates dates and rejects anything but the read-only scope before ever
touching Mongo.
mongo:6databaseservice on a new internalbackendnetwork,.env.exampledocumentation for the new variables, and root/
lib/wealthsimpleCLAUDE.mdupdates including theactual
mongoexportmigration runbook.concurrent-refresh collapse, concurrent-refresh-failure collapse) with Wealthsimple HTTP calls
mocked at the boundary; a real ephemeral
mongodb-memory-serverfor every Mongo-backed module.Known limitations (inherited from budget-tracker-api's source)
Both identified during pre-PR review and deliberately left as-is — fixing either would mean diverging
from this ticket's "port faithfully" scope, and neither is a regression introduced by this port:
lib/session'smarkDead()androtateTokens()are a blind singletonupdateOne({}, ...)with nocompare-and-swap against the specific refresh token that failed or rotated. A human running
ws:loginconcurrently with an in-flight stale refresh could theoretically have the stale callclobber the fresh login. Identical to budget-tracker-api's live source.
Config.assertWealthsimpleConfig()only checks thatWS_TOTP_SECRETis non-empty, not that it'svalid base32 — a malformed seed isn't caught until after the real password POST. Also identical to
budget-tracker-api's source.
Scope trims vs. budget-tracker-api (approved in planning)
WS_IMPERSONATEis unset in the live deployment this service'ssession was migrated from — the live session already runs on plain
fetch.wsFetchhere callsfetchdirectly with no swappable transport seam; porting the curl-impersonate Chrome binarymachinery is a separate hardening concern with zero behavioral parity gap today.
killSessionlogs aterrorlevel and marks the document dead — the whole halt contract thisticket owns. Surfacing a dead session to a human some other way is a later "WSAPI session health"
ticket.
Manual step (not part of this PR)
The live session migration — the actual
mongoexport/session:importrunbook, now documented in rootCLAUDE.md's "Running locally" section — is a manual operational step run separately against the realbudget-tracker-api and wealthsimple-api Mongo instances. No code in this PR performs it, and no real
network call to Wealthsimple happens anywhere in this PR's automated test suite.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DSc34oDVEBXLz9TJWt26Nn