2023-02-09 19:18:06 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2024-03-10 21:31:24 +00:00
|
|
|
|
2025-09-19 08:05:43 +00:00
|
|
|
"github.com/mhsanaei/3x-ui/v2/database"
|
|
|
|
"github.com/mhsanaei/3x-ui/v2/database/model"
|
|
|
|
"github.com/mhsanaei/3x-ui/v2/logger"
|
|
|
|
"github.com/mhsanaei/3x-ui/v2/util/crypto"
|
2025-09-28 19:00:16 +00:00
|
|
|
ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap"
|
2025-05-08 14:20:58 +00:00
|
|
|
"github.com/xlzd/gotp"
|
2023-02-09 19:18:06 +00:00
|
|
|
"gorm.io/gorm"
|
|
|
|
)
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// UserService provides business logic for user management and authentication.
|
|
|
|
// It handles user creation, login, password management, and 2FA operations.
|
2025-05-08 14:20:58 +00:00
|
|
|
type UserService struct {
|
|
|
|
settingService SettingService
|
|
|
|
}
|
2023-02-09 19:18:06 +00:00
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// GetFirstUser retrieves the first user from the database.
|
|
|
|
// This is typically used for initial setup or when there's only one admin user.
|
2023-02-09 19:18:06 +00:00
|
|
|
func (s *UserService) GetFirstUser() (*model.User, error) {
|
|
|
|
db := database.GetDB()
|
|
|
|
|
|
|
|
user := &model.User{}
|
|
|
|
err := db.Model(model.User{}).
|
|
|
|
First(user).
|
|
|
|
Error
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
2025-05-08 14:20:58 +00:00
|
|
|
func (s *UserService) CheckUser(username string, password string, twoFactorCode string) *model.User {
|
2023-02-09 19:18:06 +00:00
|
|
|
db := database.GetDB()
|
|
|
|
|
|
|
|
user := &model.User{}
|
2025-05-03 09:27:53 +00:00
|
|
|
|
2023-02-09 19:18:06 +00:00
|
|
|
err := db.Model(model.User{}).
|
2025-05-08 14:20:58 +00:00
|
|
|
Where("username = ?", username).
|
2023-02-09 19:18:06 +00:00
|
|
|
First(user).
|
|
|
|
Error
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
logger.Warning("check user err:", err)
|
|
|
|
return nil
|
|
|
|
}
|
2025-05-03 09:27:53 +00:00
|
|
|
|
2025-09-28 19:00:16 +00:00
|
|
|
// If LDAP enabled and local password check fails, attempt LDAP auth
|
|
|
|
if !crypto.CheckPasswordHash(user.Password, password) {
|
|
|
|
ldapEnabled, _ := s.settingService.GetLdapEnable()
|
|
|
|
if !ldapEnabled {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
host, _ := s.settingService.GetLdapHost()
|
|
|
|
port, _ := s.settingService.GetLdapPort()
|
|
|
|
useTLS, _ := s.settingService.GetLdapUseTLS()
|
|
|
|
bindDN, _ := s.settingService.GetLdapBindDN()
|
|
|
|
ldapPass, _ := s.settingService.GetLdapPassword()
|
|
|
|
baseDN, _ := s.settingService.GetLdapBaseDN()
|
|
|
|
userFilter, _ := s.settingService.GetLdapUserFilter()
|
|
|
|
userAttr, _ := s.settingService.GetLdapUserAttr()
|
|
|
|
|
|
|
|
cfg := ldaputil.Config{
|
|
|
|
Host: host,
|
|
|
|
Port: port,
|
|
|
|
UseTLS: useTLS,
|
|
|
|
BindDN: bindDN,
|
|
|
|
Password: ldapPass,
|
|
|
|
BaseDN: baseDN,
|
|
|
|
UserFilter: userFilter,
|
|
|
|
UserAttr: userAttr,
|
|
|
|
}
|
|
|
|
ok, err := ldaputil.AuthenticateUser(cfg, username, password)
|
|
|
|
if err != nil || !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// On successful LDAP auth, continue 2FA checks below
|
|
|
|
}
|
2025-05-03 09:27:53 +00:00
|
|
|
|
2025-05-08 14:20:58 +00:00
|
|
|
twoFactorEnable, err := s.settingService.GetTwoFactorEnable()
|
2025-05-03 09:27:53 +00:00
|
|
|
if err != nil {
|
2025-05-08 14:20:58 +00:00
|
|
|
logger.Warning("check two factor err:", err)
|
|
|
|
return nil
|
2025-05-03 09:27:53 +00:00
|
|
|
}
|
|
|
|
|
2025-05-08 14:20:58 +00:00
|
|
|
if twoFactorEnable {
|
|
|
|
twoFactorToken, err := s.settingService.GetTwoFactorToken()
|
2023-02-09 19:18:06 +00:00
|
|
|
|
2025-05-08 14:20:58 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Warning("check two factor token err:", err)
|
|
|
|
return nil
|
|
|
|
}
|
2023-04-21 15:30:14 +00:00
|
|
|
|
2025-05-08 14:20:58 +00:00
|
|
|
if gotp.NewDefaultTOTP(twoFactorToken).Now() != twoFactorCode {
|
|
|
|
return nil
|
|
|
|
}
|
2023-04-21 15:30:14 +00:00
|
|
|
}
|
2025-05-08 14:20:58 +00:00
|
|
|
|
2023-04-21 15:30:14 +00:00
|
|
|
return user
|
|
|
|
}
|
|
|
|
|
2025-05-08 14:20:58 +00:00
|
|
|
func (s *UserService) UpdateUser(id int, username string, password string) error {
|
2024-03-12 17:15:44 +00:00
|
|
|
db := database.GetDB()
|
2025-05-08 14:20:58 +00:00
|
|
|
hashedPassword, err := crypto.HashPasswordAsBcrypt(password)
|
2024-03-12 17:15:44 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2025-05-08 14:20:58 +00:00
|
|
|
return err
|
2024-03-12 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
2025-07-02 09:25:25 +00:00
|
|
|
twoFactorEnable, err := s.settingService.GetTwoFactorEnable()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if twoFactorEnable {
|
|
|
|
s.settingService.SetTwoFactorEnable(false)
|
|
|
|
s.settingService.SetTwoFactorToken("")
|
|
|
|
}
|
|
|
|
|
2025-05-08 14:20:58 +00:00
|
|
|
return db.Model(model.User{}).
|
|
|
|
Where("id = ?", id).
|
|
|
|
Updates(map[string]any{"username": username, "password": hashedPassword}).
|
|
|
|
Error
|
2024-03-12 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
2023-02-09 19:18:06 +00:00
|
|
|
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
|
|
|
if username == "" {
|
|
|
|
return errors.New("username can not be empty")
|
|
|
|
} else if password == "" {
|
|
|
|
return errors.New("password can not be empty")
|
|
|
|
}
|
2025-05-03 09:27:53 +00:00
|
|
|
hashedPassword, er := crypto.HashPasswordAsBcrypt(password)
|
|
|
|
|
|
|
|
if er != nil {
|
|
|
|
return er
|
|
|
|
}
|
|
|
|
|
2023-02-09 19:18:06 +00:00
|
|
|
db := database.GetDB()
|
|
|
|
user := &model.User{}
|
|
|
|
err := db.Model(model.User{}).First(user).Error
|
|
|
|
if database.IsNotFound(err) {
|
|
|
|
user.Username = username
|
2025-05-03 09:27:53 +00:00
|
|
|
user.Password = hashedPassword
|
2023-02-09 19:18:06 +00:00
|
|
|
return db.Model(model.User{}).Create(user).Error
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
user.Username = username
|
2025-05-03 09:27:53 +00:00
|
|
|
user.Password = hashedPassword
|
2023-02-09 19:18:06 +00:00
|
|
|
return db.Save(user).Error
|
|
|
|
}
|