generated from coulomb/repo-seed
- T05: /.well-known/openid-configuration — profile-only features advertised - T08: /jwks — RS256 JWK Set, stdlib crypto only, key rotation support - T13: Structured telemetry — Event types, LogEmitter/NoopEmitter/MultiEmitter, context helpers 38 server tests pass, go vet clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
3.8 KiB
Go
87 lines
3.8 KiB
Go
// Package oidc implements OIDC profile endpoints for KeyCape.
|
|
// Only profile-supported features are advertised — no implicit flow,
|
|
// no dynamic registration, no request objects.
|
|
package oidc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
)
|
|
|
|
// DiscoveryConfig holds the issuer and endpoint URLs for the discovery document.
|
|
// UserinfoEndpoint is optional; if empty it is omitted from the document.
|
|
type DiscoveryConfig struct {
|
|
Issuer string // e.g. "https://auth.netkingdom.local"
|
|
AuthorizationEndpoint string
|
|
TokenEndpoint string
|
|
JWKSUri string
|
|
UserinfoEndpoint string // optional, empty = not advertised
|
|
}
|
|
|
|
// discoveryDocument is the JSON shape of /.well-known/openid-configuration.
|
|
// Fields are ordered to match common OIDC implementations for readability.
|
|
// registration_endpoint is intentionally absent — no dynamic client registration.
|
|
type discoveryDocument struct {
|
|
Issuer string `json:"issuer"`
|
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
|
TokenEndpoint string `json:"token_endpoint"`
|
|
JWKSUri string `json:"jwks_uri"`
|
|
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
|
ResponseTypesSupported []string `json:"response_types_supported"`
|
|
GrantTypesSupported []string `json:"grant_types_supported"`
|
|
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
|
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
|
ScopesSupported []string `json:"scopes_supported"`
|
|
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
|
|
ClaimsSupported []string `json:"claims_supported"`
|
|
SubjectTypesSupported []string `json:"subject_types_supported"`
|
|
RequestParameterSupported bool `json:"request_parameter_supported"`
|
|
ClaimsParameterSupported bool `json:"claims_parameter_supported"`
|
|
}
|
|
|
|
// discoveryHandler implements http.Handler for GET /.well-known/openid-configuration.
|
|
type discoveryHandler struct {
|
|
doc []byte
|
|
}
|
|
|
|
// NewDiscoveryHandler returns an http.Handler that serves the OIDC discovery document.
|
|
// The document is pre-serialised at construction time so every request is a cheap copy.
|
|
func NewDiscoveryHandler(cfg DiscoveryConfig) http.Handler {
|
|
d := discoveryDocument{
|
|
Issuer: cfg.Issuer,
|
|
AuthorizationEndpoint: cfg.AuthorizationEndpoint,
|
|
TokenEndpoint: cfg.TokenEndpoint,
|
|
JWKSUri: cfg.JWKSUri,
|
|
UserinfoEndpoint: cfg.UserinfoEndpoint,
|
|
|
|
// Profile-locked values — not negotiable.
|
|
ResponseTypesSupported: []string{"code"},
|
|
GrantTypesSupported: []string{"authorization_code"},
|
|
CodeChallengeMethodsSupported: []string{"S256"},
|
|
IDTokenSigningAlgValuesSupported: []string{"RS256"},
|
|
ScopesSupported: []string{"openid", "profile", "email", "groups"},
|
|
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "client_secret_post", "none"},
|
|
ClaimsSupported: []string{
|
|
"sub", "iss", "aud", "exp", "iat",
|
|
"preferred_username", "email", "name", "groups", "roles",
|
|
},
|
|
SubjectTypesSupported: []string{"public"},
|
|
RequestParameterSupported: false,
|
|
ClaimsParameterSupported: false,
|
|
}
|
|
|
|
b, err := json.Marshal(d)
|
|
if err != nil {
|
|
// This can only fail if the struct contains un-marshallable types, which it does not.
|
|
panic("oidc: failed to marshal discovery document: " + err.Error())
|
|
}
|
|
return &discoveryHandler{doc: b}
|
|
}
|
|
|
|
func (h *discoveryHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "max-age=3600")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write(h.doc)
|
|
}
|