105 lines
2.8 KiB
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
|
|
}
|