172 lines
4.3 KiB
Go
172 lines
4.3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
const (
|
|
// Default OAuth configuration values
|
|
DefaultAuthorizeURL = "https://claude.ai/oauth/authorize"
|
|
DefaultTokenURL = "https://console.anthropic.com/v1/oauth/token"
|
|
DefaultClientID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
|
|
DefaultRedirectURI = "https://console.anthropic.com/oauth/code/callback"
|
|
DefaultScopes = "user:inference"
|
|
)
|
|
|
|
// getOAuthValues returns OAuth configuration values from environment variables or defaults
|
|
func getOAuthValues() (authorizeURL, tokenURL, clientID, redirectURI, scopes string) {
|
|
authorizeURL = os.Getenv("CLAUDE_AUTHORIZE_URL")
|
|
if authorizeURL == "" {
|
|
authorizeURL = DefaultAuthorizeURL
|
|
}
|
|
|
|
tokenURL = os.Getenv("CLAUDE_TOKEN_URL")
|
|
if tokenURL == "" {
|
|
tokenURL = DefaultTokenURL
|
|
}
|
|
|
|
clientID = os.Getenv("CLAUDE_CLIENT_ID")
|
|
if clientID == "" {
|
|
clientID = DefaultClientID
|
|
}
|
|
|
|
redirectURI = os.Getenv("CLAUDE_REDIRECT_URI")
|
|
if redirectURI == "" {
|
|
redirectURI = DefaultRedirectURI
|
|
}
|
|
|
|
scopes = os.Getenv("CLAUDE_SCOPES")
|
|
if scopes == "" {
|
|
scopes = DefaultScopes
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type OAuth2Credentials struct {
|
|
AuthURL string `json:"auth_url"`
|
|
CodeVerifier string `json:"code_verifier"`
|
|
State string `json:"state"`
|
|
CodeChallenge string `json:"code_challenge"`
|
|
}
|
|
|
|
// GetClaudeOAuthConfig returns the Claude OAuth2 configuration
|
|
func GetClaudeOAuthConfig() *oauth2.Config {
|
|
authorizeURL, tokenURL, clientID, redirectURI, scopes := getOAuthValues()
|
|
|
|
return &oauth2.Config{
|
|
ClientID: clientID,
|
|
RedirectURL: redirectURI,
|
|
Scopes: strings.Split(scopes, " "),
|
|
Endpoint: oauth2.Endpoint{
|
|
AuthURL: authorizeURL,
|
|
TokenURL: tokenURL,
|
|
},
|
|
}
|
|
}
|
|
|
|
// getOAuthConfig is kept for backward compatibility
|
|
func getOAuthConfig() *oauth2.Config {
|
|
return GetClaudeOAuthConfig()
|
|
}
|
|
|
|
// GenerateOAuthParams generates OAuth authorization URL and related parameters
|
|
func GenerateOAuthParams() (*OAuth2Credentials, error) {
|
|
config := getOAuthConfig()
|
|
|
|
// Generate PKCE parameters
|
|
codeVerifier := oauth2.GenerateVerifier()
|
|
state := oauth2.GenerateVerifier() // Reuse generator as state
|
|
|
|
// Generate authorization URL
|
|
authURL := config.AuthCodeURL(state,
|
|
oauth2.S256ChallengeOption(codeVerifier),
|
|
oauth2.SetAuthURLParam("code", "true"), // Claude-specific parameter
|
|
)
|
|
|
|
return &OAuth2Credentials{
|
|
AuthURL: authURL,
|
|
CodeVerifier: codeVerifier,
|
|
State: state,
|
|
CodeChallenge: oauth2.S256ChallengeFromVerifier(codeVerifier),
|
|
}, nil
|
|
}
|
|
|
|
// ExchangeCode
|
|
func ExchangeCode(authorizationCode, codeVerifier, state string, client *http.Client) (*oauth2.Token, error) {
|
|
config := getOAuthConfig()
|
|
|
|
if strings.Contains(authorizationCode, "#") {
|
|
parts := strings.Split(authorizationCode, "#")
|
|
if len(parts) > 0 {
|
|
authorizationCode = parts[0]
|
|
}
|
|
}
|
|
|
|
ctx := context.Background()
|
|
if client != nil {
|
|
ctx = context.WithValue(ctx, oauth2.HTTPClient, client)
|
|
}
|
|
|
|
token, err := config.Exchange(ctx, authorizationCode,
|
|
oauth2.VerifierOption(codeVerifier),
|
|
oauth2.SetAuthURLParam("state", state),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("token exchange failed: %w", err)
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
func ParseAuthorizationCode(input string) (string, error) {
|
|
if input == "" {
|
|
return "", fmt.Errorf("please provide a valid authorization code")
|
|
}
|
|
// URLs are not allowed
|
|
if strings.Contains(input, "http") || strings.Contains(input, "https") {
|
|
return "", fmt.Errorf("authorization code cannot contain URLs")
|
|
}
|
|
|
|
return input, nil
|
|
}
|
|
|
|
// GetClaudeHTTPClient returns a configured HTTP client for Claude OAuth operations
|
|
func GetClaudeHTTPClient() *http.Client {
|
|
return &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
}
|
|
}
|
|
|
|
// RefreshClaudeToken refreshes a Claude OAuth token using the refresh token
|
|
func RefreshClaudeToken(accessToken, refreshToken string) (*oauth2.Token, error) {
|
|
config := GetClaudeOAuthConfig()
|
|
|
|
// Create token from current values
|
|
currentToken := &oauth2.Token{
|
|
AccessToken: accessToken,
|
|
RefreshToken: refreshToken,
|
|
TokenType: "Bearer",
|
|
}
|
|
|
|
ctx := context.Background()
|
|
if client := GetClaudeHTTPClient(); client != nil {
|
|
ctx = context.WithValue(ctx, oauth2.HTTPClient, client)
|
|
}
|
|
|
|
// Refresh the token
|
|
newToken, err := config.TokenSource(ctx, currentToken).Token()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to refresh Claude token: %w", err)
|
|
}
|
|
|
|
return newToken, nil
|
|
}
|