package usecase import ( errs "backend/pkg/library/errors" "context" "encoding/json" "errors" "fmt" "io" "net/http" "strconv" "time" "backend/pkg/member/domain/usecase" ) func (use *MemberUseCase) VerifyGoogleAuthResult(ctx context.Context, req usecase.VerifyAuthResultRequest) (usecase.GoogleTokenInfo, error) { var tokenInfo usecase.GoogleTokenInfo // 發送 Google Token Info API 請求 body, err := use.fetchGoogleTokenInfo(ctx, req.Token) if err != nil { return tokenInfo, err } // 解析返回的 JSON 數據 if err := json.Unmarshal(body, &tokenInfo); err != nil { return tokenInfo, errs.SysInternalError("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 (use *MemberUseCase) 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.SvcThirdPartyErrorL( use.Logger, []errs.LogField{ {Key: "request URL", Val: uri}, {Key: "func", Val: "fetchGoogleTokenInfo"}, }, "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.SysTimeoutError("fetch google timeout") } return nil, errs.SvcThirdPartyErrorL( use.Logger, []errs.LogField{ {Key: "request URL", Val: uri}, {Key: "method", Val: http.MethodGet}, {Key: "func", Val: "fetchGoogleTokenInfo"}, }, "failed to request Google TokenInfo API", ) } defer resp.Body.Close() // 檢查返回的 HTTP 狀態碼 if resp.StatusCode != http.StatusOK { return nil, errs.SvcThirdPartyError(fmt.Sprintf("unexpected status code: %d", resp.StatusCode)) } // 讀取響應內容 body, err := io.ReadAll(resp.Body) if err != nil { return nil, errs.SvcThirdPartyError("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.AuthExpiredError("token is expired") } // **驗證 2: Audience (aud) 是否與 Google Client ID 匹配** if tokenInfo.Aud != expectedClientID { return errs.SvcThirdPartyError("invalid audience") } // **驗證 3: 是否 email 已驗證** if tokenInfo.EmailVerified == "false" { return errs.SvcThirdPartyError("email is not verified") } return nil }