Files
user-system/internal/auth/providers/facebook.go

208 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package providers
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
// FacebookProvider Facebook OAuth提供者
type FacebookProvider struct {
AppID string
AppSecret string
RedirectURI string
}
// FacebookAuthURLResponse Facebook授权URL响应
type FacebookAuthURLResponse struct {
URL string `json:"url"`
State string `json:"state"`
Redirect string `json:"redirect,omitempty"`
}
// FacebookTokenResponse Facebook Token响应
type FacebookTokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}
// FacebookUserInfo Facebook用户信息
type FacebookUserInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Picture struct {
Data struct {
URL string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
IsSilhouette bool `json:"is_silhouette"`
} `json:"data"`
} `json:"picture"`
}
// NewFacebookProvider 创建Facebook OAuth提供者
func NewFacebookProvider(appID, appSecret, redirectURI string) *FacebookProvider {
return &FacebookProvider{
AppID: appID,
AppSecret: appSecret,
RedirectURI: redirectURI,
}
}
// GenerateState 生成随机状态码
func (f *FacebookProvider) GenerateState() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// GetAuthURL 获取Facebook授权URL
func (f *FacebookProvider) GetAuthURL(state string) (*FacebookAuthURLResponse, error) {
authURL := fmt.Sprintf(
"https://www.facebook.com/v18.0/dialog/oauth?client_id=%s&redirect_uri=%s&scope=email,public_profile&response_type=code&state=%s",
f.AppID,
url.QueryEscape(f.RedirectURI),
state,
)
return &FacebookAuthURLResponse{
URL: authURL,
State: state,
Redirect: f.RedirectURI,
}, nil
}
// ExchangeCode 用授权码换取访问令牌
func (f *FacebookProvider) ExchangeCode(ctx context.Context, code string) (*FacebookTokenResponse, error) {
tokenURL := fmt.Sprintf(
"https://graph.facebook.com/v18.0/oauth/access_token?client_id=%s&client_secret=%s&redirect_uri=%s&code=%s",
f.AppID,
f.AppSecret,
url.QueryEscape(f.RedirectURI),
code,
)
req, err := http.NewRequestWithContext(ctx, "GET", tokenURL, nil)
if err != nil {
return nil, fmt.Errorf("create request failed: %w", err)
}
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 tokenResp FacebookTokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
return nil, fmt.Errorf("parse token response failed: %w", err)
}
return &tokenResp, nil
}
// GetUserInfo 获取Facebook用户信息
func (f *FacebookProvider) GetUserInfo(ctx context.Context, accessToken string) (*FacebookUserInfo, error) {
// 请求用户信息(包括头像)
userInfoURL := fmt.Sprintf(
"https://graph.facebook.com/v18.0/me?fields=id,name,email,picture&access_token=%s",
accessToken,
)
req, err := http.NewRequestWithContext(ctx, "GET", userInfoURL, nil)
if err != nil {
return nil, fmt.Errorf("create request failed: %w", err)
}
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)
}
// Facebook错误响应
var errResp struct {
Error struct {
Message string `json:"message"`
Type string `json:"type"`
Code int `json:"code"`
ErrorSubcode int `json:"error_subcode,omitempty"`
} `json:"error"`
}
if err := json.Unmarshal(body, &errResp); err == nil && errResp.Error.Message != "" {
return nil, fmt.Errorf("facebook api error: %s", errResp.Error.Message)
}
var userInfo FacebookUserInfo
if err := json.Unmarshal(body, &userInfo); err != nil {
return nil, fmt.Errorf("parse user info failed: %w", err)
}
return &userInfo, nil
}
// ValidateToken 验证访问令牌是否有效
func (f *FacebookProvider) ValidateToken(ctx context.Context, accessToken string) (bool, error) {
userInfo, err := f.GetUserInfo(ctx, accessToken)
if err != nil {
return false, err
}
return userInfo != nil && userInfo.ID != "", nil
}
// GetLongLivedToken 获取长期有效的访问令牌60天
func (f *FacebookProvider) GetLongLivedToken(ctx context.Context, shortLivedToken string) (*FacebookTokenResponse, error) {
tokenURL := fmt.Sprintf(
"https://graph.facebook.com/v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id=%s&client_secret=%s&fb_exchange_token=%s",
f.AppID,
f.AppSecret,
shortLivedToken,
)
req, err := http.NewRequestWithContext(ctx, "GET", tokenURL, nil)
if err != nil {
return nil, fmt.Errorf("create request failed: %w", err)
}
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 tokenResp FacebookTokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
return nil, fmt.Errorf("parse token response failed: %w", err)
}
return &tokenResp, nil
}