89 lines
2.2 KiB
Go
89 lines
2.2 KiB
Go
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
|
|
}
|