-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexchange_discovery.go
More file actions
76 lines (71 loc) · 2.19 KB
/
Copy pathexchange_discovery.go
File metadata and controls
76 lines (71 loc) · 2.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package aoa
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"sync"
)
// discovery resolves and caches an AS's token_endpoint from its RFC 8414
// metadata (/.well-known/oauth-authorization-server). One entry per issuer.
type discovery struct {
client *http.Client
mu sync.Mutex
cache map[string]string // issuer -> token_endpoint
}
func newDiscovery(c *http.Client) *discovery {
if c == nil {
c = http.DefaultClient
}
return &discovery{client: c, cache: map[string]string{}}
}
func (d *discovery) tokenEndpoint(ctx context.Context, issuer string) (string, error) {
d.mu.Lock()
if ep, ok := d.cache[issuer]; ok {
d.mu.Unlock()
return ep, nil
}
d.mu.Unlock()
// metaURL is built by appending the well-known suffix to the issuer
// (OIDC-Discovery style), which is what Keycloak/Auth0/Okta serve. The strict
// RFC 8414 par.3.1 form for a path-bearing issuer inserts the suffix between host
// and path; that variant is not attempted here. For such an AS, configure
// ExchangeConfig.TokenEndpoint explicitly instead of Issuer.
metaURL := strings.TrimRight(issuer, "/") + "/.well-known/oauth-authorization-server"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, metaURL, nil)
if err != nil {
return "", err
}
resp, err := d.client.Do(req)
if err != nil {
return "", fmt.Errorf("aoa: fetch AS metadata: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("aoa: AS metadata status %d", resp.StatusCode)
}
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return "", err
}
var meta struct {
Issuer string `json:"issuer"`
TokenEndpoint string `json:"token_endpoint"`
}
if err := json.Unmarshal(body, &meta); err != nil {
return "", fmt.Errorf("aoa: parse AS metadata: %w", err)
}
if meta.TokenEndpoint == "" {
return "", fmt.Errorf("aoa: AS metadata has no token_endpoint")
}
// RFC 8414 par.2: the issuer in the document must match the requested issuer.
if meta.Issuer != issuer {
return "", fmt.Errorf("aoa: AS metadata issuer %q != %q", meta.Issuer, issuer)
}
d.mu.Lock()
d.cache[issuer] = meta.TokenEndpoint
d.mu.Unlock()
return meta.TokenEndpoint, nil
}