Skip to content

feat(rbac): user-list summary flags + custom channel rule editor (#3229 follow-up)#3232

Merged
houko merged 3 commits into
mainfrom
feat/rbac-user-list-policy-flags-and-channel-add
Apr 26, 2026
Merged

feat(rbac): user-list summary flags + custom channel rule editor (#3229 follow-up)#3232
houko merged 3 commits into
mainfrom
feat/rbac-user-list-policy-flags-and-channel-add

Conversation

@houko

@houko houko commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes two gaps deferred from PR #3229 (RBAC M3 per-user policy editor):

Gap 1 — UserView exposes no policy summary

The user list view (GET /api/users) returned only name / role / channel_bindings / has_api_key. With M3 in production, operators want to see at a glance which users have a non-default tool_policy / memory_access / budget without having to click into each row.

Server-side: UserView gains three booleans:

  • has_policy — true when tool_policy OR tool_categories OR channel_tool_rules is set
  • has_memory_access — true when memory_access is set
  • has_budget — true when budget is set

These are summaries, not bodies — the per-user detail endpoints (GET /api/users/{name}/policy, the budget API) remain the only paths that expose policy contents. This keeps the list payload bounded and narrows the disclosure surface for callers that only have list-read.

Dashboard: UserItem type + UsersPage.tsx row layout updated. New badges (Policy / Memory / Budget) render alongside the existing role and API key badges using the same <Badge variant="info"> styling, with lucide-react icons (ListChecks / Database / Wallet) and title="" tooltips for context.

Gap 2 — Channel rules editor only exposes 4 default platforms

UserPolicyPage.tsx hard-coded telegram / discord / slack / email. The kernel side accepts arbitrary channel adapter names (e.g. wechat, matrix), but admins had no way to add or remove rules for them via the dashboard.

What changed:

  • New + Add channel inline input + button below the channel rules table. Submitting on Enter or click adds an empty rule slot.
  • Client-side validation: trim/lowercase the key, refuse empty, refuse duplicates.
  • Non-default channel rows gain a remove (X) control with a (custom) label so operators can clean up typos. Defaults stay un-removable so the form remains familiar.
  • The existing form-init spread already surfaced extra channels that were on the user, but they were undeletable until now.

API surface unchanged — server-side validators already accept arbitrary channel names.

Why summary booleans, not contents?

  • The list endpoint may return many users; embedding the full tool_policy / memory_access / budget object per row would balloon the payload proportionally.
  • A future caller token might be scoped to "read users list" but not "read policy" — the booleans give them enough to render a UI hint without leaking the rules themselves.
  • The detail endpoints already exist and are the right place for the contents (cache-keyed, invalidated on mutation, etc.).

Test plan

Server-side (added to crates/librefang-api/tests/users_test.rs):

  • users_list_summary_flags_reflect_policy_state — seeds two users (one bare, one with tool_policy + memory_access + budget); asserts the booleans flip only on the configured user.
  • users_list_summary_does_not_leak_policy_contents — seeds a customized user; asserts the JSON response contains has_policy / has_memory_access / has_budget but NOT tool_policy / tool_categories / memory_access / channel_tool_rules / budget / api_key_hash.

UI: no JS tests added (matches PR #3229's scope) — diff stays small.

Files touched

  • crates/librefang-api/src/routes/users.rsUserView + From<&UserConfig>
  • crates/librefang-api/tests/users_test.rs — two new tests (~140 lines)
  • crates/librefang-api/dashboard/src/api.tsUserItem type + 3 fields
  • crates/librefang-api/dashboard/src/pages/UsersPage.tsx — three new badges
  • crates/librefang-api/dashboard/src/pages/UserPolicyPage.tsx — add/remove channel UI
  • openapi.json — regenerated UserView schema

Notes / follow-ups

  • openapi.json was hand-edited to match what openapi_spec_test::generate_openapi_json will write on next run. CI will rewrite it; my hand-edit is just to keep the committed file in sync with the source.
  • Did not touch UserConfig, persist_users, or any write paths — purely additive on the read side.
  • Channel-key normalization is intentionally loose (trim + lowercase). The kernel accepts arbitrary strings; we don't want the dashboard to be stricter than the daemon.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions github-actions Bot added the size/L 250-999 lines changed label Apr 26, 2026
@github-actions github-actions Bot added the ready-for-review PR is ready for maintainer review label Apr 26, 2026
Base automatically changed from feat/rbac-user-policy-write-api to main April 26, 2026 13:37
@github-actions github-actions Bot added has-conflicts PR has merge conflicts that need resolution and removed ready-for-review PR is ready for maintainer review labels Apr 26, 2026
… follow-up)

Closes two gaps deferred from PR #3229 (UserPolicy editor):

1. UserView gains has_policy / has_memory_access / has_budget summary
   booleans. The list view (`GET /api/users`) now lets the dashboard
   render "Policy / Memory / Budget" badges per row without an extra
   round-trip per user. Bodies stay behind the per-user detail
   endpoints — the list never echoes tool_policy / memory_access /
   budget contents (smaller payload, narrower disclosure surface for
   list-read-only callers).

2. UserPolicyPage's per-channel rules editor is no longer locked to
   the four hard-coded defaults. Operators can add custom channel
   adapter slots (e.g. `wechat`, `matrix`) inline; non-default rows
   gain a remove (X) control. Pre-existing extra channels already
   render via the form-init spread, but were undeletable until now.

Tests:
- users_list_summary_flags_reflect_policy_state: bare user reports
  all three flags false; customized user reports true on the slots
  it sets.
- users_list_summary_does_not_leak_policy_contents: response carries
  the booleans but NOT tool_policy / tool_categories / memory_access /
  channel_tool_rules / budget / api_key_hash.
@houko
houko force-pushed the feat/rbac-user-list-policy-flags-and-channel-add branch from 09563eb to 92c06de Compare April 26, 2026 13:39
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels Apr 26, 2026
houko added 2 commits April 26, 2026 22:49
Collapses the multi-line `rows.iter().find(...).expect(...)` call onto
a single line so `cargo xtask fmt` stops failing the Quality CI job.
The pre-fix `validate_channel_rules` only checked `channel.trim().is_empty()`,
which let through:
  - Embedded control characters (`"foo\nbar".trim()` is non-empty)
  - 10 KB blobs that bloat the TOML round-trip
  - Non-ASCII or whitespace-bearing names that could never match a real
    channel adapter (`telegram`, `slack`, `feishu`, …)

These survived the PUT and ended up in `config.toml`. Tightened the
validator to:
  - Length cap of 64 chars (channel adapter names are short slugs)
  - Reject any control char (`c.is_control()`)
  - Charset `[a-zA-Z0-9_-]+` matching adapter naming convention

New regression test `users_policy_put_validates_channel_rules_keys`
pins each rejection branch (empty, newline, overlong, non-ASCII) plus a
positive case for `feishu` so the new validators don't over-reject.

Refs PR #3232 review item #18.
@houko
houko merged commit e988ecc into main Apr 26, 2026
13 checks passed
@houko
houko deleted the feat/rbac-user-list-policy-flags-and-channel-add branch April 26, 2026 14:04
@github-actions github-actions Bot removed the ready-for-review PR is ready for maintainer review label Apr 26, 2026
@houko houko mentioned this pull request Apr 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L 250-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant