5.0.1: distinct WebAuthn re-auth HTTP statuses (#8) + review doc notes (#5, #6)#321
Conversation
…failures Address review item #8: requireCurrentPasswordIfSet returned HTTP 400 for missing field, wrong password, AND locked account alike, so clients could not tell them apart. - Add WebAuthnReauthenticationException (wrong current password -> 401) and WebAuthnAccountLockedException (locked -> 423), both extending WebAuthnException. - requireCurrentPasswordIfSet now throws the specific subtype; missing/blank field stays a plain WebAuthnException (400). Brute-force lockout wiring unchanged. - Map the subtypes in WebAuthnManagementAPIAdvice (401 / 423); base handler still 400. - Tests: advice status-mapping unit test; tighten API unit tests to the subtypes; integration 'incorrect password' assertions 400 -> 401. - CHANGELOG/MIGRATION document the refined codes (endpoints are new in 5.0.0). Also fold in two review doc-notes: - #5: document the @EntityGraph two-level Cartesian-join trade-off on findWithRolesByEmail. - #6: note that an eagerly-loaded token.getUser() still has LAZY roles (use findWithRolesByEmail for authorities).
Code Review — PR #321: 5.0.1 WebAuthn HTTP status refinementNote: This PR is already merged; this review is retrospective and intended as notes for future reference. OverviewThis patch does two things cleanly:
Both are focused, low-blast-radius changes. The core auth logic is untouched. Code Quality and Style ✅
Potential Issue: Integration test gap for HTTP 423
|
There was a problem hiding this comment.
Pull request overview
This PR refines WebAuthn credential-management “re-authentication required” failure handling so clients can distinguish malformed requests vs authentication failures vs lockout, and it closes deferred documentation notes for 5.0.1.
Changes:
- Introduces
WebAuthnReauthenticationException(401) andWebAuthnAccountLockedException(423) and maps them viaWebAuthnManagementAPIAdvice. - Updates WebAuthn credential-management unit/integration tests to expect 401 for incorrect
currentPasswordand adds advice mapping unit tests. - Documents
@EntityGraphjoin trade-offs and clarifies tokengetUser()eager-load vs lazy roles; updates MIGRATION/CHANGELOG for the new HTTP status behavior.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPI.java | Throws distinct exception subtypes for locked vs wrong-password vs missing-field in requireCurrentPasswordIfSet. |
| src/main/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPIAdvice.java | Adds exception handlers mapping new subtypes to 423/401 while keeping base WebAuthnException at 400. |
| src/main/java/com/digitalsanctuary/spring/user/exceptions/WebAuthnAccountLockedException.java | New exception subtype representing lockout (intended 423 mapping). |
| src/main/java/com/digitalsanctuary/spring/user/exceptions/WebAuthnReauthenticationException.java | New exception subtype representing incorrect current password (intended 401 mapping). |
| src/main/java/com/digitalsanctuary/spring/user/persistence/model/PasswordResetToken.java | Adds note clarifying eager user loading vs lazy roles for authorities. |
| src/main/java/com/digitalsanctuary/spring/user/persistence/model/VerificationToken.java | Adds note clarifying eager user loading vs lazy roles for authorities. |
| src/main/java/com/digitalsanctuary/spring/user/persistence/repository/UserRepository.java | Documents entity-graph join/cross-product implications for findWithRolesByEmail. |
| src/test/java/com/digitalsanctuary/spring/user/api/WebAuthnFeatureEnabledIntegrationTest.java | Updates integration assertions for incorrect current password to expect 401. |
| src/test/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPIAdviceTest.java | Adds unit tests for the intended status mapping per exception subtype. |
| src/test/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPITest.java | Updates unit tests to assert the new exception subtypes are thrown for locked/wrong-password cases. |
| MIGRATION.md | Updates re-auth section to describe refined 400/401/423 behavior. |
| CHANGELOG.md | Adds 5.0.1 entry describing distinct HTTP statuses for WebAuthn re-auth failures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| **Behavior when the account has a password** (status codes refined in **5.0.1** — see note below): | ||
| - Missing/blank `currentPassword` → `HTTP 400 Bad Request`, message *"Current password is required to change authentication methods."* — nothing is mutated. | ||
| - Incorrect `currentPassword` → `HTTP 401 Unauthorized`, message *"Current password is incorrect."* — nothing is mutated. | ||
| - Account locked (too many failed attempts) → `HTTP 423 Locked`, message *"Account is locked due to too many failed attempts…"* — nothing is mutated. |
| void shouldRejectDeleteWhenCurrentPasswordIncorrectForPasswordAccount() throws Exception { | ||
| String payload = "{\"currentPassword\":\"wrongPassword!\"}"; | ||
| mockMvc.perform(delete("/user/webauthn/credentials/cred-1").with(user(TEST_EMAIL).roles("USER")).with(csrf()) | ||
| .contentType(MediaType.APPLICATION_JSON).content(payload)).andExpect(status().isBadRequest()) | ||
| .contentType(MediaType.APPLICATION_JSON).content(payload)).andExpect(status().isUnauthorized()) | ||
| .andExpect(jsonPath("$.message", containsString("Current password is incorrect"))); |
Closes out the deferred review notes so 5.0.1 ships clean.
#8 — WebAuthn credential-change re-auth: finer HTTP statuses
requireCurrentPasswordIfSetpreviously returned 400 for missing field, wrong password, and locked account alike — clients couldn't distinguish them. Now:currentPasswordcurrentPasswordWebAuthnReauthenticationException(401) +WebAuthnAccountLockedException(423), both extendingWebAuthnException(base stays 400).Doc notes
@EntityGraphtwo-level Cartesian-join trade-off onfindWithRolesByEmail.token.getUser()still has LAZY roles (usefindWithRolesByEmailfor authorities).token.userstays EAGER by design (kept consistent with theRole.privilegesdecision).Not changed (verified already-fine)
RolePrivilegeSetupService warn log (already mentions multi-node startup),
globalErrorKeynull path (already null-safe),OnRegistrationCompleteEventsuper() (correct defensive code).Test Plan
./gradlew clean testgreen.WebAuthnManagementAPIAdviceTestasserts 400/401/423/404 mapping; API unit tests tightened to the new subtypes; integration "incorrect" assertions now expect 401.