feat: manage bot accounts from the admin UI, API and CLI - #38966
feat: manage bot accounts from the admin UI, API and CLI#38966joestump-agent wants to merge 28 commits into
Conversation
|
👋🏼 Human operator of @joestump-agent chiming in. I picked this up from @bircni's PR. I will flip this from WIP to ready for review once status checks are green and I do a final manual review. Here are the screenshots:
|
|
I can help if needed - let me check the code again |
|
Did some adjustments:
|
|
Thanks @bircni — read through the adjustments and they all look right, especially centralising the bot invariant in |
|
Okay, I gave this a review. @joestump-agent can you flip this over from WIP to ready for review? Please make sure to rebase/sync with main before doing so. |
…dlers Extract the duplicated "scope-*" form parsing into auth.AccessTokenScopeFromForm and use it from both the user-facing ApplicationsPost and the new admin bot-token handler.
Bot accounts are meant for token-based automation only, but the reverse-proxy and external-source (LDAP/SMTP/PAM) auth paths resolved users by name/email without checking the user type. A bot whose name or email matched a proxy header or external identity could obtain an interactive session. Guard reverse-proxy auth and the UserSignIn external-source fallback so only individual users may sign in interactively, matching the existing local-password and OAuth2 behavior. Add regression tests covering both. Assisted-by: Claude:claude-opus-4-8
Allow a site admin to convert an existing account between the individual
and bot types. Only individual <-> bot is permitted; organizations and
reserved types are rejected.
When converting to a bot the account becomes a local, token-only account:
password, auth source and persisted sessions are cleared, OAuth2
applications/grants and external login links are removed, while access
tokens and owned content (repositories, org membership, ...) are kept.
Exposed through the admin Edit User page (User Type dropdown), the API
(POST /admin/users/{username}/convert-type) and the CLI
(gitea admin user change-type), all backed by user_service.ConvertUserType.
The CLI user-type parsing is shared with "user create".
Assisted-by: Claude:claude-opus-4-8
Document the bot account model: definition, the no-interactive-sign-in guarantee across all auth paths, the capability matrix, and the individual <-> bot conversion rules with every side effect spelled out. Assisted-by: Claude:claude-opus-4-8
- reject and clear passwords when creating a bot via the admin UI, so bots stay passwordless like the CLI and edit-user paths already enforce - wrap the individual->bot credential teardown in a transaction so a mid-sequence failure cannot leave a half-converted account - guard DeleteBotToken so the admin bot-token route only acts on bots Assisted-by: Claude Code:claude-opus-4-8
Bot accounts are non-interactive: converting a user to a bot strips its password and auth source so it can never sign in. Impersonation sets the session UID directly, bypassing credentials entirely, which would hand out a session that signing in could never produce. Guard the handler and hide the button on the admin user view page. Assisted-by: Claude:claude-opus-5
The admin user list mixes individuals, bots, reserved and remote accounts with no way to narrow them down. Add a "User Type" dropdown that filters to individuals or bots; unfiltered behaviour is unchanged.
- Replace the new RenderWithErrDeprecated call for the bot-admin error with a flash message and redirect, per reviewer request to not add more usages of the deprecated helper. - Drop the unit TestImpersonateUser duplicate and cover bot impersonation rejection in the TestAdminBotUser integration test. - Update TestConvertUserTypeRejectsNonConvertibleTarget to expect 400, matching APIErrorAuto's current mapping of invalid-argument errors. Pick-up of go-gitea#38181 by bircni, whose authorship is preserved via the merged commits. 💘 Generated with Crush Assisted-by: Crush:glm-5.3
…tions The organization path of CreateRepoTransferNotification skips bot members and notifies the rest by their real user id, but had no test coverage. Assisted-by: Claude Code:claude-fable-5
Reinstates the design doc removed during the original review, with the sign-in enforcement matrix and conversion side-effects corrected to match the current implementation (renamed error, SSPI/session/OpenID handling, kept vs cleared artifacts). Assisted-by: Claude Code:claude-fable-5
4044f20 to
1577db5
Compare
|
Done — flipped to ready for review. The branch had gotten tangled (an old While rebasing, upstream's form-binding/validation refactor (#38971 and friends) broke a few things, so I also fixed those up:
Verified: |
| if (!isBot) { // fields hidden as ".non-bot" must not be shown again by the local/non-local state | ||
| toggleElem('.local', isLocal); | ||
| toggleElem('.non-local', !isLocal); | ||
| } |
There was a problem hiding this comment.
These 3 class names are too short, Would refactor to a common prefix for all three and make them easily greppable. I know this partly is pre-existing but still worth this small cleanup.
There was a problem hiding this comment.
Good point. Renamed .j-reset-* to .js-reset-* in list.tmpl and users.ts for consistency with the project convention. The .non-bot, .local, .non-local classes are partly pre-existing and used across new.tmpl and edit.tmpl; a full rename would touch templates beyond this PR's scope, so I'll leave that for a separate cleanup.
There was a problem hiding this comment.
Do the full rename, a bit of scope creep is fine.
There was a problem hiding this comment.
Pull request overview
Adds first-class bot accounts (token-only, non-interactive users) across the admin UI, API, and CLI, and hardens authentication/session/reset flows to ensure only individual users can obtain interactive sessions.
Changes:
- Adds admin UI support for creating bots, filtering the user list by type, managing bot access tokens, and converting user type (individual ↔ bot).
- Adds API + CLI endpoints/commands for converting user type, plus shared token UI partials.
- Hardens multiple auth paths (reverse proxy, SSPI, sessions, password reset, external auth fallback) with regression tests.
Reviewed changes
Copilot reviewed 45 out of 46 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| web_src/js/globals.d.ts | Adds admin user-list type filter state typing. |
| web_src/js/features/admin/users.ts | Restores filter state + adds reset handling for user-type filter. |
| web_src/js/features/admin/common.ts | Adjusts admin user new/edit page field toggling for bot vs non-bot. |
| tests/integration/admin_user_test.go | Integration coverage for bot creation, edit behavior, tokens, conversion. |
| templates/user/settings/applications.tmpl | Refactors token UI to shared partial. |
| templates/swagger/v1-swagger.generated.json | Adds swagger v2 path/schema for convert-type endpoint. |
| templates/swagger/v1-openapi3.generated.json | Adds OpenAPI v3 path/schema for convert-type endpoint. |
| templates/shared/user/access_tokens.tmpl | New shared access-token UI component (list/create/modals). |
| templates/admin/user/view.tmpl | Hides impersonate for bots; shows bot token management panel. |
| templates/admin/user/new.tmpl | Adds “User Type” selector; hides auth/password fields for bots. |
| templates/admin/user/list.tmpl | Adds user-type filter dropdown to admin user list. |
| templates/admin/user/edit.tmpl | Hides auth/password/admin fields for bots; adds danger-zone conversion modal. |
| services/user/update.go | Enforces “bots can’t be admins/auth-sourced/passworded”; adds conversion logic. |
| services/user/update_test.go | Unit tests for conversion side effects + bot admin/auth restrictions. |
| services/forms/user_form.go | Factors scope parsing into reusable helper. |
| services/forms/admin.go | Adds UserType to create form; allows empty login_type for bot edits. |
| services/forms/admin_test.go | Validates AdminCreateUserForm user_type binding. |
| services/auth/sspi.go | Rejects non-individual accounts from SSPI interactive auth. |
| services/auth/signin.go | Rejects non-individual accounts returned by external auth in sign-in loop. |
| services/auth/signin_test.go | Regression test: external source resolving a bot can’t create session. |
| services/auth/session.go | Rejects sessions for non-individual users (post-conversion safety). |
| services/auth/session_test.go | Tests session rejection after user converted to bot. |
| services/auth/reverseproxy.go | Rejects non-individual accounts resolved by reverse proxy headers. |
| services/auth/reverseproxy_test.go | Regression test: reverse-proxy headers can’t authenticate a bot. |
| routers/web/web.go | Adds admin routes for convert_type and bot token CRUD. |
| routers/web/user/setting/applications.go | Refactors token creation into helper; adds stricter error signaling. |
| routers/web/auth/password.go | Blocks password reset flows for non-individual users. |
| routers/web/admin/users.go | Implements type filtering, bot token management, and type conversion actions. |
| routers/api/v1/swagger/options.go | Adds ConvertUserTypeOption to swagger body registry. |
| routers/api/v1/api.go | Adds admin convert-type API route binding. |
| routers/api/v1/admin/user.go | Adds convert-type admin API handler (204 on success). |
| routers/api/v1/admin/user_test.go | Tests convert-type rejects non-convertible users. |
| options/locale/locale_en-US.json | Adds UI strings for user type, conversion, and bot token management. |
| modules/structs/admin_user.go | Adds ConvertUserTypeOption API struct. |
| models/user/user.go | Adds ParseUserType + makes bots non-mailable. |
| models/user/error.go | Adds shared invalid-argument errors for bot admin + conversion. |
| models/activities/notification.go | Skips notifying bots for repo transfer notifications. |
| models/activities/notification_test.go | Tests that bots are skipped in notification creation. |
| models/activities/notification_list.go | Skips bots when creating/updating issue notifications. |
| cmd/admin_user.go | Registers new admin user change-type subcommand. |
| cmd/admin_user_must_change_password_test.go | Fixes test cleanup to truncate email addresses. |
| cmd/admin_user_create.go | Uses ParseUserType; prevents bot creation with --admin. |
| cmd/admin_user_create_test.go | Adds coverage for rejecting --admin when creating bots. |
| cmd/admin_user_change_type.go | New CLI subcommand to convert user type. |
| cmd/admin_user_change_type_test.go | Tests change-type command success + failure cases. |
| cmd/admin_user_change_password_test.go | Fixes test cleanup to truncate email addresses. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
45b482b to
8a14428
Compare
|
Done — rebased onto
All Go tests pass ( |
|
pls do not force push |
|
Fair point, @bircni — apologies for the force-push. I was cleaning up a tangled branch history (an earlier merge had pulled ~150 upstream commits into the PR's lineage), and I rebuilt it as a clean rebase rather than carrying that merge noise forward. Going forward I'll avoid force-pushing on this PR. If the history needs cleaning up again, I'll coordinate with you first. |
|
@bircni I'll get the force push cleaned up. |
- Fix notification.go: UserID used loop index instead of users[i].ID (Copilot) - Add comment explaining login_type "0" = LoginNoType (silverwind) - Rename j- prefix to js- for consistency with project convention (silverwind) - Add specific case for ErrUserTypeCanNotConvert before generic ErrInvalidArgument (Copilot) - Add locale key for the new error message
126e3e4 to
86c9a94
Compare
|
@bircni — force push cleaned up. Sorry for the churn; this was the undo, and it's the last one. The branch is back on its original lineage — every commit before my 2026-08-22 rebase has its original SHA again, including all 15 of yours ( New tip is The rebase had also silently dropped a commit. Verification: the tree is byte-identical to the previous head except for that one file. Going forward this branch only takes merges from 🤖 This was posted autonomously by |
Keep both TestReverseProxyAuth_BotIgnored (from PR branch) and TestReverseProxyLastLogin (from upstream/main), merging their import blocks.






Adds first-class bot accounts (
UserTypeBot): local, password-less users for automation thatauthenticate only with access tokens.
Continues #38181 by
@bircni (commits preserved), rebased on
mainwith the outstanding review threads addressed.Since Gitea squash-merges, we'd appreciate bircni's authorship being preserved via a co-author
line by a maintainer.
Bots also close a security gap: several auth paths (reverse-proxy, SSPI, external sources, sessions,
password reset) resolved users without a type check, so a bot could obtain an interactive session.
All now require an individual, with regression tests.
The full model — what a bot can and cannot do, and every side effect of conversion (password,
sessions, OAuth2 grants, external/OpenID links and notifications cleared; access tokens and owned
content kept) — is documented in
models/user/bot_user_design.md.Usage
manage their own).
POST /admin/users/{username}/convert-type, orgitea admin user change-type --username <name> --user-type bot|individual.Screenshots
Click to open
🤖 This was posted autonomously by
glm-5.3using Crush.