You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
5.4 KiB
213 lines
5.4 KiB
package agents
|
|
|
|
import (
|
|
"context"
|
|
"yunyan/pb"
|
|
"yunyan/sys/ipinfo"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
|
|
"google.golang.org/api/oauth2/v2"
|
|
"google.golang.org/api/option"
|
|
)
|
|
|
|
type (
|
|
FacebookTokenDebugResponse struct {
|
|
Data struct {
|
|
AppID string `json:"app_id"`
|
|
IsValid bool `json:"is_valid"`
|
|
UserID string `json:"user_id"`
|
|
ExpiresAt int64 `json:"expires_at"`
|
|
Scopes []string `json:"scopes"`
|
|
} `json:"data"`
|
|
}
|
|
WeChatTokenResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
OpenID string `json:"openid"`
|
|
Scope string `json:"scope"`
|
|
}
|
|
|
|
WeChatUserInfoResponse struct {
|
|
OpenID string `json:"openid"`
|
|
Nickname string `json:"nickname"`
|
|
HeadImgURL string `json:"headimgurl"`
|
|
}
|
|
)
|
|
|
|
func verifyGoogleIDToken(idToken string) (*oauth2.Tokeninfo, error) {
|
|
ctx := context.Background()
|
|
|
|
// 创建 OAuth2 服务
|
|
oauth2Service, err := oauth2.NewService(ctx, option.WithHTTPClient(http.DefaultClient))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create OAuth2 service: %v", err)
|
|
}
|
|
|
|
// 调用 Google 的 Tokeninfo 接口验证 ID Token
|
|
tokenInfo, err := oauth2Service.Tokeninfo().IdToken(idToken).Do()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid ID Token: %v", err)
|
|
}
|
|
|
|
// 检查 Token 是否有效
|
|
if tokenInfo.VerifiedEmail != true {
|
|
return nil, fmt.Errorf("email not verified")
|
|
}
|
|
|
|
return tokenInfo, nil
|
|
}
|
|
|
|
func verifyFacebookAccessToken(accessToken, facebookAppID, facebookAppSecret string) (string, error) {
|
|
// 调用 Facebook 的 Debug Token 接口
|
|
url := fmt.Sprintf(
|
|
"https://graph.facebook.com/debug_token?input_token=%s&access_token=%s|%s",
|
|
accessToken, facebookAppID, facebookAppSecret,
|
|
)
|
|
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to call Facebook API: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read response body: %v", err)
|
|
}
|
|
|
|
// 解析 Facebook 的响应
|
|
var tokenResponse FacebookTokenDebugResponse
|
|
if err := json.Unmarshal(body, &tokenResponse); err != nil {
|
|
return "", fmt.Errorf("failed to parse Facebook response: %v", err)
|
|
}
|
|
|
|
// 检查 Token 是否有效
|
|
if !tokenResponse.Data.IsValid {
|
|
return "", fmt.Errorf("token is invalid")
|
|
}
|
|
|
|
// 检查 App ID 是否匹配
|
|
if tokenResponse.Data.AppID != facebookAppID {
|
|
return "", fmt.Errorf("invalid app ID")
|
|
}
|
|
|
|
// 返回用户 ID
|
|
return tokenResponse.Data.UserID, nil
|
|
}
|
|
|
|
func getWeChatAccessToken(code, wechatAppID, wechatAppSecret string) (*WeChatTokenResponse, error) {
|
|
// 调用微信 API 获取 Access Token
|
|
url := fmt.Sprintf(
|
|
"https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
|
|
wechatAppID, wechatAppSecret, code,
|
|
)
|
|
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call WeChat API: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response body: %v", err)
|
|
}
|
|
|
|
// 解析微信的响应
|
|
var tokenResponse WeChatTokenResponse
|
|
if err := json.Unmarshal(body, &tokenResponse); err != nil {
|
|
return nil, fmt.Errorf("failed to parse WeChat response: %v", err)
|
|
}
|
|
|
|
return &tokenResponse, nil
|
|
}
|
|
|
|
func getWeChatUserInfo(accessToken, openID string) (*WeChatUserInfoResponse, error) {
|
|
// 调用微信 API 获取用户信息
|
|
url := fmt.Sprintf(
|
|
"https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s",
|
|
accessToken, openID,
|
|
)
|
|
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call WeChat API: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response body: %v", err)
|
|
}
|
|
|
|
// 解析微信的响应
|
|
var userInfo WeChatUserInfoResponse
|
|
if err := json.Unmarshal(body, &userInfo); err != nil {
|
|
return nil, fmt.Errorf("failed to parse WeChat response: %v", err)
|
|
}
|
|
|
|
return &userInfo, nil
|
|
}
|
|
|
|
// ToRegion 将 IPData 转换为区域枚举
|
|
// 优先按特大国家匹配,再按洲级别归类
|
|
func ToRegion(d *ipinfo.IPData) pb.Region {
|
|
if d == nil {
|
|
return pb.Region_RegionUnknown
|
|
}
|
|
|
|
// 优先匹配特大国家
|
|
switch d.CountryCode {
|
|
case "CN", "HK", "MO":
|
|
return pb.Region_RegionChina
|
|
case "US":
|
|
return pb.Region_RegionUSA
|
|
case "RU":
|
|
return pb.Region_RegionRussia
|
|
case "BR":
|
|
return pb.Region_RegionBrazil
|
|
case "IN":
|
|
return pb.Region_RegionIndia
|
|
}
|
|
|
|
// 亚洲内部细分
|
|
if d.ContinentCode == "AS" {
|
|
switch d.CountryCode {
|
|
// 东亚(日韩蒙等)
|
|
case "JP", "KR", "MN", "TW":
|
|
return pb.Region_RegionEastAsia
|
|
// 东南亚
|
|
case "VN", "TH", "MY", "SG", "ID", "PH", "MM", "KH", "LA", "BN", "TL":
|
|
return pb.Region_RegionSoutheastAsia
|
|
// 南亚
|
|
case "PK", "BD", "LK", "NP", "BT", "MV", "AF":
|
|
return pb.Region_RegionSouthAsia
|
|
// 西亚/中东
|
|
case "SA", "AE", "IR", "IQ", "TR", "IL", "JO", "KW", "QA", "BH", "OM",
|
|
"YE", "SY", "LB", "PS", "CY", "GE", "AM", "AZ", "UZ", "KZ", "TM", "TJ", "KG":
|
|
return pb.Region_RegionMiddleEast
|
|
default:
|
|
return pb.Region_RegionEastAsia
|
|
}
|
|
}
|
|
|
|
// 按洲归类
|
|
switch d.ContinentCode {
|
|
case "EU":
|
|
return pb.Region_RegionEurope
|
|
case "NA":
|
|
return pb.Region_RegionNorthAmerica
|
|
case "SA":
|
|
return pb.Region_RegionSouthAmerica
|
|
case "AF":
|
|
return pb.Region_RegionAfrica
|
|
case "OC":
|
|
return pb.Region_RegionOceania
|
|
}
|
|
|
|
return pb.Region_RegionUnknown
|
|
}
|
|
|