Add enterprise-auth -revoke-membership-*/-list-memberships-tenant flags

Rounds out the tenant_memberships operator flags added earlier this
phase (-create-tenant/-grant-membership-*): grant had no way to undo
itself, and there was no way to see who was actually in a tenant
without querying Postgres directly.

rbacstore.RevokeMembership deletes a tenant_memberships row, but
refuses to revoke a tenant's current Owner -- Owner is also a
dedicated tenants.owner_user_id column (SetOwner), so deleting that
membership without transferring ownership first would leave
owner_user_id pointing at a user with no membership in the tenant at
all. Ownership transfer is a deliberate, separate action per the RBAC
matrix ("Transfer tenant Owner -- Owner only"), not a side effect of
revoking access.

rbacstore.ListMembershipsForTenant is ListMembershipsForUser's
inverse -- joined with users so the result is actually useful (email,
display name), not just a bare user ID list.

enterprise-auth gains -revoke-membership-tenant/
-revoke-membership-user-email (both required together, same shape as
-grant-membership-*) and -list-memberships-tenant (prints tab-separated
id/email/display-name/role to stdout and exits -- an operator
convenience, not a machine-readable API; this binary deliberately has
no admin HTTP surface, per its own doc comment on why). Changing a
role is unchanged: re-run -grant-membership-* with a different
-grant-membership-role, SetMembership's upsert already handles it.

Covered by five new skip-gated integration tests in rbacstore_test.go
(RBACSTORE_TEST_POSTGRES_ADDR), including the Owner-revocation refusal
and cross-tenant leak check for ListMembershipsForTenant -- not run
against a live database in this environment, same disclosed gap as the
rest of this phase's Postgres-backed work. Docs updated:
phase-4-runbook.md's known-gaps bullet, enterprise/README.md's
bootstrap walkthrough and package layout table.
This commit is contained in:
2026-08-14 09:11:01 -07:00
parent 823f5d48d1
commit cfcbc77507
5 changed files with 281 additions and 14 deletions
+13 -4
View File
@@ -156,7 +156,7 @@ silently left out:
## Package layout
```
cmd/enterprise-auth/ config loading, OIDC discovery at startup, health/authorize/features endpoints, -mint-service-token, -create-tenant, -grant-membership-*
cmd/enterprise-auth/ config loading, OIDC discovery at startup, health/authorize/features endpoints, -mint-service-token, -create-tenant, -grant-membership-*, -revoke-membership-*, -list-memberships-tenant
cmd/enterprise-api/ multi-tenant-aware alternative to api/cmd/api -- see its own doc comment
internal/tenant/ the ID type -- see its package doc comment before touching it
internal/oidc/ coreos/go-oidc wiring: discovery, login redirect, code exchange + ID token verification
@@ -271,15 +271,24 @@ docker compose run --rm enterprise-auth -create-tenant=acme -display-name="Acme
# users row by then, which -grant-membership-user-email needs.
docker compose run --rm enterprise-auth \
-grant-membership-tenant=acme -grant-membership-user-email=[email protected] -grant-membership-role=owner
# See who's actually in a tenant, and take access away again:
docker compose run --rm enterprise-auth -list-memberships-tenant=acme
docker compose run --rm enterprise-auth \
-revoke-membership-tenant=acme -revoke-membership-user-email=[email protected]
```
`-create-tenant` only touches `rbacstore` -- pair with `enterprise-api
-provision-tenant` (below) for a tenant to actually be able to run
queries, not just log in. `role=owner` also calls `SetOwner`, since a
tenant's Owner is a dedicated `tenants.owner_user_id` column, not just
the highest `tenant_memberships` role. Not yet built: revoking a
membership, listing a tenant's members, or a flag for
`dashboard_permissions` grants (those go through the HTTP endpoints
the highest `tenant_memberships` role -- `-revoke-membership-*` refuses
to revoke a tenant's current Owner for the same reason (transferring
ownership first has no flag yet, only `rbacstore.SetOwner` at the
storage layer). Changing a role is just re-running
`-grant-membership-*` with a different `-grant-membership-role`
(`SetMembership`'s upsert already supports it). Not yet built: a flag
for `dashboard_permissions` grants (those go through the HTTP endpoints
`api/dashboards`' handler now exposes -- `PUT`/`DELETE
/dashboards/{id}/permissions/{userId}`, `GET .../permissions`).