Skip to content

feat(cli): add tiger user:password — console password reset - #186

Merged
WebTigers merged 1 commit into
mainfrom
feat/cli-user-password
Aug 25, 2026
Merged

feat(cli): add tiger user:password — console password reset#186
WebTigers merged 1 commit into
mainfrom
feat/cli-user-password

Conversation

@WebTigers

Copy link
Copy Markdown
Owner

Why

The console had no password reset. That leaves two real dead ends:

  • An install that can't send email. A fresh or dev box with no MTA silently drops the reset mail — and the web flow deliberately gives no hint (no account enumeration), so it looks like it worked and nothing ever arrives.
  • A lone locked-out admin with no second account to rescue them.

Found this the hard way on dev-com: mail.transport defaulting to PHP mail(), php sendmail_path pointing at /usr/sbin/sendmail, and that binary not existing.

What

vendor/bin/tiger user:password [--user=<email|username>] [--password=] [--force]

  • Resolves the target with Tiger_Model_User::findByIdentifier, so --user= accepts an email or a username.
  • Writes through Tiger_Model_UserCredential::setPassword — the same model the web reset uses.
  • Enforces the install's live Tiger_Policy_Password (length / complexity / reuse).
  • Prompts for anything not passed as a flag; --password= keeps it non-interactive.

This is a different door, not a shortcut. Going through the same model means the pepper, the history archive, and reuse-prevention all still apply, and the old password stops working. --force overrides the policy only, never the hashing — so a locked-out admin can always get back in, and a forced password is still peppered and bcrypted rather than stored raw.

Tests

bin/tiger is a procedural script, so the tests exercise the sequence the command performs (resolve → policy → setPassword) — which is where the guarantees actually live:

  • identifier resolution by both email and username, and null for an unknown one
  • the new password authenticates and the old one no longer does
  • a reset replaces the credential row (idempotent — never accumulates)
  • the outgoing hash is archived, so reuse-prevention still catches the old password
  • a --forced password is never stored in plaintext and still verifies normally

Full suite: 2048 tests, 0 failures, 15 deprecations — the existing baseline (2043 + 5 new).

The console had no way to reset a password. That left two real dead ends: an
install whose email can't be delivered (a fresh or dev box with no MTA — the
web reset silently no-ops, and by design gives no hint it did) and a lone
locked-out admin with no second account to rescue them.

`user:password` resolves the target with `Tiger_Model_User::findByIdentifier`,
so `--user=` takes an email or a username, then writes through
`Tiger_Model_UserCredential::setPassword` — the SAME model the web reset uses.
That is the point of the command: it is a different door, not a shortcut. The
pepper, the history archive, and reuse-prevention all still apply, and the old
password stops working.

The install's live `Tiger_Policy_Password` is enforced by default; `--force`
overrides the POLICY only, never the hashing, so a locked-out admin can always
get back in without a forced password ever being stored raw.

Tests exercise the command's sequence (resolve -> policy -> setPassword), since
`bin/tiger` is a procedural script: identifier resolution by both email and
username, old-password revocation, the single-row idempotence of a reset,
history archiving driving reuse-prevention, and that a forced password is still
peppered and hashed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXgMENcwa4Q8yCJaHpjHpf
@WebTigers
WebTigers merged commit ba0c6a6 into main Aug 25, 2026
13 checks passed
@WebTigers
WebTigers deleted the feat/cli-user-password branch August 25, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant