Add admin password change flow (closes #65) (#83)
All checks were successful
check / check (push) Successful in 4s
All checks were successful
check / check (push) Successful in 4s
Adds an authenticated, CSRF-protected flow that lets a user change their own password from the profile page.
## Route
- New `POST /password` under the `/user/{username}` group in `setupUserRoutes` (`internal/server/routes.go`). That group already applies `CSRF`, `NoCache`, and `RequireAuth`, so the new endpoint inherits all three.
## Handler (`internal/handlers/profile.go`)
- `HandlePasswordChange` enforces own-user access: the `{username}` path parameter must equal the session username (same 403 rule `HandleProfile` uses). This check plus the session lookup is factored into a shared `profileOwnerOrDeny` helper now used by both handlers.
- Parses `current_password`, `new_password`, and `confirm_password` (body size limited via `http.MaxBytesReader`).
- Verifies the current password with `database.VerifyPassword` against the stored hash.
- Requires the new password to be non-empty and equal to the confirmation.
- Hashes the new password with `database.HashPassword` — the same Argon2id helper used to bootstrap the admin user — and persists it on the user row. No new crypto.
- Re-renders the profile page with a clear success or error message. Wrong current password, empty new password, and mismatched confirmation are each rejected with their own message and leave the stored hash unchanged.
## Template (`templates/profile.html`)
- Adds a "Change Password" card with current / new / confirm password fields plus the hidden `csrf_token` (matching the login form's CSRF embedding).
- Renders success/error alerts using the existing `alert-success` / `alert-error` styles. No new CSS classes, so no Tailwind rebuild is required.
## Tests (`internal/handlers/profile_test.go`)
- `TestHandlePasswordChange_Success`: seeds a user, posts a valid change, asserts success message and that the stored hash changed and verifies against the new password.
- `TestHandlePasswordChange_WrongCurrentPassword`: posts a wrong current password, asserts the rejection message and that the stored hash is unchanged.
Validated with `docker build .` (fmt-check, lint, test, build) — exit 0.
Closes #65
Co-authored-by: sneak <sneak@sneak.berlin>
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #83
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #83.
This commit is contained in:
@@ -6,6 +6,18 @@
|
||||
<div class="max-w-4xl mx-auto px-6 py-12">
|
||||
<h1 class="text-2xl font-medium text-gray-900 mb-6">User Profile</h1>
|
||||
|
||||
{{if .SuccessMessage}}
|
||||
<div class="alert-success">
|
||||
<span>{{.SuccessMessage}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .ErrorMessage}}
|
||||
<div class="alert-error">
|
||||
<span>{{.ErrorMessage}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="card p-6">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="mr-4">
|
||||
@@ -43,6 +55,50 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card p-6 mt-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-3">Change Password</h3>
|
||||
<form method="POST" action="/user/{{.User.Username}}/password" class="space-y-6">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<div class="form-group">
|
||||
<label for="current_password" class="label">Current Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="current_password"
|
||||
name="current_password"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
class="input"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new_password" class="label">New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="new_password"
|
||||
name="new_password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="input"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirm_password" class="label">Confirm New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="input"
|
||||
>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary">Change Password</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<a href="/" class="btn-secondary">Back to Home</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user