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

208 lines
5.6 KiB
Go
Raw Normal View History

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
}