feat(rbac): user-list summary flags + custom channel rule editor (#3229 follow-up)#3232
Merged
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
… 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
force-pushed
the
feat/rbac-user-list-policy-flags-and-channel-add
branch
from
April 26, 2026 13:39
09563eb to
92c06de
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes two gaps deferred from PR #3229 (RBAC M3 per-user policy editor):
Gap 1 —
UserViewexposes no policy summaryThe user list view (
GET /api/users) returned onlyname / role / channel_bindings / has_api_key. With M3 in production, operators want to see at a glance which users have a non-defaulttool_policy/memory_access/budgetwithout having to click into each row.Server-side:
UserViewgains three booleans:has_policy— true whentool_policyORtool_categoriesORchannel_tool_rulesis sethas_memory_access— true whenmemory_accessis sethas_budget— true whenbudgetis setThese 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:
UserItemtype +UsersPage.tsxrow layout updated. New badges (Policy/Memory/Budget) render alongside the existingroleandAPI keybadges using the same<Badge variant="info">styling, withlucide-reacticons (ListChecks/Database/Wallet) andtitle=""tooltips for context.Gap 2 — Channel rules editor only exposes 4 default platforms
UserPolicyPage.tsxhard-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:
+ Add channelinline input + button below the channel rules table. Submitting on Enter or click adds an empty rule slot.(custom)label so operators can clean up typos. Defaults stay un-removable so the form remains familiar.API surface unchanged — server-side validators already accept arbitrary channel names.
Why summary booleans, not contents?
tool_policy/memory_access/budgetobject per row would balloon the payload proportionally.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 withtool_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 containshas_policy / has_memory_access / has_budgetbut NOTtool_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.rs—UserView+From<&UserConfig>crates/librefang-api/tests/users_test.rs— two new tests (~140 lines)crates/librefang-api/dashboard/src/api.ts—UserItemtype + 3 fieldscrates/librefang-api/dashboard/src/pages/UsersPage.tsx— three new badgescrates/librefang-api/dashboard/src/pages/UserPolicyPage.tsx— add/remove channel UIopenapi.json— regeneratedUserViewschemaNotes / follow-ups
openapi.jsonwas hand-edited to match whatopenapi_spec_test::generate_openapi_jsonwill write on next run. CI will rewrite it; my hand-edit is just to keep the committed file in sync with the source.UserConfig,persist_users, or any write paths — purely additive on the read side.