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 }