Accounts & Groups tabs under Users & access — app-owned groups alongside directory groups #550
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Cordy/Cairn#550
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-on from #533, approved with mockup
admin-accounts-groups-mockup.html(rev 1) on 2026-09-14. The Access tab's account lists can grow very large and deserve their own surface, and Cairn gains app-owned groups so admins can group users without (or alongside) an IdP.Approved design
Tab layout: Users & access becomes Access · Accounts · Groups · Seats (
#admin/users/accounts,#admin/users/groups, shared admtab idiom).Approved decisions
Competitor grounding (research 2026-09-14)
Implementation sketch
internal/auth/groupstore.go: GroupStore at/data/.cairn/groups.json(statestore-bound like local-users): id, name, members, created. CRUD + membership lookup.WithAppGroups(inner, store)provider wrapper merging app-group membership intoUser.Groups, placed under WithAdminFlag so an app group can serve as admin group.internal/api/groups.go: GET/POST/api/v1/admin/groups, DELETE/rename/{id}, POST/DELETE members; directory-group section in the same GET payload (admin-client listing where possible, else seen-names fallback with apartialflag); references counts.group-create,group-remove,group-rename,group-member-add,group-member-remove(registry group "users").Waves: 1 backend core (TDD), 2 frontend, docs ride along. Ship train per wave, live-verify on files-bao.
Shipped: v0.6.199 (backend) + v0.6.200 (UI), PR #551, live on both dogfoods.
Backend (v0.6.199, red witnessed on the runner before implementation):
auth.GroupStoreat/data/.cairn/groups.json— statestore-bound like local-users, internal ids (decision 2), case-insensitive single namespace, idempotent membership ops, sorted listings.WithAppGroupsmerges membership intoUser.Groupson every authentication, under the admin flag and above live groups — so an app group can be the admin group, and a live IdP refresh never drops app groups./api/v1/admin/groups: CRUD + rename, bulk member add with per-name{added, invalid}reporting (the oCIS lesson), directory listing (spaces.Manager.ListGroupsvia the IdP admin client; known-names fallback flaggeddirSource: "seen"), per-group directory member drill-in. Creating or renaming onto a known directory name → 409 (decision 1). Delete fails closed (decision 3) — references in peering lists and the admin group simply stop matching; nothing else is mutated.group-create/-rename/-remove/-member-add/-member-remove(registry groupusers).Frontend (v0.6.200): Access · Accounts · Groups · Seats. Access slimmed to identity wiring + counters, Administrators section removed (decision 4). Accounts: both lists moved intact, one filter (name substring /
admin/ app-group name — typing a group shows its members), account rows show their app groups, directory list pages 25 at a time. Groups: app-owned CRUD with chip member editing, comma-separated bulk add, reference counts on rows and in the delete confirm; directory groups read-only with live listing where the admin client exists, and the honest "names seen so far" note where it doesn't; hidden entirely in pure local mode. Deep links#admin/users/accountsand#admin/users/groups. i18n ×4, #389 fence gate green.Verification so far: full
go test ./...green; both pods booted clean on v0.6.200; fence gate +node --checkon the shipped page. The signed-in admin views need a human session (redeploy cleared sessions) — Nikola's checklist:#admin/users— four tabs; Access shows wiring rows only, no Administrators list.admin, and by a group name once one exists; create/reset/role/remove still work.project-rhinewith a member; check the members chip editor, bulk add with a junk name (per-name report), rename, delete confirm showing references; directory section shows keycloak groups with View members (files-bao has the admin client).Known follow-ups (not blocking): the peering "group lists need the IdP admin client" copy predates app groups and could soften; space names appear among directory groups (they ARE Keycloak groups) — a "space" badge would help; docs handbook page for groups rides with the #533 no-IdP page.
Fix round + docs shipped.
v0.6.201 (PR #553, live on both dogfoods, clean boot with
groups.jsonin the 19 statestore files): all five dogfood findings from Nikola's screenshots — Group/Members/Used-by columns with headers on both tables, View members now toggles instead of appending, New group / Add local user at mockup size beside the filter, surface-background inputs, inline duplicate-name message in the dialogs instead of the hidden 409 — plus the two ship-note follow-ups: Space marker on directory groups that are Cairn spaces (owner attribute +-rotwins, flagged throughspaces.ListGroupswith a test) and the peering receiver copy rewritten now that app-owned groups resolve without the IdP admin client. Deeper polish is parked in #552 (v0.7).Docs run (PR #554, merged): the handbook was current through v0.6.178 — now aligned through v0.6.201. New pages
local-accounts.md(the no-IdP story, Manuel's #533 request) andgroups.md;users-access.mdrewritten for four tabs;peering.md,index.md, README, ARCHITECTURE (§5.1 + new §5.5),encryption.md(#528 custody lifecycle) andfile-browser.md(#389 phone section) all updated.Remaining on this issue: nothing backend/UI — Nikola's live pass on v0.6.201 (columns, toggle, inline 409, Space markers) decides whether this closes.
Closing — feature complete and live.
Shipped across v0.6.199 (backend) → v0.6.206 (last fidelity round), all live on both dogfoods:
auth.GroupStore,WithAppGroups, full/api/v1/admin/groupsCRUD, single-namespace 409, fail-closed delete, five audit verbs, group-based peering allow-lists resolving without an IdP admin client.Docs:
docs/handbook/groups.mdandlocal-accounts.mdwritten,users-access.mdrewritten for four tabs (PR #554).Remaining polish is tracked in #552 — live collision preview while typing, and an empty/degraded-state review against mockup variant D. The Used-by share count is tracked in #529, where it belongs: the column can only gain a real number once sharing can address a group.