Files
Charon/backend/internal/services/auth_service.go
GitHub Actions 0854f94089 fix: reset models.Setting struct to prevent ID leakage in queries
- Added a reset of the models.Setting struct before querying for settings in both the Manager and Cerberus components to avoid ID leakage from previous queries.
- Introduced new functions in Cerberus for checking admin authentication and admin whitelist status.
- Enhanced middleware logic to allow admin users to bypass ACL checks if their IP is whitelisted.
- Added tests to verify the behavior of the middleware with respect to ACLs and admin whitelisting.
- Created a new utility for checking if an IP is in a CIDR list.
- Updated various services to use `Where` clause for fetching records by ID instead of directly passing the ID to `First`, ensuring consistency in query patterns.
- Added comprehensive tests for settings queries to demonstrate and verify the fix for ID leakage issues.
2026-01-28 10:30:03 +00:00

152 lines
3.4 KiB
Go

package services
import (
"errors"
"strings"
"time"
"github.com/Wikid82/charon/backend/internal/config"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"gorm.io/gorm"
)
type AuthService struct {
db *gorm.DB
config config.Config
}
func NewAuthService(db *gorm.DB, cfg config.Config) *AuthService {
return &AuthService{db: db, config: cfg}
}
type Claims struct {
UserID uint `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func (s *AuthService) Register(email, password, name string) (*models.User, error) {
email = strings.ToLower(email)
var count int64
s.db.Model(&models.User{}).Count(&count)
role := "user"
if count == 0 {
role = "admin" // First user is admin
}
user := &models.User{
UUID: uuid.New().String(),
Email: email,
Name: name,
Role: role,
APIKey: uuid.New().String(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := user.SetPassword(password); err != nil {
return nil, err
}
if err := s.db.Create(user).Error; err != nil {
return nil, err
}
return user, nil
}
func (s *AuthService) Login(email, password string) (string, error) {
email = strings.ToLower(email)
var user models.User
if err := s.db.Where("email = ?", email).First(&user).Error; err != nil {
return "", errors.New("invalid credentials")
}
if !user.Enabled {
return "", errors.New("account disabled")
}
if user.LockedUntil != nil && user.LockedUntil.After(time.Now()) {
return "", errors.New("account locked")
}
if !user.CheckPassword(password) {
user.FailedLoginAttempts++
if user.FailedLoginAttempts >= 5 {
lockTime := time.Now().Add(15 * time.Minute)
user.LockedUntil = &lockTime
}
s.db.Save(&user)
return "", errors.New("invalid credentials")
}
// Reset failed attempts
user.FailedLoginAttempts = 0
user.LockedUntil = nil
now := time.Now()
user.LastLogin = &now
s.db.Save(&user)
return s.GenerateToken(&user)
}
func (s *AuthService) GenerateToken(user *models.User) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &Claims{
UserID: user.ID,
Role: user.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
Issuer: "charon",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(s.config.JWTSecret))
}
func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword string) error {
var user models.User
if err := s.db.Where("id = ?", userID).First(&user).Error; err != nil {
return errors.New("user not found")
}
if !user.CheckPassword(oldPassword) {
return errors.New("invalid current password")
}
if err := user.SetPassword(newPassword); err != nil {
return err
}
return s.db.Save(&user).Error
}
func (s *AuthService) ValidateToken(tokenString string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(s.config.JWTSecret), nil
})
if err != nil {
return nil, err
}
if !token.Valid {
return nil, errors.New("invalid token")
}
return claims, nil
}
func (s *AuthService) GetUserByID(id uint) (*models.User, error) {
var user models.User
if err := s.db.Where("id = ?", id).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}