There is no way to change the bootstrap admin password after first login. The profile page (internal/handlers/profile.go) is view-only. For an internet-facing single-admin service, being unable to rotate the initial password is a real gap.
Definition of done:
an authenticated, CSRF-protected form (on the profile page or a dedicated settings page) to change the current user's password
it verifies the current password with database.VerifyPassword before accepting a change
it stores a new Argon2id hash via the existing password helpers (internal/database/password.go)
success and error states are shown to the user
a handler test covers the happy path and a wrong-current-password rejection
Part of the road to 1.0 (see #33).
There is no way to change the bootstrap admin password after first login. The profile page (`internal/handlers/profile.go`) is view-only. For an internet-facing single-admin service, being unable to rotate the initial password is a real gap.
Definition of done:
- an authenticated, CSRF-protected form (on the profile page or a dedicated settings page) to change the current user's password
- it verifies the current password with `database.VerifyPassword` before accepting a change
- it stores a new Argon2id hash via the existing password helpers (`internal/database/password.go`)
- success and error states are shown to the user
- a handler test covers the happy path and a wrong-current-password rejection
clawbot
added this to the 1.0.0 milestone 2026-08-07 13:11:13 +02:00
routes.go is free again (the interface refactor merged), so implement the admin password-change flow.
Scope: internal/handlers/profile.go (or a new handler file in handlers), internal/server/routes.go, templates/profile.html, and a handlers test. Reuse the existing password helpers — do not roll new crypto.
Behaviour:
Add a CSRF-protected, authenticated POST route to change the current user's password. Put it in setupUserRoutes under /user/{username} — that group already has CSRF, RequireAuth, and NoCache — e.g. POST /password.
Handler: enforce own-user (the path username must equal the session username, the same rule HandleProfile uses for its 403); parse current_password, new_password, confirm_password; verify the current password with database.VerifyPassword; require new_password to be non-empty and equal to confirm_password; hash the new password with the SAME mechanism used to create the admin user (see internal/database/password.go and how database.go bootstraps the admin) and persist it on the user row; re-render the profile page with a clear success or error message.
Add the change-password form to templates/profile.html (current / new / confirm fields plus the CSRF token, following how the other forms embed CSRF).
Definition of done:
an authenticated user can change their own password via a CSRF-protected form
a wrong current password is rejected with a clear message; a mismatched confirmation is rejected; neither changes the stored hash
the new password is persisted as a fresh hash via the existing helper (a subsequent login with the new password would succeed)
handler tests cover the happy path and the wrong-current-password rejection
Gates and process:
make fmt; validate with docker build . (must exit 0)
branch from main named issue-65-password-change; commit subject ends with (closes #65)
open a PR (base main) and comment on it with the diff summary and the docker build . result; no AI/tooling references
Note: main was just updated (delivery refactor + retention reaper + NoCache). If docker build fails on something unrelated to your change, check whether an unmodified origin/main builds, and if it does not, STOP and report that main is broken rather than trying to fix it.
## Implementation instructions
`routes.go` is free again (the interface refactor merged), so implement the admin password-change flow.
Scope: `internal/handlers/profile.go` (or a new handler file in `handlers`), `internal/server/routes.go`, `templates/profile.html`, and a handlers test. Reuse the existing password helpers — do not roll new crypto.
Behaviour:
- Add a CSRF-protected, authenticated POST route to change the current user's password. Put it in `setupUserRoutes` under `/user/{username}` — that group already has `CSRF`, `RequireAuth`, and `NoCache` — e.g. `POST /password`.
- Handler: enforce own-user (the path username must equal the session username, the same rule `HandleProfile` uses for its 403); parse `current_password`, `new_password`, `confirm_password`; verify the current password with `database.VerifyPassword`; require `new_password` to be non-empty and equal to `confirm_password`; hash the new password with the SAME mechanism used to create the admin user (see `internal/database/password.go` and how `database.go` bootstraps the admin) and persist it on the user row; re-render the profile page with a clear success or error message.
- Add the change-password form to `templates/profile.html` (current / new / confirm fields plus the CSRF token, following how the other forms embed CSRF).
Definition of done:
- an authenticated user can change their own password via a CSRF-protected form
- a wrong current password is rejected with a clear message; a mismatched confirmation is rejected; neither changes the stored hash
- the new password is persisted as a fresh hash via the existing helper (a subsequent login with the new password would succeed)
- handler tests cover the happy path and the wrong-current-password rejection
Gates and process:
- `make fmt`; validate with `docker build .` (must exit 0)
- branch from `main` named `issue-65-password-change`; commit subject ends with ` (closes #65)`
- open a PR (base `main`) and comment on it with the diff summary and the `docker build .` result; no AI/tooling references
- Note: `main` was just updated (delivery refactor + retention reaper + NoCache). If `docker build` fails on something unrelated to your change, check whether an unmodified `origin/main` builds, and if it does not, STOP and report that `main` is broken rather than trying to fix it.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of the road to 1.0 (see #33).
There is no way to change the bootstrap admin password after first login. The profile page (
internal/handlers/profile.go) is view-only. For an internet-facing single-admin service, being unable to rotate the initial password is a real gap.Definition of done:
database.VerifyPasswordbefore accepting a changeinternal/database/password.go)Implementation instructions
routes.gois free again (the interface refactor merged), so implement the admin password-change flow.Scope:
internal/handlers/profile.go(or a new handler file inhandlers),internal/server/routes.go,templates/profile.html, and a handlers test. Reuse the existing password helpers — do not roll new crypto.Behaviour:
setupUserRoutesunder/user/{username}— that group already hasCSRF,RequireAuth, andNoCache— e.g.POST /password.HandleProfileuses for its 403); parsecurrent_password,new_password,confirm_password; verify the current password withdatabase.VerifyPassword; requirenew_passwordto be non-empty and equal toconfirm_password; hash the new password with the SAME mechanism used to create the admin user (seeinternal/database/password.goand howdatabase.gobootstraps the admin) and persist it on the user row; re-render the profile page with a clear success or error message.templates/profile.html(current / new / confirm fields plus the CSRF token, following how the other forms embed CSRF).Definition of done:
Gates and process:
make fmt; validate withdocker build .(must exit 0)mainnamedissue-65-password-change; commit subject ends with(closes #65)main) and comment on it with the diff summary and thedocker build .result; no AI/tooling referencesmainwas just updated (delivery refactor + retention reaper + NoCache). Ifdocker buildfails on something unrelated to your change, check whether an unmodifiedorigin/mainbuilds, and if it does not, STOP and report thatmainis broken rather than trying to fix it.clawbot referenced this issue2026-08-17 22:44:00 +02:00