Yimaru-BackEnd/internal/services/authentication/google.go

138 lines
3.3 KiB
Go

package authentication
import (
"Yimaru-Backend/internal/domain"
"context"
"encoding/json"
"errors"
"fmt"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var googleOAuthConfig *oauth2.Config
func (s *Service) InitGoogleOAuth(clientID, clientSecret, redirectURL string) {
googleOAuthConfig = &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
},
Endpoint: google.Endpoint,
}
}
func (s *Service) GenerateGoogleLoginURL(state string) string {
return googleOAuthConfig.AuthCodeURL(state, oauth2.AccessTypeOffline)
}
func (s *Service) ExchangeGoogleCode(
ctx context.Context,
code string,
) (*oauth2.Token, error) {
if code == "" {
return nil, errors.New("missing google auth code")
}
return googleOAuthConfig.Exchange(ctx, code)
}
func (s *Service) FetchGoogleUser(
ctx context.Context,
token *oauth2.Token,
) (*domain.GoogleUser, error) {
client := googleOAuthConfig.Client(ctx, token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
return nil, fmt.Errorf("failed to fetch google user: %w", err)
}
defer resp.Body.Close()
var user domain.GoogleUser
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, fmt.Errorf("failed to decode google user: %w", err)
}
if user.Email == "" {
return nil, errors.New("google account has no email")
}
return &user, nil
}
func (s *Service) LoginWithGoogle(
ctx context.Context,
gUser domain.GoogleUser,
) (domain.LoginSuccess, error) {
// 1. Try Google ID login
user, err := s.userStore.GetUserByGoogleID(ctx, gUser.ID)
if err != nil {
// 2. Try account linking by email
user, err = s.userStore.GetUserByEmailPhone(ctx, gUser.Email, "")
if err != nil {
// 3. Create new user
user, err = s.userStore.CreateGoogleUser(ctx, gUser)
if err != nil {
return domain.LoginSuccess{}, err
}
} else {
// Link Google account
if err := s.userStore.LinkGoogleAccount(ctx, user.ID, gUser.ID); err != nil {
return domain.LoginSuccess{}, err
}
}
}
// 4. Status checks (identical to Login)
if user.Status == domain.UserStatusPending {
return domain.LoginSuccess{}, domain.ErrUserNotVerified
}
if user.Status == domain.UserStatusSuspended {
return domain.LoginSuccess{}, ErrUserSuspended
}
// 5. Revoke existing refresh token
oldToken, err := s.tokenStore.GetRefreshTokenByUserID(ctx, user.ID)
if err != nil && !errors.Is(err, ErrRefreshTokenNotFound) {
return domain.LoginSuccess{}, err
}
if err == nil && !oldToken.Revoked {
if err := s.tokenStore.RevokeRefreshToken(ctx, oldToken.Token); err != nil {
return domain.LoginSuccess{}, err
}
}
// 6. Generate new refresh token
refreshToken, err := generateRefreshToken()
if err != nil {
return domain.LoginSuccess{}, err
}
if err := s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
Token: refreshToken,
UserID: user.ID,
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
}); err != nil {
return domain.LoginSuccess{}, err
}
// 7. Return standard response
return domain.LoginSuccess{
UserId: user.ID,
Role: user.Role,
RfToken: refreshToken,
}, nil
}