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 }