Skip to content

docs: change password - #9

Merged
stsepelin merged 6 commits into
mainfrom
docs/change-password
Aug 21, 2026
Merged

stsepelin merged 6 commits into
mainfrom
docs/change-password

Conversation

@stsepelin

@stsepelin stsepelin commented Aug 21, 2026 •

Copy link
Copy Markdown
Owner

Documents POST /auth/password from stsepelin/lukk#27, and moves the roadmap item from Planned to Shipped.

Leads with why the endpoint asks for the current password, since that's the entire security story and isn't self-evident — a stolen token already grants the account until it expires; if it could change the password it would grant the account permanently.

Explains the shared throttle and the session sweep rather than just stating them, and is honest that lukk-nuxt has no composable yet, showing the useLukkFetch call to use meanwhile.

Cross-linked from password-reset, the configuration feature table, and the events table.

pnpm docs:build passes with ignoreDeadLinks: false.

Greptile Summary

Documents the authenticated change-password endpoint and moves the feature into the shipped roadmap.

  • Adds endpoint, security rationale, throttling, session-revocation, event, configuration, and temporary Nuxt usage guidance.
  • Adds navigation and cross-links from password reset, configuration, events, and the roadmap.
  • The temporary Nuxt example currently targets the app-API transport and overstates automatic form-error handling.

Confidence Score: 3/5

The documentation should not merge until the Nuxt example uses the correct authentication transport and accurately explains validation-error handling.

The server documentation is broadly consistent, but copying the client example directs the request through the application API base rather than the lukk auth endpoint, and its direct fetch call cannot provide the promised automatic field-error mapping.

Files Needing Attention: docs/change-password.md, docs/configuration.md, docs/events.md

Important Files Changed

Filename Overview
docs/change-password.md Adds the primary endpoint guide, but the Nuxt fallback uses the wrong transport base and promises error mapping absent from the shown code.
docs/configuration.md Adds the feature-table entry while leaving the adjacent canonical feature snippet incomplete.
docs/events.md Adds the correct event class under the wrong event category.
docs/password-reset.md Adds a clear cross-link distinguishing authenticated password changes from forgotten-password resets.
docs/roadmap.md Moves change password from planned work to the shipped feature list.
docs/.vitepress/config.mts Adds the new change-password page to feature navigation.

Fix all with Greploop Fix All in Claude Code

Prompt To Fix All With AI
### Issue 1
docs/change-password.md:93
**Wrong transport for auth endpoint**

When a Nuxt user copies this example, `useLukkFetch` resolves `/password` against the application API base rather than the lukk authentication base, causing the request to miss the documented `POST /auth/password` endpoint or invoke an unrelated application route.

### Issue 2
docs/change-password.md:103
**Automatic form mapping is absent**

The shown code calls `useLukkFetch` directly and never submits a `useLukkForm`, so a 422 rejects with a `LukkError` instead of mapping validation errors onto fields “for free.” Users copying this example will not receive the documented form-error behavior.

### Issue 3
docs/configuration.md:194
**Feature snippet omits new toggle**

The table adds `change_password`, but the adjacent feature-toggle snippet still lists only the pre-existing keys. This leaves the copyable configuration reference incomplete and obscures how to disable an endpoint that is enabled by default.

### Issue 4
docs/events.md:98
**Package event is misclassified**

`Lukk\Events\PasswordChanged` is placed in the table described as containing standard Laravel authentication events, while the page otherwise separates `Lukk\Events\*` package events from `Illuminate\Auth\Events\*` framework events. This makes the event taxonomy misleading even though the class name itself is correct.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "docs: change password" | Re-trigger Greptile

Greptile also left 4 inline comments on this PR.

New page for `POST /auth/password`, and moves the roadmap item from Planned to
Shipped.

Leads with why the endpoint asks for the current password, because that is the
entire security story and it is not self-evident: a stolen access token already
grants the account until it expires, and if it could also change the password it
would grant the account permanently while locking the owner out of their own
recovery.

Explains the shared throttle rather than just stating it — one budget with
step-up, because both verify the same secret and separate allowances would only
widen the total. Same for the session sweep: which session survives, and that
the one to keep comes from the caller's own verified token so it cannot be
aimed at someone else's.

The client section is honest that `lukk-nuxt` has no composable for this yet and
shows the `useLukkFetch` call to use meanwhile.

Also cross-links from password-reset (a signed-in user wants the other page),
the configuration feature table, and the events table — `PasswordChanged` is
deliberately distinct from Laravel's `PasswordReset`, since "your password was
changed" and "your password was reset" mean different things to a reader and an
unexpected one of either is how takeover gets noticed.
Comment thread docs/change-password.md Outdated
Comment thread docs/change-password.md Outdated
Comment thread docs/configuration.md Outdated
Comment thread docs/events.md Outdated
The page said the client had no composable and showed a `useLukkFetch` call —
true when written, not any more (stsepelin/lukk-js#46). Documents the composable
and keeps a `useLukkForm` example, since the 422 validation bag is the thing
most callers actually need to wire up.

Two corrections from the security review of the server side:

A success releases the `login` counter as well as `confirm`. Without that, a
user who was being brute-forced, noticed, and changed their password stayed
locked out of login everywhere else — the page now says so, because "changing
your password fixes it" is exactly what a reader would otherwise assume.

The session sweep is not unconditional: a token carrying no `fid` identifies no
session to preserve, so every session is revoked rather than none. The page
described only the common case.
A second call while one is in flight rejects with 409 and sends nothing. Worth
documenting rather than leaving as an implementation detail, because the reason
is not obvious: the second request would carry a `current_password` the first
has already replaced, so it reads as a wrong password and spends one of the
account's consecutive-failure attempts.
My change-password example was wrong. It showed `form.post('/password')` for
mapping a 422 onto fields, but `useLukkForm` submits through `useLukkFetch`,
which resolves against `apiBaseURL` — the app's API base (`api.target`, or the
proxy mount in BFF mode), not lukk's auth base. So the call would have reached
the reader's own `/password` route, or 404'd, and never `POST /auth/password`.

Replaced with the pattern that works: catch the `LukkError` and read its
`errors` bag, which is what the composable rejects with anyway.

The root cause was upstream of my mistake — `use-lukk-form.md` never said which
base its URLs resolve against, and two of its examples (`/register`,
`/password`) read exactly like lukk auth endpoints. It now states the split
before any example uses a URL: useLukkForm and useLukkFetch are for endpoints
you own; lukk's are reached through the composables, which hold the right base.
Both examples now say so inline.
I added the table row and not the copyable snippet beside it, so the one thing a
reader actually pastes was missing the only default-ON flag in the block — the
one case where you need the key precisely because you might want to turn it off.

Also reordered the table row to sit after `lockout`, matching both the snippet
and config/lukk.php. It had been appended at the end, so the two lists read in
different orders despite listing the same keys.

Checked programmatically rather than by eye: the snippet and the table now hold
the same keys in the same order, and that order matches the config file.
I added it as a row in the table introduced as "lukk also dispatches standard
Laravel auth events" — which it isn't. Every other `Lukk\Events\*` class on the
page has its own subsection under Security events; this was the one exception,
and it made the taxonomy say something false about where the class comes from.

Now a `### PasswordChanged` subsection alongside the others, with a listener
example matching their format. The contrast with `Illuminate\Auth\Events\
PasswordReset` is kept and now reads as an explicit cross-reference rather than
as two adjacent rows the reader has to tell apart — which was the only reason
the row was there.

Verified all six package events are documented and none is left in the framework
table.
@stsepelin
stsepelin merged commit 95fb663 into main Aug 21, 2026
2 checks passed
@stsepelin
stsepelin deleted the docs/change-password branch August 21, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant