Phase 2 preflight (npm run supertokens:check) + Phase 0 docs correction - #9
Merged
Merged
Conversation
The v1.8.1 shadow report names the database it read, and the production run answered the question these docs were still hedging about: [shadow] auditing postgres postgresql://rackstack_user@...:5432/rackstack So v1.7's Postgres cutover has in fact happened. Both the migration plan and the runbook still said it was outstanding or unconfirmed, which is exactly the kind of stale "not verified" claim this project has been bitten by in the other direction. Phase 0 is now fully satisfied - the only work left before AUTH_MODE=dual is widening the OAuth redirect URLs and standing up the SuperTokens core. Docs only; no code touched, so no version bump. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 2 stands up the SuperTokens core, and until now nothing verified it was configured correctly - the operator's only feedback was to set AUTH_MODE and watch what broke. One of the failure modes does not break visibly at all: a core running without API_KEYS mints a login session for ANY user id, and because the id mapping makes session.getUserId() return `github:37058311` verbatim, anyone who can reach it can mint a session for any SUPER_ADMIN_IDS value without a request ever touching RackStack. Nothing inside the app would ever surface that. So the preflight checks it directly, along with the four other things that otherwise only surface after cutover: - core reachable (the localhost-from-inside-a-container mistake) - core REQUIRES authentication (the invisible one above) - our SUPERTOKENS_API_KEY is actually accepted - a mismatch fails every login the moment AUTH_MODE is set - the core has its OWN database, detected by looking for SuperTokens tables inside rackstack's - public origin and providers resolve, the two documented boot failures It also prints the exact redirect URLs to register, including the reason GitHub's is the PARENT path: GitHub allows one URL and matches subdirectories, so registering the SuperTokens path directly would break every existing login. Deliberately does not read AUTH_MODE and does not import server/db/index.js - the whole value is running it before anything is switched, and it must be safe on a box still serving the legacy stack. Same read-only discipline as shadow:check, which gates the data half of the same cutover. Loopback is treated the same way init.js treats it: a missing API key is a WARN rather than a FAIL when only this host can reach the core. 648 tests green on SQLite, 674 on Postgres, 39 smoke assertions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The runbook and compose file pinned SuperTokens core 9.3. That version is incompatible: supertokens-node@24 speaks core-driver-interface 5.4 only, and core 9.3 tops out at CDI 5.2. Verified against the core's own coreDriverInterfaceSupported.json - 9.x and 10.x offer up to 5.2, 11.x up to 5.3, and 12.x is the first with 5.4. The failure mode is the nasty kind: that core starts, answers /hello, accepts its API key, and then fails every request on a version mismatch. Nothing about "the container is running" tells you. I picked 9.3 while hardening the compose file away from :latest and never checked it against the SDK. So the preflight now negotiates the version explicitly - it reads the SDK's own declared cdiSupported rather than a hardcoded list, so an SDK upgrade moves the check with it - and both pins move to 12.0. Also switches the image reference from registry.supertokens.io to Docker Hub. Same image, but the SuperTokens registry's certificate chains to ISRG Root YR, a new Let's Encrypt root that older CA bundles do not carry, so pulling from it fails with `x509: certificate signed by unknown authority`. Confirmed the registry itself is healthy (401 on /v2/, chain verifies) - the gap is in the puller's trust store, so this is a workaround for the operator's benefit and the runbook explains how to tell the two apart. Runbook gains both cases in its quick reference, including the openssl command that distinguishes a stale CA bundle from TLS interception. 651 tests green on SQLite, 677 on Postgres. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Answering "should we just use latest?": no, but not a frozen patch either. `:latest` would cross a MAJOR boundary unannounced, and that is the only place core-driver-interface support realistically changes. supertokens-node@24 speaks CDI 5.4 *only* - one version, not a range - so the compatible window is narrow, and leaving it does not degrade, it breaks every login. Freezing a patch has the opposite problem: a stale core signing every session, missing fixes. So the compose file pins `:12` - major held, minor and patch float. Within a major it is very safe: core 12 still serves CDI 2.7 through 5.5. That still leaves residual risk, so the residual risk is now loud. initSuperTokens verifies the negotiated version at BOOT and refuses to start on a mismatch, naming both versions. The SDK does detect this itself, but only from inside a request - getAPIVersion is called by the request helpers - so without this the container looks healthy, passes its health check, and the first sign of trouble is a player reporting they cannot sign in. Unreachable stays a warning, same as the API-key probe: a core still starting must not turn a boot-ordering hiccup into an outage. Both the boot check and the preflight read the SDK's own declared cdiSupported rather than a hardcoded "5.4", so upgrading supertokens-node moves them with it instead of leaving a stale literal asserting the wrong thing. 654 tests green on SQLite, 680 on Postgres. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ships the Phase 2 preflight and the boot-time core version check, which the rollout needs in hand before the SuperTokens core can be verified. Inert for anyone not rolling SuperTokens out: AUTH_MODE still defaults to passport, and both new checks only run in dual/supertokens mode or when the operator invokes them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two things, both serving the next step of the SuperTokens rollout.
1.
npm run supertokens:check— a Phase 2 deployment preflightPhase 2 stands up the SuperTokens core, and nothing verified it was configured
correctly. The operator's only feedback was to set
AUTH_MODEand watch whatbroke — and one failure mode does not break visibly at all: a core running
without
API_KEYSwill mint a login session for any user id. Because the idmapping makes
session.getUserId()returngithub:37058311verbatim, anyonewho can reach that port can mint a session for any
SUPER_ADMIN_IDSvaluewithout a request ever touching RackStack. Nothing inside the app would surface
it.
Checks, and why each is there:
localhost-from-inside-a-container mistakeAUTH_MODEis setIt also prints the exact redirect URLs to register, including why GitHub's is
the parent path — GitHub allows one URL and matches subdirectories, so
registering the SuperTokens path directly breaks every existing login.
Deliberately does not read
AUTH_MODE, and does not importserver/db/index.js. The entire value is running it before anything isswitched, so it has to be safe on a box still serving the legacy stack — the
same read-only discipline
shadow:checkhas. Loopback gets the same treatmentinit.jsgives it: a missing API key is a WARN, not a FAIL, when only thishost can reach the core.
Added to the runbook as B3a, the gate for Part B.
shadow:checkgates thedata half of the cutover; this gates the deployment half.
2. Phase 0 docs correction (original content of this PR)
The v1.8.1 shadow report named the database it read
(
postgres postgresql://rackstack_user@…:5432/rackstack), which confirms v1.7'sPostgres cutover has happened. Both the migration plan and the runbook still
said it was outstanding.
Phase 0 is now fully satisfied.
Verification
/hellois not used forthe auth check (it answers unauthenticated by design, so it would read an open
core as healthy), key mismatch, missing key remote-vs-loopback, shared
database, unreachable core invents no auth verdict, empty run is not a PASS
No version bump —
supertokens:checkis additive and unreleased; it ships withwhatever goes out next.
🤖 Generated with Claude Code