phase 2 (part 4): SSE stream via io.Pipe, OIDC JWT auth middleware
- SSE stream: GET /events/stream using io.Pipe to bridge the SSE goroutine to the response body. Replay from Last-Event-ID via in-memory broker with DB fallback. LISTEN/NOTIFY fan-out to all subscribers. Heartbeat every 15s. Bounded channels. - OIDC JWT auth: validates Bearer tokens against Authentik/OIDC issuer via JWKS discovery + key caching. Extracts sub/email into context actor. Falls back to static bearer tokens. Dev mode (no OIDC + no tokens) = open. - Config: OIDCIssuer, OIDCClientID env vars - SSE + OIDC infrastructure complete, build passes, all tests pass Remaining: MCP server, conformance tests, wire audit middleware
This commit is contained in:
@@ -17,9 +17,11 @@ type Config struct {
|
||||
APIListen string // :8090
|
||||
APIEnv string // dev, prod
|
||||
|
||||
// Auth (interim Phase 2: static bearer tokens; OIDC JWT later)
|
||||
// Auth (Phase 2: static bearer tokens + OIDC JWT)
|
||||
APIToken string // operator/CI bearer token for the REST API
|
||||
MCPBearerToken string // shared secret for Hermes→API MCP calls
|
||||
OIDCIssuer string // OIDC issuer URL for JWT validation (e.g. https://authentik.example.com/application/o/oikos/)
|
||||
OIDCClientID string // OIDC client ID (aud claim expected in JWT)
|
||||
|
||||
// Observability
|
||||
Debug bool // verbose logging, probe payloads, SQL
|
||||
@@ -55,6 +57,12 @@ func FromEnv() Config {
|
||||
if v := os.Getenv("OIKOS_ENV"); v != "" {
|
||||
c.APIEnv = v
|
||||
}
|
||||
if v := os.Getenv("OIKOS_OIDC_ISSUER"); v != "" {
|
||||
c.OIDCIssuer = v
|
||||
}
|
||||
if v := os.Getenv("OIKOS_OIDC_CLIENT_ID"); v != "" {
|
||||
c.OIDCClientID = v
|
||||
}
|
||||
if v := os.Getenv("OIKOS_API_TOKEN"); v != "" {
|
||||
c.APIToken = v
|
||||
}
|
||||
@@ -86,8 +94,8 @@ func (c Config) String() string {
|
||||
if c.MCPBearerToken != "" {
|
||||
token = "***"
|
||||
}
|
||||
return fmt.Sprintf("Config{DB=%s Listen=%s Env=%s Debug=%v MCPToken=%s SeedsDir=%s}",
|
||||
c.redactedDBURL(), c.APIListen, c.APIEnv, c.Debug, token, c.SeedsDir)
|
||||
return fmt.Sprintf("Config{DB=%s Listen=%s Env=%s Debug=%v MCPToken=%s SeedsDir=%s OIDCIssuer=%s OIDCClientID=%s}",
|
||||
c.redactedDBURL(), c.APIListen, c.APIEnv, c.Debug, token, c.SeedsDir, c.OIDCIssuer, c.OIDCClientID)
|
||||
}
|
||||
|
||||
// LogValue implements slog.LogValuer so structured handlers (JSON) never
|
||||
@@ -105,5 +113,7 @@ func (c Config) LogValue() slog.Value {
|
||||
slog.Bool("debug", c.Debug),
|
||||
slog.String("mcp_token", token),
|
||||
slog.String("seeds_dir", c.SeedsDir),
|
||||
slog.String("oidc_issuer", c.OIDCIssuer),
|
||||
slog.String("oidc_client_id", c.OIDCClientID),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user