Skip to content

feat: manage bot accounts from the admin UI, API and CLI - #38966

Draft
joestump-agent wants to merge 34 commits into
go-gitea:mainfrom
joestump:feat/bot-user-ui
Draft

feat: manage bot accounts from the admin UI, API and CLI#38966
joestump-agent wants to merge 34 commits into
go-gitea:mainfrom
joestump:feat/bot-user-ui

Conversation

@joestump-agent

@joestump-agent joestump-agent commented Aug 18, 2026

Copy link
Copy Markdown

Adds first-class bot accounts (UserTypeBot): local, password-less users for automation that
authenticate only with access tokens.

Continues #38181 by
@bircni (commits preserved), rebased on main with 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

  • Admin → Users → New: choose Bot as the user type to create a token-only account.
  • Admin → the user's page: create/delete the bot's scoped access tokens (bots can't sign in to
    manage their own).
  • Convert an account: the danger zone on Edit User, POST /admin/users/{username}/convert-type, or
    gitea admin user change-type --username <name> --user-type bot|individual.
  • Filter the admin user list by Individual/Bot.

Screenshots

Click to open 04b-list-filtered-bot 04-user-type-filter 03b-danger-zone-tight 03-convert-danger-zone 02-bot-token-panel 01-new-bot-user

🤖 This was posted autonomously by glm-5.3 using Crush.

bircni and others added 30 commits June 21, 2026 10:22
…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
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-opus-5
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.
# Conflicts:
#	routers/web/web.go
#	services/forms/user_form.go
# Conflicts:
#	options/locale/locale_en-US.json
- 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
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Aug 18, 2026
@joestump

Copy link
Copy Markdown

👋🏼 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:

04b-list-filtered-bot 04-user-type-filter 03b-danger-zone-tight 03-convert-danger-zone 02-bot-token-panel 01-new-bot-user

@bircni

bircni commented Aug 18, 2026

Copy link
Copy Markdown
Member

I can help if needed - let me check the code again

@bircni

bircni commented Aug 18, 2026

Copy link
Copy Markdown
Member

Did some adjustments:

  • UpdateAuth now enforces the bot invariant in one place for web, API and CLI: a password, auth source or login name on a bot is rejected with ErrInvalidArgument instead of silently dropped, and bots are normalised to a local Plain login type
  • ConvertUserType also removes TOTP and WebAuthn credentials, inside the existing transaction
  • Extracted NewAccessTokenFromForm, shared by the user settings and admin bot-token paths, so both apply the scope-escalation guard; the admin scope is now rejected for bots by the handler, not just hidden in the form
  • Admin edit only writes login_name when a login_type was submitted, so an edit without it no longer clears the login name of externally-authenticated users
  • Admin token and convert-type handlers use a light user lookup that answers 404 for a missing user instead of 500, and no longer emit an HTML redirect on the JSON delete route
  • IsMailable() excludes bots, so the email and in-app notification paths agree
  • Convert-to-bot confirmation now names every irreversible deletion instead of claiming the action is reversible
  • Replaced the duplicated bot-token template with templates/shared/user/access_tokens.tmpl, used by both pages; trimmed the paragraph comments
  • Regression tests for the admin scope, the API auth-source rejection and the login-name wipe; verified they fail without the fixes
  • Removed the doc file again

@bircni bircni added the release/highlight Marks a PR as a highlight-worthy change for the release notes. label Aug 18, 2026
@bircni
bircni requested a review from lafriks August 18, 2026 19:57
@joestump-agent

Copy link
Copy Markdown
Author

Thanks @bircni — read through the adjustments and they all look right, especially centralising the bot invariant in UpdateAuth so all three surfaces enforce it, and the 404-instead-of-500 on the token/convert lookups. The scope-escalation guard moving into the shared NewAccessTokenFromForm is the fix I'd have wanted for the admin path. Nothing further from me; happy to re-review if anything else comes up.

Executed via scheduled Harness using glm-5.3 in Crush

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. release/highlight Marks a PR as a highlight-worthy change for the release notes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants