- 7b2d4ea: Add
seamless webhooks,seamless hooks,seamless claimsandseamless audit-sinksto manage signed webhooks and their deliveries,post_loginandpre_tokenlogin hooks, static custom claims, and audit log streaming to OTLP and syslog (syslog needs auth server v0.23.1, which v0.23.0 refuses). Signing secrets are printed once, when they are issued.hooks setandaudit-sinks updatechange only the settings a flag names, andclaims setasks before removing a claim. Every command takes--json. They need auth server v0.23 or later, and an older instance gets a message saying so. - 612b37b: New projects scaffold Seamless Auth API v0.23.0, the admin dashboard v0.14.0 and the starter templates v0.22.0, so a fresh project has roles and permissions in its tokens, the starters' permission guard demo, and the console's Roles and Integrations pages. Existing projects keep the images they were scaffolded with.
- 2eb878e: Add
seamless permissionsandseamless rolesto manage the permission catalogue, organization roles, and the permissions each instance or organization role grants.roles setreplaces a role's whole mapping and asks before taking a permission away. Every command takes--json. They need auth server v0.23 or later, and an older instance gets a message saying so.
- 5c60a5f:
seamless initscaffolds admin dashboard v0.13.0, which adds the Roles page for permissions and organization roles, pinned to the commit and image digest its tag names.
-
5e62f4a:
seamless profile loginsigns in through the OpenID Provider, which brings passkey sign-in to the CLI. It opens the system browser with a loopback redirect, and--deviceuses the device grant for a machine with no browser. Either way the CLI exchanges the sign-in for a first-party session of its own atPOST /sessions/exchangeand stores that in the OS keychain, keeping no token from the provider. Needs auth server v0.23 and the provider edge.The CLI reads the provider from
oidcProvideronGET /system-config/publicwhen the instance advertises it. On one that does not, pass--oidc-issuerand--client-idonce, toseamless profile loginorseamless profile add. Flags win over what the profile saved, and that wins over the advertisement, so an instance that stops advertising cannot downgrade a profile. With no provider anywhere the email code stays, and--email-codeasks for it.Every provider sign-in prints the issuer it uses. The CLI asks before using an advertised issuer on another origin than the instance, and without a terminal it stops and asks for the flags. It reads the advertisement only over https or on loopback, so an instance on plain http needs the flags.
Every sign-in now checks the OS keychain before it starts, including
seamless loginand the email code, and fails early on a machine with none, rather than after a browser approval or an emailed code.seamless clients createandupdategain--session-exchangeand--no-session-exchange, andgetshows the setting.A browser session can step up with a passkey as well as the authenticator app. A device session uses the authenticator app only, and needs a step-up before admin and organization commands, reads included.
- 3aea9df: The adapter conformance suite covers permission guards, under the pending contract
permission-guards. It holds a reference app's new/api/permissionsroutes to one behavior: exact matching, any-of and all-of, organization scope bound to the route, removal from an organization reaching the guard at the next token, and the live read when a token omits its permissions. The cases are skipped unless a run opts in withSEAMLESS_VERIFY_CONTRACTS, because they need the auth API at v0.23. The Express and Fastify reference apps add the routes only when the installed adapter hasrequirePermission.
-
3890db9: The adapter conformance suite now sends
x-seamless-auth-transport: cookieon its cookie-transport requests, as@seamless-auth/clientdoes, so body-lessPOSTandDELETEcalls pass the content-type gate server adapters add in v0.23 (fells-code/seamless-auth-api#558). Two new cases hold adapters to the request guards: a cross-site unsafe request answers403 cross_site_request_blockedand leaves the session alone, and a body-less form-encodedPOST /auth/recovery-codeswithout the transport header answers415 unsupported_media_type. The 415 case is a pending contract: it is skipped unlessSEAMLESS_VERIFY_CONTRACTS=content-type-gate(orall) is set, so an adapter that has not released the gate yet keeps passing.verify/CONFORMANCE.mdlists pending contracts. The conformance grid no longer counts a skipped case as a failure.Requests that ask for external delivery (
x-seamless-auth-delivery-mode: external) now carry a service token with theseamless-auth-external-deliverysubject, as the server adapters send it, so the suite keeps passing once the API requires that subject (fells-code/seamless-auth-api#556). -
ba479f2: Depend on
@seamless-auth/types^0.35.0, which adds the OpenID Provider session binding contract.
- 3c94d99:
seamless initscaffolds templates v0.21.2, whose API and full-stack starters declare the v0.21 server adapters:@seamless-auth/express0.23,@seamless-auth/fastify0.14,@seamless-auth/nextjs0.7, andseamless-auth0.4 for Go, Rust and Python.
-
0413554:
seamless clientsshows whether an application requires PKCE and the nonce, andcreateandupdateaccept--pkce/--no-pkceand--nonce/--no-nonce. Only a confidential client can turn PKCE off, and the nonce can be turned off only once PKCE is off. An application with both off relies only onstateand client authentication against authorization code injection, solist,get,createandupdatewarn about it. Pins@seamless-auth/types0.34.0. -
d6304a1: Set hosted page branding from the CLI (#320).
seamless config hosted-ui get|set|reset(aliasbranding) reads the instance'shosted_uisystem config, merges the fields you pass (--app-name,--logo,--font-family, and the six color flags) into the current branding, validates the result withHostedUiSchemafrom@seamless-auth/types, and writes the whole object back, since the instance replaces it rather than merging.resetremoves named fields or, after a confirmation (--forceto skip), clears all branding.hosted_uiis also writable as a whole value withconfig set hosted_ui '<json>'orconfig set hosted_ui null. The logo must be an https URL where the image is hosted: the instance does not store uploaded files, so a local path is refused with that message. Needs auth server v0.21 or later; an older instance reports that it does not support branding. Bumps@seamless-auth/typesto 0.34.0 and lists the four new OpenID Provider keys as not yet writable (#332). -
8aea8aa:
seamless migrate assess <csv|entra-id|auth0|cognito> [source args] [--report <path>] [--json]writes a readiness report from the source alone, with no Seamless Auth instance and no login. It reuses each source's parsing and validation and reports users found, how many would import and why the rest would not (including duplicate emails), contact coverage (verified email, phone only, neither), the roles and organization memberships that would be granted and the fields left behind, source features Seamless Auth does not cover (such as Auth0 enterprise connections or Cognito SAML providers, plus what an export cannot show, such as Cognito identity pools) with a link to the matching guide, and a link to the docs cost calculator with the user count filled in. The Markdown report holds counts and names of fields, roles and organizations, never a user's email, phone or id.--jsonprints the same assessment. -
6b8105e:
seamless migrate auth0 <export.ndjson>andseamless migrate cognito <list-users.json>...read an Auth0 user export and Amazon Cognitolist-usersoutput directly, so the migration guides no longer need ajqconversion to CSV. Both share the dry run,--apply,--reportand--jsonofmigrate csv, and read files only (no AWS or Auth0 SDK or credentials).- Auth0:
user_idbecomes the external id, withemailandphone_number. A gzip-compressed export is read as downloaded. Roles and organizations come fromapp_metadataoruser_metadataonly through--map. - Cognito: the
subattribute becomes the external id, withemailandphone_number.--groupsturns the guide's username-to-groups map into roles (ignoring Cognito's per-provider groups), and--mapcan read an attribute such ascustom:tenantinto organizations. One response, theUsersarray, an array of responses, concatenated responses, and several chunk files are all accepted.
Blocked or disabled users, users whose email the source marks unverified (
--allow-unverifiedimports them) and unconfirmed Cognito sign-ups (--include-unconfirmed) are skipped and listed in the report. Users with no email, including phone-only users, are reported invalid. Unlike the guides' earlierjqrecipes, unverified emails are no longer imported by default. - Auth0:
-
471b05a: Configure an OAuth provider from its issuer.
seamless config oauth-providers addandupdatetake--issuer <url>, and a provider with an issuer can leave outauthorizationUrl,tokenUrl,userInfoUrl, andjwksUri: the instance reads the missing ones from the issuer's/.well-known/openid-configuration. Without an issuer,addrefuses before sending anything and names the missing endpoints. The newseamless config oauth-providers refresh <id>(aliasdiscover) asks the instance to read the discovery document again and drop its cached signing keys, then prints the endpoints sign-in will use (--jsonfor the provider object); like other provider writes it steps up when the instance asks.oauth-providers listshows a provider's issuer. Needs auth server v0.21 or later. Bumps@seamless-auth/typesto 0.34.0, which also adds theoidc_account_notice_sentandoidc_account_notice_failedauth event types. -
411691e: Add
seamless clientsandseamless resourcesfor the OpenID Provider.clientslists, creates, updates and deletes OIDC clients, rotates a shared secret (printed once), registers a key file or JWKS URI, and sets which resources and scopes a client may use. Public clients, loopback and private-use redirect URIs, the device grant and token TTLs are flags, and redirect URIs and cross-field rules are checked with the@seamless-auth/typesschemas before the instance is asked.resourcesmanages the APIs clients may get tokens for and their scopes. Writes use the existing step-up flow.seamless appsstill means managed applications in the portal. Needs an auth API with the/admin/applicationsand/admin/resourcesroutes. -
2e758a2: Write the OpenID Provider config keys (#332).
oidc_provider_enabled,oidc_issuer,allow_client_secrets, andthird_party_clients_enabledare now writable withseamless config setandconfig apply.config setchecks their values before sending: the three switches taketrueorfalse, andoidc_issuertakes a URL, ornullto clear it.seamless config --helpdocuments them: the instance refuses to enable the provider untiloidc_issueris set,allow_client_secretsenables the weaker shared-secret client authentication (andseamless clientsrefuses shared-secret clients until it is on), and third-party clients needthird_party_clients_enabled. Needs auth server v0.21.0 or later, whose strict patch schema is the first to accept these keys. Bumps@seamless-auth/typesto 0.34.0.
- 9ce5813: Pin source downloads to commits.
--auth=localnow checks that the clonedseamless-auth-apitag is the commit this CLI pins and removes the clone, before anything in it runs, when it is not.--admin=sourcedownloads the dashboard by its pinned commit and checks the archive's commit before unpacking. The auth server and dashboard images are referenced by digest as well as tag, andnpm run check:pinschecks every pin against its tag. - ff1736c:
seamless initscaffolds auth API v0.21.0 and admin dashboard v0.12.0, pinned to the commits and image digests their tags name. - 877e90a:
seamless init --auth=localand--auth=docker(andseamless add --local) now write a generatedJWKS_KIDsuch aslocal-3f9a0c1b2d4einstead ofdev-main, so the scaffolded adapter no longer warns on every boot that its kid is a placeholder. Re-runninginiton an existing project keeps a kid it already wrote. The value is only thekidheader on the service tokens the adapter signs with the service secret; nothing verifies against it. - 0bbdb1d: Scaffold templates v0.21.1, whose production images run as non-root and listen on port 8080, matching the compose file the CLI now generates.
- 364bd55: Depend on
@seamless-auth/types^0.34.0, the stable release, instead of the 0.34.0 prerelease. - babb5bd: Map the web container to port 8080. The seamless-templates web and Next.js images now run as non-root and listen on 8080 instead of 80 (seamless-templates#133), so the generated compose file publishes
127.0.0.1:5173:8080and health-checks/healthon 8080, and the verify harness serves every web, reference, and Next.js app on 8080 (the nginx ones fromnginxinc/nginx-unprivileged, as the templates do). This needs a templates release that carries the change: a scaffold from the templates pinned before it would map a port its web image no longer listens on.
-
02d9f0e: Admin writes step up with an authenticator app code, which the auth server requires from v0.20 (fells-code/seamless-auth-api#490). When an instance answers
403 step_up_required, the CLI asks for the code, verifies it withPOST /totp/verify-mfaon the same session, and retries the write once. This coversconfig set,config apply,config oauth-providers add|update|remove,users delete,users prepare-device-replacement, theorgandorg memberswrites, and every batch ofmigrate. A wrong code can be re-entered up to three times; a lockout, a rate limit, and an account with no authenticator app enrolled each get their own message instead of the old "requires an admin role" one. Without a terminal,SEAMLESS_TOTP_CODEsupplies the code for the first step-up of a run.Breaking:
users prepare-device-replacementnow requires--proofing <in_person|remote_exception>and--evidence-ref <ref>(with an optional--approver <name>), because the auth server rejects a device replacement that does not record how the user's identity was established. -
79590f0: New projects scaffold auth server v0.20.0, admin dashboard v0.11.0 and seamless-templates v0.21.0 (the v0.20 SDKs). These include the fixes for GHSA-qx82-28gf-w744, GHSA-9xg2-j8fg-427c, GHSA-v9q2-hcqj-ggqr, GHSA-v2q2-q246-8chm and GHSA-m5x9-cjv4-6q3w, first shipped in auth server v0.19.4 and seamless-templates v0.20.3.
--auth=localnow clones the auth server at the samev0.20.0tag the Docker image is built from, instead of the tip ofmain. -
d0f5f9c:
config setandconfig applynow writerecovery_codesandaccount_recovery, which auth server v0.20 accepts (#310). Both are objects, and a field left out takes its default, so send the whole object, for exampleconfig set account_recovery '{"enabled":true,"delay":"24h"}'. An instance older than v0.20 rejects a patch that carries either key. -
55a9fd2:
seamless initkeeps every secret out of the generateddocker-compose.yml.- With
--auth=docker, the auth server's environment is now written toauth/.envand loaded withenv_file: ./auth/.env, the same file--auth=localalready used. The compose file no longer has an inlineenvironment:block for the auth service. - The api service (or the web service of a full-stack app) no longer sets
SEAMLESS_SERVICE_TOKENinline.initwrites it toapi/.env(orweb/.env), which compose already loads. initwrites a root.gitignorecovering.env,.env.*(keeping.env.example),keys/and*.pem. An existing.gitignorekeeps its lines and gains only the missing patterns.seamless owner-codeand the generatedAGENTS.mdpoint atauth/.envforOWNER_EMAILin both auth modes. Projects scaffolded earlier, with the variable inline in compose, still work.
The scaffold now also generates
DECOY_SUBJECT_SECRETfor the auth server, which v0.20 requires instead of falling back to the service token (OAUTH_STATE_SECRETis still generated whenever OAuth providers are configured).Every generated
.envfile (frominit,add, template env and env merges) is written with mode 0600, and an existing file is tightened to 0600 when it is rewritten. - With
- 525ed2c:
seamless verifysteps up the owner's admin session with a software passkey before admin writes, which the auth server requires from v0.20 (fells-code/seamless-auth-api#490). - 3bd0e2d: Update
@seamless-auth/typesto 0.30.0. - 60ee807: Update
@seamless-auth/typesto 0.33.0.
- ec0ea10: New projects scaffold auth server v0.19.3, admin dashboard v0.10.1 and seamless-templates v0.20.2. The auth server binds the OAuth state to the client that started the sign-in, and the dashboard and every starter are on the SDKs that send that binding, so OAuth sign-in works end to end in a new project.
-
e768ec0: New
seamless migrate entra-idimports users straight from Microsoft Entra ID through Microsoft Graph, with the same dry run, batches and report asmigrate csv. Each user's Entra object id becomes theirexternalId, so re-runs match people without a hand-edited export. It signs in as an app registration with app-onlyUser.Read.All(andGroupMember.Read.Allfor groups), readingENTRA_TENANT_ID,ENTRA_CLIENT_IDandENTRA_CLIENT_SECRETfrom the environment only.--groupimports one group's members,--groupsmaps groups to organizations and roles, disabled accounts and guests are skipped and reported, and--allow-upnuses theuserPrincipalNamefor users with no mail.Migration reports gain a
skippedcount and status, for rows left out on purpose. Skipped rows do not make the command exit 1.
- ce96113:
seamless initnow creates the directories the generated compose file keeps in a volume (node_modulesunder each bind-mounted source tree, and.nextfor the Next.js full-stack layout) before Docker does. On a Linux host Docker created them as root, sonpm installon the host inapi/orweb/failed withEACCESuntil they were removed with sudo. A directory that already exists keeps its owner. - 1ce71d7:
seamless verifynow forwards thebindingthatPOST /oauth/:providerId/startreturns in the body of its OAuth callbacks, so the OAuth specs pass against an auth API that ties the callback to the browser that started the flow. Older API images ignore the extra field. - bce0a9b:
seamless init --auth=localnow produces an auth service that starts. The generated compose bind-mounts./authover/app, which hid the image'schmod +x ./validateEnvs.sh(the script is mode 644 in the clone) and left the entrypoint looking for adist/server.jsthatDockerfile.devnever builds. The auth service now runs the same command as the auth server's owndocker-compose.dev.yml: sync dependencies, run migrations (creating the database on first boot), then watch the TypeScript sources. - 888a781: Depend on
@seamless-auth/types0.29.0, which adds the OAuth statebindingfield.
-
9e63557: New
seamless owner-codecommand prints a one-time code for the owner of a local stack, so the first admin finishes registering without reading container logs. Register in the browser with the owner address, and when the page asks for a code, runnpx seamless-cli owner-codefrom the project directory. It waits for the auth server, readsOWNER_EMAILfromauth/.envordocker-compose.yml(or takes an address), and only talks to a local auth server.seamless initnow ends with that step, and the generated AGENTS.md names it in its first admin section. -
59d583d: Applications now read their service token as
SEAMLESS_SERVICE_TOKEN, the name the managed portal already uses and the auth server's own name for it since 0.19.0. The generated auth server environment uses it too.seamless init: the generateddocker-compose.ymlgives theapiservice (or a full-stackwebservice)SEAMLESS_SERVICE_TOKEN, with a comment saying it is the same secret as theauthservice's. Connecting a managed application writesSEAMLESS_SERVICE_TOKENintoapi/.env, and the values printed when there is noapi/directory use the new name.seamless add: the backend's.envgetsSEAMLESS_SERVICE_TOKEN, and the printed Node, Go, Rust and Python code reads it.seamless verify: the Express and Fastify reference apps readSEAMLESS_SERVICE_TOKEN, and the Next.js starter is given both names so starters from before the rename still run.
Existing projects keep working. When a project's
.envalready hasAPI_SERVICE_TOKENand notSEAMLESS_SERVICE_TOKEN,seamless addandseamless init(connecting a managed application) updateAPI_SERVICE_TOKENin place, say so, and print code that reads that name, rather than adding a second variable the app does not read.The starters in
seamless-templatesthat readSEAMLESS_SERVICE_TOKENrequire this version.
-
e781293:
seamless org members addnow says the user is already a member when the instance answers 409, instead of a bareRequest failed (409).(an organization's creator is a member automatically, so adding yourself afterorg createhits this). A 404 on add no longer claims the organization is missing when it is the user. The otherorganduserscommands name the action that failed and include the instance's own error message, for exampleCould not update member (400: Organization must keep at least one owner). -
a672c49: New projects scaffold auth server v0.19.1, admin dashboard v0.10.0 and seamless-templates v0.20.0. The auth server includes the fixes for GHSA-9jg2-jjqq-3346 and GHSA-66gq-4pwg-33jq, and the starters are on the SDKs that work with its cross-device magic link confirmation.
The generated auth server environment now holds the service token as
SEAMLESS_SERVICE_TOKEN, the auth server's own name for it since 0.19.0, instead ofAPI_SERVICE_TOKEN. The auth server's.env.examplecarries a placeholder under the new name, and the server refuses to start when the two names hold different values, so writing only the old name leftseamless init --auth=local(which clones the auth server's main branch) unable to start. Re-running against an existingauth/.envreads either name.
-
7cdcaaa:
seamless add --localnow names the Compose project inseamless/docker-compose.ymlafter the repository (name: <repo>-seamless, lowercased and reduced to the characters Compose allows). Before, every repository's stack ran as projectseamless, so two repositories on one machine shared containers, theseamless_pgdatadatabase and theseamless_auth-keyssigning keys. A re-run, with or without--force, keeps the name already in the file, including one you edited.Upgrading: a stack written by an earlier version keeps working until you re-run
seamless add --local --force. The new stack starts empty under the new name, andaddsays so when it replaces a file without a name. Stop the old containers first withdocker compose -p seamless -f seamless/docker-compose.yml down, since they hold port 5312. The old data stays in theseamless_pgdataandseamless_auth-keysvolumes, which may hold another repository's data too; remove them withdocker volume rmonce no repository uses them. -
585f79f:
seamless addfixes from writing its docs:- The backend origin (
--api-url) now defaults by ecosystem when the backend's.envhas noPORT: the port in the entry file's listen call when there is one, otherwise 3000 for Node, 8000 for FastAPI and Django (the uvicorn andrunserverdefaults), and 8080 for Go and Rust (the adapters' own examples). It was 3000 for every backend, which pointed FastAPI and Django apps, and the auth server'sAPP_ORIGINS, at the wrong port. - The "Found no backend" error,
seamless help addand the README list the folders detection searches from the same list detection uses, so they now includeapp/(andweb/,client/andfrontend/in the error). - Go, Axum and Django get working credentialed CORS for
UI_ORIGINS, like Express, Fastify and FastAPI: net/http middleware for net/http and chi, a Gin middleware, Echo's CORS middleware, a tower-httpCorsLayer(cargo add tower-http --features corsis now part of the install), and django-cors-headers (now installed with the Django adapter). - FastAPI and Django install
python-dotenv, and the printed code callsload_dotenv()before reading the environment. The hint no longer suggestsuvicorn --env-file .env, which needed python-dotenv without installing it. - React gets the bundled sign-in screens:
BrowserRouteraroundAuthProvider, thenAuthRoutesfrom@seamless-auth/react/routeswith guards for signed-in and signed-out pages, insrc/Appwhen it exists.react-router-domis installed when the app does not have it. - Removed the em dashes from
seamless <command> --helpheaders, the top-level help, theverifysummary and the admin dashboard prompt.
- The backend origin (
-
99f7c3b: Describe
JWKS_KIDcorrectly. It is thekidheader on the service tokens the adapter signs withAPI_SERVICE_TOKEN, and the auth server does not check it. The warning printed when a managed instance's key id cannot be read no longer says the adapter warns "until it matches", andseamless help addand the README call it the service token kid instead of the signing key id. -
0fc9b73: Security: new projects scaffold the starters from
seamless-templatesv0.19.1(wasv0.19.0). The Express, Fastify, and Next.js starters there depend on@seamless-auth/express^0.20.0,@seamless-auth/fastify^0.11.1, and@seamless-auth/nextjs^0.4.0, on@seamless-auth/core0.20.0. Earlier starters resolved core 0.19.1, whose cookie guard accepted the pre-auth cookie/loginissues from an email address alone as a signed-in session for that account (fixed in fells-code/seamless-auth-server#211). Projects scaffolded by an earlier CLI should move to those adapter versions.
- 162c6f4: Scaffold from seamless-templates v0.19.0, which adds Angular, Vue and SvelteKit web starters (
seamless init --angular,--vueor--svelte), each usable with any api template.
- 5f73b07:
seamless initnow writes a rootAGENTS.mdand aCLAUDE.md(See @AGENTS.md) besidedocker-compose.ymlandseamless.config.json. The guide is built from the choices init made: the directory and template each starter came from (linking its ownAGENTS.md), how the auth server runs, the admin console, the ports, how to start the stack, the owner email that becomes the first admin, and the rules for agents (use the SDK and adapter, never your own JWT, password, session cookie or login endpoint code), plus a link to https://docs.seamlessauth.com/llms.txt. - 4404708:
seamless addnow wires Go (net/http, Gin, chi, Echo), Rust (Axum) and Python (FastAPI, Django) backends on the seamless-auth-go, seamless-auth crate and seamless-auth PyPI adapters. It reads go.mod, Cargo.toml, pyproject.toml or requirements.txt, installs withgo get,cargo add,uv addorpoetry add(printing thepip installfor a plain pip project), says how the language loads.env, and prints the code for that framework, including the development-only code logging and the admin console mount. - 18ce407:
seamless addnow wires Angular, Vue and SvelteKit web apps on the@seamless-auth/angular,@seamless-auth/vueand@seamless-auth/sveltebindings, alongside React. It installs the binding (andvue-routeror@angular/routerwhen the app has no router), writesVITE_API_URLfor Vue and SvelteKit, and prints the code for each file: the provider or plugin, the bundled sign-in screens as routes, and guards for signed-in and signed-out pages. Angular, which does not read.env, gets the backend URL in the printed config. Nuxt and Svelte without SvelteKit are reported rather than wired. - e0a7ff2: Add
seamless add, which adds Seamless Auth to an existing Express or Fastify backend and React web app. It connects a local auth server (written toseamless/) or a managed application, writes the backend's.envand the web app's API URL while keeping existing values, installs the adapter and SDK with the project's package manager, and prints the code to add for TypeScript or JavaScript. It never edits source files. Commands now run without a shell outside Windows, which also stops Node 24's DEP0190 warning. - 8eb2380:
seamless verifynow drives an Angular app through the same browser specs as the React template. Theangularandangular-devlayers run the reference app inverify/angular-appon@seamless-auth/angular(production build, andng servewith--dev), or an Angular web template once the registry has one.SEAMLESS_ANGULAR_DIRpoints the run at another Angular app, and--localinstalls the locally built@seamless-auth/clientand@seamless-auth/angular. - 2d7955a:
seamless verifynow drives a SvelteKit app through the same browser specs as the React template. Thesvelteandsvelte-devlayers run the reference app inverify/svelte-appon@seamless-auth/svelte(SvelteKit 3 as a single-page app, as a production build and onvite devwith--dev), or a Svelte web template once the registry has one.SEAMLESS_SVELTE_DIRpoints the run at another Svelte app, and--localinstalls the locally built@seamless-auth/clientand@seamless-auth/svelte. - b5fdefb:
seamless verifynow drives a Vue app through the same browser specs as the React template. Thevueandvue-devlayers run the reference app inverify/vue-appon@seamless-auth/vue(a production build, andvitewith--dev), or a Vue web template once the registry has one.SEAMLESS_VUE_DIRpoints the run at another Vue app, and--localinstalls the locally built@seamless-auth/clientand@seamless-auth/vue.
- ac648ad: Scaffold the auth server at v0.18.0, whose OTP and magic-link send routes are POST, as the Go, Rust and Python adapters and the current
@seamless-auth/clientsend them. v0.18.0 still serves the deprecated GET routes, so projects on the Express and Fastify starters keep working. The next steps afterseamless initnow give each api starter its own command (go run .,cargo run,uv run uvicorn ...) instead ofnpm install && npm run dev. - b52d886: New projects scaffold the starters from
seamless-templatesv0.18.0(wasv0.17.0). Every starter now ships anAGENTS.mdand aCLAUDE.md, so the root guideseamless initwrites links each starter'sAGENTS.mdinstead of its README, and the Go (Gin), Rust (Axum) and Python (FastAPI) api starters are available asbeta. - 59b9ef7: Spell the product name as "Seamless Auth" in the
initauth server prompt, and describe the package as the CLI for Seamless Auth, open source passwordless auth. - fca70ae:
seamless verifynow waits for each browser service's healthcheck before running its specs. Thevue-devandsvelte-devservices compile their app at startup and report healthy only once a compiled module and the prebundled SDK are served, so a cold Vite dev server no longer makes the first browser spec time out on a slow runner. The production web services' healthchecks now probe 127.0.0.1, since nginx listens on IPv4 only and they never passed onlocalhost. A service that never becomes healthy fails its own layer, with its recent logs, and the run continues.
-
64a9907: Add an adapter conformance suite that any server adapter can be held to (#246).
seamless verify --adapter-url=<url>starts Postgres and the auth API and runs the adapter specs against a reference app you started at that URL, for adapters this repository does not build (Go, Rust, Python).verify/CONFORMANCE.mddocuments what the reference app must serve.- The adapter specs, run against the Express and Fastify reference apps on every
seamless verify, now cover cookie attributes and clearing, refresh rotation, concurrent refresh and refresh-token reuse, bearer transport, the adapter's own guard on an app route, error passthrough, the absence of tokens in cookie-transport bodies, and forwarding of the client's address and user agent to the auth API. - The reference apps trust one proxy hop, so each virtual user gets its own client address and rate-limit bucket on the auth API, and pin the current adapter releases (
@seamless-auth/express0.19,@seamless-auth/fastify0.10).
-
b051bff:
seamless config setandconfig applyacceptprompt_passkey_enrollmentandphishing_resistant_only, which need auth server v0.17.0 or later.prompt_passkey_enrollmentmakes email code, phone code and magic link sign-ins ask a user with no passkey to enroll one.phishing_resistant_onlylimits sign-in to passkeys. An older instance rejects a patch that includes either key.
- 4385906:
seamless usersandseamless orgnow read admin responses with the shared@seamless-auth/typesschemas.users credentialsnames each passkey by the name its owner gave it (friendlyName), then by the device the auth server recorded, where it used to print "credential" for every one because it looked for fields the API never sends. A response missing a field the CLI needs now fails with a message naming the field, where it used to print placeholders such as(no id). Fields an instance sends that this CLI version does not know about are kept, so--jsonoutput still carries them. - f13e646:
seamless login --localnow works for a phone number. The auth server sends an SMS code as a number in the delivery block, and the CLI only accepted a string, so every local phone login failed saying the instance had not returned the code. Login responses are now read with the shared@seamless-auth/typesschemas, and a login method the instance offers that this CLI version does not know no longer gets in the way of an OTP login.seamless profile add --identifier-typeis validated with the same schema. - d3708b2:
seamless sessionsnow reads the session list with the shared@seamless-auth/typesschema. A session the instance sends malformed fails the command with a message naming the field, where it used to be dropped from the list without a word.sessions list --jsonnow carries every field the API sends, includingdeviceName,ipAddressanduserAgentasnullwhen the API has none, where it used to leave them out. - 062630f:
seamless config setandconfig applynow acceptflow_rate_limits, which the auth server has read from system config since v0.14.0;config applyused to drop it from a config file without a word.config applynow tells read-only keys from unknown ones, andconfig diffflags a key no config has, which is usually a typo. The writable keys are checked against the shared@seamless-auth/typespatch schema.config getandoauth-providers liststill show exactly what the instance stores: they check the response's shape but never fill in schema defaults or reject a stored value. - 4569033:
seamless initnow downloads the starter templates by the commit their release tag pointed at when the CLI was published, rather than by the tag, so a moved tag cannot change what a scaffold extracts. Template extraction also fails with a clear error on an archive entry that would write outside the project, as the--admin=sourcedashboard download already did.SEAMLESS_TEMPLATES_REFandSEAMLESS_TEMPLATES_DIRstill override the source as before.
-
25a267b:
seamless initcan now include the admin console with a full-stack template such as the Next.js starter.--admin=api(the default, and the recommended prompt option) has the web app serve the dashboard athttp://localhost:5173/console. The auth server getsSERVE_ADMIN_DASHBOARD=true, the app's.envgetsSERVE_ADMIN_CONSOLE=truefrom the starter's{{serveAdminConsole}}, andORIGINSstays the web origin alone, since the console shares it.seamless.config.json,seamless check, and the closing summary point at that URL.--admin=nonestill leaves the console out.--admin=imageand--admin=sourceare refused with an explanation: a dashboard container on:5174would call the app's/authcross-origin, which the Next.js route handler does not allow.- A starter from a templates release without the
/consoleroute is scaffolded without the console, with a warning, rather than with an auth server serving a dashboard nothing proxies. An explicit--admin=apifor such a release is an error. seamless verifychecks the Next.js starter's console:/consoleserves the dashboard and its assets from the app's origin, and an admin signed in on the app loads the console's admin API calls through it.
- 63ac8f9: Map the standalone admin console (
--admin=imageand--admin=source) to container port 8080, where the dashboard's unprivileged nginx listens. The generated compose published5174:80, so nothing answered onhttp://localhost:5174. - da190d7:
seamless init --mobilewith no value now selects the Expo template, whose registry alias ismobile. A trailing--mobileused to be dropped silently, andseamless init --mobile my-apptookmy-appas the template id and lost the project name; it now scaffoldsmy-appwith Expo.--mobile=expoand--mobile expoare unchanged, and a bare--webor--api(neither is an alias) now says it needs a value instead of being ignored. - bb94d5b: New projects scaffold the auth server at
v0.16.0(wasv0.13.1) and the admin dashboard atv0.9.1(wasv0.7.0). The auth server now links or creates an account through OAuth only when the provider asserts a verified email, and adds user import and OpenID Connect provider support. The dashboard's new Passkey enrollment page needs/admin/enrollmentroutes that ship in the next auth server release, so it reports an error againstv0.16.0.adm-zip, which unpacks the downloaded templates and dashboard, moves to0.6.1to clear its security advisories. - 0f49e6b: New projects scaffold the auth server at
v0.17.0(wasv0.16.0) and the starters fromseamless-templatesv0.17.0(wasv0.16.0). The auth server now serves the passkey enrollment, authentication coverage, store review account and audit integrity routes the bundled admin dashboardv0.9.1calls, and the Express, Fastify and Next.js starters move to adapters (@seamless-auth/express^0.19.1,@seamless-auth/fastify^0.10.1,@seamless-auth/nextjs^0.3.1) that pass them through, so the console's Enrollment, Coverage and Audit Trail pages work on a fresh local stack. The starters also readAUTH_SERVER_ISSUER, so a starter run on the host signs in against the Docker stack, and the Next.js starter serves the console at/console.
-
99fb8b2: Let a starter run on the host sign in against the local Docker stack, and keep the auth server's dev signing keys across a container recreate.
- Templates can now use a
{{authServerIssuer}}placeholder. On a local stack it resolves tohttp://auth:5312, the issuer the auth container signs with, so a starter run withnpm run dev(which reaches the server athttp://localhost:5312) can verify signed auth responses once it setsAUTH_SERVER_ISSUERfrom it. On a managed instance it resolves to the instance URL. The generated compose file also setsAUTH_SERVER_ISSUERon the app container. - The Docker auth service mounts a named
auth-keysvolume at/app/keys, sodocker compose upafter a config change no longer mints a new dev key pair under the same key id and breaks every adapter's cached key. - The generated
docker-compose.ymlno longer has runs of blank lines between services. --apiwith a non-API template now says it "expects an API template".
- Templates can now use a
-
061f749:
seamless verifycovers what a developer sees on their firstnpm run dev, and the Next.js starter.--devruns every browser template a second time on its development server, where React Strict Mode runs every effect twice. The production pass alone could not see an effect that is not idempotent, such as the magic-link screen spending its single-use link twice (fells-code/seamless-auth-js#161). The conformance workflow passes it on every pull request (#225).- Full-stack templates are no longer skipped. The Next.js starter is built from its own Dockerfile's
runtimetarget (and itsdevtarget under--dev) and driven by specs written for its own screens: passkey enrollment and sign-in, an emailed code, a magic link, the server-rendered session page, and its role-gated route (#222). - The React magic-link spec fails when the link is verified more than once, and the JWKS spec no longer expects the signing key's kid to be
dev-main. - The browser resolves
localhostto 127.0.0.1, so a server of your own on[::1]:5173no longer answers in place of the stack, andSEAMLESS_API_URL,SEAMLESS_ADAPTER_URL, andSEAMLESS_FASTIFY_ADAPTER_URLare honored when set.
- 01d9b3d: Support Node 22 and newer. The
enginesfield now requires>=22instead of>=24 <25, and CI runs on Node 22, 24, and the latest release (fells-code/seamless-auth-api#339). - 9a21318: Depend on
@seamless-auth/types^0.28.0. The new release adds time range schemas for the dashboard metrics and security anomalies; its new response fields are optional, so nothing the CLI reads changes.
-
306e23f:
seamless initsupports full-stack templates (kind: "fullstack"in the template registry), starting with the Next.js App Router starter:seamless init --nextjs,--web=nextjs, or pick it at the web prompt. A full-stack template is the web app and its own backend, so it fills the web layer and the project gets noapi/. The backend question is skipped, and--apibeside it is refused before anything is created.- The local stack runs it as the
webservice: the template's dev container on port 5173, with the source mounted for reload and the auth wiring the api service would have had. One-time codes and sign-in links print indocker compose logs web. - The admin console is not available for full-stack templates yet, since the Next.js adapter cannot host it. Only
--admin=noneapplies, and other values are refused. seamless.config.jsonrecords the web service askind: "fullstack"with noapientry, andseamless checkchecks the web app's/healthinstead of the API.seamless verifysays it is skipping full-stack templates rather than dropping them silently.
- The local stack runs it as the
-
6c226be: The Microsoft preset now signs in with OpenID Connect: it sets
issuerandjwksUrifor the tenant so the auth server verifies the ID token, reads email verification from thexms_edovoptional claim (add it to the app registration's token configuration), and links users imported withseamless migrateunder sourceentra-idby theiroid. The tenant prompt now takes the directory (tenant) id as a GUID only, because ID tokens name the tenant that way. Needs an auth server that supports theissuer,jwksUriandexternalIdSourceprovider fields. -
4711f7d: The Microsoft OAuth preset is now scoped to one tenant.
init --oauthasks for the directory (tenant) id, refusescommon,organizationsandconsumers, and writes tenant-specific endpoints. Without a tenant, Microsoft is scaffolded disabled with a placeholder, like a missing client id. Projects scaffolded earlier still point atcommon; replace it with your tenant id in the auth server'sOAUTH_PROVIDERS. -
0928a39: Add
seamless migrate csv <file>to import users from a CSV export into an instance. It is a dry run unless--applyis passed, finds columns by header (or a--mapfile kept beside the export), validates each row locally with the shared@seamless-auth/typesschema, sends rows in batches of 200, and writes a CSV and JSON report of every row's outcome. Exits 1 when any row is rejected or invalid. Needs an auth server withPOST /admin/users/import. -
b651ff2: Relicense from AGPL-3.0-only to the Apache License, Version 2.0 (fells-code/seamless-auth-api#335). The
LICENSEfile, thelicensefield and the license header in source files now say Apache-2.0, and the AGPL summary inLICENSE.mdis removed. -
37c8c8a:
seamless verifycovers the OAuth migration cutover path (fells-code/seamless-auth-api#337) at the API layer. The spec:- Imports a user from a directory, then signs them in through that directory's OIDC provider. The link is made on the ID token's
oid, not the email. - Checks that the sign-in answers
nextStep: 'enroll_passkey', enrolls a passkey, and checks the prompt stops. - Retires the provider for the user's organization. It checks that the user's sessions are revoked and that the next sign-in is refused with
oauth_provider_retired. - Restores the provider and checks the user can sign in again.
The mock OIDC provider now issues signed ID tokens, serves
/jwks, and can sign in as a named user. The existingmockprovider is unaffected.Needs an auth API that includes fells-code/seamless-auth-api#348 (session revocation on retirement).
- Imports a user from a directory, then signs them in through that directory's OIDC provider. The link is made on the ID token's
- 80991a5: Correct the advice printed after connecting a project to a managed application. It told you to set the auth server URL on your frontend, which cannot sign in; the frontend points at your backend, which runs the adapter. It now also names
JWKS_KIDamong the backend values. - 65e9668: Depend on
@seamless-auth/types^0.26.0(was^0.24.0), so the CLI's wire types track the current auth API contract.
-
2bcfa33: Scaffold from seamless-templates
v0.15.0, the release that carries the Expo starter.0.16.0taughtinitthemobiletemplate kind but left the registry pin onv0.14.0, which has no such kind, so the question was never asked and--mobile=expowas refused as an unknown option. With the pin onv0.15.0the registry lists the starter asexpo(aliasmobile, beta):initoffers it after the backend,--mobilescaffolds it atmobile/withEXPO_PUBLIC_API_URLfilled from the manifest, and the success output says how to start it. The starter is a complete Expo Router app on@seamless-auth/react-native0.1.0(email code, sign-in link, passkeys, a protected call to the companion API), and itstools/associations/generator writes the files native passkeys need.--yesstill scaffolds without it.The same release moves the API starters to
@seamless-auth/express0.16.0and@seamless-auth/fastify0.7.0and passesauthServerUrlandaudienceintorequireAuth, so a scaffolded API accepts the auth API's access token from the native app alongside browser cookies, and its/authmount answers a bearer client with tokens in the body. The conformance harness's adapters take the same^0.16.0and^0.7.0. The starters also keep their SSL options whenDATABASE_URLcarriessslmode(templates0.15.0's patch note). The web starters stay on@seamless-auth/react0.12.0, and the auth API and admin dashboard pins do not move.
-
b9529c6:
seamless initlearns themobiletemplate kind.seamless-templatesgains a third kind next towebandapi, starting with an Expo (React Native) starter. The CLI treated an unknown kind as if it were not there: the alias resolver skipped it, the prompts never offered it, andverifyfiltered it out without a word.A mobile starter is optional.
initasks "Mobile app" after the backend, defaulting to none;--mobile=<id|alias>(or the bare alias,--mobile/--expo) includes one;--yesscaffolds without, since a native app brings prerequisites (an associated domain for passkeys) an unattended run should not opt into; and a registry that predates the kind offers nothing, so there is no question to ask. A chosen starter lands atmobile/with its.envfilled from the manifest, is recorded asservices.mobileinseamless.config.json, is checked byseamless checkonly when recorded, and is called out in the success output with how to start it and the Android emulator host.verifyannounces the mobile templates it cannot drive instead of filtering them silently. Template copying skips.expo,ios, andandroid.The templates registry pin moves to the release that carries the starter in a follow-up.
-
4bbf7ac:
seamless verify --localpacks@seamless-auth/clientalongside@seamless-auth/reactwhen the React SDK checkout is an npm workspace.The client SDK repo is becoming a workspace that publishes a framework-agnostic
@seamless-auth/clientnext to@seamless-auth/react, and the react tarball depends on the client one. Packing only the root of such a checkout would produce the private root package, and installing the react tarball alone would go to the registry for a client version that is not published yet. The harness now readsworkspacesfrom the checkout'spackage.json, packs both packages when it finds one, and the react image installs every tarball in onenpm installso the dependency resolves from the sibling file. A checkout that predates the workspace is packed as before. The version line reported for@seamless-auth/reactreads the react package's own manifest in a workspace.
-
c99c759: Move the scaffold onto auth API
v0.13.1, admin dashboardv0.7.0, and seamless-templatesv0.14.0.v0.13.0instruments the auth path. Everyauth_eventsrow now carries a deployment id, a device class folded from the user agent, the subject's mail provider (a provider name, never the domain), an owner flag, and the attempt the row belongs to (the ephemeral token's newjti).GET /internal/metrics/funnelreports time to registration, time to login and passkey adoption, andGET /internal/metrics/sign-insreports sign-in outcomes per method, device class, mail provider and owner flag, with where attempts stop. The same release ships dashboardv0.7.0inside the API image, which is what that dashboard release needs: its Overview gains a Passwordless Funnel section and a Sign-in Outcomes section that read those two routes.v0.13.1is the same server on a release image that applies Debian security updates at build time, sincev0.13.0never published an image (its scan failed on a base-imagepcre2finding).The admin dashboard pin moves with it, so the standalone console (
--admin=imageand--admin=source) serves the same release the API image serves at/console. Against an older API the two new Overview sections report themselves unavailable in place and the rest of the screen is unaffected.Templates
v0.14.0carries@seamless-auth/express0.15.0and@seamless-auth/fastify0.6.0in the API starters, and the conformance harness's adapters take the same^0.15.0and^0.6.0. Those adapters forward the browser's user agent to the API asx-seamless-client-user-agentbeside the client address they already forward, and pass the sign-ins route through. The adapter is the only client the API sees, so a project on the older adapters has every audit row recorded as the adapter's own user agent and its breakdown by device readsunknown; the other dimensions do not depend on it. Nothing else in the starters moves: the manifest contract and the registry are byte-identical tov0.13.0, and the React starters stay on@seamless-auth/react0.12.0, since the token claim the API added is one the client never reads.
-
af41ba4: Move the scaffold onto auth API
v0.12.0, admin dashboardv0.6.0, and seamless-templatesv0.13.0.v0.11.0puts passkey enrollment behind an access session. It is a breaking change to the wire contract:/webAuthn/register/startand/webAuthn/register/finishused to accept the ephemeral token the API mints from an email address alone, so anyone who knew an address could enroll a credential against that account and sign in as its owner. Both routes read the access session now, and enrollment no longer issues a session of its own, answering200with the credential instead. The same release declares and validates the window onGET /admin/users, and adds organization deletion along with a paged, searchable admin organization list.v0.12.0ships dashboardv0.6.0inside the API image, which is what that dashboard release needs: it pages and searches organizations on the server, gains a remove action, takes a date range on Overview and Security, and grows an Authenticator Policy section.The admin dashboard pin moves with it, so the standalone console (
--admin=imageand--admin=source) serves the same release the API image serves at/console.The enrollment change has no safe release order, so every side of it moves here at once. Templates
v0.13.0carries@seamless-auth/react0.12.0,@seamless-auth/express0.14.0and@seamless-auth/fastify0.5.0in the starters, and the conformance harness's adapters take the same^0.14.0and^0.5.0. The adapters forward the access session the API now requires; an older one sends what this API refuses, so a scaffold pinned to either half alone would answer401at enrollment. No shipped flow loses a step, because registration proves an address with an email OTP and verifying it issues the session before the passkey screen appears.The adapters also proxy
DELETE /admin/organizations/:organizationId, and forward the query string onGET /admin/usersandGET /internal/auth-events/login-stats, which both dropped it, so the dashboard's user search and its login statistics range now reach the API as sent. -
206e3cd: Move the scaffold onto the current Seamless ecosystem: auth API
v0.10.0, admin dashboardv0.5.0, and seamless-templatesv0.12.0.Three releases of auth API work land together.
v0.8.0gives WebAuthn challenges their own store with a five-minute expiry and one-time use, so a registration and a login can be outstanding at once instead of clobbering each other, and it addsAUTHENTICATOR_POLICY, which decides attachment, user verification, attestation, and whether a synced passkey may enrol.v0.9.0puts the lockout policy and the per-IP and per-identity limiters on TOTP step-up, which had none of the three, and stops running refresh tokens through bcrypt.v0.10.0drops thesessions.refreshTokenHashcolumn the previous release stopped writing.A scaffold's compose file is built from the pinned release's
.env.example, so a new project picks upAUTHENTICATOR_POLICYalong withSESSION_IDLE_TTL,MAX_CONCURRENT_SESSIONSand a commentedTRUST_PROXY, andREFRESH_TOKEN_TTLmoves from1hto1d.The admin dashboard bump keeps the standalone console (
--admin=imageand--admin=source) in step with the one the API image now serves at/console, sincev0.10.0embeds dashboardv0.5.0itself. That release names the acting administrator separately from the subject in the events table, and collects identity proofing before preparing a device replacement, which is what the API already records and requires.Templates
v0.12.0moves the starters onto@seamless-auth/react0.11.0,@seamless-auth/express0.13.0and@seamless-auth/fastify0.4.0. All three are 0.x minors, so the caret ranges the starters carried could never have resolved to them and a scaffolded project stayed on the older versions however long ago they were pinned. It also carries per-application auth cookie names read fromAUTH_COOKIE_PREFIX, so two Seamless apps on one host stop overwriting each other's session. The manifest contract is unchanged, so nothing ininitmoves with it.The conformance harness's adapters follow the same SDKs the starters now install:
@seamless-auth/express^0.13.0and@seamless-auth/fastify^0.4.0. Both stop repeating the access and refresh tokens in the body of the response that sets them ashttpOnlycookies, and both forward a magic link'sredirectUrito the API. -
ec85b59: Match the list windows the auth API now enforces, and let
org listpage.The API validates the window on its admin list routes:
limitis 1 to 100 andoffsetis 0 or more. The CLI checked both flags against one range with a floor of zero, which was right for--offsetand wrong for--limit, sousers list --limit 0andusers list --limit 500were sent and came back as a 400 naming neither the flag nor the bound. Each flag is checked against its own range now, and the message says which one was wrong and what it accepts.--limit 0is therefore an error rather than a request for nothing. Asking the server for zero rows and reporting "No users." said there were none when the CLI had not looked, which is worse than saying the flag is out of range.org listgains--limit,--offsetand--search. It sent no window at all, so once the API started defaulting to 50 it printed the first 50 organizations and then a count of every organization, claiming rows it had not shown. It now reports where the page sits, the wayusers listalready did, and--searchmatches the name and slug server-side.
-
63ff4ff: Add
seamless check --strict, which exits non-zero when a check fails.checkis what you would reach for in a health-check script or a CI gate, and it always exited 0: an empty directory, a stack that is down, and a fully healthy project were indistinguishable to anything reading the exit status.--strictexits 1 if any check failed, reporting how many. Without it the exit status is unchanged, because this output has been parsed by scripts since before the flag existed.Every check still runs either way. A gate that stops at the first problem hides the rest, and the whole picture is the reason to run
checkat all.
-
79aa298: Clean
distbefore building, so a stale artifact cannot be packed.buildwas a baretsc, which overwrites but never removes. A file deleted or renamed insrcleft its old output behind, andprepublishOnlyruns the same script, so a local publish could ship something no longer in the source tree. Thecleanscript already existed and is now wired in. -
cfed9e6: Correct the documentation that had drifted from the rest of the ecosystem.
seamless checkandseamless verifynow 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 itmigrate:up, and the Docker path runs it for you at container start. init --profilewas 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.mdthe CLI does not write, and omittedseamless.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-serverandseamless-auth-react. config sethelp was missingsession_idle_ttlfrom the string-typed keys.
-
d039e18: Add
--jsontoseamless whoamiandseamless sessions list.config get/roles,users list/credentials, andorg list/get/members listall had it; these two, both natural scripting targets, did not, so reading an identity or a session id from a script meant parsing formatted output.Both print machine-readable output and nothing else: an empty session list is
[]rather than "No active sessions.", andwhoami --jsonreports a missingsuboremailasnullinstead of the(unknown)the table shows. -
723d581: Point a managed scaffold at the instance URL the portal computes.
initcomposed the scaffolded backend'sAUTH_SERVER_URLfromapplication.domain, a stored column the portal superseded. It goes stale when a trial is upgraded and its tenant moves zones, and mvp and business instances are served atdomain/<infraId>rather than atdomain. Either way the scaffold pointed at a URL that does not answer, and the failure surfaced as an SDK error in the developer's app rather than as anything the CLI said.resolveAppInstanceUrlalready existed for exactly this, andappsand the application picker already used it; the two scaffold paths did not. They do now.The connectable-application filter asked for
domaintoo, so an application the portal computed aninstanceUrlfor but whosedomaincolumn was never populated was filtered out and never offered. It now asks the same question the scaffold does. -
e1d3687: Write the signing key id a managed instance actually publishes.
A managed scaffold hardcoded
JWKS_KID=dev-main. Managed instances pin their kid per tier (trialkey1for trials,paidkey1for paid), so the value was never the instance's own. Nothing verifies against it, adapters resolve the key from the token header through the remote JWKS, but they do warn on boot while it is the dev default, so every managed scaffold produced an app that reported itself misconfigured.initnow reads the kid from the instance's/.well-known/jwks.json. If the instance cannot be reached it keeps the old default, says so, and explains that nothing breaks except the warning. -
976081b: Say what could not be reached when a scaffold's network read fails.
seamless initmakes three remote reads: the template registry, the templates archive, and the auth server's.env.example. Each of them handles a non-ok HTTP response with a message naming the status and the URL, but a connection-level failure (offline, DNS, TLS, no route) rejects with a bareTypeError: fetch failedthat propagated untouched to the top-level handler. The whole output was "Error: fetch failed", which named neither the host nor which of the three reads had failed.The three call sites now go through a shared helper that turns that rejection into a message naming the URL, what the CLI wanted from it, and the network as the likely cause, in the style
loginalready uses for an unreachable instance. Non-ok responses keep the messages they had, and the original error is preserved as the thrown error'scause. -
4418970: Recognize the addresses a local dev instance actually answers on.
isLocalInstanceUrlaccepted onlylocalhost,127.0.0.1,::1and.localhostsubdomains. It gates two things: whether plaintexthttpis allowed for an instance URL, and whether--localOTP delivery is permitted. A dev instance is commonly reached at none of those, a container bound to0.0.0.0, a LAN address from a phone on the same network, or an mDNS.localname, and each was rejected as if it were production, forcinghttpsonto a box with no certificate.Now also treated as local: the whole
127.0.0.0/8loopback range,0.0.0.0and::, the private IPv4 ranges (10/8,172.16/12,192.168/16), link-local (169.254/16andfe80::/10), IPv6 unique-local (fc00::/7), and.localnames. Ranges are matched by octet rather than by prefix, so172.15,172.32and1.10.0.1stay public. -
f4ccb30: Stop writing
AUTH_MODEinto scaffolded projects.The scaffold set
AUTH_MODE=serverin 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 onlyAPI_URL), and not the web or api templates. It was config that looked meaningful and meant nothing. -
8e5c0f7: Remove code nothing calls.
src/utils/writeEnv.ts, an unused duplicate ofcore/env.ts'swriteEnvthat still emitted unquoted values, the bug fixed in the real one.buildJWKSConfigin the docker generator, which had no callers and was the only user ofcore/jwks.ts, so that module went with it.generateKidincore/secrets.ts.generateSecretbeside it stays; it is widely used.setupDockerAuthin the auth generator, unreachable becausegenerateAuthServeris only ever called with"local". It also wrote a compose file mountingpgdataat/var/lib/postgresql/data, which the pinnedpostgres:18ignores, so it had gone stale as well as unreachable.
With the unreachable branch gone,
generateAuthServerno longer needs a mode, and itscontext: anyandmode: "local" | "docker" | Symbolparameters become a plainroot: string. -
74ae06c: Stop printing
Error: undefinedwhen something other than anErroris thrown.The top-level handler in
index.tsand the catch blocks inwhoamiandsessionsall read.messageoff the thrown value, whichthrowdoes not guarantee exists. A rejected promise carrying a string, a parsed response body, orundefinedprinted nothing useful, naming neither the failure nor the fact that something unexpected came back.A new
errorMessagerenders any thrown value: anError's message (or its name when the message is empty), a thrown string as it is, amessagefield off a thrown object, and otherwise the object's shape or a labelled primitive. A thrown object is scrubbed first, since it arrives from a rejected request as often as from our own code.
-
cc54666: Make
--admin=sourceproduce a stack that runs."Separate container, clone repo for modification" wrote
build: ./admininto the compose file and recordedpath: "./admin"inseamless.config.json, but nothing ever createdadmin/.docker compose upfailed on a missing build context, and the success output said nothing about it, so a first-class menu option scaffolded a project that could not start.initnow unpacks the admin dashboard intoadmin/for that mode, from the same tag the published image is built from, so both admin modes scaffold the same dashboard. It arrives as plain files rather than a git clone, which is what a directory destined for the developer's own repository wants, and needs no git binary. Override the ref withSEAMLESS_ADMIN_DASHBOARD_REF, or scaffold from a local checkout withSEAMLESS_ADMIN_DASHBOARD_DIR.The fetch runs before the compose file is written, so a failed download stops the scaffold instead of leaving a project that cannot come up, and the success output now points at
admin/.Extraction is constrained to the project directory: an archive entry whose resolved path would escape it fails the scaffold rather than being written or silently skipped. Not reachable while the archive is our own repository over https, but this is a new extraction site and it should not add to what #135 already tracks for the templates source.
-
464f0c0: Stop
config setfrom changing the type of a string value, and check the key first.parseValueranJSON.parseon every value, soconfig set app_name 123sent the number123andconfig set rpid truesent the booleantruefor keys the instance types as strings.setalso skipped theWRITABLE_KEYSfilter thatapplyapplies, so a key the instance's strict patch schema rejects was sent anyway and came back as an opaque failure.String-typed keys (
app_name,rpid,access_token_ttl,session_idle_ttl,refresh_token_ttl) now take their value verbatim; every other key parses as JSON with the existing fallback to a string. An unwritable key is refused before the request, naming the keys that are writable.That guard is only correct if the list is, and it had drifted:
authenticator_policy,session_idle_ttl,max_concurrent_sessions, andmagic_link_redirect_urisare all accepted by the instance but were missing fromWRITABLE_KEYS, soconfig applyhad been silently dropping them. They are now included. -
40c533e: Remove the
useDockerflag that could only ever be true.With the Docker confirm gone,
runProjectSetupPromptsstill returneduseDocker: trueunconditionally andinitstill gated the Docker Compose generation on it. The condition had no false case, so the guard read like a real choice while describing a scaffold the CLI never produced.The field is off the answers object and the compose file is now generated outright on the local scaffold path. Nothing about what
initgenerates changes. -
5450c34: Stop asking a question whose answer was never used.
Choosing to run the auth server from local source prompted "Auth server still requires Docker for full stack. Enable Docker?", then discarded the answer:
useDockerwas returned astrueeither way, and declining only printed "Enabling automatically". The full stack needs Docker regardless, so the prompt cost a keystroke to tell the developer their answer did not count.The prompt is gone, and with it the entire non-docker branch of the success output, which described a workflow (bring your own PostgreSQL,
npm run devper service) that no run could ever reach. Nothing about whatinitgenerates changes. -
cccc0ed: Stop reading every failed code verification as a wrong code.
completeLoginhandled only200and429specially, so a500, a423lockout, a403for a login method disabled mid-flow, and a400all fell through to "That code was not accepted", spent an attempt, and ended three attempts later on a generic message with the real reason nowhere in sight.Only a
401is retried now, because that is how the instance answers a genuinely wrong code. Everything else stops immediately and reports what the instance said, with a lockout formatted the same way/loginalready formats it.Separately, when the five minute login window lapses while a code is being typed, the CLI drops that code and requests a new one. It now says so rather than appearing to ignore what was entered.
-
9e13d09: Stop the login prompt from deciding what a valid one-time code looks like.
The email branch required
/^[A-Za-z]{6}$/and the phone branch/^\d{4,8}$/, so a code outside those shapes was refused locally, before the instance ever saw it, with no way to override. The email code was also force-uppercased on the way out. Both encode a detail that belongs to the instance: the shape it issues can change, and it normalizes email OTP case itself, so the uppercasing was redundant at best and corrupting for a case-sensitive code.The prompt now refuses only an empty answer and sends what was typed, trimmed. The placeholder still shows today's shape as a hint.
-
da160ca: Redact secrets in the server validation details the CLI prints.
patchSystemConfigand the OAuth provider mutations splice a rejected request'sdetailspayload into the error they throw, andrunConfigprints it to stderr. Validation errors commonly quote the offending value back, and forseamless config oauth-providers add|updatethat request body carries aclientSecret, so a rejected provider config could print a client secret, in CI, into a build log. Both sites now scrub before stringifying.The scrubber itself grew to match. It covers
secret,clientSecret,client_secret,password,apiKey,api_key,otp, andcodealongside the token keys it already knew, and it now masks by shape as well as by key: a JWT or aBearer ...credential quoted inside a message string is caught wherever it appears, including in a bare string body, which key matching alone could never reach.redactTokenandscrubTokensmoved fromcore/keychain.tsto a newcore/redact.ts. They are a logging concern with no keychain dependency.--jsonoutput is deliberately left unscrubbed: it is a machine-readable contract, and rewriting values there would break scripts and hide data the caller asked for. -
1f9e25f: Make transparent token refresh survive rotation, concurrency, and a persistent 401.
Four problems, all sharpened by the same instance behaviour:
/refreshrotates the refresh token on every call, and a second use of a spent one is read as theft and revokes the whole session chain.- A refresh that renewed only the access token wiped the session.
tokensFromAuthResponserequired both tokens, which is right for a login but not for a refresh: the response schema marks both optional. A newtokensFromRefreshResponsekeeps the current refresh token when the instance does not send a new one. - Concurrent 401s fired parallel refreshes. With reuse detection that does not merely race, it logs the developer out. Refreshes are now single-flight, and a request whose token was already replaced while it was in flight simply retries instead of refreshing again.
- A headless run broke itself after the first rotation.
SEAMLESS_REFRESH_TOKENis read fresh on every run, so a rotated token written to the keychain was never read back, and the next run re-sent the spent one, revoking the session. The CLI no longer writes it, and a rejected environment token now explains rotation and names the command that issues a new one. - A 401 that survived a refresh was returned verbatim, leaving commands to report an
opaque failure rather than saying the session is gone. It now raises
ReauthRequired.
- A refresh that renewed only the access token wiped the session.
-
eab9c29: Send
--limitand--offsetto the server onseamless users list.Both flags were parsed and then dropped:
listUserscalledGET /admin/userswith no query, and the command sliced the single page the server returned (its own default of 50) client-side. An--offsetpast that page printed "No users." while the summary line reported a largertotal, and--jsonignored paging entirely, so a script could never walk past the first 50 users.The flags now travel as query params, which the API already honours, and
--jsonreturns the same page the table does. A non-numeric or negative--limit/--offsetis rejected instead of being coerced toNaNand silently returning nothing.
-
2acea63: Stop reading a
401from/loginas "no such account", and stop claiming an account exists when the instance will not say.POST /loginno longer answers401. An identifier with no usable account, which used to cover unknown, unverified, and no-permitted-method, now gets200and a decoy pre-auth token so the response cannot be used to test whether an account exists. Two branches incompleteLoginread that401and are now unreachable: the "not verified yet" message and "No account was found for X". Both are removed, because there is no longer an answer for them to read.The messages that surrounded them were making a claim the CLI can no longer support. "A code was sent to X" is now "If an account exists for X, a code is on its way", and "This account cannot use email otp login" is now phrased as what the instance offered, since that method list comes back for an unknown identifier too.
An unknown identifier therefore runs the ordinary flow and fails at the code step. That is the intended behaviour and not something the CLI can shortcut, so the final error now says so: it names the identifier and points at registering, instead of "Could not verify a code" with no explanation of the likeliest reason.
423is now reported on its own terms, with how long to wait when the instance says. Previously it fell through to "Login request failed (423)". It is also the one answer left that does imply an account exists, which is a deliberate and documented tradeoff on the API side.Adds
verify/harness/api/loginEnumeration.spec.tsto the conformance matrix, pinning the guarantee against a running instance: an unknown identifier gets the same status and the same fields as a registered one, the same identifier keeps the same subject and the same method list across attempts, the OTP send reports success and sends nothing, the verify fails the way a wrong code fails, and a credential id that cannot exist is refused identically for both.The
loginMethodsassertion is the one worth reading. It is deliberately not equality between one account and one decoy: that list is filtered by what an account can do, and a decoy's capabilities are derived per identifier, so any two can legitimately differ. What must hold is that a real account's list is one a decoy can also produce, which is what makes a narrow list stop being proof of existence. The spec asserts that over a sample. -
c3c9d57: Give
seamless verifya build cache in CI, and stop paying for provenance attestations nothing reads.The conformance job spends nearly all of its time building Docker images, and none of it running tests. On the run that prompted this,
Run seamless verifywas 28m02s and the tests inside it took 23.7 seconds. Every image is rebuilt from scratch on every run, because CI has no Docker layer cache, and the host-side npm caches do nothing for a build that happens inside Docker.seamless verifynow layers an extra compose file over the base one whenSEAMLESS_VERIFY_COMPOSE_OVERRIDEis set. CI points it atverify/docker-compose.cache.yml, which attaches atype=ghabuild cache scoped per service, and per web template for the react image since each template is different source. Nothing changes for a local run: the variable is unset, the base compose file is used as it always was, andtype=ghawould be an error outside Actions anyway.The Dockerfiles were already ordered for this. Each installs dependencies from a lockfile before copying source, so a source change leaves the expensive layer intact and only a lockfile change invalidates it.
Also sets
BUILDX_NO_DEFAULT_ATTESTATIONSin the workflow. The images are thrown away when the job ends, so the provenance attestation buys nothing, and on that same runresolving provenance for metadata filetook 301s by itself. -
20f8e66: Follow the React SDK's passkey enrollment through the removal of its naming step.
@seamless-auth/reactused to open a "Name This Device" modal after "Register Passkey" was pressed, and the harness typed into it before the WebAuthn ceremony would start. That view is gone from the SDK, soregisterWithPasskeywaited 30 seconds for a placeholder that no longer renders and then timed out, failingreact/passkeyRegister.spec.tsandreact/passkeyLogin.spec.ts(which registers before it signs back in).The helper now stops at the button, which is where the ceremony begins. This tracks the SDK release that drops the modal, so a
--localrun against an older React checkout, or a published run before that release lands on npm, will fail on the modal this no longer dismisses.
-
35d9f3e: Scaffold new projects on PostgreSQL 18, and run the conformance harness on it too.
seamless initgenerated a stack pinned topostgres:17while the verify harness ranpostgres:16. Both are nowpostgres:18, so what the harness certifies is the major a fresh scaffold actually gets.This does not touch an existing project. The image is written into each scaffold's own
docker-compose.ymlat generation time, so a project keeps whatever major it was scaffolded with and its data directory is never pulled out from under it. Only newly scaffolded projects get 18, on a fresh volume. Upgrading an existing project is a deliberate act: change the image in itsdocker-compose.yml, and dump and restore the volume, since PostgreSQL will not start against a data directory written by a different major. -
a39c5c6: Scaffold from
seamless-templatesv0.9.0.Both React starters move to
@seamless-auth/react^0.9.0, which makes the bundled auth screens themeable: every colour reads from a--seamless-*CSS custom property with the previous literal as its fallback, so a scaffolded project can match the auth UI to its brand by setting those variables on:rootor on any ancestor of<AuthRoutes />. The public API is unchanged, and a project that sets no variables renders exactly as before.Both API starters now pin
postgres:18-alpine, with the volume mounted at/var/lib/postgresql. Until this bump a scaffold named two different majors in the same directory —postgres:18in the CLI-generated compose file andpostgres:16-alpinein the API starter copied in beside it.Every starter also ships a working test, lint, and format setup out of the box: Vitest with tests that pass on a fresh
npm install, Prettier alongside ESLint, and the same script names across all four (typecheck,lint,lint:fix,format,format:check,test,test:watch,test:coverage, and acheckthat runs the whole gate). -
d5da781:
seamless verifynow exercises the Fastify starter. The scaffold has offered a Fastify API since the templates bump, but the conformance harness only ever drove the Express adapter, so a green run said nothing about whether a Fastify-scaffolded project actually worked.The stack gains a second adopter backend (
verify/adapter-fastify-app, on port 3001) built on@seamless-auth/fastify, a twin of the Express one: same routes, same env contract, same capture transport. The existing adapter specs run against both without being duplicated, since the two Playwright projects share a test directory and differ only in which backend they point at. The conformance grid gains anadapter-fastifycolumn, so a failure in one framework is attributable to that framework.--api-onlyand--no-reactare unchanged, and--localbuilds and packs@seamless-auth/fastifyfrom source alongside core and express.
-
5cb6911: Fix the scaffolded database failing to start on PostgreSQL 18.
The PostgreSQL 18 bump moved the image tag but not the volume mount. PostgreSQL 18+ images store data in a major-versioned subdirectory (
/var/lib/postgresql/18/docker), so a mount at/var/lib/postgresql/datais ignored and the container refuses to start, restart-looping on:Error: in 18+, these Docker images are configured to store database data in a format which is compatible with "pg_ctlcluster" ... Counter to that, there appears to be PostgreSQL data in: /var/lib/postgresql/data (unused mount/volume)The generated
docker-compose.ymlnow mountspgdata:/var/lib/postgresql. See docker-library/postgres#1259.This only ever affected projects scaffolded from the unreleased PostgreSQL 18 change, so no published version of the CLI produced a broken scaffold.
-
3ef3b68: Add
seamless apps, so a portal account can see what it owns without opening the dashboard.apps listprints name, plan, status, and instance URL;apps get <id>adds the console URL, region, owners, trial expiry, and whether a service token has been issued (masked, never the live value). Both take--jsonand both require a portal session.Applications are now read through the portal's
instanceUrl, which is derived from the service plan, rather than the storeddomaincolumn that goes stale when a trial is upgraded. Applications that have not finished provisioning are listed instead of being silently dropped.initis unchanged: it still readsdomainand still considers only applications that have one.apps listshows the infra id as the reference (falling back to the id before provisioning), andapps getaccepts an id, a name, or an infra id. -
9d089c7: Move the scaffold onto the current Seamless ecosystem: auth API
v0.7.1, admin dashboardv0.4.0, and seamless-templatesv0.8.1(which carries@seamless-auth/react^0.8.0in both React starters,@seamless-auth/express^0.12.0in the Express starter, and@seamless-auth/fastify^0.3.1in the Fastify starter).A scaffolded project can now finish registration without a passkey. Registration used to end on a screen with a single control, leaving anyone who did not want a passkey, or whose device could not make one, with no way forward. The starters offer a skip when the instance has another login method enabled, and say so plainly when it does not. That reads from
GET /system-config/public, a new unauthenticated route on the auth server that returns the configured login methods, so the sign-in screens can offer what an instance actually has enabled instead of a hardcoded guess. The API, the adapters, and the web templates all had to move together for it to work, which is why this bumps them as a set.Registration against a scaffolded Fastify API used to fail with a 500 and
TypeError: option maxAge is invalid: 300. The auth server sent the registration response'sttlas the string"300", and the Fastify adapter handed it to a cookie library that requires an integer. The Express starter never showed this, because its adapter multiplies the value into milliseconds and so coerced the string on the way past. It is fixed from both ends:@seamless-auth/core0.12.1parses the lifetime before it reaches an adapter and rejects anything that is not a positive whole number of seconds, and auth APIv0.7.1sends the value as a number.seamless initnow offers Fastify as a backend, listed as "Fastify (beta)" beside Express. It serves the same surface as the Express starter on the same environment contract, including the admin console at/consolebehindSERVE_ADMIN_CONSOLE. Two boot-time fixes land with it: an emptyPORT=in.envnow falls back to 3000 rather than binding a random free port, andpino-prettymoves to a runtime dependency so an install without dev dependencies boots. Both Express and Fastify starters ship.env.examplesecret placeholders long enough to clear the adapter's 32 character minimum, so the documentedcp .env.example .env && npm run devpath boots. A project fromseamless initwas already unaffected, because the CLI fillsCOOKIE_SIGNING_KEYitself.Both React starters gain a protected
/sessionroute that shows the issued claims, roles, organization context, step-up freshness, and registered passkeys, so the first authenticated screen reads as an app rather than aJSON.stringifydump. Missing configuration now stops a scaffolded project with a message naming the variable instead of surfacing later as a 500, and the Express starter reports every configuration problem at once.The auth API drops the admin bootstrap invite flow in favor of the
OWNER_EMAILgrant the CLI already writes, so the generated.envno longer carriesSEAMLESS_BOOTSTRAP_ENABLED,SEAMLESS_BOOTSTRAP_SECRET, orSEAMLESS_AUTH_DEBUG_SECRETS.AVAILABLE_ROLESnow offersadmin:readandadmin:writealongside bareadmin, and assigning a role the instance does not list is rejected rather than silently doing nothing. Postgres TLS is configurable throughDB_SSL,DB_SSL_CA, andDB_SSL_REJECT_UNAUTHORIZED, andDB_URIis accepted as aDATABASE_URLalias.The conformance harness adapter moves to
@seamless-auth/express^0.12.0, which is also what proxies the new public system-config route. The breaking change in0.11.0splitserrorintoerrorCodeanderrorBodyon the handler result types, which only affects code importing handlers from@seamless-auth/coredirectly; the adapter usescreateSeamlessAuthServer, so it needed no source change. -
4917717: Make
seamless inittemplate flags discoverable, predictable, and safe to get wrong.Add
seamless templates list [--json], which prints every starterinitcan scaffold with its id, kind, framework, selecting flags, and status. It reads the same registryinitdoes (soSEAMLESS_TEMPLATES_DIRandSEAMLESS_TEMPLATES_REFapply) and needs no login, so the available templates no longer have to be looked up in the source.Every template now answers to
--<id>as well as its shorter--<alias>, soseamless init --react-viteworks alongside--basic, and the api starters (--express,--fastify) have a flag for the first time. The "unknown option" error lists both spellings and points atseamless templates list.Template flags are also resolved before
initcreates a directory or asks whether to write into one that is not empty. An unrecognized or conflicting flag now fails immediately instead of surfacing only after the overwrite confirmation. -
6e107cb:
initnow offers the managed path instead of assuming it. A portal session used to make managed the default silently, with--localas the only escape and no way to learn you needed it until after the template prompts. When your account has a provisioned application,initasks whether to connect it or scaffold a local stack, with managed leading.Whether managed is even possible is resolved before the first prompt. An account with nothing to connect no longer answers two prompts and then fails: it says why and continues to a local scaffold. That message now distinguishes "no applications yet" from "still provisioning", which the old
NoApplicationsErrorgot wrong for anyone mid-provision.A directory that already has files is no longer forced down the integrate path.
initasks whether to connect it to a managed application or scaffold in place. Scaffolding into a non-empty directory was previously impossible, so a strayREADMEor.gitwas enough to block a local project, and every route that now reaches it confirms first: starter files overwrite anything with the same name, and the confirmation defaults to no.An unreachable control plane asks before scaffolding a local stack rather than degrading silently.
--localand--app <id>skip the new prompts, and--appwithout a session still fails rather than falling back. -
490391d: Add a non-interactive
seamless init.--yes(-y) answers every question with the option the prompt marks as recommended, so a scaffold runs from CI, a Dockerfile, or a script with no terminal attached:seamless init my-app --local --yes --email=you@example.com
Each question also gets its own flag, honored with or without
--yes:--web=<id|alias>and--api=<id|alias>choose the starters,--email=<address>sets the owner who becomes the admin,--auth=<docker|local>picks how the auth server runs, and--admin=<api|image|source|none>picks where the admin console is hosted. Unspecified values fall back to the recommended option, except the owner email, which has no safe default and is taken from--emailor the portal session.--yesdeliberately stops rather than guessing in three places. Choosing between a managed application and a local stack needs--app <id>or--local. Scaffolding into a directory that is not empty needs--force, since starter files overwrite anything with the same name. Rotating a managed application's existing service token needs--forcetoo, because it breaks whatever is already deployed on the old one. -
cc13a6b: Connecting a project to a managed application now wires up its bundled database.
initreads the application's database and writesDATABASE_URLintoapi/.envaspostgres://USER:PASSWORD@host:port/db?sslmode=require.The user and password stay as literal placeholders. The control plane only returns them for
?reveal=true, which this CLI never asks for, so a live database credential never reaches the developer's disk or terminal: they copy those from the dashboard. Anything printed as a connection string has its userinfo masked regardless.An application whose database is still provisioning produces a warning rather than a failure, and the database is read before the service token is rotated so a missing one is never reported against a project whose old token has already been invalidated. Running
initinside an existing project addsDATABASE_URLonly when there is not one already, so a working connection string is never replaced by a placeholder.This needs the templates release that teaches the express starter to read
DATABASE_URLand negotiate TLS. UntilSEAMLESS_TEMPLATES_REFis bumped to it, the value is computed but the pinned starter does not declare the placeholder, so nothing is written. -
15b487a:
initnow asks for your email and writes it to the scaffolded auth server asOWNER_EMAIL. The auth server grants the admin role at account creation to a signup matching that address, so the local flow isinit,docker compose up, register. When you are signed in to the portal, the prompt defaults to that account's email.seamless bootstrap-adminis removed. It existed to mint the first admin invite, which the owner grant now covers, and the scaffolded stack no longer enables the bootstrap route or carries a bootstrap secret.seamless verifykeeps its own bootstrap secret for the conformance stack and is unaffected.The grant applies at signup only, so changing
OWNER_EMAILafter an account exists promotes nobody. The success output and the README both say so. -
4ecf296: Add per-command help. Every command now answers
-h/--helpwith usage, flags, subcommands, and examples scoped to that command (seamless init -h,seamless verify --help), andseamless help <command>prints the same thing. The help text lives in one registry (src/commands/helpTopics.ts) that both the fullseamless --helpoutput and the per-command output render from, so a flag is documented once and appears in both.The help check runs before a command parses its own arguments, so
seamless init -hprints help instead of treating-has a project name. A--separator ends the check, so a command can still take a literal-hvalue (seamless config set key -- -h). -
6deafb1:
seamless loginnow signs in to the Seamless portal instead of the active profile's instance, and no longer needs a profile to exist first. The portal session is stored beside the profile map inconfig.jsonand is the only sessioninituses to connect a managed application, so a session for a local or self-hosted instance no longer sends its token to the control plane.Instance login moves to
seamless profile login [name], which signs in without changing the active profile.seamless login --profile <name>keeps working for one more minor version and prints a pointer to the new command.whoamiandlogoutdefault to the portal session and take--profile <name>to target an instance.Set
SEAMLESS_PORTAL_AUTH_URLto point the portal login at a different auth host. -
51d9a9e: No command renders a prompt when stdin is not a terminal.
seamless initgot this in 0.11.0; it now covers every command that prompts (login,profile add,users delete,users prepare-device-replacement,sessions revoke,org members remove,config apply, andconfig oauth-providers remove). Each one stops naming the flag that answers the question, so a CI step or a scripted run fails immediately instead of hanging until its job times out.The confirmations that guard a destructive action now take
--force, matching whatinitalready means by it:seamless users delete <id> --force seamless sessions revoke --all --force seamless org members remove <orgId> <userId> --force seamless config apply config.json --force
--yesand-yare accepted aliases everywhere, soseamless config oauth-providers remove --yeskeeps working.--forcedoes not override--dry-run:config apply --dry-run --forcestill changes nothing, and cancelling a confirmation still reads as declining rather than as an error. -
f08c21a: Scaffolding now requires
init. An unrecognized command reports itself and exits instead of being treated as a project name, soseamless verfyno longer silently creates a directory calledverfyand drops into the interactive scaffold. The error namesseamless init <name>for anyone who was using the old shortcut.Ctrl-C is handled everywhere. Clack answers an interrupted prompt with a symbol, which several prompts cast straight to a string; that surfaced as a
TypeErrormid-scaffold, or as "Selected template Symbol(...) is not in the registry". Every prompt in init, the OAuth setup, the managed application picker, andbootstrap-adminnow cancels cleanly and exits 130.initno longer leaves a project directory behind. Any failure or cancellation after the directory is created removes it, including a Ctrl-C during a download or a git clone, so a retry is not blocked by "Directory already exists". Only a directory the command itself created is ever removed, never an existing one and never the working directory.Declining the service token rotation prompt, or cancelling the application picker, now cancels the whole command rather than returning quietly part-way through.
-
bf857b1: Update the seamless auth api image to v0.5.0.
-
e2f53b3:
seamless initno longer hangs when it has no terminal to prompt on. Run on a pipe, it used to render a prompt nobody could answer and wait forever, so a CI step failed only when its job timed out. It now stops on the first unanswered question and names the flag that answers it:$ seamless init --local < /dev/null Error: "Web example" needs an interactive terminal, and this run does not have one. Pass --web=<id> to choose one (see `seamless templates list`), or --yes to take the recommended template.A run whose answers all come from flags is unaffected and works the same on a pipe as on a terminal. A terminal too narrow to render a prompt (a pty allocated without a size reports one column, which used to print one character per line) now warns instead of just looking broken.
-
f725752: Generated compose files now publish every port on
127.0.0.1instead of all interfaces. A scaffolded stack was reachable from any machine on the same network, which mattered most for the auth server: it is configured withALLOW_UNCREDENTIALED_DELIVERY_SECRETS=truesoseamless login --localcan read OTP codes from the response, and that opt-in is honored before any service-token check. Anyone on the LAN could request a code for any user of the stack and read it. Postgres was exposed on the same terms, with the fixed credentials the compose file ships.Local development is unchanged: the browser, the CLI, and inter-container traffic all still work.
-
ffe4331: Update the conformance harness for the removal of the admin bootstrap invite flow in seamless-auth-api. The verify stack now sets
OWNER_EMAILinstead ofSEAMLESS_BOOTSTRAP_ENABLED/SEAMLESS_BOOTSTRAP_SECRET, and the first-admin spec registers the owner address and asserts the admin role is granted at signup. -
2d898e2: Fix conformance project routing when the checkout path contains a directory named with an
api/segment. Playwright applies atestMatchregex to the absolute file path, so theapiproject's pattern also claimed every adapter and react spec, running browser tests in a project with nobaseURL. Each project now scopes itself withtestDirinstead. -
c2e4e1b: Scaffold from seamless-templates v0.5.0, which teaches the express starter to read
DATABASE_URLand negotiate TLS when the connection string carriessslmode=require. This is what turns on the managed bundled database wiring: the CLI already computed the connection string, but the pinned v0.4.0 starter did not declare the placeholder, so nothing was written.
- 5c71d8c: Fix the released conformance smoke (
released-smoke): bump the harness adapter's@seamless-auth/expresspin from^0.8.0to^0.9.0. The published 0.8.0 still served the OTP/magic-link generate routes asGET, while the harness flows and@seamless-auth/react0.5.0 bothPOSTthem, so every adapter (and downstream react) flow failed withgenerate-email-otp -> 404. 0.9.0 serves them asPOST, matching what the source (--local) conformance already builds.
- 166afdd: Bump the pinned Seamless component versions the CLI pulls to their latest
published tags: auth-api
v0.3.0→v0.4.0, admin dashboardv0.1.0→v0.3.0, and the templates refv0.3.0→v0.4.0.
-
46ed8dd: Let adopters choose how the admin console is hosted during
seamless init.The old "Include Admin Dashboard?" / image-vs-source prompts are replaced by a single question with four options:
- Served by your API at /console (recommended default) — the app backend
proxies the console via the SDK's
createSeamlessConsoleProxy, so it loads from the API's own origin. The scaffold setsSERVE_ADMIN_CONSOLE=trueon the API,SERVE_ADMIN_DASHBOARD=trueon the auth server, and adds the API origin to the auth server'sORIGINSso console passkey ceremonies verify. No separate admin container. - Separate container — official image or cloned source, as before, on
http://localhost:5174. - None — no console is scaffolded.
Each choice pre-configures the auth-server env, the app-backend env, the Docker Compose services,
seamless.config.json, the success output, andseamless checkaccordingly. Pins the auth API image tov0.3.0and the templates tov0.3.0(which env-gate the console proxy). - Served by your API at /console (recommended default) — the app backend
proxies the console via the SDK's
-
e612a10: Enable
email_otpin the scaffolded auth server's default login methods.The auth server's own default (
passkey,magic_link) has no method the CLI can drive without a browser authenticator, soseamless logincould not sign in to a freshly scaffolded local stack.buildAuthEnvnow appendsemail_otptoLOGIN_METHODS(composing with the OAuth method when providers are configured), so email-OTP login works out of the box. -
7fbaff0: Enable
ALLOW_UNCREDENTIALED_DELIVERY_SECRETS=truein the scaffolded auth server env.Companion to the
email_otpdefault: with it set,seamless login --localreads the OTP straight from the auth server's response instead of needing a mail provider, so signing in to a freshly scaffolded local stack works end to end. It's a dev-only escape hatch — the auth server ignores it under a productionNODE_ENV, and the scaffold runs as development.
-
2e27156: Fix
seamless bootstrap-adminto target the app API instead of the login profile's auth server.The bootstrap invite route (
/auth/internal/bootstrap/admin-invite) and its delivery are exposed by the app API (the SeamlessAuth server adapter), not the auth server directly — the auth server does not serve that path. Previouslybootstrap-adminfell back to the active profile'sinstanceUrl, so once a profile pointed at the auth server (asseamless loginand the admin commands require), bootstrap requests 404'd.bootstrap-adminnow resolves its target independently of any profile:--api-url <url>→SEAMLESS_API_URL→ the local defaulthttp://localhost:3000. The--profileflag is removed from this command (it no longer affects the target; the bootstrap secret is still resolved from the local project). -
3dbdc61: Reconcile documentation with actual behavior.
- README: correct the Node requirement (24, per
.nvmrc/engines, not 18); updatebootstrap-admindocs to the--api-url→SEAMLESS_API_URL→http://localhost:3000resolution (the removed--profileflag and auth-server-profile wording are gone). - AGENTS.md: drop the
npm run typecheck/lint/format:checkcommands that don't exist (there is no lint/format tooling yet); note the--filter=<flow>(=form) forverify; list the instance-management commands; remove the stale top-leveltemplates/reference (templates live in theseamless-templatesmonorepo). package.json: drop the deadtemplatesentry fromfiles.
Closes #94.
- README: correct the Node requirement (24, per
-
e81a07d: Make
seamless checkmanaged-aware and resilient to partial config.checkComposedereferencedconfig.docker.composeFile, which isnullfor managed projects, soseamless checkcrashed with aTypeErroron any managed scaffold.checknow branches onservices.auth.mode === "managed": it validates the remote instance's/health/statusand skips the Docker/compose/container checks (which don't apply remotely). It also wrapsJSON.parseand guards missing service entries so a malformed or partialseamless.config.jsonprints a friendly message instead of a stack trace. The local console health check is derived fromservices.admin.mode(image/source → :5174, api → :3000/console, none/hosted → skipped).Closes #78.
-
c4a0f93: Fix the conformance adapter flows to match the SDK's POST OTP/magic-link routes.
@seamless-auth/expressnow serves OTP generate routes (and/magic-link) over POST, but the harness adapter flows still called them with GET, so every adapter spec failed atgenerate-email-otp -> 404once the stack came up. The adapter flows now POST/auth/otp/generate-email-otp,/auth/otp/generate-login-email-otp, and/auth/magic-link.Closes #111.
-
545aafc: Disable auth rate limits in the conformance stack.
The conformance suite drives many OTP/registration/magic-link flows from a single IP and trips auth-api's dedicated per-IP limiters (which
RATE_LIMITdoesn't tune), so the adapter layer failed with 429s once the stack came up. The verify compose now setsDISABLE_AUTH_RATE_LIMITS=trueon the auth-api service — a dev-only flag (ignored underNODE_ENV=production) added in seamless-auth-api.Requires seamless-auth-api with
DISABLE_AUTH_RATE_LIMITSsupport; conformance builds it from source, so no release is needed. -
f7e5b5d: Fix the conformance stack and surface container logs on failure.
auth-api 0.3.0 requires
FRONTEND_URLat startup (a required system config), which the verify compose never set — so the auth-api container exited on boot and everyconformancerun (here and in sibling repos calling the reusable workflow) aborted with a bare "docker failed" before any layer ran.verify/docker-compose.verify.ymlnow setsFRONTEND_URL, andseamless verifydumps recent container logs on a setup failure so a container that exits on startup is diagnosable instead of hidden.Closes #107.
-
54f1f02: Quote generated
.envvalues that a dotenv parser would otherwise misread.writeEnvwrote bareKEY=value, so a value containing#, whitespace, quotes, a backslash, or a newline (e.g. a managedAPI_SERVICE_TOKENor a pasted OAuth secret) produced a.envthat dotenv truncates or mis-parses. Values that need it are now double-quoted and escaped, andparseEnv/parseEnvStringunquote on read so the CLI round-trips its own output. Simple values (tokens, URLs, hex secrets) are still written bare.Closes #81.
-
943d13d: Harden the managed
initflow.- Explicit managed intent no longer silently scaffolds local. When
--appis given but there is no usable session (expired or control plane unreachable),initnow fails with an actionable message instead of quietly scaffolding a self-hosted project and ignoring the flag. Without--app, a missing session or an unreachable control plane falls back to local with a clear warning (rather than aborting on transient network errors, as it previously did for non-reauth failures). Closes #79. - Service-token rotation is now recoverable. Rotation invalidates the app's
previous token, so it runs after templates are copied (the likeliest failure
point), and every step after it is guarded: if scaffolding fails post-rotation,
the freshly issued token is printed so a deployed app can be re-wired instead of
left bricked. The same guard covers
integrateExistingProject. Closes #80.
- Explicit managed intent no longer silently scaffolds local. When
-
9eaaf0e: Fix the conformance adapter crash: the dev service token was too short.
seamless verifydefaultedAPI_SERVICE_TOKENto a 24-char constant, which the adapter reuses as its cookie secret; a newer@seamless-auth/expressrejects a cookieSecret shorter than 32, so the adapter container exited and conformance failed after the stack came up. The dev service token and bootstrap secret defaults are now >=32 characters.Closes #109.
- 3de03ef: Add
seamless config oauth-providers <list|add|update|remove>for per-provider OAuth management, backed by the auth API's dedicated provider routes (GET/POST /system-config/oauth-providers,PATCH/DELETE /system-config/oauth-providers/:id). Each command touches a single provider, so concurrent edits no longer clobber the wholeoauth_providersarray the wayconfig set oauth_providers/config applydo.addandupdateaccept an inline JSON object or--file <path>;removeconfirms first (skip with--yes). Client secrets stay server-side: providers are referenced byclientSecretEnvand the secret value is never sent. The whole-config editor commands are unchanged. - 9043643: Add
seamless login --localfor self-hosted and local instances. It asks the instance to return the email or phone OTP in the response body instead of sending it, then verifies with that code automatically, so logins work without a real mail or SMS provider. It only runs against local hosts and requires the auth API to run outside production withALLOW_UNCREDENTIALED_DELIVERY_SECRETS=true.
- 26a78d6: Fix
seamless loginrejecting valid email OTP codes. Email OTPs are six letters, but the code prompt only accepted digits, so no real email code could be entered. The prompt is now channel-aware: it accepts a six-letter code (case-insensitive, normalized to uppercase) for email logins and a numeric code for phone logins, with matching placeholder text. - f8dc6fc: Fix the conformance harness (
seamless verify --local) failing to build after the ecosystem moved to Express 5 on Node 24. The verify adapter app pinnedexpress@^4, but@seamless-auth/expressnow requiresexpress@>=5as a peer, so installing the locally-built SDK tarball aborted with anERESOLVEpeer conflict and the Docker build failed. The adapter now depends onexpress@^5.1.0(and@seamless-auth/express@^0.8.0), and the verify Docker images are bumped fromnode:20tonode:24to match the ecosystem's supported runtime.
- 8468863: Target
bootstrap-adminat the active profile's instance URL instead of requiring a localseamless.config.json. The command now resolves the instance from the active profile (respecting--profileandSEAMLESS_PROFILE), withSEAMLESS_API_URLas an override andhttp://localhost:3000as the fallback when no profile is configured. The bootstrap-secret auth flow is unchanged.
- 5126998: Document
bootstrap-adminin the README: add a "Creating the first admin" section covering the command, its profile-based instance targeting (with theSEAMLESS_API_URLoverride andhttp://localhost:3000fallback), and the bootstrap-secret authentication it uses because it runs before any admin exists.
-
6af3216: feat(init): connect scaffolds to a managed instance when a profile is logged in
seamless initnow defaults to the managed control plane whenever a profile has an active session. It reads the logged-in session, lists your applications, issues the application's service token from the control plane (rather than generating a local secret), and points the scaffolded web and api at the managed auth instance. Runninginitinside an existing project wires the managed credentials intoapi/.envin place, or prints them when there is no api directory. The self-hosted flow stays available withseamless init --local, and is still used automatically when no session is present. SetSEAMLESS_PORTAL_API_URLto override the control-plane host.
-
f8c4478: chore(templates): scaffold from seamless-templates v0.2.4
Bump
SEAMLESS_TEMPLATES_REFtov0.2.4. The Express template now has a working productionnpm run start(fixed extensionless ESM imports and the migration runner path) and ships thedocker-compose.ymlits scripts and README referenced. The web templates (react-vite and react-oauth) drop the unusedVITE_AUTH_SERVER_URL, and all templates document both the local and managed init paths.
- d09317c: Add admin verbs for users and organizations (requires an admin role).
seamless userscoverslist(with client-side--limit/--offsetpaging and--json),delete <id>(with confirmation),credentials <id>(from the admin user detail endpoint), andprepare-device-replacement <id>for admin-assisted recovery.seamless orgcoverslist,create,get, andupdate, andseamless org memberscoverslist,add(by--userid or--email, with--roles/--scopes),update, andremove(with confirmation). Every command surfaces a 403 as a clear permission error, and device replacement explains the step-up requirement when the CLI session is not elevated. Accepts--profileand honorsSEAMLESS_PROFILE. - 29cef7d: Add a multi-profile config store and
seamless profilecommands so the CLI can target multiple Seamless Auth instances (self-hosted, managed tenant, local dev) under named profiles. Profiles live in~/.config/seamless/config.json(respectingXDG_CONFIG_HOME) and hold no secrets. New subcommands:profile list,profile add,profile use, andprofile remove. The active profile can be selected per command with--profile <name>or theSEAMLESS_PROFILEenvironment variable, defaulting to thedefaultprofile. - 4d9cc23: Add an authenticated HTTP client that targets the active profile's instance,
attaches the Bearer access token, and transparently refreshes on expiry. On a
401 it calls
POST /refreshwith the opaque refresh token, persists the rotated pair, and retries the original request once. A rotated or reused refresh token clears the local session and raises a clear re-login prompt instead of a stack trace. Non-JSON and empty response bodies are parsed defensively, and rate-limit (429) responses are surfaced without triggering a refresh. - f24a71d: Add
seamless config, config-as-code for an instance's system configuration (requires an admin role).config get [key] [--json]reads the config fromGET /system-config/admin,config set <key> <value>writes one key viaPATCH /system-config/admin(the value is parsed as JSON, falling back to a string, so TTLs, arrays, booleans, and numbers all work), andconfig roleslists the instance's roles.config diff <file>shows how a local JSON config file differs from the instance, andconfig apply <file>applies the delta after a confirmation prompt, with--dry-runto preview. Read-only or unknown keys in a file are ignored on apply, and a non-admin user gets a clear permission error. Accepts--profileand honorsSEAMLESS_PROFILE. - 69fb8c8: Store session tokens in the OS keychain (macOS Keychain, Windows Credential
Manager, Linux Secret Service) via
@napi-rs/keyringinstead of on disk. Tokens are scoped per profile (keyed by profile name and instance URL) so multiple instances never collide, and the refresh token, the durable secret, never touches config or logs.seamless profile removenow clears the profile's keychain entry. When no keychain is available (for example headless CI), the CLI reads a refresh token fromSEAMLESS_REFRESH_TOKENif set and otherwise fails with a clear, documented error rather than writing secrets to disk. - 4301da5: Add
seamless login, an interactive email OTP login for the active profile's Seamless Auth instance. It callsPOST /login(honoring the instance's returnedloginMethods), triggers the code withGET /otp/generate-login-email-otp, prompts for the code you paste from your inbox, and verifies it withPOST /otp/verify-login-email-otp. On success it stores the session in the OS keychain and records the identity (sub, email, identifier type) on the profile. The command caps local code retries so it does not trip the per-IP OTP limiter, surfaces a 429 clearly, refreshes the code automatically if the 5 minute ephemeral window lapses, and reports unreachable instances without a stack trace. Accepts the identifier positionally or with--identifier, and targets a specific profile with--profile. - 79d076e: Add
seamless sessionsto list and revoke the logged-in user's active sessions.seamless sessions(orsessions list) callsGET /sessionsand renders each session's id, device or user agent, IP, and last-used time, marking the current session.seamless sessions revoke <id>callsDELETE /sessions/:id, andseamless sessions revoke --allcallsDELETE /sessions. Revoking the current session, or all sessions, prompts for confirmation first and then clears the local keychain tokens, since that request signs you out. Accepts--profileand honorsSEAMLESS_PROFILE. - a9c1ef3: Add
seamless whoamiandseamless logout.whoamicallsGET /users/methrough the authenticated client and prints the identity (sub, email, roles) alongside the active profile and instance URL, failing cleanly with a "not logged in" message when there is no session.logoutends the current session withDELETE /logoutand then clears the profile's keychain tokens;logout --allrevokes every session for the user withDELETE /logout/allfirst. Both commands accept--profile(and honorSEAMLESS_PROFILE) and always clear the local tokens even if the server session was already gone.
- f8e8cdb: Document the CLI authentication commands in the README (profiles, login, whoami,
sessions, logout, config, and the users and organizations admin verbs), including
per-platform keychain behavior and the headless
SEAMLESS_REFRESH_TOKENfallback. Add a gated end-to-end test that drives the real login flow, an authenticated call, transparent refresh, and refresh-reuse rejection against a running instance (enabled withSEAMLESS_E2E_URL), plus an opt-in rate-limit check. - f978890:
seamless verifynow prints a consolidated summary report at the end of the run: the seamless package versions under test (source versions for--local, the declared pins for released runs), one line per conformance layer (API / adapter and each web template) with its pass/fail status and duration, and an overall verdict. It is printed after teardown so it stays on screen without scrolling back through the phase output.
- 9992373: Bump the pinned
seamless-templatesref tov0.2.3, soseamless initscaffolds the templates that ship@seamless-auth/react^0.4.0(TOTP support) and@seamless-auth/express^0.7.0.
- 9a9953a: Fix local auth mode regenerating the auth server's
.enva second time with fresh secrets, which left the scaffolded API'sAPI_SERVICE_TOKENmismatched with the auth server's when services run outside Docker. The compose builder now reads the already-written auth env instead of rewriting it.
- 9bfaa5f: When the OAuth template is selected,
seamless initnow prompts for OIDC providers (Google, GitHub, Microsoft, GitLab) and their client id/secret, then wires them into the scaffolded auth server: OAUTH_PROVIDERS config, a per-provider*_CLIENT_SECRETenv var, theoauthlogin method, and thehttp://localhost:5173/oauth/callbackredirect URI. Providers left without credentials are scaffolded disabled with a printed next-steps note. Apple is documented as manual (its client secret is a signed JWT and it has no userinfo endpoint). The scaffold now also generatesREFRESH_TOKEN_LOOKUP_SECRET,TOTP_SECRET_ENCRYPTION_KEY, andOAUTH_STATE_SECRET(previously left empty, falling back to the service token), and adds a healthcheck to the generated web container.
- 7409238:
seamless init --<example>selects a use-case starter by its registry alias (e.g.seamless init --oauth), skipping the web prompt. Aliases are defined in the templates registry, so adding an example needs no CLI change. Unknown flags list the available examples, and the interactive web prompt now presents the available examples. - fcac569: Scaffold web and api starters from the seamless-templates registry instead of hardcoded per-framework generators. The CLI now reads the registry to build its prompts, downloads the selected templates from the templates monorepo at a pinned ref, and applies each template's env contract. Adding a new framework is a templates-repo change, not a CLI change. Set SEAMLESS_TEMPLATES_DIR to scaffold from a local checkout, or SEAMLESS_TEMPLATES_REF to pin a different ref.
- 27a6ec1:
seamless verifynow conformance-tests every web template in the registry. It runs the API and adapter layers once, then builds, serves, and drives each web template in turn, scoping the browser suite to the flows the template's manifest declares (verify.flows, e.g.["oauth"]). React specs are tagged by feature (@login,@oauth) so a template runs only its relevant flows; a template with no declared flows runs the full suite.SEAMLESS_REACT_DIRstill overrides with a single template.
- fbcc17e: Bump the verify adapter to
@seamless-auth/express@^0.6.0(the stable release that includes the registration-session and non-JSON-response fixes), so the released conformance run tests against the current published packages. - 57c7819: Point the conformance harness at the seamless-templates monorepo.
seamless verifynow resolves the React web template from../seamless-templates/templates/web/react-viteby default (still overridable withSEAMLESS_REACT_DIR), and the reusableverify-conformance.ymlworkflow checks outseamless-templates(inputtemplates-ref) instead of the standalone starter repo. - c41e68b:
seamless verifynow resolves the web template to conformance-test from the seamless-templates registry (the first runnable web template) instead of a hardcoded path. SEAMLESS_REACT_DIR still overrides with a direct template path, and SEAMLESS_TEMPLATES_DIR points at a local templates checkout. Running the browser suite against every web template is a follow-up.
-
993d386: Add
seamless verify, a one-command cross-package auth conformance harness. It stands up the auth API, the Express adapter, and the React starter, then runs a matrix of flows (register, email and phone OTP, magic-link, passkey register and login, OAuth, TOTP, step-up, refresh, sessions, logout, organizations, admin bootstrap, and JWKS) across the api, adapter, and browser layers, and prints a pass/fail grid alongside JUnit and HTML reports.Supports
--local(build the@seamless-auth/*packages from source for pre-publish contract testing) and a released mode against the published packages, plus filters like--api-only,--no-react,--filter, and--keep-up. Ships a reusable GitHub workflow so a change in any ecosystem repo runs the matrix against the others.