feat(server): admin-initiated password reset#74
Merged
Conversation
Add POST /api/v1/admin/users/{id}/reset-password (admin only). Mirrors
the invite flow: an admin sets a new temporary password, the server flags
the user must_change_password=1 and revokes the target's existing
sessions, so on next login the existing ChangePasswordPage forces a new
password (same as first login). Any admin may reset any user, including
another admin — a reset neither demotes nor disables anyone, so no
last-admin guard applies.
- users.AdminResetPassword: sibling of UpdatePassword that SETS the flag
instead of clearing it.
- Handler gates on mustBeAdmin, validates new_password (>=8), revokes
sessions via Sessions.DeleteAllForUser, returns the updated user.
- Dashboard: ResetPasswordDialog (cloned from InviteUserDialog) wired into
the users table, plus the useResetUserPassword hook.
- Documented in doc/openapi.yaml (source of truth).
Mounted directly in router.go, matching the existing embedding-provider
admin routes — the committed openapi.gen.go predates the pinned
oapi-codegen, so it is not regenerated here. The follow-up commit
reconciles that drift and moves these routes onto the generated mux.
Purely additive: no DB migration, no changed/removed endpoints or
behavior; the dashboard ships inside the server binary so there is no
version skew. Backward-compatible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he generated mux The committed internal/httpapi/openapi/openapi.gen.go had drifted from the pinned oapi-codegen (v2.7.0): it predated the embedding-provider endpoints, which were added to doc/openapi.yaml but mounted directly in router.go as a stopgap. The previous commit added the password-reset endpoint the same way. Regenerate gen.go from the spec so the generated mux owns all of them, and drop the direct mounts (keeping both would double-register the routes and panic chi at startup): - TestEmbeddingProvider and ResetUserPassword now take their path params from the generated wrapper (kind/id) instead of chi.URLParam; chi import dropped from auth.go, swapped for the openapi pkg in admin_embeddings.go. - router.go no longer hand-mounts the embedding-provider or reset-password routes — HandlerFromMux registers them from the spec. - The embedded /openapi.json blob now lists these endpoints (previously it under-reported the API in Swagger UI / docs). Incidental churn: regeneration also reorders some struct fields and adds .Valid() enum helpers that the stale file lacked. `make openapi-check` is green again. No wire/behavior change — same routes, methods, and shapes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-depth) The ≥8 length check previously lived only in the HTTP handler. Move the policy behind a shared users.MinPasswordLength constant and also enforce it in the AdminResetPassword service method, so a non-HTTP caller can't set a too-short password. The handler now references the constant instead of a literal. Adds service-level tests for the set-flag and reject-short paths. Pre-existing Create/UpdatePassword keep their empty-only checks (untouched to avoid affecting bootstrap and other flows); hardening those uniformly is a separate concern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an admin-only password reset for existing users, plus an incidental
reconciliation of the OpenAPI codegen drift.
POST /api/v1/admin/users/{id}/reset-password— an admin sets a new temporarypassword; the server flags the user
must_change_password=1and revokes thetarget's existing sessions, so on next login the existing
ChangePasswordPageforces a new password (same as first login). Any admin may reset any user,
including another admin (a reset neither demotes nor disables anyone, so no
last-admin guard applies). Dashboard gets a "Reset password" action in the
users table.
Why
The only way to set a forced-change password was at user creation
(
InviteUserDialog). There was no equivalent for an existing user who islocked out or requests a reset. This mirrors the invite flow end-to-end and
reuses the existing first-login
must_change_passwordmachinery.How
Two commits, each builds and tests green independently:
feat(server): admin-initiated password reset— the feature.users.AdminResetPassword: sibling ofUpdatePasswordthat sets theflag instead of clearing it.
mustBeAdmin, validatesnew_password(≥8), revokessessions via
Sessions.DeleteAllForUser, returns the updated user.ResetPasswordDialog(cloned fromInviteUserDialog) + theuseResetUserPasswordhook, wired into the users table.chore(openapi): regenerate gen.go and route admin endpoints through the generated mux— the drift fix.openapi.gen.gohad drifted from the pinned oapi-codegen(v2.7.0): it predated the embedding-provider endpoints, which were in the
spec but hand-mounted in
router.goas a stopgap. The feature commitadded the reset route the same way.
gen.gosoHandlerFromMuxowns all of them andremoves the direct mounts (keeping both would double-register and panic
chi).
TestEmbeddingProvider/ResetUserPasswordnow take path params fromthe generated wrapper instead of
chi.URLParam. The embedded/openapi.jsonblob now lists these endpoints (Swagger UI previouslyunder-reported the API).
make openapi-checkis green again..Valid()enum helpers the stale file lacked.Backward-compatible / safe to ship: purely additive — no DB migration, no
changed/removed endpoints or behavior, no wire change in commit 2 (same routes,
methods, shapes). The dashboard ships inside the server binary, so there's no
version skew.
Pre-existing nit left untouched:
router.gohas a gofmt import-order quirk(
githubtokens/gitrepos) in a block this PR doesn't otherwise modify.Type of change
Checklist
go build ./...+go test ./...passmake openapi-checkgreen (gen.go in sync with doc/openapi.yaml)npm run build/ typecheck pass🤖 Generated with Claude Code