RBAC Phase 1: membership authority endpoint + fail-closed API auth (#14)
Implements the model-B2 membership authority (ratified compliance auth design) and inbound token verification.
- GET /v1/users/{id}/memberships: Keycloak supplies user->tenant links + org_roles (attribute projection until the realm migrates to Organizations); registered tenants override status/plan/products from registry tables (source: registry|keycloak). Closes the M5.2-deferred org_roles stub.
- New internal/authn: OIDC discovery + JWKS verification (go-oidc/v3), audience tenant-registry. /healthz + /readyz stay PUBLIC_EXPLICIT; all other routes INTERNAL_SERVICE_ONLY. AUTH_ENABLED=true refuses to start on incomplete config (fail-closed); false (dev default) keeps current behavior.
- Full suite green incl. postgres testcontainers; openapi.yaml updated (contract test passes).
Activation is a separate step (Auth-5): requires the orca-infra env merge and a portal service token (portal currently calls with no auth).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com>
Reviewed-on: #14
This commit was merged in pull request #14.
This commit is contained in:
+56
-1
@@ -8,7 +8,10 @@ info:
|
||||
`PLATFORM_ARCHITECTURE.md §5c` for the schema, and
|
||||
`PRODUCT_INTEGRATION_SPEC.md §8.4` for the audit shape.
|
||||
|
||||
This API is not yet authenticated — M4.3 adds Keycloak JWT validation.
|
||||
Auth (RBAC Phase 1): with AUTH_ENABLED=true every route except
|
||||
/healthz and /readyz requires a Keycloak-issued bearer token whose
|
||||
audience contains AUTH_EXPECTED_AUDIENCE (INTERNAL_SERVICE_ONLY
|
||||
posture). With AUTH_ENABLED=false (dev default) the API is open.
|
||||
contact:
|
||||
email: oncall@breakpilot.com
|
||||
license:
|
||||
@@ -250,6 +253,49 @@ paths:
|
||||
description: Revoked.
|
||||
"404": { $ref: "#/components/responses/NotFound" }
|
||||
|
||||
/v1/users/{id}/memberships:
|
||||
get:
|
||||
summary: Resolve which tenants a user belongs to (membership authority).
|
||||
description: |
|
||||
The B2 membership-authority endpoint (ratified compliance auth
|
||||
design). Product backends call this with the JWT `sub` instead of
|
||||
trusting token claims or client-supplied headers. Keycloak supplies
|
||||
the user→tenant links and org_roles; where the tenant is registered
|
||||
here, tenant_status / plan / products are overridden from registry
|
||||
tables (source: "registry"), otherwise the Keycloak attribute
|
||||
projection is returned as-is (source: "keycloak").
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Keycloak user id (the JWT `sub`).
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Memberships (possibly empty) for the user.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [user_id, memberships]
|
||||
properties:
|
||||
user_id: { type: string }
|
||||
memberships:
|
||||
type: array
|
||||
items:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/Claims"
|
||||
- type: object
|
||||
required: [source]
|
||||
properties:
|
||||
source: { type: string, enum: [registry, keycloak] }
|
||||
"400": { $ref: "#/components/responses/BadRequest" }
|
||||
"404": { $ref: "#/components/responses/NotFound" }
|
||||
"503":
|
||||
description: Keycloak unreachable — membership cannot be resolved.
|
||||
content:
|
||||
application/json: { schema: { $ref: "#/components/schemas/Error" } }
|
||||
|
||||
/v1/internal/keycloak/claims:
|
||||
post:
|
||||
summary: Resolve the up-to-date claim bundle for a user/tenant.
|
||||
@@ -356,6 +402,15 @@ paths:
|
||||
"400": { $ref: "#/components/responses/BadRequest" }
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
Keycloak-issued token (client_credentials for services). Enforced
|
||||
on all non-health routes when AUTH_ENABLED=true.
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: Input failed validation.
|
||||
|
||||
Reference in New Issue
Block a user