fix(auth): make account lockout recoverable and visible

The lockout was checked before the password was verified, so a correct password on a locked
account returned the same generic invalid-credentials error as a wrong one. Users had no way to
tell the two apart, and the natural response was to keep retrying a password that was already
right.

Completing a password reset did not clear the lock either, so the one recovery path the UI
advertises appeared to fail for up to fifteen minutes after a successful reset.

Verify the password first and report the lockout, with the remaining wait, only when the supplied
password was correct. Someone guessing wrong passwords still gets the generic failure, so account
existence is not leaked. Clear the lock whenever a password is reset or changed, and give admins
an explicit unlock action for the case where waiting is not acceptable.
This commit is contained in:
neon
2026-08-09 05:05:50 +00:00
parent 9a349311cf
commit 6769bf4b0f
21 changed files with 366 additions and 58 deletions
+5 -28
View File
@@ -8,9 +8,7 @@ Closes #
- **Maintainer approval:** (link to the approval comment)
- **Assigned contributor:** @
- **Type:** bug fix | feature | refactor | docs | chore
- **UI changed:** yes | no
- **How I ran this:** `pnpm dev` | Docker | I did not run it
- **Evidence captured at commit:** (run `git rev-parse --short HEAD`)
## What changed
@@ -38,33 +36,13 @@ summarize it.
## Evidence
Upload a recording that proves the changed behavior works in the real client. Show the starting state,
the user action, and the final result. If the workflow crosses multiple clients, show the result in
each affected client.
Upload a video of yourself using the change in the app. Show what it looked like before, what you
did, and what happened.
Ready-for-review PRs without the required evidence will not be reviewed. Keep the PR in draft until
the evidence has been uploaded.
Record it yourself. Videos made by a model, or by a coding agent driving the app, do not count, and
the PR will be closed without review.
Examples:
- **Web:** Start from a page load, perform the affected workflow, and show the saved result after
refreshing the page.
- **KOReader:** Perform the affected workflow inside KOReader and show the result there. For sync
changes, also show the corresponding result in BookOrbit.
- **Kobo:** Perform the affected workflow on the device, run the sync, and show the resulting state on
the device and in BookOrbit where applicable.
- **Scanner, import, or metadata:** Add or modify a real book, run the operation through its normal
BookOrbit control, and show the resulting book state in the BookOrbit UI.
- **Server code used by a client:** Trigger the behavior from that client and show the result in the
client. Calling the endpoint directly is not end-to-end evidence.
"No UI changes," written testing claims, automated test output, logs, API requests, database queries,
and direct service calls are not evidence. They may support the recording but cannot replace it.
Evidence must match the final commit. If the workflow cannot be demonstrated in its real client or
device, keep the PR in draft and state what remains untested.
<!-- Upload the recording here. -->
<!-- Video here. -->
## Authorship and review
@@ -83,7 +61,6 @@ AI assistance is welcome on this project; unreviewed AI output is not. See the
- [ ] One focused change, linked above to an issue a maintainer approved
- [ ] I discussed my approach and was assigned the issue before writing code or opening this PR
- [ ] I read every line of the final diff
- [ ] `pnpm verify` passes against the final diff
- [ ] Tests added or updated per the [testing expectations](https://github.com/bookorbit/bookorbit/blob/main/docs/CONTRIBUTING.md#phase-7-write-and-run-tests)
- [ ] User-facing text uses Vue I18n keys added only to `client/src/locales/en.json`; non-English catalogs are unchanged, and accessibility was checked