app-cloudep-member-server/pkg/usecase/verify_google.go

129 lines
3.2 KiB
Go
Raw Normal View History

2024-12-30 03:58:14 +00:00
package usecase
import (
"app-cloudep-member-server/pkg/domain"
"app-cloudep-member-server/pkg/domain/usecase"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"time"
"code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/library-go/errs/code"
)
func (use *MemberUseCase) VerifyGoogleAuthResult(ctx context.Context, req usecase.VerifyAuthResultRequest) (usecase.GoogleTokenInfo, error) {
var tokenInfo usecase.GoogleTokenInfo
// 發送 Google Token Info API 請求
body, err := fetchGoogleTokenInfo(ctx, req.Token)
if err != nil {
return tokenInfo, err
}
// 解析返回的 JSON 數據
if err := json.Unmarshal(body, &tokenInfo); err != nil {
return tokenInfo, errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogle,
"failed to parse token info",
)
}
// 驗證 Token 資訊
if err := validateGoogleTokenInfo(tokenInfo, use.Config.GoogleAuth.ClientID); err != nil {
return tokenInfo, err
}
return tokenInfo, nil
}
// fetchGoogleTokenInfo 發送 Google TokenInfo API 請求並返回響應內容
func fetchGoogleTokenInfo(ctx context.Context, token string) ([]byte, error) {
uri := fmt.Sprintf("https://oauth2.googleapis.com/tokeninfo?id_token=%s", token)
// 發送請求
r, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogle,
"failed to create request", err.Error())
}
resp, err := http.DefaultClient.Do(r)
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogleTimeout,
"request timeout",
)
}
return nil, errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogleTimeout,
"failed to request Google TokenInfo API",
)
}
defer resp.Body.Close()
// 檢查返回的 HTTP 狀態碼
if resp.StatusCode != http.StatusOK {
return nil, errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogleHTTPCode,
fmt.Sprintf("unexpected status code: %d", resp.StatusCode),
)
}
// 讀取響應內容
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogle,
"failed to read response body",
)
}
return body, nil
}
// validateGoogleTokenInfo 驗證 Google Token 資訊
func validateGoogleTokenInfo(tokenInfo usecase.GoogleTokenInfo, expectedClientID string) error {
// **驗證 1: Token 是否過期**
expiration, err := strconv.ParseInt(tokenInfo.Exp, 10, 64)
if err != nil || expiration <= time.Now().UTC().Unix() {
return errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogleTokenExpired,
"token is expired",
)
}
// **驗證 2: Audience (aud) 是否與 Google Client ID 匹配**
if tokenInfo.Aud != expectedClientID {
return errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogleInvalidAudience,
"invalid audience",
)
}
// **驗證 3: 是否 email 已驗證**
if tokenInfo.EmailVerified == "false" {
return errs.ThirdPartyError(
code.CloudEPMember,
domain.FailedToVerifyGoogle,
"email is not verified",
)
}
return nil
}