blob: acbc978424628a5742d1b0f422682b0707fa653e [file] [log] [blame]
Akron90f65212025-06-12 14:32:55 +02001package auth
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "time"
8
9 "golang.org/x/oauth2"
10 "golang.org/x/oauth2/clientcredentials"
11
12 "github.com/korap/korap-mcp/config"
13)
14
15// OAuthClient handles OAuth2 authentication for KorAP API
16type OAuthClient struct {
17 config *config.OAuthConfig
18 oauth2Config *oauth2.Config
19 token *oauth2.Token
20 httpClient *http.Client
21}
22
23// NewOAuthClient creates a new OAuth2 client
24func NewOAuthClient(cfg *config.OAuthConfig) (*OAuthClient, error) {
25 if cfg == nil {
26 return nil, fmt.Errorf("oauth config cannot be nil")
27 }
28
29 if err := cfg.Validate(); err != nil {
30 return nil, fmt.Errorf("invalid oauth config: %w", err)
31 }
32
33 client := &OAuthClient{
34 config: cfg,
35 oauth2Config: cfg.ToOAuth2Config(),
36 }
37
38 return client, nil
39}
40
41// GetAuthURL returns the authorization URL for the OAuth2 flow
42func (c *OAuthClient) GetAuthURL(state string) string {
43 if c.oauth2Config == nil {
44 return ""
45 }
46 return c.oauth2Config.AuthCodeURL(state, oauth2.AccessTypeOnline)
47}
48
49// ExchangeCode exchanges an authorization code for an access token
50func (c *OAuthClient) ExchangeCode(ctx context.Context, code string) error {
51 if c.oauth2Config == nil {
52 return fmt.Errorf("oauth2 not configured")
53 }
54
55 token, err := c.oauth2Config.Exchange(ctx, code)
56 if err != nil {
57 return fmt.Errorf("failed to exchange code for token: %w", err)
58 }
59
60 c.token = token
61 c.httpClient = c.oauth2Config.Client(ctx, token)
62 return nil
63}
64
65// SetToken sets the OAuth2 token directly
66func (c *OAuthClient) SetToken(token *oauth2.Token) {
67 c.token = token
68 if c.oauth2Config != nil {
69 c.httpClient = c.oauth2Config.Client(context.Background(), token)
70 }
71}
72
73// GetToken returns the current OAuth2 token
74func (c *OAuthClient) GetToken() *oauth2.Token {
75 return c.token
76}
77
78// GetHTTPClient returns an HTTP client with OAuth2 authentication
79func (c *OAuthClient) GetHTTPClient() *http.Client {
80 if c.httpClient != nil {
81 return c.httpClient
82 }
83
84 // Return default client if not authenticated
85 return &http.Client{
86 Timeout: time.Second * 30,
87 }
88}
89
90// IsAuthenticated checks if the client has a valid token
91func (c *OAuthClient) IsAuthenticated() bool {
92 if c.token == nil {
93 return false
94 }
95
96 // Check if token is expired (with 5 minute buffer)
97 return c.token.Valid() && c.token.Expiry.After(time.Now().Add(5*time.Minute))
98}
99
100// ClientCredentialsFlow performs client credentials OAuth2 flow
101func (c *OAuthClient) ClientCredentialsFlow(ctx context.Context) error {
102 if c.config == nil || !c.config.Enabled {
103 return fmt.Errorf("oauth2 not configured")
104 }
105
106 ccConfig := &clientcredentials.Config{
107 ClientID: c.config.ClientID,
108 ClientSecret: c.config.ClientSecret,
109 TokenURL: c.config.TokenURL,
110 Scopes: c.config.Scopes,
111 }
112
113 token, err := ccConfig.Token(ctx)
114 if err != nil {
115 return fmt.Errorf("failed to get client credentials token: %w", err)
116 }
117
118 c.token = token
119 c.httpClient = ccConfig.Client(ctx)
120 return nil
121}
122
123// RefreshToken refreshes the OAuth2 token if possible
124func (c *OAuthClient) RefreshToken(ctx context.Context) error {
125 if c.oauth2Config == nil {
126 return fmt.Errorf("oauth2 not configured")
127 }
128
129 if c.token == nil {
130 return fmt.Errorf("no token to refresh")
131 }
132
133 tokenSource := c.oauth2Config.TokenSource(ctx, c.token)
134 newToken, err := tokenSource.Token()
135 if err != nil {
136 return fmt.Errorf("failed to refresh token: %w", err)
137 }
138
139 c.token = newToken
140 c.httpClient = c.oauth2Config.Client(ctx, newToken)
141 return nil
142}
143
144// AddAuthHeader adds authentication header to an HTTP request
145func (c *OAuthClient) AddAuthHeader(req *http.Request) error {
146 if c.token == nil {
147 return fmt.Errorf("no authentication token available")
148 }
149
150 c.token.SetAuthHeader(req)
151 return nil
152}