- 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.
152 lines
3.4 KiB
Go
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
|
|
}
|