feat: auth hardening — scope enforcement, tenant isolation, and docs
All checks were successful
CI / build (push) Successful in 38s
CI / docker (push) Successful in 39s

Add @PreAuthorize annotations to all API controllers (14 endpoints
across 6 controllers) enforcing OAuth2 scopes: apps:manage, apps:deploy,
billing:manage, observe:read, platform:admin.

Enforce tenant isolation: TenantResolutionFilter now rejects cross-tenant
access on /api/tenants/{id}/* paths. New TenantOwnershipValidator checks
environment/app ownership for paths without tenantId. Platform admins
bypass both layers.

Fix frontend: OrgResolver split into two useEffect hooks so scopes
refresh on org switch. Scopes now served from /api/config (single source
of truth). Bootstrap cleaned — standalone org permissions removed.

Update docs/architecture.md, docs/user-manual.md, and CLAUDE.md to
reflect all auth hardening changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-05 15:32:53 +02:00
parent b459a69083
commit 051f7fdae9
21 changed files with 408 additions and 136 deletions

View File

@@ -201,12 +201,12 @@ create_scope() {
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"
log " Scope '$name' exists: $existing_id" >&2
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"
log " Created scope '$name': $new_id" >&2
echo "$new_id"
fi
}
@@ -328,40 +328,11 @@ 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."
# Assign API resource scopes to org roles (these appear in org-scoped resource tokens)
log "Assigning API resource scopes to organization roles..."
api_put "/api/organization-roles/${ORG_ADMIN_ROLE_ID}/resource-scopes" "{\"scopeIds\": [$ALL_TENANT_SCOPE_IDS]}" >/dev/null 2>&1
api_put "/api/organization-roles/${ORG_MEMBER_ROLE_ID}/resource-scopes" "{\"scopeIds\": [$MEMBER_SCOPE_IDS]}" >/dev/null 2>&1
log "API resource scopes assigned to organization roles."
# ============================================================
# PHASE 5: Create users
@@ -427,10 +398,10 @@ fi
# Add users to organization
if [ -n "$TENANT_USER_ID" ] && [ "$TENANT_USER_ID" != "null" ]; then
log "Adding tenant admin to organization..."
log "Adding tenant user to organization..."
api_post "/api/organizations/$ORG_ID/users" "{\"userIds\": [\"$TENANT_USER_ID\"]}" >/dev/null 2>&1
api_put "/api/organizations/$ORG_ID/users/$TENANT_USER_ID/roles" "{\"organizationRoleIds\": [\"$ORG_ADMIN_ROLE_ID\"]}" >/dev/null 2>&1
log "Tenant admin added to org with admin role."
api_put "/api/organizations/$ORG_ID/users/$TENANT_USER_ID/roles" "{\"organizationRoleIds\": [\"$ORG_MEMBER_ROLE_ID\"]}" >/dev/null 2>&1
log "Tenant user added to org with member role."
fi
if [ -n "$ADMIN_USER_ID" ] && [ "$ADMIN_USER_ID" != "null" ]; then