fix(dashboard): seed default operator, fix infinite /setup redirect - #119
Merged
Conversation
The user-create hook mirrored a new better-auth user into suite_users, suite_memberships, and suite_operators inside one all-or-nothing transaction. Any partial failure (e.g. the default-tenant row not existing when suite_memberships is written) rolled back the suite_operators insert too — leaving the deployment with ZERO operators. operatorCount() then stays 0 forever and every route bounces to /setup: the infinite redirect. Split the three mirror steps so each runs independently and best-effort: a failure in one is logged but can never undo operator creation. Also set emailAndPassword.disableSignUp so the operator console no longer exposes public self-provisioning. The first operator is now seeded at boot (next commit); sign-in, magic-link, OAuth, and SSO stay enabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes the setup chicken-and-egg: a default operator (configurable via AF_STACK_DEFAULT_OPERATOR_EMAIL / _PASSWORD / _NAME) is seeded the first time the dashboard boots, so the console is usable immediately with no signup wizard. Credentials are documented in the README and .env.example. The seed runs in a Next.js instrumentation hook and is: - idempotent: gated on an empty suite_operators table, so it never re-runs or clobbers a changed password / a curated operator set; - compatible with better-auth login: hashes the password with better-auth/crypto and writes the credential account row directly (providerId='credential', accountId=<user id>); - resilient to boot ordering: migrations run in the runtime container, which the dashboard only depends_on as service_started, so the seed retries until the auth tables exist and never crashes the process. Set AF_STACK_DEFAULT_OPERATOR_DISABLED=true to skip seeding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ogin With a default operator seeded at boot there is no operator-less state to divert to a wizard, and the operator console no longer offers public self-signup. Remove the /setup and /signup pages, the count===0 → /setup redirects in the login page and admin layout, the /setup and /signup entries from the middleware allow-list, and the "Sign up" link on the login form. The admin layout now relies on requireOperator() alone, which sends anyone without a valid operator session to /login — no loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an "Operator login" section with the default credentials, the override env vars, the change-password guidance, and the opt-out flag. Update the identity row to reflect the seeded default operator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Author
|
The shared customer/operator identity concern noted above is now tracked in #120. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Signing into the operator dashboard infinitely redirected back to the setup screen, and the console offered a public "Create operator account" signup that doesn't make sense for an operator console.
Root cause of the redirect: operator status lives in a separate
suite_operatorstable, populated only as a side-effect of the first better-auth signup — inside one all-or-nothing transaction that also wrotesuite_users+suite_memberships(the latter depends on a default-tenant row existing). Any partial failure rolled the whole thing back, sosuite_operatorsstayed empty,operatorCount()stayed0forever, and/login,/, and everything else bounced to/setup.Approach
Replace the fragile signup-bootstrap with a seeded default operator account, so there's never an operator-less state:
lib/bootstrap-operator.ts, run from a Next.jsinstrumentation.tshook). Credentials are configurable viaAF_STACK_DEFAULT_OPERATOR_EMAIL/_PASSWORD/_NAMEand documented in the README +.env.example. The seed is idempotent (gated on an emptysuite_operatorstable — never re-runs, never clobbers a changed password), login-compatible (hashes withbetter-auth/crypto, writes thecredentialaccount row), and resilient to boot ordering (retries until the migrated auth tables exist; never crashes the process).user.createhook's three mirror steps now run independently/best-effort, so a partial failure can never undo operator creation again. Also setemailAndPassword.disableSignUpto close public self-provisioning./setupand/signuppages, thecount===0 → /setupredirects, the allow-list entries, and the login-form "Sign up" link. The admin layout relies onrequireOperator()alone.Default credentials
operator@af-stack.localchangeme123Documented in the README with change-password guidance and an
AF_STACK_DEFAULT_OPERATOR_DISABLEDopt-out. Scope was confirmed with the requester as "fix login + default account now"; an in-dashboard "add more operators" UI is intentionally left as a follow-up.Validation contract
operatorCount() == 1./loginalways shows the sign-in form (never redirects to/setup)./with no redirect loop.disableSignUp).Testing
tsc --noEmitclean;eslintclean on all changed files;prettier --checkclean.origin/main(unrelated local changes excluded).docker compose up— worth a manual smoke (boot fresh stack, sign in with the default account).Note (separate concern, flagged by the requester)
The customer app and operator dashboard share the same better-auth
user/accounttables, so one credential authenticates against both — operator access is gated only at authorization (suite_operators). Tracking that separately (see follow-up issue).🤖 Generated with Claude Code