138 lines
3.3 KiB
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
|
|
}
|