feat(auth): replace default password with console setup token - #7
Conversation
There was no way to run 10router without a well-known credential: a fresh install accepted `123456`, and the login page printed it on screen. Anyone who reached the port before the operator did could claim the instance. First run now mints a one-time setup token, prints it to the host console, and locks every dashboard/API route until /setup is completed with that token plus a password (min 8 chars). Console access is the proof of ownership. Portainer-style, the token expires 5 minutes after the server starts; a restart mints a new one. - Token is minted at most once per process, so no request can refresh its own expiry window or invalidate a token the CLI reset just printed. - requireLogin=false no longer bypasses an unclaimed instance — that opt-out is only meaningful for an instance someone already owns. - Existing installs are stamped `legacyDefaultPassword` in _meta by the DB migration (fresh DBs never are) and get exactly one more login on the old default before a password change is forced. - INITIAL_PASSWORD still works as a headless bootstrap, but is ignored when shorter than 8 chars or set to the old default. - CLI "Reset Password to Default" becomes "Reset Password (re-run setup)": it clears the hash and prints a fresh token instead of restoring a guessable credential.
|
Warning Review limit reached
Next review available in: 46 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (31)
📝 WalkthroughWalkthroughThe change replaces default-password bootstrap with one-time setup tokens, an eight-character password policy, setup-state API handling, first-run routing, legacy-installation migration, CLI reset support, and updated documentation and tests. ChangesAuthentication bootstrap flow
Estimated code review effort: 4 (Complex) | ~60 minutes Mergeability Score: 🟠 High · up to The authentication change can still allow legacy installations to access the dashboard with the old default password, while concurrent setup or failed recovery writes can leave password state inconsistent or unusable. Merge should be blocked until the server-side legacy-session restriction and atomic setup/recovery transitions are fixed. Sequence Diagram(s)sequenceDiagram
participant Operator
participant SetupPage
participant SetupRoute
participant setupToken
participant SettingsDatabase
participant Dashboard
Operator->>SetupPage: enter setup token and new password
SetupPage->>SetupRoute: submit setup request
SetupRoute->>setupToken: verify one-time token
setupToken-->>SetupRoute: return validity and expiry state
SetupRoute->>SettingsDatabase: store hashed password and clear setup metadata
SetupRoute-->>SetupPage: return success and session cookie
SetupPage->>Dashboard: redirect to dashboard
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The GitBook pages still told users to log in with `123456`, which no longer works and was the exact instruction the setup-token change exists to remove. Updates the dashboard-login, quick-start, FAQ and cloud-env sections across en/es/ja/vi/zh-CN to document the console setup token, the 5-minute window, the 8-character minimum and the CLI reset path. INITIAL_PASSWORD examples in the deployment sections are kept — it still works as an explicit headless bootstrap — but are no longer described as having a default.
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/ARCHITECTURE.md`:
- Around line 527-530: Update the setup-token expiry statement in the
architecture documentation to say that each token expires five minutes after it
is minted or issued, rather than five minutes after server startup. Preserve the
surrounding behavior and restart details.
In `@src/app/api/auth/login/route.js`:
- Around line 67-74: Enforce legacy password changes server-side: in
src/app/api/auth/login/route.js lines 67-74, add a signed restricted-session
claim when bootstrapState is "legacy"; in src/dashboardGuard.js lines 165-170,
prevent restricted sessions from satisfying general authentication; and in
src/dashboardGuard.js lines 190-203, redirect restricted page sessions to the
password-change flow while allowing only the required password-update and logout
endpoints.
In `@src/app/api/auth/reset-password/route.js`:
- Around line 13-18: Update the password-reset flow around updateSettings and
setMeta so clearing the password and removing LEGACY_GRACE_META_KEY form one
required transition: propagate any setMeta failure and avoid leaving the
password cleared without the legacy-grace marker removed. Only call
issueSetupToken and printSetupBanner after both operations succeed; do not issue
a token when the transition fails.
In `@src/app/api/auth/setup/route.js`:
- Around line 63-84: Update the setup flow around verifySetupToken and
updateSettings so the password is validated before reserving the token, then
atomically reserve or consume the setup token across token verification,
password persistence, and clearSetupToken. Ensure concurrent requests cannot
both pass verification or create sessions; only the request that successfully
completes the atomic operation may update the password.
In `@src/app/api/settings/route.js`:
- Around line 89-95: Move clearLegacyGrace() and clearSetupToken() out of the
password-hashing block and invoke them only after updateSettings(body) completes
successfully, ensuring the replacement password hash is persisted before
recovery state is revoked.
In `@tests/unit/auth-setup.test.js`:
- Around line 163-168: Update the “mints and prints a token for an unclaimed
instance” test around ensureSetupToken to spy on printSetupBanner, then assert
the banner output includes the issued token. Preserve the existing truthiness
and verifySetupToken assertions while ensuring the banner invocation is
observed.
In `@tests/unit/dashboard-guard.test.js`:
- Around line 307-313: Update the test case around the existing
requireLogin=false scenario to request /dashboard instead of /api/settings, then
assert that the response redirects to /setup. Keep the mocked settings and
unclaimed-instance setup unchanged so the test specifically exercises the
first-run setup gate.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: dc3423d7-cac5-4d00-adfc-ed6c60126d52
📒 Files selected for processing (22)
.env.exampleAGENTS.mdREADME.mdcli/src/cli/menus/settings.jsdocs/ARCHITECTURE.mdsrc/app/(dashboard)/dashboard/profile/page.jssrc/app/api/auth/login/route.jssrc/app/api/auth/reset-password/route.jssrc/app/api/auth/setup/route.jssrc/app/api/auth/status/route.jssrc/app/api/settings/route.jssrc/app/login/page.jssrc/app/setup/page.jssrc/dashboardGuard.jssrc/lib/auth/dashboardSession.jssrc/lib/auth/setupBootstrap.jssrc/lib/auth/setupState.jssrc/lib/auth/setupToken.jssrc/lib/db/migrate.jssrc/shared/services/initializeApp.jstests/unit/auth-setup.test.jstests/unit/dashboard-guard.test.js
Review found the forced change was advisory only. A legacy login issued a normal session cookie and returned `mustChangePassword` for the client to act on; anyone who knew the old default could ignore the form, request /dashboard, and get in — or PATCH /api/settings with requireLogin:false and remove authentication altogether. The legacy session now carries a `pwChange` claim. The guard treats such a session as unauthenticated everywhere except /api/auth/change-password (plus logout and status), redirecting pages to /login and answering APIs with 403. The new endpoint is the only way to spend that session: it accepts a password for a legacy install, persists the hash, clears the grace flag, and swaps in a full session. Also from review: - Consume the setup token atomically. verify-then-clear left a window where two concurrent claims both passed and each wrote a hash; the password is now validated first (so a bad one cannot burn the token) and consumeSetupToken() verifies and unlinks in one synchronous step. Verified: 10 concurrent claims yield exactly one success. - reset-password no longer swallows a failed legacy-grace clear. The flag is cleared before the hash, so a failure leaves the old password working rather than stranding the install between "legacy" and "setup". - Settings PATCH persists the new hash before revoking the legacy grace and setup token, so a failed write cannot leave no password and no way back in. - ARCHITECTURE.md: the token expires 5 minutes after it is minted, not after server start — a CLI reset issues one at an arbitrary time. - Tests: assert the console banner actually carries the token; the requireLogin=false case now exercises the setup gate via /dashboard rather than an /api/* 401 that proved nothing.
What
Removes the built-in default dashboard password (
123456) and replaces first-run bootstrap with a Portainer-style console setup token.Why
A fresh install accepted
123456, and the login page printed it on screen. Anyone who reached the port before the operator did could claim the instance.How it works now
Fresh install — the server mints a one-time token, prints it to the host console, and locks everything (
/dashboardand/loginboth 307 →/setup) until someone pastes that token plus a new password (min 8 chars). Console access is the proof of ownership.Existing installs — the DB migration stamps
legacyDefaultPasswordin_metafor DBs that already existed (a fresh DB is never stamped, so new installs can't slip into this path). Those get exactly one more login on the old default, then a forced password change before the dashboard loads.Recovery — CLI → Settings → Reset Password clears the hash and prints a fresh token, instead of restoring a guessable credential.
Reviewer notes
Two behavior changes beyond the strict scope, both load-bearing for the gate:
requireLogin: falseno longer bypasses an unclaimed instance (src/dashboardGuard.js). Without this the setup gate has a trivial hole. It still works normally once someone owns the instance.INITIAL_PASSWORDstill works as a headless Docker bootstrap and skips the setup flow (it's an explicit operator choice), but is now ignored when shorter than 8 chars or set to123456— logs a warning and falls back to the token flow.Token minting is capped at once per process. That's what makes "restart for a new token" true while ensuring no request can refresh its own expiry window, and that a
/api/auth/setupGET can't mint a second token that silently invalidates one the CLI just printed. Both of those were bugs found during live verification and are fixed + covered by tests.Verification
tests/unit/auth-setup.test.jsplus 6 new guard cases intests/unit/dashboard-guard.test.js.tests/__baseline__/verify-no-regression.mjsis unusable in a local checkout: it splits paths on/app/, so every failure reads as a regression.DATA_DIR: gate → token → claim → session cookie → login; old default rejected afterward; simulated legacy-DB upgrade (one grace login → forced change → default dead); CLI reset → re-claim. The 5-minute lock fired for real mid-verification and refused the claim until restart.Docs
README.md,.env.example,docs/ARCHITECTURE.md,AGENTS.mdupdated, plus all GitBook locales (en/es/ja/vi/zh-CN) — dashboard-login, quick-start, FAQ and cloud-env sections now document the console setup token instead of123456.Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests