Self-service spaces: create + member management brokered to the IdP (#94) #98

Merged
Cordy merged 14 commits from feat/self-service-spaces into main 2026-08-04 09:51:33 +00:00
Owner

Implements #94 (route A, greenlit 2026-08-03). Cairn is the UI; Keycloak stays the single source of truth.

Server

  • New internal/spaces: Keycloak Admin API broker — Create (write group <name> + read-only group <name>-ro (#64) + owner recorded as group attribute cairnSpaceOwner + creator joined as write member), Members, SetMember (role switch joins target group, leaves the other — never both), RemoveMember (both groups). Client-credentials token cache, same service-account client key custody uses.
  • New internal/api/spaces.go: POST /api/v1/spaces (any authenticated user), GET/POST /api/v1/spaces/{name}/members, DELETE .../members/{user} — member management owner-or-admin only; owner can never be demoted/removed. Name validation rejects the -ro suffix, hidden names, path separators. Audit events: space-create, space-member-set, space-member-remove.
  • Wiring in main.go gated on the Keycloak encryption config; the backend /spaces/<name> dir is created via the unscoped stack at creation, which also fixes the admin-visibility gap found in dogfood (admins see the raw backend, and the boot probe no longer 404s once a space exists).

UI

  • "New folder" at the Spaces root creates a space (no new chrome to learn).
  • "Members" button appears at a space's root; dialog lists members with role dropdowns (write / read-only), add-by-username, remove. i18n en/de/fr/it.
  • Session-staleness trade documented in copy: access applies at next sign-in (#96 will remove this).

Tests: internal/spaces fake-Keycloak suite (create + groups + owner attr + creator join, duplicate → conflict, role switch no-dupe, removal, unknown user/space). Full repo suite green; JS syntax checked.

Implements #94 (route A, greenlit 2026-08-03). Cairn is the UI; Keycloak stays the single source of truth. **Server** - New `internal/spaces`: Keycloak Admin API broker — `Create` (write group `<name>` + read-only group `<name>-ro` (#64) + owner recorded as group attribute `cairnSpaceOwner` + creator joined as write member), `Members`, `SetMember` (role switch joins target group, leaves the other — never both), `RemoveMember` (both groups). Client-credentials token cache, same service-account client key custody uses. - New `internal/api/spaces.go`: `POST /api/v1/spaces` (any authenticated user), `GET/POST /api/v1/spaces/{name}/members`, `DELETE .../members/{user}` — member management owner-or-admin only; owner can never be demoted/removed. Name validation rejects the `-ro` suffix, hidden names, path separators. Audit events: `space-create`, `space-member-set`, `space-member-remove`. - Wiring in `main.go` gated on the Keycloak encryption config; the backend `/spaces/<name>` dir is created via the **unscoped** stack at creation, which also fixes the admin-visibility gap found in dogfood (admins see the raw backend, and the boot probe no longer 404s once a space exists). **UI** - "New folder" at the Spaces root creates a space (no new chrome to learn). - "Members" button appears at a space's root; dialog lists members with role dropdowns (write / read-only), add-by-username, remove. i18n en/de/fr/it. - Session-staleness trade documented in copy: access applies at next sign-in (#96 will remove this). **Tests**: `internal/spaces` fake-Keycloak suite (create + groups + owner attr + creator join, duplicate → conflict, role switch no-dupe, removal, unknown user/space). Full repo suite green; JS syntax checked.
Cordy merged commit 55aee8cb69 into main 2026-08-04 09:51:33 +00: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: Cordy/Cairn#98
No description provided.