backend/pkg/member/usecase/verify_google.go

105 lines
2.8 KiB
Go

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
}