From 574c719148d7096e1aa622c416270b9742766437 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:05:12 +0200 Subject: [PATCH] docs: server role mapping design spec Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-06-server-role-mapping-design.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-06-server-role-mapping-design.md diff --git a/docs/superpowers/specs/2026-04-06-server-role-mapping-design.md b/docs/superpowers/specs/2026-04-06-server-role-mapping-design.md new file mode 100644 index 0000000..9c9565c --- /dev/null +++ b/docs/superpowers/specs/2026-04-06-server-role-mapping-design.md @@ -0,0 +1,78 @@ +# Server Role Mapping via Logto Scopes + +## Problem + +When a Logto user SSOs into the cameleer3-server, they get `VIEWER` role by default (OIDC auto-signup). There's no automatic mapping between Logto organization roles and server roles. A SaaS admin must manually promote users in the server. + +## Constraint + +SaaS platform roles must NOT map to server roles. A `platform:admin` is not automatically a `server:admin`. The two role systems are independent — only server-specific scopes trigger server role assignment. + +## Solution + +Add namespaced server scopes (`server:admin`, `server:operator`, `server:viewer`) to the Logto API resource. Assign them to organization roles. The server reads the `scope` claim from the JWT and maps to its RBAC roles. + +## Scopes + +New scopes on API resource `https://api.cameleer.local`: + +| Scope | Server Role | Description | +|-------|-------------|-------------| +| `server:admin` | ADMIN | Full server access | +| `server:operator` | OPERATOR | Deploy, manage apps | +| `server:viewer` | VIEWER | Read-only observability | + +Existing SaaS scopes remain unchanged (`platform:admin`, `tenant:manage`, etc.). + +## Organization Role Assignments + +| Org Role | SaaS Scopes (existing) | Server Scopes (new) | +|----------|----------------------|-------------------| +| `admin` | `platform:admin`, `tenant:manage`, `billing:manage`, `team:manage`, `apps:manage`, `apps:deploy`, `secrets:manage`, `observe:read`, `observe:debug`, `settings:manage` | `server:admin` | +| `member` | `apps:manage`, `apps:deploy`, `observe:read`, `observe:debug` | `server:viewer` | + +## Changes + +### Bootstrap (`docker/logto-bootstrap.sh`) + +1. Add `server:admin`, `server:operator`, `server:viewer` to the `create_scope` calls for the API resource +2. Include them in the org role → API resource scope assignments + +### SaaS Frontend (`ui/src/main.tsx` or `PublicConfigController`) + +Add the server scopes to the requested scopes list so Logto includes them in access tokens. The SaaS app ignores them; the server reads them. + +### Server Team + +Update scope-to-role mapping in `JwtAuthenticationFilter`: +```java +// Before: +if (scopes.contains("admin")) return List.of("ADMIN"); +// After: +if (scopes.contains("server:admin")) return List.of("ADMIN"); +if (scopes.contains("server:operator")) return List.of("OPERATOR"); +if (scopes.contains("server:viewer")) return List.of("VIEWER"); +``` + +### OIDC Config (bootstrap Phase 7) + +Set `rolesClaim: "scope"` in the server OIDC config so the server reads roles from the scope claim: +```json +{ + "rolesClaim": "scope", + "defaultRoles": ["VIEWER"] +} +``` + +## Token Flow + +1. User logs into SaaS at `/platform/` via Logto +2. Token contains: `scope: "platform:admin tenant:manage ... server:admin"` +3. User clicks "View Dashboard" → SSOs into server at `/server/` +4. Server reads token scope → finds `server:admin` → maps to ADMIN role +5. User has full admin access in server — no manual promotion + +## Files to Modify + +- `docker/logto-bootstrap.sh` — add server scopes, assign to org roles, set rolesClaim in Phase 7 +- `src/main/java/.../config/PublicConfigController.java` — add server scopes to the scopes list (so they're requested in tokens)