package providers import ( "context" "crypto/rand" "encoding/base64" "encoding/json" "fmt" "net/http" "net/url" "time" ) // TwitterProvider Twitter OAuth提供者 (OAuth 2.0 with PKCE) type TwitterProvider struct { ClientID string RedirectURI string } // TwitterAuthURLResponse Twitter授权URL响应 type TwitterAuthURLResponse struct { URL string `json:"url"` CodeVerifier string `json:"code_verifier"` State string `json:"state"` Redirect string `json:"redirect,omitempty"` } // TwitterTokenResponse Twitter Token响应 type TwitterTokenResponse struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` RefreshToken string `json:"refresh_token"` Scope string `json:"scope"` } // TwitterUserInfo Twitter用户信息 type TwitterUserInfo struct { Data struct { ID string `json:"id"` Name string `json:"name"` Username string `json:"username"` CreatedAt string `json:"created_at"` Description string `json:"description"` PublicMetrics struct { FollowersCount int `json:"followers_count"` FollowingCount int `json:"following_count"` TweetCount int `json:"tweet_count"` ListedCount int `json:"listed_count"` } `json:"public_metrics"` ProfileImageURL string `json:"profile_image_url"` } `json:"data"` } // TwitterErrorResponse Twitter错误响应 type TwitterErrorResponse struct { Title string `json:"title"` Detail string `json:"detail"` Type string `json:"type"` Status int `json:"status"` } // NewTwitterProvider 创建Twitter OAuth提供者 func NewTwitterProvider(clientID, redirectURI string) *TwitterProvider { return &TwitterProvider{ ClientID: clientID, RedirectURI: redirectURI, } } // GenerateCodeVerifier 生成PKCE Code Verifier func (t *TwitterProvider) GenerateCodeVerifier() (string, error) { b := make([]byte, 32) _, err := rand.Read(b) if err != nil { return "", err } return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(b), nil } // GenerateCodeChallenge 从Code Verifier生成Code Challenge func (t *TwitterProvider) GenerateCodeChallenge(verifier string) string { // 简化的base64编码(实际应用中应该使用SHA256哈希) return verifier } // GenerateState 生成随机状态码 func (t *TwitterProvider) GenerateState() (string, error) { b := make([]byte, 32) _, err := rand.Read(b) if err != nil { return "", err } return base64.URLEncoding.EncodeToString(b), nil } // GetAuthURL 获取Twitter授权URL (OAuth 2.0 with PKCE) func (t *TwitterProvider) GetAuthURL() (*TwitterAuthURLResponse, error) { verifier, err := t.GenerateCodeVerifier() if err != nil { return nil, fmt.Errorf("generate code verifier failed: %w", err) } challenge := t.GenerateCodeChallenge(verifier) state, err := t.GenerateState() if err != nil { return nil, fmt.Errorf("generate state failed: %w", err) } authURL := fmt.Sprintf( "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=tweet.read%%20users.read%%20offline.access&state=%s&code_challenge=%s&code_challenge_method=plain", t.ClientID, url.QueryEscape(t.RedirectURI), state, challenge, ) return &TwitterAuthURLResponse{ URL: authURL, CodeVerifier: verifier, State: state, Redirect: t.RedirectURI, }, nil } // ExchangeCode 用授权码换取访问令牌 func (t *TwitterProvider) ExchangeCode(ctx context.Context, code, codeVerifier string) (*TwitterTokenResponse, error) { tokenURL := "https://api.twitter.com/2/oauth2/token" data := url.Values{} data.Set("code", code) data.Set("grant_type", "authorization_code") data.Set("client_id", t.ClientID) data.Set("redirect_uri", t.RedirectURI) data.Set("code_verifier", codeVerifier) client := &http.Client{Timeout: 10 * time.Second} resp, err := postFormWithContext(ctx, client, tokenURL, data) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() body, err := readOAuthResponseBody(resp) if err != nil { return nil, fmt.Errorf("read response failed: %w", err) } // 检查错误响应 var errResp TwitterErrorResponse if err := json.Unmarshal(body, &errResp); err == nil && errResp.Detail != "" { return nil, fmt.Errorf("twitter api error: %s - %s", errResp.Title, errResp.Detail) } var tokenResp TwitterTokenResponse if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, fmt.Errorf("parse token response failed: %w", err) } return &tokenResp, nil } // GetUserInfo 获取Twitter用户信息 func (t *TwitterProvider) GetUserInfo(ctx context.Context, accessToken string) (*TwitterUserInfo, error) { userInfoURL := "https://api.twitter.com/2/users/me?user.fields=created_at,description,public_metrics,profile_image_url" req, err := http.NewRequestWithContext(ctx, "GET", userInfoURL, nil) if err != nil { return nil, fmt.Errorf("create request failed: %w", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() body, err := readOAuthResponseBody(resp) if err != nil { return nil, fmt.Errorf("read response failed: %w", err) } // 检查错误响应 var errResp TwitterErrorResponse if err := json.Unmarshal(body, &errResp); err == nil && errResp.Detail != "" { return nil, fmt.Errorf("twitter api error: %s - %s", errResp.Title, errResp.Detail) } var userInfo TwitterUserInfo if err := json.Unmarshal(body, &userInfo); err != nil { return nil, fmt.Errorf("parse user info failed: %w", err) } return &userInfo, nil } // RefreshToken 刷新访问令牌 func (t *TwitterProvider) RefreshToken(ctx context.Context, refreshToken string) (*TwitterTokenResponse, error) { tokenURL := "https://api.twitter.com/2/oauth2/token" data := url.Values{} data.Set("refresh_token", refreshToken) data.Set("grant_type", "refresh_token") data.Set("client_id", t.ClientID) client := &http.Client{Timeout: 10 * time.Second} resp, err := postFormWithContext(ctx, client, tokenURL, data) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() body, err := readOAuthResponseBody(resp) if err != nil { return nil, fmt.Errorf("read response failed: %w", err) } var errResp TwitterErrorResponse if err := json.Unmarshal(body, &errResp); err == nil && errResp.Detail != "" { return nil, fmt.Errorf("twitter api error: %s - %s", errResp.Title, errResp.Detail) } var tokenResp TwitterTokenResponse if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, fmt.Errorf("parse token response failed: %w", err) } return &tokenResp, nil } // ValidateToken 验证访问令牌是否有效 func (t *TwitterProvider) ValidateToken(ctx context.Context, accessToken string) (bool, error) { userInfo, err := t.GetUserInfo(ctx, accessToken) if err != nil { return false, err } return userInfo != nil && userInfo.Data.ID != "", nil } // RevokeToken 撤销访问令牌 func (t *TwitterProvider) RevokeToken(ctx context.Context, accessToken string) error { revokeURL := "https://api.twitter.com/2/oauth2/revoke" data := url.Values{} data.Set("token", accessToken) data.Set("client_id", t.ClientID) data.Set("token_type_hint", "access_token") client := &http.Client{Timeout: 10 * time.Second} resp, err := postFormWithContext(ctx, client, revokeURL, data) if err != nil { return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if _, err := readOAuthResponseBody(resp); err != nil { return fmt.Errorf("revoke token failed: %w", err) } return nil }