Skip to content

Phase 2 preflight (npm run supertokens:check) + Phase 0 docs correction - #9

Merged
NeverEndingCode merged 5 commits into
mainfrom
docs-phase0-postgres-confirmed
Aug 8, 2026
Merged

Phase 2 preflight (npm run supertokens:check) + Phase 0 docs correction#9
NeverEndingCode merged 5 commits into
mainfrom
docs-phase0-postgres-confirmed

Conversation

@NeverEndingCode

@NeverEndingCode NeverEndingCode commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Two things, both serving the next step of the SuperTokens rollout.

1. npm run supertokens:check — a Phase 2 deployment preflight

Phase 2 stands up the SuperTokens core, and nothing verified it was configured
correctly. The operator's only feedback was to set AUTH_MODE and watch what
broke — and one failure mode does not break visibly at all: a core running
without API_KEYS will mint a login session for any user id. Because the id
mapping makes session.getUserId() return github:37058311 verbatim, anyone
who can reach that port can mint a session for any SUPER_ADMIN_IDS value
without a request ever touching RackStack. Nothing inside the app would surface
it.

=== SuperTokens deployment preflight ===
[PASS] SUPERTOKENS_CONNECTION_URI    http://192.168.x.x:3567
[PASS] public origin                 https://rackstack.example.com
[PASS] OAuth providers               github, discord
[PASS] github redirect to register   .../auth/callback/github  (register the PARENT path .../auth …)
[PASS] core reachable                answered 200
[PASS] core requires authentication  anonymous request rejected (401)
[PASS] SUPERTOKENS_API_KEY           accepted by the core
[PASS] core has its own database     no SuperTokens tables in RackStack's database

PREFLIGHT: PASS - the deployment side is ready for AUTH_MODE=dual.

Checks, and why each is there:

Check Otherwise discovered by
Core reachable The localhost-from-inside-a-container mistake
Core requires authentication Never — see above
API key accepted Every login failing the moment AUTH_MODE is set
Core has its own database Two schemas entangled in backup/rollback
Public origin + providers The two boot failures in runbook D3

It 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 import
server/db/index.js.
The entire value is running it before anything is
switched, so it has to be safe on a box still serving the legacy stack — the
same read-only discipline shadow:check has. Loopback gets the same treatment
init.js gives it: a missing API key is a WARN, not a FAIL, when only this
host can reach the core.

Added to the runbook as B3a, the gate for Part B. shadow:check gates the
data 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's
Postgres cutover has happened. Both the migration plan and the runbook still
said it was outstanding.

Phase 0 is now fully satisfied.

Verification

  • 648 tests SQLite / 674 Postgres, 39 smoke assertions
  • 14 new tests: open core FAILs, closed core PASSes, /hello is not used for
    the 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:check is additive and unreleased; it ships with
whatever goes out next.

🤖 Generated with Claude Code

Evan Phyillaier and others added 2 commits August 7, 2026 23:28
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>
@NeverEndingCode NeverEndingCode changed the title Docs: Phase 0 is done — the Unraid box is on Postgres Phase 2 preflight (npm run supertokens:check) + Phase 0 docs correction Aug 8, 2026
Evan Phyillaier and others added 3 commits August 8, 2026 00:26
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>
@NeverEndingCode
NeverEndingCode merged commit 80b81f7 into main Aug 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant