convoy: scripts/rotate-admin-password.js — one-shot admin rotation (rotate-default-admin) #141

Merged
varutasu merged 1 commit from convoy/rotate-default-admin-script into main 2026-06-13 00:23:44 -04:00
varutasu commented 2026-06-13 00:15:57 -04:00 (Migrated from github.com)

Why

Closes the operator caveat from `drop-public-setup`: deployed envs that ran `npm run setup-db` BEFORE `ff80753` (2026-05-22) still carry the historical `admin123` bcrypt hash. The seed is idempotent (`ON CONFLICT DO NOTHING`), so `npm run setup-db` is a no-op on existing rows — those envs need a deliberate rotation step.

What

`scripts/rotate-admin-password.js`, a one-shot parameterized rotation script. Usage:

```bash
POSTGRES_URL= \
ADMIN_NEW_PASSWORD=$(openssl rand -base64 24) \
node scripts/rotate-admin-password.js
```

Safety guarantees:

  • Fail-loud-exits BEFORE opening a DB connection if `POSTGRES_URL` / `ADMIN_NEW_PASSWORD` are missing or the password is shorter than 12 chars.
  • Validates the target row EXISTS AND has `role = 'admin'` before touching it. Refuses to rotate non-admin rows.
  • Verifies the new bcrypt hash matches the supplied plaintext via `bcrypt.compare` post-update.
  • Never echoes the password to stdout / stderr / shell history.
  • Optional `ADMIN_EMAIL` override (defaults to `admin@deckhearth.com`).

Same ESM + `@neondatabase/serverless` shape as `scripts/migrations/2026-05-24-rename-admin-email.js` — keeps the "11 scripts/* using neon() directly" graveyard consistent (no new patterns to clean up in the queued `purge-neondatabase-serverless-fully` convoy).

Decision D1 — option B from the 3-option menu

Option Picked? Why
A. Close as no-op (defer rotation to manual app login) No Leaves a real-world residue if any pre-`drop-public-setup` env still exists. Audit is harder than shipping the script.
B. One-shot parameterized rotation script Yes ~120 LoC. Audit-trail-preserving (`updated_at` bump). Reusable. No new auth surface in the app.
C. First-login forced password reset flow in the app No Right product answer; heavier scope. Deferred as the queued `force-admin-password-reset-flow` convoy.

Out of scope

  • Test-user fixtures (alice/bob in `scripts/create-test-users.js`) — intentionally NOT rotated. Documented inline + in AGENTS.md.
  • First-login forced reset (the C-shaped feature) — deferred.

Test plan

  • `node --check scripts/rotate-admin-password.js` — syntax OK
  • `npm run lint` — clean (1 pre-existing unrelated warning)
  • `npm run test:run` — 118 tests pass
  • CI on this PR
  • Operator-side smoke (NOT in CI): rotate against a throwaway Neon branch DB, log in via the app, rotate again with a different password, log in again — skip if no convenient throwaway DB.

Made with Cursor

## Why Closes the operator caveat from \`drop-public-setup\`: deployed envs that ran \`npm run setup-db\` BEFORE \`ff80753\` (2026-05-22) still carry the historical \`admin123\` bcrypt hash. The seed is idempotent (\`ON CONFLICT DO NOTHING\`), so \`npm run setup-db\` is a no-op on existing rows — those envs need a deliberate rotation step. ## What \`scripts/rotate-admin-password.js\`, a one-shot parameterized rotation script. Usage: \`\`\`bash POSTGRES_URL=<prod-url> \\ ADMIN_NEW_PASSWORD=\$(openssl rand -base64 24) \\ node scripts/rotate-admin-password.js \`\`\` Safety guarantees: - Fail-loud-exits BEFORE opening a DB connection if \`POSTGRES_URL\` / \`ADMIN_NEW_PASSWORD\` are missing or the password is shorter than 12 chars. - Validates the target row EXISTS AND has \`role = 'admin'\` before touching it. Refuses to rotate non-admin rows. - Verifies the new bcrypt hash matches the supplied plaintext via \`bcrypt.compare\` post-update. - Never echoes the password to stdout / stderr / shell history. - Optional \`ADMIN_EMAIL\` override (defaults to \`admin@deckhearth.com\`). Same ESM + \`@neondatabase/serverless\` shape as \`scripts/migrations/2026-05-24-rename-admin-email.js\` — keeps the \"11 scripts/* using neon() directly\" graveyard consistent (no new patterns to clean up in the queued \`purge-neondatabase-serverless-fully\` convoy). ## Decision D1 — option B from the 3-option menu | Option | Picked? | Why | |---|---|---| | A. Close as no-op (defer rotation to manual app login) | No | Leaves a real-world residue if any pre-\`drop-public-setup\` env still exists. Audit is harder than shipping the script. | | B. One-shot parameterized rotation script | **Yes** | ~120 LoC. Audit-trail-preserving (\`updated_at\` bump). Reusable. No new auth surface in the app. | | C. First-login forced password reset flow in the app | No | Right product answer; heavier scope. Deferred as the queued \`force-admin-password-reset-flow\` convoy. | ## Out of scope - Test-user fixtures (alice/bob in \`scripts/create-test-users.js\`) — intentionally NOT rotated. Documented inline + in AGENTS.md. - First-login forced reset (the C-shaped feature) — deferred. ## Test plan - [x] \`node --check scripts/rotate-admin-password.js\` — syntax OK - [x] \`npm run lint\` — clean (1 pre-existing unrelated warning) - [x] \`npm run test:run\` — 118 tests pass - [ ] CI on this PR - [ ] Operator-side smoke (NOT in CI): rotate against a throwaway Neon branch DB, log in via the app, rotate again with a different password, log in again — skip if no convenient throwaway DB. Made with [Cursor](https://cursor.com)
vercel[bot] commented 2026-06-13 00:15:59 -04:00 (Migrated from github.com)

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tcg-vault Ready Ready Preview, Comment Jun 13, 2026 4:16am

Request Review

[vc]: #cas1wmB18ds1TRubRm+IaTHu84KY2CvlUL4U4fzATIo=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ0Y2ctdmF1bHQiLCJwcm9qZWN0SWQiOiJwcmpfRjZXOEVvRkd3Y0g3aWVGcnRvRlNlOXdVVkFhNSIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJ0Y2ctdmF1bHQtZ2l0LWNvbnZveS1yb3RhdGUtZDk2YzY1LXJhbmRhbGwtc3RpbGx3ZWxscy1wcm9qZWN0cy52ZXJjZWwuYXBwIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9yYW5kYWxsLXN0aWxsd2VsbHMtcHJvamVjdHMvdGNnLXZhdWx0LzlnTWZtellFZ3lxeXlzbXRGdjZtdHlmRmRqVksiLCJwcmV2aWV3VXJsIjoidGNnLXZhdWx0LWdpdC1jb252b3ktcm90YXRlLWQ5NmM2NS1yYW5kYWxsLXN0aWxsd2VsbHMtcHJvamVjdHMudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCJ9XSwicmVxdWVzdFJldmlld1VybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS92ZXJjZWwtYWdlbnQvcmVxdWVzdC1yZXZpZXc/b3duZXI9c3R3bC1sYWJzJnJlcG89dGNnLXZhdWx0JnByPTE0MSJ9 The latest updates on your projects. Learn more about [Vercel for GitHub](https://vercel.link/github-learn-more). | Project | Deployment | Actions | Updated (UTC) | | :--- | :----- | :------ | :------ | | [tcg-vault](https://vercel.com/randall-stillwells-projects/tcg-vault) | ![Ready](https://vercel.com/static/status/ready.svg) [Ready](https://vercel.com/randall-stillwells-projects/tcg-vault/9gMfmzYEgyqyysmtFv6mtyfFdjVK) | [Preview](https://tcg-vault-git-convoy-rotate-d96c65-randall-stillwells-projects.vercel.app), [Comment](https://vercel.live/open-feedback/tcg-vault-git-convoy-rotate-d96c65-randall-stillwells-projects.vercel.app?via=pr-comment-feedback-link) | Jun 13, 2026 4:16am | <a href="https://vercel.com/vercel-agent/request-review?owner=stwl-labs&repo=tcg-vault&pr=141" rel="noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://agents-vade-review.vercel.sh/request-review-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://agents-vade-review.vercel.sh/request-review-light.svg"><img src="https://agents-vade-review.vercel.sh/request-review-light.svg" alt="Request Review"></picture></a>
github-actions[bot] commented 2026-06-13 00:16:23 -04:00 (Migrated from github.com)

Pipeline Health

Build + CI gates

Gate Status
Vercel build (Preview) pass
CI: Lint pass
CI: Schema map fresh skipped
Preview smoke pass
Visual diff ⏭ skipped or pending

Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build).

Role reports

Role Status
Reviewer report pending
A11y audit pending
Design system audit pending

See individual comments above for details. This rollup updates automatically.

<!-- pipeline-rollup --> ## Pipeline Health ### Build + CI gates | Gate | Status | | --- | --- | | Vercel build (Preview) | ✅ pass | | CI: Lint | ✅ pass | | CI: Schema map fresh | ❌ skipped | | Preview smoke | ✅ pass | | Visual diff | ⏭ skipped or pending | _Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build)._ ### Role reports | Role | Status | | --- | --- | | Reviewer report | ⏳ pending | | A11y audit | ⏳ pending | | Design system audit | ⏳ pending | See individual comments above for details. This rollup updates automatically.
Sign in to join this conversation.
No description provided.