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
61 lines
1.9 KiB
Go
61 lines
1.9 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
Env string // dev | stage | prod
|
|
Addr string // listen address, e.g. ":8090"
|
|
KeycloakIssuer string // e.g. http://localhost:8080/realms/breakpilot-dev
|
|
DatabaseURL string // postgres DSN (unused in skeleton; in-memory store)
|
|
|
|
// Keycloak Admin API — only used if KeycloakAdminURL is set. Empty
|
|
// values disable the adapter and tenant-registry falls back to the
|
|
// Mock (dev convenience).
|
|
KeycloakAdminURL string
|
|
KeycloakRealm string
|
|
KeycloakClientID string
|
|
KeycloakClientSecret string
|
|
KeycloakTimeout time.Duration
|
|
|
|
// Inbound API auth (RBAC Phase 1). With AuthEnabled the server refuses
|
|
// to start unless OIDC discovery against KeycloakIssuer succeeds, and
|
|
// every non-health route requires a bearer token whose audience
|
|
// contains AuthAudience.
|
|
AuthEnabled bool
|
|
AuthAudience string
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
env := getenv("APP_ENV", "dev")
|
|
if env != "dev" && env != "stage" && env != "prod" {
|
|
return nil, fmt.Errorf("invalid APP_ENV %q", env)
|
|
}
|
|
return &Config{
|
|
Env: env,
|
|
// :8090 — Keycloak owns :8080 in the dev stack.
|
|
Addr: getenv("ADDR", ":8090"),
|
|
KeycloakIssuer: getenv("KEYCLOAK_ISSUER", "http://localhost:8080/realms/breakpilot-dev"),
|
|
DatabaseURL: os.Getenv("DATABASE_URL"),
|
|
|
|
KeycloakAdminURL: os.Getenv("KEYCLOAK_ADMIN_URL"),
|
|
KeycloakRealm: getenv("KEYCLOAK_REALM", "breakpilot-dev"),
|
|
KeycloakClientID: os.Getenv("KEYCLOAK_CLIENT_ID"),
|
|
KeycloakClientSecret: os.Getenv("KEYCLOAK_CLIENT_SECRET"),
|
|
KeycloakTimeout: 10 * time.Second,
|
|
|
|
AuthEnabled: getenv("AUTH_ENABLED", "false") == "true",
|
|
AuthAudience: getenv("AUTH_EXPECTED_AUDIENCE", "tenant-registry"),
|
|
}, nil
|
|
}
|
|
|
|
func getenv(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|