// // Copyright 2026 The InfiniFlow Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package oauth import ( "context" "encoding/json" "fmt" "io" "net/http" "strings" ) // oidcClient is the OIDC flavor: it resolves the authorization / // token / userinfo URLs from the Issuer's discovery document // (.well-known/openid-configuration) before delegating the OAuth flow to // oauthClient. // // We do not currently verify or parse the id_token: id_token claims are // only used as enrichment in the Python implementation, and the /userinfo // endpoint (already called) returns the same canonical claims authenticated // via the access_token. Tracked as a follow-up to add full JWKS-based // id_token verification once a JWT library is vendored. type oidcClient struct { *oauthClient issuer string } func newOIDCClient(cfg Config) (*oidcClient, error) { if cfg.Issuer == "" { return nil, fmt.Errorf("Missing issuer in configuration.") } meta, err := loadOIDCMetadata(cfg.Issuer) if err != nil { return nil, fmt.Errorf("Failed to fetch OIDC metadata: %w", err) } if meta.Issuer != "" { cfg.Issuer = meta.Issuer } if cfg.AuthorizationURL == "" { cfg.AuthorizationURL = meta.AuthorizationEndpoint } if cfg.TokenURL == "" { cfg.TokenURL = meta.TokenEndpoint } if cfg.UserinfoURL == "" { cfg.UserinfoURL = meta.UserinfoEndpoint } base, err := newOAuthClient(cfg) if err != nil { return nil, err } return &oidcClient{oauthClient: base, issuer: cfg.Issuer}, nil } // oidcMetadata is the subset of fields we use from the discovery document. type oidcMetadata struct { Issuer string `json:"issuer"` AuthorizationEndpoint string `json:"authorization_endpoint"` TokenEndpoint string `json:"token_endpoint"` UserinfoEndpoint string `json:"userinfo_endpoint"` JWKSURI string `json:"jwks_uri"` } // loadOIDCMetadata is the indirection used to fetch a provider's discovery // document. Tests override it. var loadOIDCMetadata = func(issuer string) (*oidcMetadata, error) { metadataURL := strings.TrimRight(issuer, "/") + "/.well-known/openid-configuration" ctx, cancel := context.WithTimeout(context.Background(), HTTPRequestTimeout) defer cancel() req, err := http.NewRequestWithContext(ctx, http.MethodGet, metadataURL, nil) if err != nil { return nil, err } req.Header.Set("Accept", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20)) if err != nil { return nil, err } if resp.StatusCode >= 400 { return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) } meta := &oidcMetadata{} if err := json.Unmarshal(body, meta); err != nil { return nil, fmt.Errorf("parse OIDC metadata: %w", err) } return meta, nil }