docs: password change plan uses the AD lockout threshold

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
This commit is contained in:
2026-10-18 10:03:07 +02:00
co-authored by Claude Sonnet 5.5
parent b0f21f24e7
commit c3b85799a3
@@ -39,7 +39,7 @@ context:
| Policy shown | Domain policy, or a fine-grained policy that applies | Minimum length, character kinds when complexity is on, how many old passwords are remembered, minimum age, expiry | Fine-grained policy not readable: rules shown with a note that a stricter rule may apply |
| Success | Correct current, new password accepted by AD | AD changed; other sessions of the user ended, this one kept; `password.changed` audited; e-mail queued; redirect to a done page | n/a |
| Wrong current | Current password does not match | "The current password is not correct." Nothing changed; counts toward the limit; failure audited | No AD change is attempted |
| Limit reached | 5 failed current-password checks | "Too many attempts. Please wait a few minutes." (HTTP 429), no AD call, no mail; throttled attempt audited | Recovers after the pause or after a success |
| Limit reached | The effective number of failed current-password checks (5, or the AD lockout threshold minus 1 when that is lower) | "Too many attempts. Please wait a few minutes." (HTTP 429), no AD call, no mail; throttled attempt audited | Recovers after the pause or after a success |
| Mismatch or empty | New and repeat differ, a field empty, over 256 characters | Error under the field and in a summary at the top; nothing sent to AD; not counted | n/a |
| Same as current | New equals current | "The new password must be different." Not sent to AD | n/a |
| Locally detectable rule | Shorter than the minimum, or too few character kinds | Specific reason ("The password is too short.") after the current password was verified; not sent to AD; not counted | n/a |
@@ -64,7 +64,7 @@ context:
## Tasks & Acceptance
**Execution:**
- [ ] `profile/services.py` -- the ordered flow, throttle counting only for a wrong current password, one transaction for AD call, audit, mail and session ending -- AD-4, AD-8
- [ ] `profile/services.py` -- the ordered flow, throttle counting only for a wrong current password with the limit derived from the policy's lockout threshold, one transaction for AD call, audit, mail and session ending -- AD-4, AD-8
- [ ] `profile/services.py` -- policy in words and the local hints (minimum length, character kinds) -- FR-5
- [ ] `profile/forms.py`, `views.py`, templates, menu entry -- the page, the done page, the error mapping to messages and status codes -- AD-17
- [ ] mail template `password_changed` in German formal with the time and zone, no password
@@ -75,7 +75,7 @@ context:
**Acceptance Criteria:**
- Given a signed-in user, when they open `/password`, then the policy rules from the directory are listed before any field and the page states the effect.
- Given the correct current password and an acceptable new one, when they submit, then the directory password changed, their other sessions are gone and this one stays, an audit entry exists and one mail is queued.
- Given a wrong current password five times, when a sixth request arrives, then it is refused with 429 before any directory call and the attempt is audited.
- Given a wrong current password as often as the effective limit (5, or lower when AD locks sooner), when the next request arrives, then it is refused with 429 before any directory call and the attempt is audited.
- Given AD refuses the new password, when they submit, then nothing changed, no session ended, no mail queued, and the message is in plain words.
- Given any request, then no password appears in a log line, an audit entry, the session, the database or the rendered response.
- Given a failure after the AD call inside the transaction, then the error is logged at error level with the change details (AD-4) and the user is told the outcome is unknown.
@@ -84,7 +84,7 @@ context:
**Counting.** Only a wrong current password counts toward the limit (FR-6). Policy rejections and local mistakes do not: they happen after the current password was proven, and punishing them would lock out people who are only choosing a password. The limit uses the existing `password-change` endpoint counters per user and per IP with the default of 5 failures and a 15-minute pause.
**Account lockout.** `verify_password` binds as the user, so every wrong attempt also raises AD's own bad-password counter. The portal limit must stay below the domain's lockout threshold or the portal can lock the account. The threshold is not read by the directory adapter today. Decision needed (below).
**Account lockout.** `verify_password` binds as the user, so every wrong attempt also raises AD's own bad-password count, and attempts made elsewhere (for example at authentik) count too. The directory adapter therefore reads `lockoutThreshold` and `lockoutObservationWindow` (or the fine-grained `msDS-Lockout*` values) with the password policy (built first, on its own branch, and merged before this ticket). The portal's limit is `min(5, threshold - 1)` and at least 1; with a threshold of 0 (the domain never locks) it stays 5. The pause is at least the observation window. A threshold of 1 cannot be protected from the portal; the page still works and the audit notes it.
**One verification per change.** The portal verifies the current password once, then calls the adapter's `change_password`, which authenticates as the user again. That is a second correct bind, not a second chance for an attacker.