129 lines
3.2 KiB
Go
129 lines
3.2 KiB
Go
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
|
|
}
|