133 lines
3.7 KiB
Go
133 lines
3.7 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
xaiauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/xai"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/browser"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
|
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// XAIAuthenticator implements the xAI Grok OAuth device-code flow.
|
|
type XAIAuthenticator struct{}
|
|
|
|
// NewXAIAuthenticator constructs a new xAI authenticator.
|
|
func NewXAIAuthenticator() Authenticator {
|
|
return &XAIAuthenticator{}
|
|
}
|
|
|
|
// Provider returns the provider key for xAI.
|
|
func (XAIAuthenticator) Provider() string {
|
|
return "xai"
|
|
}
|
|
|
|
// RefreshLead instructs the manager to refresh before token expiry.
|
|
func (XAIAuthenticator) RefreshLead() *time.Duration {
|
|
lead := xaiauth.RefreshLead()
|
|
return &lead
|
|
}
|
|
|
|
// Login launches the OAuth device-code flow to obtain xAI tokens and persists them.
|
|
func (a XAIAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) {
|
|
if cfg == nil {
|
|
return nil, fmt.Errorf("cliproxy auth: configuration is required")
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if opts == nil {
|
|
opts = &LoginOptions{}
|
|
}
|
|
|
|
authSvc := xaiauth.NewXAIAuth(cfg)
|
|
|
|
fmt.Println("Starting xAI authentication...")
|
|
deviceCode, err := authSvc.StartDeviceFlow(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("xai: failed to start device flow: %w", err)
|
|
}
|
|
|
|
verificationURL := strings.TrimSpace(deviceCode.VerificationURIComplete)
|
|
if verificationURL == "" {
|
|
verificationURL = strings.TrimSpace(deviceCode.VerificationURI)
|
|
}
|
|
|
|
fmt.Printf("\nTo authenticate, please visit:\n%s\n\n", verificationURL)
|
|
if deviceCode.UserCode != "" {
|
|
fmt.Printf("Then enter this code: %s\n\n", deviceCode.UserCode)
|
|
}
|
|
|
|
if !opts.NoBrowser {
|
|
if browser.IsAvailable() {
|
|
if errOpen := browser.OpenURL(verificationURL); errOpen != nil {
|
|
log.Warnf("Failed to open browser automatically: %v", errOpen)
|
|
} else {
|
|
fmt.Println("Browser opened automatically.")
|
|
}
|
|
} else {
|
|
log.Warn("No browser available; please open the URL manually")
|
|
}
|
|
}
|
|
|
|
fmt.Println("Waiting for authorization...")
|
|
if deviceCode.ExpiresIn > 0 {
|
|
fmt.Printf("(This will timeout in %d seconds if not authorized)\n", deviceCode.ExpiresIn)
|
|
}
|
|
|
|
bundle, errWait := authSvc.WaitForAuthorization(ctx, deviceCode)
|
|
if errWait != nil {
|
|
return nil, fmt.Errorf("xai: %w", errWait)
|
|
}
|
|
|
|
tokenStorage := authSvc.CreateTokenStorage(bundle)
|
|
if tokenStorage == nil || strings.TrimSpace(tokenStorage.AccessToken) == "" {
|
|
return nil, fmt.Errorf("xai token storage missing access token")
|
|
}
|
|
|
|
fileName := xaiauth.CredentialFileName(tokenStorage.Email, tokenStorage.Subject)
|
|
label := strings.TrimSpace(tokenStorage.Email)
|
|
if label == "" {
|
|
label = "xAI"
|
|
}
|
|
|
|
metadata := map[string]any{
|
|
"type": "xai",
|
|
"access_token": tokenStorage.AccessToken,
|
|
"refresh_token": tokenStorage.RefreshToken,
|
|
"id_token": tokenStorage.IDToken,
|
|
"token_type": tokenStorage.TokenType,
|
|
"expires_in": tokenStorage.ExpiresIn,
|
|
"expired": tokenStorage.Expire,
|
|
"last_refresh": tokenStorage.LastRefresh,
|
|
"base_url": tokenStorage.BaseURL,
|
|
"token_endpoint": tokenStorage.TokenEndpoint,
|
|
"auth_kind": "oauth",
|
|
}
|
|
if tokenStorage.Email != "" {
|
|
metadata["email"] = tokenStorage.Email
|
|
}
|
|
if tokenStorage.Subject != "" {
|
|
metadata["sub"] = tokenStorage.Subject
|
|
}
|
|
|
|
fmt.Println("xAI authentication successful")
|
|
|
|
return &coreauth.Auth{
|
|
ID: fileName,
|
|
Provider: a.Provider(),
|
|
FileName: fileName,
|
|
Label: label,
|
|
Storage: tokenStorage,
|
|
Metadata: metadata,
|
|
Attributes: map[string]string{
|
|
"auth_kind": "oauth",
|
|
"base_url": tokenStorage.BaseURL,
|
|
},
|
|
}, nil
|
|
}
|