Files
AuthCentral/internal/services/auth_service.go
Beyhan Oğur 8b1fbdee99 first commit
2026-04-26 21:37:58 +03:00

241 lines
7.2 KiB
Go

package services
import (
"errors"
"time"
"gauth-central/internal/database"
"gauth-central/internal/models"
"gauth-central/pkg/utils"
"github.com/markbates/goth"
"gorm.io/gorm"
)
type AuthService struct {
jwtService *JWTService
}
func NewAuthService() *AuthService {
return &AuthService{
jwtService: NewJWTService(),
}
}
func (s *AuthService) Register(username, email, password string) (*models.User, string, string, string, error) {
// Check if user exists (including soft-deleted users)
var count int64
database.DB.Model(&models.User{}).Unscoped().Where("email = ?", email).Count(&count)
if count > 0 {
return nil, "", "", "", errors.New("email already registered")
}
hashedPassword, err := utils.HashPassword(password)
if err != nil {
return nil, "", "", "", err
}
verifyToken, err := utils.GenerateSecureToken(32)
if err != nil {
return nil, "", "", "", err
}
// Email/password users must verify email; tokens are not issued until verified
falseBool := false
user := models.User{
UserName: username,
Email: email,
Password: hashedPassword,
EmailVerified: &falseBool, // Explicitly set to false
EmailVerifyToken: verifyToken,
}
// Create user - EmailVerified will be false by default or explicitly set
if err := database.DB.Create(&user).Error; err != nil {
// Fallback check for duplicate key error just in case race condition
if utils.IsDuplicateKeyError(err) {
return nil, "", "", "", errors.New("email already registered")
}
return nil, "", "", "", err
}
// Assign default "user" role
var userRole models.Role
if err := database.DB.Where("name = ?", "user").First(&userRole).Error; err == nil {
database.DB.Model(&user).Association("Roles").Append(&userRole)
}
// Reload user with roles (no JWT until email verified)
database.DB.Preload("Roles.Permissions").First(&user, user.ID)
return &user, "", "", verifyToken, nil
}
func (s *AuthService) Login(email, password string) (*models.User, string, string, error) {
var user models.User
// Preload Roles and Permissions
if err := database.DB.Preload("Roles.Permissions").Where("email = ?", email).First(&user).Error; err != nil {
return nil, "", "", errors.New("invalid credentials")
}
if !utils.CheckPasswordHash(password, user.Password) {
return nil, "", "", errors.New("invalid credentials")
}
if !user.IsEmailVerified() {
return nil, "", "", errors.New("email not verified")
}
accessToken, refreshToken, err := s.jwtService.GenerateTokenPair(user)
if err != nil {
return nil, "", "", err
}
return &user, accessToken, refreshToken, nil
}
func (s *AuthService) RefreshToken(refreshToken string) (string, string, error) {
claims, err := s.jwtService.ValidateToken(refreshToken)
if err != nil {
return "", "", err
}
// Here you might want to check against DB if user still exists or is banned
// Also you could implement token rotation (revoke used refresh token)
var user models.User
// Parse UUID from claims and preload permissions
if err := database.DB.Preload("Roles.Permissions").Where("id = ?", claims.UserID).First(&user).Error; err != nil {
return "", "", errors.New("user not found")
}
return s.jwtService.GenerateTokenPair(user)
}
func (s *AuthService) FindOrCreateSocialUser(gothUser goth.User, provider string) (*models.User, string, string, error) {
var socialAccount models.SocialAccount
var user models.User
// Check if social account exists
err := database.DB.Where("provider = ? AND provider_id = ?", provider, gothUser.UserID).First(&socialAccount).Error
if err == nil {
// Social account exists, get user
if err := database.DB.Preload("Roles.Permissions").First(&user, socialAccount.UserID).Error; err != nil {
return nil, "", "", err
}
// Update avatar if changed
if gothUser.AvatarURL != "" && user.Avatar != gothUser.AvatarURL {
database.DB.Model(&user).Update("avatar", gothUser.AvatarURL)
user.Avatar = gothUser.AvatarURL
}
} else if errors.Is(err, gorm.ErrRecordNotFound) {
// Check if user with same email exists
err := database.DB.Where("email = ?", gothUser.Email).First(&user).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
// Create new user - use name from provider or generate from email
username := gothUser.NickName
if username == "" {
username = gothUser.Name
}
if username == "" {
// Generate username from email (part before @)
for i, c := range gothUser.Email {
if c == '@' {
username = gothUser.Email[:i]
break
}
}
}
// OAuth providers have already verified email; no email verification required
trueBool := true
user = models.User{
UserName: username,
Email: gothUser.Email,
EmailVerified: &trueBool,
Avatar: gothUser.AvatarURL, // Save avatar from OAuth provider
}
if err := database.DB.Create(&user).Error; err != nil {
return nil, "", "", err
}
} else {
// Linking OAuth to existing user: treat as verified and update avatar
updates := map[string]interface{}{
"email_verified": true,
"email_verify_token": "",
}
if gothUser.AvatarURL != "" {
updates["avatar"] = gothUser.AvatarURL
}
database.DB.Model(&user).Updates(updates)
trueBool := true
user.EmailVerified = &trueBool
user.EmailVerifyToken = ""
if gothUser.AvatarURL != "" {
user.Avatar = gothUser.AvatarURL
}
}
// Create social account with avatar and name
socialAccount = models.SocialAccount{
UserID: user.ID,
Provider: provider,
ProviderID: gothUser.UserID,
Email: gothUser.Email,
Name: gothUser.Name,
AvatarURL: gothUser.AvatarURL,
}
if err := database.DB.Create(&socialAccount).Error; err != nil {
return nil, "", "", err
}
// Assign default "user" role
var userRole models.Role
if err := database.DB.Where("name = ?", "user").First(&userRole).Error; err == nil {
database.DB.Model(&user).Association("Roles").Append(&userRole)
}
} else {
return nil, "", "", err
}
// Make sure we have the user with all roles/permissions and social accounts loaded
database.DB.Preload("Roles.Permissions").Preload("SocialAccounts").First(&user, user.ID)
accessToken, refreshToken, err := s.jwtService.GenerateTokenPair(user)
if err != nil {
return nil, "", "", err
}
return &user, accessToken, refreshToken, nil
}
func (s *AuthService) GetUserByID(userID string) (*models.User, error) {
var user models.User
if err := database.DB.Preload("Roles.Permissions").Where("id = ?", userID).First(&user).Error; err != nil {
return nil, errors.New("user not found")
}
return &user, nil
}
// VerifyEmail marks the user as verified when the token matches. Only for email/password registrations.
func (s *AuthService) VerifyEmail(token string) error {
if token == "" {
return errors.New("invalid verification token")
}
var user models.User
if err := database.DB.Where("email_verify_token = ?", token).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("invalid or expired verification token")
}
return err
}
now := time.Now()
return database.DB.Model(&user).Updates(map[string]interface{}{
"email_verified": true,
"email_verify_token": "",
"email_verified_at": &now,
}).Error
}