package auth import ( "bytes" "context" "encoding/json" "fmt" "net/http" "os" "time" ) const ( tokenEndpoint = "https://api.anthropic.com/v1/oauth/token" clientID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e" ) type tokenRequest struct { ClientID string `json:"client_id"` GrantType string `json:"grant_type"` RefreshToken string `json:"refresh_token"` } type tokenResponse struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` Account struct { EmailAddress string `json:"email_address"` } `json:"account"` } type authFileJSON struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` Email string `json:"email"` Expired string `json:"expired"` Type string `json:"type"` } // RefreshToken performs an OAuth token refresh for the given credential. func RefreshToken(ctx context.Context, cred *Credential) error { reqBody := tokenRequest{ ClientID: clientID, GrantType: "refresh_token", RefreshToken: cred.RefreshToken, } body, err := json.Marshal(reqBody) if err != nil { return fmt.Errorf("marshal refresh request: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenEndpoint, bytes.NewReader(body)) if err != nil { return fmt.Errorf("create refresh request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("execute refresh request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("refresh failed with status %d", resp.StatusCode) } var tokenResp tokenResponse if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { return fmt.Errorf("decode refresh response: %w", err) } cred.mu.Lock() cred.AccessToken = tokenResp.AccessToken cred.RefreshToken = tokenResp.RefreshToken cred.ExpiresAt = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second) if tokenResp.Account.EmailAddress != "" { cred.Email = tokenResp.Account.EmailAddress } cred.mu.Unlock() return persistCredential(cred) } func persistCredential(cred *Credential) error { cred.mu.Lock() data := authFileJSON{ AccessToken: cred.AccessToken, RefreshToken: cred.RefreshToken, Email: cred.Email, Expired: cred.ExpiresAt.Format(time.RFC3339), Type: "claude", } filePath := cred.FilePath cred.mu.Unlock() out, err := json.MarshalIndent(data, "", " ") if err != nil { return fmt.Errorf("marshal auth file: %w", err) } if err := os.WriteFile(filePath, out, 0600); err != nil { return fmt.Errorf("write auth file %s: %w", filePath, err) } return nil }