From 277d5ea63856e45eb47388ec38708319931b77bd Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:01:43 +0200 Subject: [PATCH] feat: define OAuth2 scopes on API resource and assign to Logto roles Creates 10 API resource scopes (platform:admin + 9 tenant-level) on the Cameleer SaaS API resource, assigns all to platform-admin role, creates matching organization scopes, and wires them declaratively to org roles (admin gets all, member gets deploy/observe). All operations are idempotent. Co-Authored-By: Claude Sonnet 4.6 --- docker/logto-bootstrap.sh | 81 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/docker/logto-bootstrap.sh b/docker/logto-bootstrap.sh index 116f5df..4041afe 100644 --- a/docker/logto-bootstrap.sh +++ b/docker/logto-bootstrap.sh @@ -189,6 +189,47 @@ else log "Created API resource: $API_RESOURCE_ID" fi +# ============================================================ +# PHASE 3b: Create API resource scopes +# ============================================================ + +log "Creating API resource scopes..." +EXISTING_SCOPES=$(api_get "/api/resources/${API_RESOURCE_ID}/scopes") + +create_scope() { + local name="$1" + local desc="$2" + local existing_id=$(echo "$EXISTING_SCOPES" | jq -r ".[] | select(.name == \"$name\") | .id") + if [ -n "$existing_id" ]; then + log " Scope '$name' exists: $existing_id" + echo "$existing_id" + else + local resp=$(api_post "/api/resources/${API_RESOURCE_ID}/scopes" "{\"name\": \"$name\", \"description\": \"$desc\"}") + local new_id=$(echo "$resp" | jq -r '.id') + log " Created scope '$name': $new_id" + echo "$new_id" + fi +} + +# Platform-level scope +SCOPE_PLATFORM_ADMIN=$(create_scope "platform:admin" "SaaS platform administration") + +# Tenant-level scopes +SCOPE_TENANT_MANAGE=$(create_scope "tenant:manage" "Manage tenant settings") +SCOPE_BILLING_MANAGE=$(create_scope "billing:manage" "Manage billing") +SCOPE_TEAM_MANAGE=$(create_scope "team:manage" "Manage team members") +SCOPE_APPS_MANAGE=$(create_scope "apps:manage" "Create and delete apps") +SCOPE_APPS_DEPLOY=$(create_scope "apps:deploy" "Deploy apps") +SCOPE_SECRETS_MANAGE=$(create_scope "secrets:manage" "Manage secrets") +SCOPE_OBSERVE_READ=$(create_scope "observe:read" "View observability data") +SCOPE_OBSERVE_DEBUG=$(create_scope "observe:debug" "Debug and replay operations") +SCOPE_SETTINGS_MANAGE=$(create_scope "settings:manage" "Manage settings") + +# Collect scope IDs for role assignment +ALL_TENANT_SCOPE_IDS="\"$SCOPE_TENANT_MANAGE\",\"$SCOPE_BILLING_MANAGE\",\"$SCOPE_TEAM_MANAGE\",\"$SCOPE_APPS_MANAGE\",\"$SCOPE_APPS_DEPLOY\",\"$SCOPE_SECRETS_MANAGE\",\"$SCOPE_OBSERVE_READ\",\"$SCOPE_OBSERVE_DEBUG\",\"$SCOPE_SETTINGS_MANAGE\"" +ALL_SCOPE_IDS="\"$SCOPE_PLATFORM_ADMIN\",$ALL_TENANT_SCOPE_IDS" +MEMBER_SCOPE_IDS="\"$SCOPE_APPS_DEPLOY\",\"$SCOPE_OBSERVE_READ\",\"$SCOPE_OBSERVE_DEBUG\"" + # --- M2M app --- M2M_ID=$(echo "$EXISTING_APPS" | jq -r ".[] | select(.name == \"$M2M_APP_NAME\" and .type == \"MachineToMachine\") | .id") M2M_SECRET="" @@ -249,11 +290,14 @@ EXISTING_ROLES=$(api_get "/api/roles") PA_ROLE_ID=$(echo "$EXISTING_ROLES" | jq -r '.[] | select(.name == "platform-admin" and .type == "User") | .id') if [ -n "$PA_ROLE_ID" ]; then log "platform-admin role exists: $PA_ROLE_ID" + # Ensure scopes are assigned (idempotent) + api_post "/api/roles/${PA_ROLE_ID}/scopes" "{\"scopeIds\": [$ALL_SCOPE_IDS]}" >/dev/null 2>&1 else PA_RESPONSE=$(api_post "/api/roles" "{ \"name\": \"platform-admin\", \"description\": \"SaaS platform administrator\", - \"type\": \"User\" + \"type\": \"User\", + \"scopeIds\": [$ALL_SCOPE_IDS] }") PA_ROLE_ID=$(echo "$PA_RESPONSE" | jq -r '.id') log "Created platform-admin role: $PA_ROLE_ID" @@ -284,6 +328,41 @@ if [ -z "$ORG_MEMBER_ROLE_ID" ]; then log "Created org member role: $ORG_MEMBER_ROLE_ID" fi +# --- Organization permissions (scopes) --- +log "Creating organization permissions..." +EXISTING_ORG_SCOPES=$(api_get "/api/organization-scopes") + +create_org_scope() { + local name="$1" + local desc="$2" + local existing_id=$(echo "$EXISTING_ORG_SCOPES" | jq -r ".[] | select(.name == \"$name\") | .id") + if [ -n "$existing_id" ]; then + echo "$existing_id" + else + local resp=$(api_post "/api/organization-scopes" "{\"name\": \"$name\", \"description\": \"$desc\"}") + echo "$(echo "$resp" | jq -r '.id')" + fi +} + +ORG_SCOPE_TENANT_MANAGE=$(create_org_scope "tenant:manage" "Manage tenant settings") +ORG_SCOPE_BILLING_MANAGE=$(create_org_scope "billing:manage" "Manage billing") +ORG_SCOPE_TEAM_MANAGE=$(create_org_scope "team:manage" "Manage team members") +ORG_SCOPE_APPS_MANAGE=$(create_org_scope "apps:manage" "Create and delete apps") +ORG_SCOPE_APPS_DEPLOY=$(create_org_scope "apps:deploy" "Deploy apps") +ORG_SCOPE_SECRETS_MANAGE=$(create_org_scope "secrets:manage" "Manage secrets") +ORG_SCOPE_OBSERVE_READ=$(create_org_scope "observe:read" "View observability data") +ORG_SCOPE_OBSERVE_DEBUG=$(create_org_scope "observe:debug" "Debug and replay operations") +ORG_SCOPE_SETTINGS_MANAGE=$(create_org_scope "settings:manage" "Manage settings") + +ALL_ORG_SCOPE_IDS="\"$ORG_SCOPE_TENANT_MANAGE\",\"$ORG_SCOPE_BILLING_MANAGE\",\"$ORG_SCOPE_TEAM_MANAGE\",\"$ORG_SCOPE_APPS_MANAGE\",\"$ORG_SCOPE_APPS_DEPLOY\",\"$ORG_SCOPE_SECRETS_MANAGE\",\"$ORG_SCOPE_OBSERVE_READ\",\"$ORG_SCOPE_OBSERVE_DEBUG\",\"$ORG_SCOPE_SETTINGS_MANAGE\"" +MEMBER_ORG_SCOPE_IDS="\"$ORG_SCOPE_APPS_DEPLOY\",\"$ORG_SCOPE_OBSERVE_READ\",\"$ORG_SCOPE_OBSERVE_DEBUG\"" + +# Assign organization scopes to org roles +log "Assigning organization scopes to roles..." +api_put "/api/organization-roles/${ORG_ADMIN_ROLE_ID}/scopes" "{\"organizationScopeIds\": [$ALL_ORG_SCOPE_IDS]}" >/dev/null 2>&1 +api_put "/api/organization-roles/${ORG_MEMBER_ROLE_ID}/scopes" "{\"organizationScopeIds\": [$MEMBER_ORG_SCOPE_IDS]}" >/dev/null 2>&1 +log "Organization scopes assigned." + # ============================================================ # PHASE 5: Create users # ============================================================