Skip to content

feat(auth): reapply UI_PASSWORD to admin account on every startup#57

Merged
tobias-gp merged 2 commits into
mainfrom
feature/admin-password-reset-on-startup
May 16, 2026
Merged

feat(auth): reapply UI_PASSWORD to admin account on every startup#57
tobias-gp merged 2 commits into
mainfrom
feature/admin-password-reset-on-startup

Conversation

@tobias-gp
Copy link
Copy Markdown
Contributor

@tobias-gp tobias-gp commented May 16, 2026

Summary

  • seedAdmin() now reconciles the admin credential against UI_PASSWORD on every API startup instead of skipping when the user already exists. If the stored hash already verifies the current UI_PASSWORD, it's a no-op; if it differs, the credential is replaced via internalAdapter.updatePassword and a log line is emitted.
  • Existing first-startup seeding and missing-credential repair branches are preserved.
  • Docs (README.md, configuration.mdx, docker.mdx, self-hosting.mdx) are updated to describe UI_PASSWORD as authoritative on every startup and to document the rotate-by-restart workflow.

Why

Previously, changing UI_PASSWORD after the first run had no effect — the env var silently became inert and there was no in-product way to recover a lost admin password. This makes UI_PASSWORD the source of truth and gives operators a documented recovery path: bump UI_PASSWORD, restart the container, log in.

Operational note: any password the admin changes through the UI is overwritten on the next API restart unless UI_PASSWORD is updated to match.

OpenSpec

  • Proposal: openspec/changes/update-admin-password-reset-on-startup/
  • Modifies the auth capability spec — Admin User Seeding is rewritten as a startup reconciliation contract with scenarios for first-seed, no-op-when-matching, reset-when-changed, and missing-credential repair.
  • openspec validate update-admin-password-reset-on-startup --strict is green.

Test plan

  • pnpm --filter @archmax/api typecheck
  • pnpm --filter @archmax/api build
  • pnpm typecheck (workspace, 7/7 tasks)
  • pnpm exec vitest run --project api src/lib/seed-admin.test.ts — 5/5 pass (no user, hash matches, hash differs, missing credential, null-password credential row)
  • openspec validate update-admin-password-reset-on-startup --strict
  • Manual container check: start with UI_PASSWORD=passwordA, log in; stop, restart with UI_PASSWORD=passwordB, confirm passwordA no longer works and passwordB does.

Note: the existing apps/api/src/routes/test-agents.integration.test.ts failure is pre-existing on main and unrelated to this change.

Made with Cursor


Note

Medium Risk
Changes admin authentication behavior by resetting the stored admin credential to match UI_PASSWORD on each startup, which can unexpectedly override UI-rotated passwords after restarts. Touches login/credential handling but is scoped to the seeded admin user and includes unit coverage for the main branches.

Overview
Makes UI_PASSWORD authoritative for the admin account on every API/container startup. seedAdmin now reconciles the existing admin credential account: it verifies the stored hash against the current UI_PASSWORD and, when mismatched (or null), replaces it via internalAdapter.updatePassword; when it matches, it becomes a no-op.

Adds focused unit tests for the new reconciliation paths, and updates docs/specs to remove “initial” wording and document the rotate/recover workflow (change UI_PASSWORD, restart) plus the operational caveat that UI-changed passwords are overwritten on restart if they differ from UI_PASSWORD.

Reviewed by Cursor Bugbot for commit 61f7e88. Bugbot is set up for automated code reviews on this repo. Configure here.

Today seedAdmin() only writes a credential when no admin user exists,
so changing UI_PASSWORD after the first run silently has no effect.
This makes the env var the source of truth on every API startup:

- If the stored credential's hash already verifies UI_PASSWORD, do nothing.
- If it differs, replace it via internalAdapter.updatePassword and log it.
- Existing first-startup and missing-credential repair branches are kept.

Operational note: any UI-side password change is overwritten on the next
restart unless UI_PASSWORD is updated to match. This is the supported
recovery path when the admin password is forgotten — bump UI_PASSWORD,
restart the container, log in.

Spec: openspec/changes/update-admin-password-reset-on-startup
Tests: apps/api/src/lib/seed-admin.test.ts (5 cases)
Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app
Copy link
Copy Markdown

railway-app Bot commented May 16, 2026

🚅 Deployed to the archmax-pr-57 environment in archmax SemLayer

Service Status Web Updated (UTC)
archmax_standalone ✅ Success (View Logs) May 16, 2026 at 11:04 am
archmax_external_dbs ✅ Success (View Logs) May 16, 2026 at 11:04 am
archmax_standalone_with_volume ✅ Success (View Logs) May 16, 2026 at 11:04 am

@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-57 May 16, 2026 10:09 Destroyed
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default mode and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2f34081. Configure here.

Comment thread apps/api/src/lib/seed-admin.ts
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No security issues found in the changed files.

Checked the requested threat surfaces against this PR:

  • MCP endpoint auth: no MCP route/tool files changed; no new path bypasses token/project scoping.
  • Query execution sandboxing: no execute_query, DuckDB, or semantic-model query execution code changed.
  • Admin auth: BETTER_AUTH_SECRET remains validated at startup via Zod min length, Better Auth cookie attributes remain HttpOnly, production Secure, and SameSite=Lax; the new seed flow hashes UI_PASSWORD and does not log it.
  • API input validation: no Hono route handlers or request parsing paths changed.
  • Environment secrets: docs and code do not commit, return, or log secret values; logs mention only username/reset status.
  • Dependency exposure: no dependency manifests or lockfile changes.

Validation run:

  • pnpm --filter @archmax/api typecheck
  • pnpm exec vitest run apps/api/src/lib/seed-admin.test.ts
Open in Web View Automation 

Sent by Cursor Automation: archmax Security Review

@github-actions
Copy link
Copy Markdown

Docker image ready

docker pull ghcr.io/archmaxai/archmax:pr-57

The previous reconciliation collapsed "no credential row" and "credential
row with a null password" into a single createAccount() call, which would
have produced a duplicate credential account for the same user in the
second case (caught by Cursor Bugbot on PR #57).

Now:
- No credential row -> createAccount (unchanged).
- Credential row exists -> updatePassword via internalAdapter, regardless
  of whether the existing hash is null or just stale. updatePassword
  scopes by (userId, providerId="credential") so it updates in place.

Test for the null-password case is updated to assert the in-place update
(accounts stays length 1, updatePassword is called, no createAccount).

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-57 May 16, 2026 11:03 Destroyed
@tobias-gp tobias-gp merged commit 9a29a6f into main May 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant