From cfed9e6b63a116e5398c3262bcbceaa3131bf29e Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Mon, 7 Sep 2026 07:31:43 -0400 Subject: [PATCH 1/2] docs: correct the docs and comments that drifted from the ecosystem Checked every cross-repo claim against the current seamless-auth-api (0.7.4, including its OpenAPI document and .env.example), seamless-templates, the seamless-auth-server adapters, and the React SDK. Fixed: - The local auth-server steps named a script that does not exist (db:migrate). The auth API spells it migrate:up, and the Docker path runs it at container start, so say that too. - init --profile was documented as selecting a profile. It has been accepted and ignored since managed connect moved to the portal session. - The generated project layout listed a README.md the CLI does not write, and omitted seamless.config.json, admin/, and what a managed project skips. - "No redirects or third-party auth providers" predated OAuth sign-in, which the CLI has configured since the provider prompts shipped. - The included-projects list named two repositories. It is four: the adapters and the React SDK are what the generated api/ and web/ actually run on. - The verify compose header described one adapter, NODE_ENV=test (the file itself sets development twenty lines later), a SEAMLESS_ADAPTER_DIR that does not exist, and a .env.verify that is not written. - SEAMLESS_REACT_DIR was documented as the default web template source. It is a single-template override; the default drives every registry web template. - config set help was missing session_idle_ttl from the string-typed keys. Added README sections for check and verify. Verify is half of what the CLI does, ships in the package, and had no coverage at all. Recorded one real contract drift: WRITABLE_KEYS carries magic_link_redirect_uris, which the auth API does not accept yet (the key is defined in @seamless-auth/types on an untagged commit, and the API still treats the configured origins as the magic-link allowlist). The key stays listed so a released API needs no CLI change, but the comment and AGENTS.md now say it is rejected today rather than claiming the lists are in step. Compressed the repeats rather than rewriting: the --yes/--force rule was stated four times in init.ts and now has one canonical statement, an identical rotation-ordering comment at two call sites moved onto the shared helper, and comments written as diffs ("no longer", "this replaces") now describe the contract they document. --- .changeset/docs-and-comments-sweep.md | 20 ++++++ AGENTS.md | 28 ++++++-- README.md | 94 +++++++++++++++++++++------ resources/coverage-badge.svg | 8 +-- src/commands/helpTopics.ts | 7 +- src/commands/init.ts | 30 ++++----- src/commands/verify.ts | 5 +- src/core/loginFlow.ts | 16 ++--- src/core/systemConfig.ts | 12 +++- src/core/templates.ts | 5 +- verify/docker-compose.verify.yml | 42 ++++++------ 11 files changed, 180 insertions(+), 87 deletions(-) create mode 100644 .changeset/docs-and-comments-sweep.md diff --git a/.changeset/docs-and-comments-sweep.md b/.changeset/docs-and-comments-sweep.md new file mode 100644 index 0000000..3ff0d39 --- /dev/null +++ b/.changeset/docs-and-comments-sweep.md @@ -0,0 +1,20 @@ +--- +"seamless-cli": patch +--- + +Correct the documentation that had drifted from the rest of the ecosystem. + +- `seamless check` and `seamless verify` now have README sections. Verify is half of + what the CLI does and had none. +- The auth server's local-development steps named a script that does not exist + (`db:migrate`); the auth API spells it `migrate:up`, and the Docker path runs it for + you at container start. +- `init --profile` was documented as selecting a profile. It has been accepted and + ignored since managed connect moved to the portal session, so the help now says so. +- The generated project layout listed a `README.md` the CLI does not write, and omitted + `seamless.config.json`, `admin/`, and which pieces a managed project skips. +- "No redirects or third-party auth providers" predated OAuth sign-in, which the CLI has + configured since the provider prompts shipped. +- The included-projects list now names all four repositories the CLI scaffolds from and + conformance-tests against, including `seamless-auth-server` and `seamless-auth-react`. +- `config set` help was missing `session_idle_ttl` from the string-typed keys. diff --git a/AGENTS.md b/AGENTS.md index 4fb6ad1..86bd491 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,9 +146,15 @@ Modes and sibling repos: - `--local` builds the `@seamless-auth/*` packages from source (pre-publish contract testing); the default uses the published packages. +- The browser layer runs once per web template, not once. `verify` reads the templates registry + and drives every `kind: web` entry that is not `coming-soon`, each served at :5173 in turn and + scoped to the flow tags its `template.json` declares in `verify.flows` (the whole suite when it + declares none). So `react-oauth` runs only `@oauth`, and `react-vite` runs everything. - The sibling repos are resolved relative to this repo, overridable with `SEAMLESS_API_DIR`, - `SEAMLESS_SERVER_DIR`, `SEAMLESS_REACT_SDK_DIR` (the React SDK), and `SEAMLESS_REACT_DIR` (the - `react-vite` web template, defaulting to `../seamless-templates/templates/web/react-vite`). + `SEAMLESS_SERVER_DIR`, `SEAMLESS_REACT_SDK_DIR` (the React SDK), and `SEAMLESS_TEMPLATES_DIR` + (the templates checkout the web templates come from, defaulting to `../seamless-templates`). + `SEAMLESS_REACT_DIR` is the narrower override: it names a single template directory and runs + that one instead of the registry's set. - Useful flags: `--api-only`, `--no-react`, `--filter=` (the `=` form; a space-separated `--filter ` is not parsed), `--keep-up`. ## Important Folders @@ -222,7 +228,17 @@ since the generated compose pins both the ports and the container names. limiter (10 per 15 minutes, hardcoded) bounds adapter / react OTP traffic. Keep specs off it where possible (for example, magic-link login instead of a second email-OTP round trip). - **Version pins**: [verify/adapter-app](verify/adapter-app) pins `@seamless-auth/express`, - [verify/adapter-fastify-app](verify/adapter-fastify-app) pins `@seamless-auth/fastify`, and the - `react-vite` template pins `@seamless-auth/react`. Bump these when new versions publish. -- **Templates ref**: the CLI scaffolds from `seamless-templates` at `SEAMLESS_TEMPLATES_REF` - ([src/core/images.ts](src/core/images.ts)); bump it when a new templates release publishes. + [verify/adapter-fastify-app](verify/adapter-fastify-app) pins `@seamless-auth/fastify`, and each + web template pins `@seamless-auth/react`. Bump these when new versions publish. +- **The four pins in [src/core/images.ts](src/core/images.ts)** are what a scaffold gets, and each + drifts on its own: `SEAMLESS_AUTH_API_VERSION` (the auth server image), the admin dashboard image + and ref, and `SEAMLESS_TEMPLATES_REF`. Check them against the sibling repos' latest tags before a + release; nothing fails when they lag, the scaffold just quietly ships an older stack. +- **`--auth=local` is not pinned**: it `git clone`s `seamless-auth-api` at its default branch + ([src/generators/auth/auth.ts](src/generators/auth/auth.ts)) while `--auth=docker` runs the pinned + image, so the two auth modes can scaffold different servers from the same CLI version. +- **Config keys ahead of the API**: `WRITABLE_KEYS` in + [src/core/systemConfig.ts](src/core/systemConfig.ts) mirrors the instance's strict patch schema. + `magic_link_redirect_uris` is currently ahead of it (defined in `@seamless-auth/types`, not yet + released there, and not yet read by the auth API), so an instance rejects that key today. Adding a + key here before the API accepts it makes `config set` fail against every live instance. diff --git a/README.md b/README.md index 5c57a17..d72401d 100644 --- a/README.md +++ b/README.md @@ -201,11 +201,12 @@ Depending on your selections, the CLI generates a project like this: ```text my-app/ -├─ auth/ # Seamless Auth server (optional) -├─ web/ # React web application (optional) -├─ api/ # Express or Fastify API server (optional) -├─ docker-compose.yml (optional) -└─ README.md +├─ auth/ # Seamless Auth server (local auth mode only) +├─ web/ # React web application (optional) +├─ api/ # Express or Fastify API server (optional) +├─ admin/ # Admin console source (--admin=source only) +├─ docker-compose.yml # not written for a managed project +└─ seamless.config.json ``` All services are preconfigured to work together. @@ -228,14 +229,13 @@ If you choose Docker during setup: docker compose up ``` -This starts: - -- PostgreSQL -- Auth server -- API server -- Web app +This starts PostgreSQL, the auth server, the API server, and the web app, plus a +standalone admin console container under `--admin=image` or `--admin=source`. All services are configured to communicate correctly inside the container network. +Ports are published on `127.0.0.1` only: the auth server returns OTP codes in the +response for local login, which would be an authentication bypass for anyone else +on the network. --- @@ -256,11 +256,14 @@ cd auth npm install npm run db:create -npm run db:migrate +npm run migrate:up npm run dev ``` +The Docker path runs both of these for you at container start, so they are only +needed when the auth server runs on the host. + --- #### 3. Start the API @@ -283,6 +286,54 @@ npm run dev --- +## Checking and verifying + +Two commands answer two different questions: is *this project* healthy, and does the +*whole auth surface* still behave. + +### `seamless check` + +Health-checks the project in the current directory, reading `seamless.config.json` to +decide what applies. A local project gets the Docker and Compose checks; a managed one +skips them and validates the remote instance instead. Every check runs, so one failure +never hides the rest. + +```bash +seamless check # always exits 0, whatever it reported +seamless check --strict # exit 1 if any check failed, for a CI gate +``` + +The exit status is 0 without `--strict` because this output has been parsed by scripts +since before the flag existed. + +### `seamless verify` + +The cross-package conformance harness. It stands up the ecosystem with Docker Compose +(Postgres, the auth API, an Express adapter, a Fastify adapter, and the web starter), +runs a Playwright matrix over it, and prints a flow x layer pass/fail grid plus JUnit and +HTML reports. It ships with the package, so it needs no checkout of its own, but it does +need Docker and a sibling `seamless-auth-api` source tree to build the auth server from. + +```bash +seamless verify # everything, against the published SDKs +seamless verify --api-only # fast pass: the API layer alone +seamless verify --no-react # skip the browser layer, keep the adapters +seamless verify --local # build @seamless-auth/* from source first +seamless verify --filter=passkey # one flow (the = form only) +seamless verify --keep-up # leave the stack running afterwards +``` + +`--local` is the pre-publish check: it builds and packs the local SDK source rather than +installing from npm, so an SDK regression surfaces before a release rather than after. +The browser layer runs once per web template in the registry, each scoped to the flows +its `template.json` declares. + +Sibling repositories are resolved next to this one and can be pointed elsewhere with +`SEAMLESS_API_DIR`, `SEAMLESS_SERVER_DIR`, `SEAMLESS_REACT_SDK_DIR`, and +`SEAMLESS_TEMPLATES_DIR`. + +--- + ## Creating the first admin `init` asks for your email and writes it to the auth server as `OWNER_EMAIL`. The auth server grants @@ -518,17 +569,18 @@ Everything is aligned across services so the system works immediately after setu ## Included projects -Seamless CLI pulls from the following repositories: +Seamless CLI scaffolds from, and conformance-tests against, these repositories: -- Seamless Auth API - [https://github.com/fells-code/seamless-auth-api](https://github.com/fells-code/seamless-auth-api) - -- Seamless Templates (the frontend and API starters) - [https://github.com/fells-code/seamless-templates](https://github.com/fells-code/seamless-templates) +| Repository | What it provides | How the CLI uses it | +| --- | --- | --- | +| [seamless-auth-api](https://github.com/fells-code/seamless-auth-api) | The auth server | Run as a pinned image (`--auth=docker`) or cloned into `auth/` (`--auth=local`) | +| [seamless-templates](https://github.com/fells-code/seamless-templates) | The web and API starters | Scaffolded from its registry at a pinned ref | +| [seamless-auth-server](https://github.com/fells-code/seamless-auth-server) | `@seamless-auth/core`, `/express`, `/fastify` | The adapters the scaffolded `api/` runs on | +| [seamless-auth-react](https://github.com/fells-code/seamless-auth-react) | `@seamless-auth/react` | The client SDK the scaffolded `web/` runs on | The starters live in the templates monorepo and are listed in its registry, so the set of frameworks the CLI offers grows there. Each project can be used independently, but the CLI connects -them into a working system. +them into a working system, and `seamless verify` checks that connection holds across all four. --- @@ -544,8 +596,8 @@ Full documentation is available at: Seamless Auth is built around a few principles: -- Passwordless authentication only -- No redirects or third-party auth providers +- Passwordless authentication only (passkeys, magic links, email and phone OTP) +- Optional OIDC sign-in, configured explicitly and self-hosted like everything else - Self-hosted by default - Production-shaped local development - Explicit configuration over hidden behavior diff --git a/resources/coverage-badge.svg b/resources/coverage-badge.svg index fdefd25..270ff0d 100644 --- a/resources/coverage-badge.svg +++ b/resources/coverage-badge.svg @@ -1,5 +1,5 @@ - - coverage: 99.3% + + coverage: 99.4% @@ -17,7 +17,7 @@ coverage coverage - 99.3% - 99.3% + 99.4% + 99.4% diff --git a/src/commands/helpTopics.ts b/src/commands/helpTopics.ts index 5f782b3..e98fd76 100644 --- a/src/commands/helpTopics.ts +++ b/src/commands/helpTopics.ts @@ -40,7 +40,8 @@ With a template flag (e.g. --oauth, --react-oauth, --fastify): • Run seamless templates list to see every id, alias, and flag --profile - • Use that profile instead of the active one + • Accepted and ignored, with a warning. Managed connect uses your portal + session (seamless login), not an instance profile --app • Connect the project to that managed application (needs a portal @@ -315,8 +316,8 @@ config get [key] [--json] config set • Update one writable key. String-typed keys (app_name, rpid, - access_token_ttl, refresh_token_ttl) take the value verbatim; every other - key parses it as JSON, falling back to a string + access_token_ttl, session_idle_ttl, refresh_token_ttl) take the value + verbatim; every other key parses it as JSON, falling back to a string (for example: config set app_name 123 sets the string "123", config set login_methods '["email_otp","passkey"]') diff --git a/src/commands/init.ts b/src/commands/init.ts index c6ab0d6..b15a03f 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -78,9 +78,10 @@ export interface InitOptions { email?: string; auth?: string; admin?: string; - // --yes: answer every remaining question with the recommended option rather - // than prompting. It never stands in for a destructive confirmation; those - // take --force. + // The split every prompt below is written against: --yes answers the ordinary + // questions with the recommended option, --force answers the destructive ones + // (overwriting a non-empty directory, rotating a live service token), and + // neither answers managed-or-local, which --app and --local settle outright. yes?: boolean; force?: boolean; } @@ -269,9 +270,7 @@ async function scaffold( } // Writing starter files over a directory someone already has work in is the one -// destructive step in a scaffold, so --yes is deliberately not enough to reach -// it. --force is, and it says nothing about the integrate-or-scaffold question, -// which --app answers instead. +// destructive step in a scaffold, so this takes --force rather than --yes. async function resolveExistingDirectoryAction( canConnect: boolean, opts: InitOptions, @@ -296,8 +295,8 @@ async function resolveExistingDirectoryAction( return "scaffold"; } -// Managed or local decides where the project's auth lives for good, so --yes -// alone will not pick: --app means managed and --local means self-hosted. +// Where the project's auth lives is settled for good here, so --yes will not pick: +// --app means managed, --local means self-hosted. async function resolveScaffoldTarget( appCount: number, opts: InitOptions, @@ -318,6 +317,10 @@ async function resolveScaffoldTarget( // The bundled database as a connection string with placeholder credentials, or // an empty string when the control plane has not provisioned one yet. Never // requests ?reveal=true, so no live credential reaches this machine. +// +// Both callers run this before rotating the service token: a database that is not +// provisioned yet is a warning rather than a failure, and finding that out after +// the rotation would mean reporting it against a half-wired project. async function resolveDatabaseUrl( client: AuthClient, app: PortalApp, @@ -400,9 +403,6 @@ async function scaffoldManaged( await source.copyInto(entry, dir); } - // Read before rotating: a database that is not provisioned yet is a warning, - // not a failure, and finding that out after the token is rotated would mean - // reporting it against a half-wired project. const databaseUrl = await resolveDatabaseUrl(client, app); const serviceToken = await issueServiceToken(client, app, opts); @@ -595,9 +595,6 @@ async function integrateExistingProject( ) { const app = await selectApplication(apps, opts.appId); - // Read before rotating: a database that is not provisioned yet is a warning, - // not a failure, and finding that out after the token is rotated would mean - // reporting it against a half-wired project. const databaseUrl = await resolveDatabaseUrl(client, app); const serviceToken = await issueServiceToken(client, app, opts); @@ -686,9 +683,8 @@ async function issueServiceToken( opts: InitOptions, ): Promise { if (app.hasServiceToken) { - // Rotation breaks whatever is running on the old token, so it is a - // destructive confirmation like the overwrite one: --yes does not answer it, - // --force does. + // Rotation breaks whatever is running on the old token, so it confirms like + // the overwrite step does. if (opts.yes) { if (!opts.force) { throw new Error( diff --git a/src/commands/verify.ts b/src/commands/verify.ts index b9a129c..e712d71 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -88,8 +88,9 @@ function readTemplateFlows(dir: string): string[] | undefined { } // The web templates to build, serve, and drive. Each is served at :5173 in turn and -// pointed at the adapter. Resolution order: an explicit SEAMLESS_REACT_DIR (a single -// template path, used by CI), otherwise every runnable web template in the registry. +// pointed at the adapter. SEAMLESS_REACT_DIR narrows the run to one template directory; +// otherwise every runnable web template in the registry is driven, which is what CI does +// (it sets SEAMLESS_TEMPLATES_DIR and leaves the set alone). function resolveWebTemplates(): WebTemplate[] { const override = process.env.SEAMLESS_REACT_DIR; if (override) { diff --git a/src/core/loginFlow.ts b/src/core/loginFlow.ts index 525ddbf..5141c35 100644 --- a/src/core/loginFlow.ts +++ b/src/core/loginFlow.ts @@ -120,12 +120,11 @@ async function startLogin( ); } - // `/login` no longer answers 401. An identifier with no usable account, which used to - // mean unknown, unverified, or with no permitted method, now gets a 200 and a decoy - // pre-auth token so the response cannot be used to test whether an account exists. The - // branches that read 401 as "no such user" and "not verified yet" were removed with it: - // there is no longer an answer for them to read. Such a login fails at the code step - // instead, which is what `completeLogin` reports. + // `/login` never answers 401. An identifier with no usable account (unknown, + // unverified, or with no permitted method) gets a 200 and a decoy pre-auth token, so + // no answer here can be used to test whether an account exists. Nothing to read means + // nothing to report: such a login fails at the code step, which `completeLogin` names. + // Do not add a branch that reads a status as "no such user"; there is not one. if (!res.ok) { if (res.status === 400) { throw new LoginError( @@ -133,9 +132,8 @@ async function startLogin( ); } if (res.status === 423) { - // The one remaining answer that does imply an account, and the one worth naming: - // it needs prior failed attempts against this identifier, and the developer can - // act on it by waiting. + // The one answer that does imply an account, and the one worth naming: it needs + // prior failed attempts against this identifier, and waiting resolves it. throw new LoginError(lockedMessage(res.data, identifier)); } if (res.status === 403) { diff --git a/src/core/systemConfig.ts b/src/core/systemConfig.ts index 793e328..cfee6ac 100644 --- a/src/core/systemConfig.ts +++ b/src/core/systemConfig.ts @@ -6,6 +6,12 @@ export type SystemConfig = Record; // Mirrors the instance's patch schema, which is strict: a key missing here is one // `config apply` silently drops and `config set` refuses, so the two lists have to // stay in step. +// +// `magic_link_redirect_uris` is the one entry ahead of it. The key is defined in +// @seamless-auth/types but not yet released there, and the auth API still treats the +// configured origins as the magic-link allowlist, so an instance rejects it today. +// Leave it listed (a released API accepts it without a CLI change) but expect the +// rejection until that release lands. export const WRITABLE_KEYS = [ "app_name", "default_roles", @@ -201,9 +207,9 @@ export async function deleteOAuthProvider( // The writable keys the instance types as a plain string. Their values are never // JSON-parsed, so `config set app_name 123` sends the string "123" rather than the -// number 123, and `config set rpid true` sends "true". Everything else (arrays, -// objects, numbers, booleans) is parsed, falling back to the raw string when the -// value is not valid JSON, which is what makes `access_token_ttl 15m` work. +// number 123, and `config set rpid true` sends "true". Everything else is parsed as +// JSON, falling back to the raw string, which is what makes `access_token_ttl 15m` +// work: a TTL is string-typed, but the fallback would have carried it anyway. const STRING_KEYS = new Set([ "app_name", "access_token_ttl", diff --git a/src/core/templates.ts b/src/core/templates.ts index dfc543e..c4cfaa5 100644 --- a/src/core/templates.ts +++ b/src/core/templates.ts @@ -220,8 +220,9 @@ export function assertCliSupports(manifest: TemplateManifest, label: string) { } // Copies the template's example env to .env (if declared), then applies the -// manifest's env.set, resolving {{placeholders}} against the scaffold context. -// This replaces the per-framework configure step. +// manifest's env.set, resolving {{placeholders}} against the scaffold context. This +// is the whole per-template configuration contract: a starter that needs a new value +// declares it in template.json rather than earning a branch here. export function applyTemplateEnv( destDir: string, manifest: TemplateManifest, diff --git a/verify/docker-compose.verify.yml b/verify/docker-compose.verify.yml index 2d8a7ce..622c75c 100644 --- a/verify/docker-compose.verify.yml +++ b/verify/docker-compose.verify.yml @@ -1,15 +1,19 @@ # Conformance stack for `seamless verify`. -# Brings up Postgres + the auth API + the Express adapter from local source -# (paths supplied by the verify command via env) so the Playwright harness can -# drive every auth flow. NODE_ENV=test enables the API's external-delivery seam -# so OTP / magic-link tokens are returned in responses (no real email/SMS). # -# Required env (the `seamless verify` command writes these into .env.verify): -# SEAMLESS_API_DIR absolute path to seamless-auth-api source -# SEAMLESS_ADAPTER_DIR absolute path to the express adapter source -# API_SERVICE_TOKEN shared secret between API and adapter -# JWKS_KID key id the adapter expects (must match the API's active kid) -# OWNER_EMAIL tenant owner; this email gets the admin role at signup +# Postgres, the auth API built from local source, and two adopter backends that are +# deliberate twins (Express on 3000, Fastify on 3001) so a failure is attributable to +# one framework. The web starter is behind the `react` profile, since the api and +# adapter layers need no browser. +# +# Env comes from the `seamless verify` process, not a file: +# SEAMLESS_API_DIR absolute path to the seamless-auth-api source (build context) +# SEAMLESS_REACT_DIR absolute path to the web template to serve (react profile only) +# API_SERVICE_TOKEN shared secret between the API and both adapters +# JWKS_KID key id the adapters expect (must match the API's active kid) +# OWNER_EMAIL tenant owner; this email gets the admin role at signup +# +# The adapters are built from ./adapter-app and ./adapter-fastify-app in this repo, +# so they take no path from the environment. name: seamless-verify @@ -30,19 +34,17 @@ services: retries: 20 auth-api: - # Built with the production Dockerfile (compiles dist/), but run with - # NODE_ENV=test so signing keys auto-generate and prod-only env (JWKS keys) - # is skipped. validateEnvs.sh gates startup on the env below. + # Built with the production Dockerfile (compiles dist/). Its validateEnvs.sh + # entrypoint runs the migrations and gates startup on the env below. build: context: ${SEAMLESS_API_DIR} dockerfile: Dockerfile ports: - '5312:5312' environment: - # `development` (not `test`): the API's JWKS endpoint only serves the - # auto-generated dev public key when NODE_ENV === 'development', even though - # signing treats any non-production env as dev. Running as development keeps - # JWKS publication working so the adapter/SDKs can verify tokens. + # `development`, not `test`: signing treats any non-production env as dev, but + # the JWKS endpoint only serves the auto-generated dev public key under + # `development`. The adapters and SDKs need that key to verify tokens. NODE_ENV: development PORT: '5312' APP_NAME: Seamless Verify @@ -54,9 +56,9 @@ services: DEFAULT_ROLES: user AVAILABLE_ROLES: user,admin LOGIN_METHODS: passkey,magic_link,email_otp,phone_otp,oauth - # Required system config as of auth-api 0.3.0: the base URL for emailed - # magic links. Without it (and with a fresh DB, so system_config isn't seeded) - # the server exits at startup. The browser-visible web app runs on 5173. + # The base URL for emailed magic links, required at boot: with a fresh DB + # there is no seeded system_config row to fall back on, so the server exits + # without it. The browser-visible web app runs on 5173. FRONTEND_URL: http://localhost:5173 DB_LOGGING: 'false' ACCESS_TOKEN_TTL: 15m From f4ccb30c437f97ddb7219b6b06147293de4a837c Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Mon, 7 Sep 2026 07:31:55 -0400 Subject: [PATCH 2/2] chore: stop writing the unused AUTH_MODE into scaffolds The scaffold set AUTH_MODE=server in the auth server env and on the admin console container. Nothing reads it: not the auth API, not the admin dashboard (its entrypoint takes only API_URL), and not the web or api templates. The tests now assert its absence, so it cannot come back unnoticed. --- .changeset/remove-dead-auth-mode.md | 10 ++++++++++ src/generators/docker/docker.test.ts | 7 ++++--- src/generators/docker/docker.ts | 2 -- 3 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 .changeset/remove-dead-auth-mode.md diff --git a/.changeset/remove-dead-auth-mode.md b/.changeset/remove-dead-auth-mode.md new file mode 100644 index 0000000..0c5ce25 --- /dev/null +++ b/.changeset/remove-dead-auth-mode.md @@ -0,0 +1,10 @@ +--- +"seamless-cli": patch +--- + +Stop writing `AUTH_MODE` into scaffolded projects. + +The scaffold set `AUTH_MODE=server` in the auth server env and on the admin console +container, but nothing reads it: not the auth API, not the admin dashboard (its +entrypoint takes only `API_URL`), and not the web or api templates. It was config that +looked meaningful and meant nothing. diff --git a/src/generators/docker/docker.test.ts b/src/generators/docker/docker.test.ts index 2565d8c..0d205b3 100644 --- a/src/generators/docker/docker.test.ts +++ b/src/generators/docker/docker.test.ts @@ -69,13 +69,14 @@ describe("buildAuthEnv", () => { expect(env.ISSUER).toBe("http://auth:5312"); expect(env.DB_HOST).toBe("db"); - expect(env.AUTH_MODE).toBe("server"); expect(env.PORT).toBe("5312"); expect(env.NODE_ENV).toBe("development"); // The scaffolded stack no longer enables the bootstrap route: the first // admin comes from the OWNER_EMAIL grant at signup. expect(env.SEAMLESS_BOOTSTRAP_ENABLED).toBeUndefined(); expect(env.SEAMLESS_BOOTSTRAP_SECRET).toBeUndefined(); + // Nothing in the ecosystem reads AUTH_MODE, so the scaffold does not write it. + expect(env.AUTH_MODE).toBeUndefined(); expect("bootstrapSecret" in shared).toBe(false); expect(env.API_SERVICE_TOKEN).toBe(shared.apiToken); expect(env.REFRESH_TOKEN_LOOKUP_SECRET).toMatch(/^[0-9a-f]{64}$/); @@ -171,7 +172,7 @@ describe("configureAuthLocalEnv", () => { const written = fs.readFileSync(path.join(tmpDir, "auth", ".env"), "utf-8"); expect(written).toContain(`API_SERVICE_TOKEN=${shared.apiToken}`); expect(written).toContain("SOME_KEY=placeholder"); - expect(written).toContain("AUTH_MODE=server"); + expect(written).not.toContain("AUTH_MODE"); expect(written).toContain("ISSUER=http://localhost:5312"); expect(written).toContain("DB_HOST=localhost"); expect(written.endsWith("\n")).toBe(true); @@ -309,7 +310,7 @@ describe("generateDockerCompose", () => { ); expect(compose).toContain("container_name: admin"); expect(compose).toContain("build: ./admin"); - expect(compose).toContain("AUTH_MODE: server"); + expect(compose).not.toContain("AUTH_MODE"); expect(compose).toContain("- ./admin:/app"); expect(compose).toContain( "UI_ORIGINS: http://localhost:5173,http://localhost:5174", diff --git a/src/generators/docker/docker.ts b/src/generators/docker/docker.ts index 52404c6..7160b45 100644 --- a/src/generators/docker/docker.ts +++ b/src/generators/docker/docker.ts @@ -240,7 +240,6 @@ function adminService(mode: "image" | "source") { - "127.0.0.1:5174:80" environment: API_URL: http://localhost:3000/ - AUTH_MODE: server volumes: - ./admin:/app - /app/node_modules @@ -297,7 +296,6 @@ export function buildAuthEnv( env.PORT = "5312"; env.NODE_ENV = "development"; - env.AUTH_MODE = "server"; env.ISSUER = mode === "docker" ? "http://auth:5312" : "http://localhost:5312";