feat(admin): add user create, disable/enable, and OAuth/authorization management#162
feat(admin): add user create, disable/enable, and OAuth/authorization management#162
Conversation
…authorizations management - Add admin create user page with auto-generated password support - Add disable/enable user with IsActive field, middleware enforcement, and token revocation - Add per-user OAuth connections view with provider badges and unlink action - Add per-user authorized apps view with revoke action - Make user detail stat cards clickable, linking to tokens, connections, and authorizations - Add Status column and Create User button to users list page - Show disabled user count on admin dashboard - Extract getFlashMessage and flashAndRedirect helpers to reduce handler duplication - Sanitize username input on admin user creation using existing sanitizeUsername - Count only active admins in last-admin guard to prevent disabled admins inflating count - Revoke tokens before disabling user to close security window - Add GetOAuthConnectionByUserAndID for efficient ownership-verified queries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Adds comprehensive admin-side user management capabilities (create local users, enable/disable accounts, and manage per-user OAuth connections/authorizations), plus related UI/metrics updates.
Changes:
- Introduces
User.IsActivewith admin enable/disable flows and session enforcement for disabled users. - Adds new admin pages for creating users, viewing/unlinking OAuth connections, and viewing/revoking authorized apps.
- Enhances admin UI (clickable stat cards, status badges, dashboard disabled-user count) and refactors flash-message helpers.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/templates/static/css/pages/admin-forms.css | Adds link styling for clickable user stat cards. |
| internal/templates/props.go | Adds props structs for new admin user management pages. |
| internal/templates/admin_users.templ | Adds “Create User” CTA and user status column/badge. |
| internal/templates/admin_user_detail.templ | Displays user status, adds clickable stat cards, adds enable/disable actions. |
| internal/templates/admin_user_created.templ | New “created” page showing one-time password with copy action. |
| internal/templates/admin_user_create.templ | New admin “create user” form. |
| internal/templates/admin_user_connections.templ | New OAuth connections list + unlink action UI. |
| internal/templates/admin_user_authorizations.templ | New authorized-apps list + revoke action UI. |
| internal/templates/admin_dashboard.templ | Shows disabled user count when present. |
| internal/store/user.go | Ensures external users are active; changes CountUsersByRole to count only active users. |
| internal/store/user_test.go | Updates test user factory for new IsActive field. |
| internal/store/types/dashboard.go | Adds DisabledUsers to dashboard counts DTO. |
| internal/store/sqlite.go | Seeds default admin with IsActive: true. |
| internal/store/oauth_connection.go | Adds ownership-verified connection lookup by (user_id, id). |
| internal/store/dashboard.go | Adds disabled-users scalar count to dashboard query. |
| internal/services/user.go | Adds admin create user, enable/disable, and OAuth connection deletion service methods. |
| internal/models/user.go | Adds IsActive field to User model. |
| internal/models/audit_log.go | Adds new audit event types for user created/disabled/enabled and OAuth connection deletion. |
| internal/mocks/mock_store.go | Updates mocks for new store interface method. |
| internal/middleware/auth.go | Clears session when a disabled user is encountered. |
| internal/handlers/utils.go | Adds getFlashMessage / flashAndRedirect helpers. |
| internal/handlers/user_admin.go | Adds routes/handlers for create user, enable/disable, connections, authorizations; refactors flash usage. |
| internal/core/store.go | Extends OAuthConnectionStore interface with GetOAuthConnectionByUserAndID. |
| internal/bootstrap/router.go | Registers new admin routes. |
| internal/bootstrap/handlers.go | Wires AuthorizationService into UserAdminHandler. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- URL-encode query params in user detail and authorization templates - Handle non-NotFound DB errors in username/email uniqueness checks - Handle non-NotFound DB errors in OAuth connection lookup - Use operation-neutral error message for self-active-status guard - Block disabled users from authenticating in Authenticate method - Differentiate 400 vs 500 status in CreateUser error handling - Validate disable/enable before revoking tokens to prevent side effects - Add ValidateSetUserActiveStatus for pre-flight guard checks - Add 14 unit tests for CreateUserAdmin, SetUserActiveStatus, DeleteUserOAuthConnection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Replace deprecated strings.Title with manual capitalization - Use sentinel ErrOAuthConnectionNotFound for proper 404 vs 500 handling - Handle gorm.ErrDuplicatedKey race in CreateUserAdmin - Update CountUsersByRole docstring to reflect active-only behavior - Add test for CountUsersByRole excluding disabled users Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Block disabled users from OAuth login paths (AuthenticateWithOAuth) - Differentiate email vs username conflict in CreateUserAdmin DuplicatedKey - Map toggleUserActive errors to proper HTTP status codes (400/404/500) - Return 404 for authorization not found in RevokeUserAuthorization - Add tests for OAuth disabled user blocking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Replace duplicated validation checks in SetUserActiveStatus with a call to ValidateSetUserActiveStatus for defense-in-depth, reducing drift risk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…tyle - Map ErrAccountDisabled to 403 in OAuth callback handler instead of falling through to generic 500 error - Add :focus-visible style for clickable stat card links (accessibility) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Map ErrAccountDisabled to specific message in login handler instead of generic "Invalid username or password" - Use require.ErrorIs for sentinel error assertions in OAuth connection delete tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… inputs - Replace brittle error text inspection with DB re-query to determine which unique constraint was violated on CreateUser race - Trim whitespace on form inputs in handler so repopulated form shows normalized values matching what the service validates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Extract toAuthorizationDisplaySlice helper to deduplicate conversion loop - Move input sanitization to service layer as single source of truth - Rename ErrCannotDisableSelf to ErrCannotChangeOwnStatus for clarity - Remove redundant re-validation in SetUserActiveStatus to match DeleteUserAdmin pattern - Apply getFlashMessage/flashAndRedirect helpers to client handler Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…in SetUserActiveStatus CountUsersByRole only counts active users, so guards in UpdateUserAdmin, ValidateDeleteUser, and DeleteUserAdmin must skip when the target itself is disabled. Otherwise deleting or demoting a disabled admin is rejected because the active count drops to 1. Move validation into SetUserActiveStatus so direct callers cannot bypass self or last-admin invariants. Handlers still call ValidateSetUserActiveStatus to gate token revocation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… session errors Authenticate previously only checked IsActive when GetUserByUsername hit. If the username changed upstream, the HTTP-API path could still resolve a disabled account by external_id and let it log in. Re-check IsActive after syncExternalUser to close that gap. updateOAuthConnectionAndGetUser used to persist refreshed OAuth tokens before checking the owning user, so a disabled account could rotate stored credentials simply by completing the provider redirect. Fetch the user and verify IsActive before any update. The session_save_error context key set by getFlashMessage / flashAndRedirect was never read, so session save failures were effectively silent. Log them instead, since flash messages are advisory and a save failure does not warrant aborting the request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
/admin/users/newpage with form, auto-generated password, and one-time password displayIsActivefield on User model, middleware enforcement (disabled users auto-logout), token revocation on disable, admin UI toggle/admin/users/:id/connectionspage showing linked providers (GitHub/Gitea/Microsoft) with unlink action/admin/users/:id/authorizationspage showing granted apps with revoke actiongetFlashMessage/flashAndRedirecthelpers, unified disable/enable handler, consolidated error variablesCountUsersByRoleonly counts active admins, username sanitized on create, efficient ownership-verified OAuth connection queriesNew Routes (8)
/admin/users/new/admin/users/admin/users/:id/disable/admin/users/:id/enable/admin/users/:id/connections/admin/users/:id/connections/:conn_id/delete/admin/users/:id/authorizations/admin/users/:id/authorizations/:uuid/revokeModel Change
User.IsActive(bool,default:true) — GORM AutoMigrate handles the migration automatically.Test plan
make generate && make buildsucceedsgo test ./internal/...all packages pass/admin/users/new— verify password shown once🤖 Generated with Claude Code