gofmt
Some checks failed
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
Release 3X-UI / Build for Windows (push) Has been cancelled

This commit is contained in:
mhsanaei 2025-10-21 13:02:55 +02:00
parent 01d4a7488d
commit 713a7328f6
No known key found for this signature in database
GPG key ID: D875CD086CF668A0
5 changed files with 223 additions and 226 deletions

View file

@ -1,144 +1,142 @@
package ldaputil
import (
"crypto/tls"
"fmt"
"crypto/tls"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/go-ldap/ldap/v3"
)
type Config struct {
Host string
Port int
UseTLS bool
BindDN string
Password string
BaseDN string
UserFilter string
UserAttr string
FlagField string
TruthyVals []string
Invert bool
Host string
Port int
UseTLS bool
BindDN string
Password string
BaseDN string
UserFilter string
UserAttr string
FlagField string
TruthyVals []string
Invert bool
}
// FetchVlessFlags returns map[email]enabled
func FetchVlessFlags(cfg Config) (map[string]bool, error) {
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
var conn *ldap.Conn
var err error
if cfg.UseTLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return nil, err
}
defer conn.Close()
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
var conn *ldap.Conn
var err error
if cfg.UseTLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return nil, err
}
defer conn.Close()
if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
return nil, err
}
}
if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
return nil, err
}
}
if cfg.UserFilter == "" {
cfg.UserFilter = "(objectClass=person)"
}
if cfg.UserAttr == "" {
cfg.UserAttr = "mail"
}
// if field not set we fallback to legacy vless_enabled
if cfg.FlagField == "" {
cfg.FlagField = "vless_enabled"
}
if cfg.UserFilter == "" {
cfg.UserFilter = "(objectClass=person)"
}
if cfg.UserAttr == "" {
cfg.UserAttr = "mail"
}
// if field not set we fallback to legacy vless_enabled
if cfg.FlagField == "" {
cfg.FlagField = "vless_enabled"
}
req := ldap.NewSearchRequest(
cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
cfg.UserFilter,
[]string{cfg.UserAttr, cfg.FlagField},
nil,
)
req := ldap.NewSearchRequest(
cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
cfg.UserFilter,
[]string{cfg.UserAttr, cfg.FlagField},
nil,
)
res, err := conn.Search(req)
if err != nil {
return nil, err
}
res, err := conn.Search(req)
if err != nil {
return nil, err
}
result := make(map[string]bool, len(res.Entries))
for _, e := range res.Entries {
user := e.GetAttributeValue(cfg.UserAttr)
if user == "" {
continue
}
val := e.GetAttributeValue(cfg.FlagField)
enabled := false
for _, t := range cfg.TruthyVals {
if val == t {
enabled = true
break
}
}
if cfg.Invert {
enabled = !enabled
}
result[user] = enabled
}
return result, nil
result := make(map[string]bool, len(res.Entries))
for _, e := range res.Entries {
user := e.GetAttributeValue(cfg.UserAttr)
if user == "" {
continue
}
val := e.GetAttributeValue(cfg.FlagField)
enabled := false
for _, t := range cfg.TruthyVals {
if val == t {
enabled = true
break
}
}
if cfg.Invert {
enabled = !enabled
}
result[user] = enabled
}
return result, nil
}
// AuthenticateUser searches user by cfg.UserAttr and attempts to bind with provided password.
func AuthenticateUser(cfg Config, username, password string) (bool, error) {
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
var conn *ldap.Conn
var err error
if cfg.UseTLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return false, err
}
defer conn.Close()
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
var conn *ldap.Conn
var err error
if cfg.UseTLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return false, err
}
defer conn.Close()
// Optional initial bind for search
if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
return false, err
}
}
// Optional initial bind for search
if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
return false, err
}
}
if cfg.UserFilter == "" {
cfg.UserFilter = "(objectClass=person)"
}
if cfg.UserAttr == "" {
cfg.UserAttr = "uid"
}
if cfg.UserFilter == "" {
cfg.UserFilter = "(objectClass=person)"
}
if cfg.UserAttr == "" {
cfg.UserAttr = "uid"
}
// Build filter to find specific user
filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username))
req := ldap.NewSearchRequest(
cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false,
filter,
[]string{"dn"},
nil,
)
res, err := conn.Search(req)
if err != nil {
return false, err
}
if len(res.Entries) == 0 {
return false, nil
}
userDN := res.Entries[0].DN
// Try to bind as the user
if err := conn.Bind(userDN, password); err != nil {
return false, nil
}
return true, nil
// Build filter to find specific user
filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username))
req := ldap.NewSearchRequest(
cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false,
filter,
[]string{"dn"},
nil,
)
res, err := conn.Search(req)
if err != nil {
return false, err
}
if len(res.Entries) == 0 {
return false, nil
}
userDN := res.Entries[0].DN
// Try to bind as the user
if err := conn.Bind(userDN, password); err != nil {
return false, nil
}
return true, nil
}

View file

@ -74,30 +74,30 @@ type AllSetting struct {
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` // JSON subscription fragment configuration
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
// LDAP settings
LdapEnable bool `json:"ldapEnable" form:"ldapEnable"`
LdapHost string `json:"ldapHost" form:"ldapHost"`
LdapPort int `json:"ldapPort" form:"ldapPort"`
LdapUseTLS bool `json:"ldapUseTLS" form:"ldapUseTLS"`
LdapBindDN string `json:"ldapBindDN" form:"ldapBindDN"`
LdapPassword string `json:"ldapPassword" form:"ldapPassword"`
LdapBaseDN string `json:"ldapBaseDN" form:"ldapBaseDN"`
LdapUserFilter string `json:"ldapUserFilter" form:"ldapUserFilter"`
LdapUserAttr string `json:"ldapUserAttr" form:"ldapUserAttr"` // e.g., mail or uid
LdapVlessField string `json:"ldapVlessField" form:"ldapVlessField"`
LdapSyncCron string `json:"ldapSyncCron" form:"ldapSyncCron"`
LdapEnable bool `json:"ldapEnable" form:"ldapEnable"`
LdapHost string `json:"ldapHost" form:"ldapHost"`
LdapPort int `json:"ldapPort" form:"ldapPort"`
LdapUseTLS bool `json:"ldapUseTLS" form:"ldapUseTLS"`
LdapBindDN string `json:"ldapBindDN" form:"ldapBindDN"`
LdapPassword string `json:"ldapPassword" form:"ldapPassword"`
LdapBaseDN string `json:"ldapBaseDN" form:"ldapBaseDN"`
LdapUserFilter string `json:"ldapUserFilter" form:"ldapUserFilter"`
LdapUserAttr string `json:"ldapUserAttr" form:"ldapUserAttr"` // e.g., mail or uid
LdapVlessField string `json:"ldapVlessField" form:"ldapVlessField"`
LdapSyncCron string `json:"ldapSyncCron" form:"ldapSyncCron"`
// Generic flag configuration
LdapFlagField string `json:"ldapFlagField" form:"ldapFlagField"`
LdapTruthyValues string `json:"ldapTruthyValues" form:"ldapTruthyValues"`
LdapInvertFlag bool `json:"ldapInvertFlag" form:"ldapInvertFlag"`
LdapInboundTags string `json:"ldapInboundTags" form:"ldapInboundTags"`
LdapAutoCreate bool `json:"ldapAutoCreate" form:"ldapAutoCreate"`
LdapAutoDelete bool `json:"ldapAutoDelete" form:"ldapAutoDelete"`
LdapDefaultTotalGB int `json:"ldapDefaultTotalGB" form:"ldapDefaultTotalGB"`
LdapDefaultExpiryDays int `json:"ldapDefaultExpiryDays" form:"ldapDefaultExpiryDays"`
LdapDefaultLimitIP int `json:"ldapDefaultLimitIP" form:"ldapDefaultLimitIP"`
LdapFlagField string `json:"ldapFlagField" form:"ldapFlagField"`
LdapTruthyValues string `json:"ldapTruthyValues" form:"ldapTruthyValues"`
LdapInvertFlag bool `json:"ldapInvertFlag" form:"ldapInvertFlag"`
LdapInboundTags string `json:"ldapInboundTags" form:"ldapInboundTags"`
LdapAutoCreate bool `json:"ldapAutoCreate" form:"ldapAutoCreate"`
LdapAutoDelete bool `json:"ldapAutoDelete" form:"ldapAutoDelete"`
LdapDefaultTotalGB int `json:"ldapDefaultTotalGB" form:"ldapDefaultTotalGB"`
LdapDefaultExpiryDays int `json:"ldapDefaultExpiryDays" form:"ldapDefaultExpiryDays"`
LdapDefaultLimitIP int `json:"ldapDefaultLimitIP" form:"ldapDefaultLimitIP"`
// JSON subscription routing rules
}

View file

@ -1569,21 +1569,20 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
return !clientOldEnabled, needRestart, nil
}
// SetClientEnableByEmail sets client enable state to desired value; returns (changed, needRestart, error)
func (s *InboundService) SetClientEnableByEmail(clientEmail string, enable bool) (bool, bool, error) {
current, err := s.checkIsEnabledByEmail(clientEmail)
if err != nil {
return false, false, err
}
if current == enable {
return false, false, nil
}
newEnabled, needRestart, err := s.ToggleClientEnableByEmail(clientEmail)
if err != nil {
return false, needRestart, err
}
return newEnabled == enable, needRestart, nil
current, err := s.checkIsEnabledByEmail(clientEmail)
if err != nil {
return false, false, err
}
if current == enable {
return false, false, nil
}
newEnabled, needRestart, err := s.ToggleClientEnableByEmail(clientEmail)
if err != nil {
return false, needRestart, err
}
return newEnabled == enable, needRestart, nil
}
func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) (bool, error) {

View file

@ -74,26 +74,26 @@ var defaultValueMap = map[string]string{
"externalTrafficInformEnable": "false",
"externalTrafficInformURI": "",
// LDAP defaults
"ldapEnable": "false",
"ldapHost": "",
"ldapPort": "389",
"ldapUseTLS": "false",
"ldapBindDN": "",
"ldapPassword": "",
"ldapBaseDN": "",
"ldapUserFilter": "(objectClass=person)",
"ldapUserAttr": "mail",
"ldapVlessField": "vless_enabled",
"ldapSyncCron": "@every 1m",
"ldapFlagField": "",
"ldapTruthyValues": "true,1,yes,on",
"ldapInvertFlag": "false",
"ldapInboundTags": "",
"ldapAutoCreate": "false",
"ldapAutoDelete": "false",
"ldapDefaultTotalGB": "0",
"ldapDefaultExpiryDays": "0",
"ldapDefaultLimitIP": "0",
"ldapEnable": "false",
"ldapHost": "",
"ldapPort": "389",
"ldapUseTLS": "false",
"ldapBindDN": "",
"ldapPassword": "",
"ldapBaseDN": "",
"ldapUserFilter": "(objectClass=person)",
"ldapUserAttr": "mail",
"ldapVlessField": "vless_enabled",
"ldapSyncCron": "@every 1m",
"ldapFlagField": "",
"ldapTruthyValues": "true,1,yes,on",
"ldapInvertFlag": "false",
"ldapInboundTags": "",
"ldapAutoCreate": "false",
"ldapAutoDelete": "false",
"ldapDefaultTotalGB": "0",
"ldapDefaultExpiryDays": "0",
"ldapDefaultLimitIP": "0",
}
// SettingService provides business logic for application settings management.
@ -565,83 +565,83 @@ func (s *SettingService) GetIpLimitEnable() (bool, error) {
// LDAP exported getters
func (s *SettingService) GetLdapEnable() (bool, error) {
return s.getBool("ldapEnable")
return s.getBool("ldapEnable")
}
func (s *SettingService) GetLdapHost() (string, error) {
return s.getString("ldapHost")
return s.getString("ldapHost")
}
func (s *SettingService) GetLdapPort() (int, error) {
return s.getInt("ldapPort")
return s.getInt("ldapPort")
}
func (s *SettingService) GetLdapUseTLS() (bool, error) {
return s.getBool("ldapUseTLS")
return s.getBool("ldapUseTLS")
}
func (s *SettingService) GetLdapBindDN() (string, error) {
return s.getString("ldapBindDN")
return s.getString("ldapBindDN")
}
func (s *SettingService) GetLdapPassword() (string, error) {
return s.getString("ldapPassword")
return s.getString("ldapPassword")
}
func (s *SettingService) GetLdapBaseDN() (string, error) {
return s.getString("ldapBaseDN")
return s.getString("ldapBaseDN")
}
func (s *SettingService) GetLdapUserFilter() (string, error) {
return s.getString("ldapUserFilter")
return s.getString("ldapUserFilter")
}
func (s *SettingService) GetLdapUserAttr() (string, error) {
return s.getString("ldapUserAttr")
return s.getString("ldapUserAttr")
}
func (s *SettingService) GetLdapVlessField() (string, error) {
return s.getString("ldapVlessField")
return s.getString("ldapVlessField")
}
func (s *SettingService) GetLdapSyncCron() (string, error) {
return s.getString("ldapSyncCron")
return s.getString("ldapSyncCron")
}
func (s *SettingService) GetLdapFlagField() (string, error) {
return s.getString("ldapFlagField")
return s.getString("ldapFlagField")
}
func (s *SettingService) GetLdapTruthyValues() (string, error) {
return s.getString("ldapTruthyValues")
return s.getString("ldapTruthyValues")
}
func (s *SettingService) GetLdapInvertFlag() (bool, error) {
return s.getBool("ldapInvertFlag")
return s.getBool("ldapInvertFlag")
}
func (s *SettingService) GetLdapInboundTags() (string, error) {
return s.getString("ldapInboundTags")
return s.getString("ldapInboundTags")
}
func (s *SettingService) GetLdapAutoCreate() (bool, error) {
return s.getBool("ldapAutoCreate")
return s.getBool("ldapAutoCreate")
}
func (s *SettingService) GetLdapAutoDelete() (bool, error) {
return s.getBool("ldapAutoDelete")
return s.getBool("ldapAutoDelete")
}
func (s *SettingService) GetLdapDefaultTotalGB() (int, error) {
return s.getInt("ldapDefaultTotalGB")
return s.getInt("ldapDefaultTotalGB")
}
func (s *SettingService) GetLdapDefaultExpiryDays() (int, error) {
return s.getInt("ldapDefaultExpiryDays")
return s.getInt("ldapDefaultExpiryDays")
}
func (s *SettingService) GetLdapDefaultLimitIP() (int, error) {
return s.getInt("ldapDefaultLimitIP")
return s.getInt("ldapDefaultLimitIP")
}
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {

View file

@ -7,7 +7,7 @@ import (
"github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/util/crypto"
ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap"
ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap"
"github.com/xlzd/gotp"
"gorm.io/gorm"
)
@ -49,38 +49,38 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
return nil
}
// 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
}
// 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()
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
}
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
}
twoFactorEnable, err := s.settingService.GetTwoFactorEnable()
if err != nil {