docs(authentik): password change is a prompt field, not a PasswordStage
The UserWriteStage writes prompt_data['password'] via user.set_password(). A PasswordStage only authenticates the current password. Document the correct mechanism (password field on the default-user-settings prompt + empty-password guard policy) and the throwaway-user verification.
This commit is contained in:
1 parent
8108dc4254
commit
650dcba0ea
1 file changed
+24
-11
@@ -285,20 +285,33 @@ insertion order):
|
|||||||
|
|
||||||
| order | stage | type | what it does |
|
| order | stage | type | what it does |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 20 | `default-user-settings` | `PromptStage` | intro prompt |
|
| 20 | `default-user-settings` | `PromptStage` | name / email / locale **+ password** |
|
||||||
| 100 | `default-user-settings-write` | `UserWriteStage` | edit name / email / locale |
|
| 100 | `default-user-settings-write` | `UserWriteStage` | writes the prompt data to the user |
|
||||||
| 101 | `default-user-settings-password` | `PasswordStage` | change password (`InbuiltBackend`) |
|
|
||||||
|
|
||||||
**The `PasswordStage` was missing until 2026-09-25.** The stock flow only
|
**Password change is a prompt field, not a stage.** The `UserWriteStage`
|
||||||
carried the prompt + `UserWriteStage`, so users could edit name/email/locale
|
writes whatever the preceding `PromptStage` collected: for the key `password`
|
||||||
but had no way to change their password. The fix: create a `PasswordStage`
|
it calls `user.set_password(value)` (`stages/user_write/stage.py`). So the fix
|
||||||
(backend `authentik.core.auth.InbuiltBackend`) and bind it to the flow. Two
|
is a `password` field on the `default-user-settings` prompt
|
||||||
ORM gotchas: `Flow.stages.add()` fails on this through model
|
(`field_key=password`, `type=password`, `required=False`, order 204) — NOT a
|
||||||
|
`PasswordStage`. A `PasswordStage` is an *authentication* stage: its
|
||||||
|
`validate_password` runs `authenticate()` against the backends to check the
|
||||||
|
*current* password; it never sets a new one. (A `PasswordStage` was added on
|
||||||
|
2026-09-25 and removed the same day for exactly this reason.)
|
||||||
|
|
||||||
|
Two ORM gotchas: `Flow.stages.add()` fails on this through model
|
||||||
(`Can't bulk create a multi-table inherited model` — `FlowStageBinding` is
|
(`Can't bulk create a multi-table inherited model` — `FlowStageBinding` is
|
||||||
MTI), so create the `FlowStageBinding` row directly with an explicit `order`;
|
MTI), so create the `FlowStageBinding` row directly with an explicit `order`;
|
||||||
and `PasswordStage` requires the `backends` array (a bare `name` insert hits a
|
and a prompt field is a `Prompt` row linked through `PromptStageField`
|
||||||
`NOT NULL` violation). Verified via the `FlowPlanner` — the plan now lists all
|
(`prompt_stage` + `prompt`; the order comes from `Prompt.order`).
|
||||||
three stages.
|
|
||||||
|
**A blank password must not set an empty password.** `set_password("")` would
|
||||||
|
make the account log in with no password, so a validation policy
|
||||||
|
(`default-user-settings-no-empty-password`, bound to the prompt via
|
||||||
|
`PromptStageValidationPolicy`) denies the form when `prompt_data["password"]`
|
||||||
|
is `""` and allows it when set. Verified with the `PolicyEvaluator`
|
||||||
|
(empty → deny, set → allow, absent → allow) and end-to-end by driving
|
||||||
|
`UserWriteStageView.update_user` on a throwaway user (new password valid, old
|
||||||
|
invalid).
|
||||||
|
|
||||||
## Outpost claim cache (`proxysession`) — 403 after a group add
|
## Outpost claim cache (`proxysession`) — 403 after a group add
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user