package oidc import ( "encoding/json" "fmt" "io" "net/http" "sync" "time" "trankilou.fr/lassistanoque/backend/internal/domain" ) // providerMetadata est le document de découverte OpenID // (.well-known/openid-configuration). type providerMetadata struct { Issuer string `json:"issuer"` AuthorizationEndpoint string `json:"authorization_endpoint"` TokenEndpoint string `json:"token_endpoint"` JwksURI string `json:"jwks_uri"` } type metadataCacheEntry struct { metadata *providerMetadata expiresAt time.Time } // discovery maintient un cache des documents de découverte par fournisseur. type discovery struct { httpClient *http.Client mu sync.Mutex entries map[string]*metadataCacheEntry } func newDiscovery() *discovery { return &discovery{ httpClient: &http.Client{Timeout: 10 * time.Second}, entries: make(map[string]*metadataCacheEntry), } } // Metadata retourne les endpoints du fournisseur, avec mise en cache. func (d *discovery) Metadata(provider *domain.OIDCProvider) (*providerMetadata, error) { d.mu.Lock() entry, ok := d.entries[provider.ID] if ok && time.Now().Before(entry.expiresAt) { d.mu.Unlock() return entry.metadata, nil } d.mu.Unlock() metadata, err := d.fetch(provider.WellknownURL) if err != nil { return nil, fmt.Errorf("error fetching provider discovery document: %s", err) } if metadata.Issuer == "" || metadata.AuthorizationEndpoint == "" || metadata.TokenEndpoint == "" || metadata.JwksURI == "" { return nil, fmt.Errorf("incomplete discovery document for provider %s", provider.ID) } d.mu.Lock() d.entries[provider.ID] = &metadataCacheEntry{ metadata: metadata, expiresAt: time.Now().Add(1 * time.Hour), } d.mu.Unlock() return metadata, nil } func (d *discovery) fetch(wellknownURL string) (*providerMetadata, error) { res, err := d.httpClient.Get(wellknownURL) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status %d", res.StatusCode) } body, err := io.ReadAll(res.Body) if err != nil { return nil, err } metadata := &providerMetadata{} if err := json.Unmarshal(body, metadata); err != nil { return nil, err } return metadata, nil }