feat(integration): first-class bot identity (LC-73) #168

Merged
nrupard merged 2 commits from feat/lc-73-bots into main 2026-05-20 21:43:25 +02:00
Owner

Summary

First-class bot identity (LC-73). A bot is a users row with is_bot = 1 (migration 0020). Bots authenticate only via API tokens (LC-72); the cookie login refuses them. Builds on the merged LC-72 token surface.

Changes

  • Schema: is_bot column on users (default 0). Threaded through UserRecord/User + From, row_to_user_record, and every users SELECT that feeds it (by-username/id/session, list, blocked-users).
  • Admin (/admin/bots, standalone-only): create a bot (username + token scopes) -> inserts the bot user and mints an API token shown once; disable -> bans the bot and revokes all its tokens (revoke_all_for_user).
  • Login: rejects is_bot users (generic error, no enumeration). Bot password hash is empty/non-verifiable.
  • First-user-is-admin: counts only WHERE is_bot = 0 (register + 2FA-register), so a bot can't grab the admin slot.
  • Rendering: AuthorMeta/MessageView carry author_is_bot (set at all 9 construction sites); the message template renders a "bot" pill next to the username.
  • Mentions resolve bots like any user. Bots receive no notifications (no subscriptions / no live socket).

Acceptance criteria

  • Admins create / disable bots from /admin/bots.
  • Bot API token authenticates; cookie login rejects bots.
  • Bot-authored messages render a bot badge.
  • @mention of a bot resolves like a normal user.
  • Bots can't become Admin via the first-user rule.
  • Bots respect normal RBAC (room access + ban/mute on the API path).
  • Disabling a bot revokes all its API tokens.

Tests

routes_bots.rs (standalone-gated): create + token auth, cookie-login rejection, disable-revokes-tokens, bot badge in a room. Migration 0020 appended to all hand-rolled auth lists (db_auth.rs / rbac.rs / db_invite.rs filled in 0018-0020). just check, just test, just test-saas green.

🤖 Generated with Claude Code

## Summary First-class bot identity (LC-73). A bot is a `users` row with `is_bot = 1` (migration 0020). Bots authenticate only via API tokens (LC-72); the cookie login refuses them. Builds on the merged LC-72 token surface. ## Changes - **Schema**: `is_bot` column on `users` (default 0). Threaded through `UserRecord`/`User` + `From`, `row_to_user_record`, and every users SELECT that feeds it (by-username/id/session, list, blocked-users). - **Admin** (`/admin/bots`, standalone-only): create a bot (username + token scopes) -> inserts the bot user and mints an API token shown once; disable -> bans the bot and revokes all its tokens (`revoke_all_for_user`). - **Login**: rejects `is_bot` users (generic error, no enumeration). Bot password hash is empty/non-verifiable. - **First-user-is-admin**: counts only `WHERE is_bot = 0` (register + 2FA-register), so a bot can't grab the admin slot. - **Rendering**: `AuthorMeta`/`MessageView` carry `author_is_bot` (set at all 9 construction sites); the message template renders a "bot" pill next to the username. - Mentions resolve bots like any user. Bots receive no notifications (no subscriptions / no live socket). ## Acceptance criteria - [x] Admins create / disable bots from `/admin/bots`. - [x] Bot API token authenticates; cookie login rejects bots. - [x] Bot-authored messages render a bot badge. - [x] `@mention` of a bot resolves like a normal user. - [x] Bots can't become Admin via the first-user rule. - [x] Bots respect normal RBAC (room access + ban/mute on the API path). - [x] Disabling a bot revokes all its API tokens. ## Tests `routes_bots.rs` (standalone-gated): create + token auth, cookie-login rejection, disable-revokes-tokens, bot badge in a room. Migration 0020 appended to all hand-rolled auth lists (db_auth.rs / rbac.rs / db_invite.rs filled in 0018-0020). `just check`, `just test`, `just test-saas` green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(integration): first-class bot identity (LC-73)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 1m21s
2c1e3c384b
Bots are a users row with is_bot = 1 (migration 0020). They authenticate only via API tokens (LC-72): the cookie login path refuses them (same generic error as a wrong password) and they carry an empty, non-verifiable password hash.

Admin surface (standalone-only, /admin/bots): create a bot (username + token scopes) which inserts the bot user and mints an API token shown exactly once; disable a bot, which bans it and revokes all its API tokens (new db::api_tokens::revoke_all_for_user). Bot rows are listed newest-first.

Identity threading: is_bot added to UserRecord/User + the From impl, read in row_to_user_record, and included in every users SELECT that feeds it (by-username, by-id, by-session, list, blocked-users). AuthorMeta + MessageView carry author_is_bot, set at all nine MessageView construction sites, and the message template renders a "bot" pill next to the username.

Safety: the first-user-is-admin rule counts only WHERE is_bot = 0 (both the register and 2FA-register paths), so a bot can never become admin by being the first registration. Mentions resolve bots like any user (no change needed). Bots get no notifications (no subscriptions, no live socket). The API post path already refuses banned users, so a disabled bot's writes 403 even before its tokens are revoked.

Tests: routes_bots.rs (standalone-gated: admin surface is standalone-only) covers bot create + token auth, cookie-login rejection, disable-revokes-tokens, and the bot badge rendering in a room. Migration 0020 appended to every hand-rolled auth migration list (db_auth.rs and rbac.rs and db_invite.rs gained 0018-0020). just check, just test, just test-saas all green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(bots): roll back orphan bot row if token minting fails (LC-73)
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 5s
9468118b21
post_bots created the bot user then minted its API token. If the token insert failed, the bot row was left orphaned, and retrying the same username hit a unique-violation. Delete the just-created bot when the token insert fails so the admin can retry cleanly. (Login defense-in-depth confirmed: verify_password rejects the empty bot hash because PasswordHash::new("") errors, independent of the is_bot gate.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nrupard deleted branch feat/lc-73-bots 2026-05-20 21:43:25 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!168
No description provided.