feat(auth): membership authority endpoint + fail-closed API auth (RBAC Phase 1)
GET /v1/users/{id}/memberships answers 'which tenants does this JWT
subject belong to, with which org_roles and entitlements' (ratified
auth design, model B2). Keycloak supplies user->tenant links and roles
via the Adapter (attribute projection until the realm migrates to
Organizations); registered tenants override status/plan/products from
registry tables.
New internal/authn verifies Keycloak bearer tokens (OIDC discovery +
JWKS, audience AUTH_EXPECTED_AUDIENCE, default tenant-registry). With
AUTH_ENABLED=true all routes except /healthz + /readyz require a token
and the server refuses to start if the verifier cannot initialize;
false (dev default) keeps the API open. Completes the M5.2-deferred
org_roles lookup and replaces the 'M4.3 adds JWT validation' TODO in
the spec.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
31cb06cf3d
commit
4a49c630d4
+16
-1
@@ -10,6 +10,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/authn"
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/config"
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/keycloak"
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/server"
|
||||
@@ -59,7 +60,21 @@ func main() {
|
||||
kc = keycloak.NewMock()
|
||||
}
|
||||
|
||||
handler := server.NewRouter(&server.Server{Cfg: cfg, Log: logger, Store: s, Keycloak: kc})
|
||||
var av *authn.Verifier
|
||||
if cfg.AuthEnabled {
|
||||
av, err = authn.New(bootCtx, cfg.KeycloakIssuer, cfg.AuthAudience)
|
||||
if err != nil {
|
||||
// Fail closed: never start an "authenticated" server that
|
||||
// cannot actually verify tokens.
|
||||
slog.Error("AUTH_CONFIG_INCOMPLETE — AUTH_ENABLED=true but verifier init failed", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("api auth enabled", "issuer", cfg.KeycloakIssuer, "audience", cfg.AuthAudience)
|
||||
} else {
|
||||
slog.Warn("AUTH_ENABLED=false — API is unauthenticated (dev only)")
|
||||
}
|
||||
|
||||
handler := server.NewRouter(&server.Server{Cfg: cfg, Log: logger, Store: s, Keycloak: kc, Auth: av})
|
||||
srv := &http.Server{
|
||||
Addr: cfg.Addr,
|
||||
Handler: handler,
|
||||
|
||||
Reference in New Issue
Block a user