Summary
The authenticated password-change endpoint verifies the current password but is not wired into the account-lockout / brute-force protections that the rest of the codebase uses. This is inconsistent with WebAuthn credential management, which verifies the same credential and does integrate lockout.
Severity: Medium (defense-in-depth / consistency; presupposes an authenticated session)
Affected code
src/main/java/com/digitalsanctuary/spring/user/api/UserAPI.java — updatePassword (~336).
src/main/java/com/digitalsanctuary/spring/user/service/UserService.java — checkIfValidOldPassword (~787): a bare passwordEncoder.matches().
- Contrast:
src/main/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPI.java — requireCurrentPasswordIfSet (~255) performs lock check + loginFailed() + loginSucceeded() around the same verification method.
Details
updatePassword → checkIfValidOldPassword does not check whether the account is locked, does not record a failure via LoginAttemptService.loginFailed(), does not reset the counter on success, and applies no rate limit. LoginAttemptService is not injected into UserAPI on this path, and a wrong current-password guess does not emit a Spring Security authentication-failure event, so the configured lockout is never engaged.
Impact
Within an authenticated session, current-password guesses are not bounded by the configured lockout. bcrypt strength 12 slows each attempt but does not impose a finite attempt limit (CWE-307).
Recommended fix
- Centralize critical-operation password reauthentication and reuse it across the password-change and WebAuthn-management paths.
- Check lock state before hashing, increment the failure counter atomically on a wrong guess (return
401/423), and reset it on success.
- Consider per-account/session/IP throttling around critical reauthentication.
- Low-risk change: mirror the three
LoginAttemptService calls already present in requireCurrentPasswordIfSet.
Identified during a code-first security assessment of the current main branch.
Summary
The authenticated password-change endpoint verifies the current password but is not wired into the account-lockout / brute-force protections that the rest of the codebase uses. This is inconsistent with WebAuthn credential management, which verifies the same credential and does integrate lockout.
Severity: Medium (defense-in-depth / consistency; presupposes an authenticated session)
Affected code
src/main/java/com/digitalsanctuary/spring/user/api/UserAPI.java—updatePassword(~336).src/main/java/com/digitalsanctuary/spring/user/service/UserService.java—checkIfValidOldPassword(~787): a barepasswordEncoder.matches().src/main/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPI.java—requireCurrentPasswordIfSet(~255) performs lock check +loginFailed()+loginSucceeded()around the same verification method.Details
updatePassword→checkIfValidOldPassworddoes not check whether the account is locked, does not record a failure viaLoginAttemptService.loginFailed(), does not reset the counter on success, and applies no rate limit.LoginAttemptServiceis not injected intoUserAPIon this path, and a wrong current-password guess does not emit a Spring Security authentication-failure event, so the configured lockout is never engaged.Impact
Within an authenticated session, current-password guesses are not bounded by the configured lockout. bcrypt strength 12 slows each attempt but does not impose a finite attempt limit (CWE-307).
Recommended fix
401/423), and reset it on success.LoginAttemptServicecalls already present inrequireCurrentPasswordIfSet.Identified during a code-first security assessment of the current
mainbranch.