package services import ( "fmt" "opsxloki/models" "opsxloki/pkg/crypto" "opsxloki/pkg/jwt" "opsxloki/repository" "opsxloki/utils" ) type AuthService struct{} func NewAuthService() *AuthService { return &AuthService{} } func (s *AuthService) Login(username, password string) (*models.User, *jwt.TokenPair, error) { db := repository.GetDB() db.RLock() var local *models.User for i := range db.Users { if db.Users[i].Username == username { u := db.Users[i] local = &u break } } db.RUnlock() // 本地用户密码校验(普通本地用户唯一路径;LDAP 用户不使用本地密码) if local != nil && local.AuthSource != "ldap" { if !crypto.VerifyPassword(password, local.Password) { return nil, nil, utils.ErrInvalidCredentials } return s.issueTokenPair(local) } // LDAP 用户(本地已导入或首次登录自动创建);enabled=false 或验证失败返回统一错误 if ldapCfg := GetLDAPConfig(); ldapCfg.Enabled && ldapCfg.Host != "" { if entry, err := verifyLDAPPassword(ldapCfg, username, password); err == nil { user, err := s.syncLDAPUser(entry) if err != nil { return nil, nil, err } return s.issueTokenPair(user) } } return nil, nil, utils.ErrInvalidCredentials } func (s *AuthService) issueTokenPair(u *models.User) (*models.User, *jwt.TokenPair, error) { pair, err := jwt.GenerateTokenPair(u.ID, u.Username, u.Role, u.TokenVersion) if err != nil { return nil, nil, err } return u, pair, nil } // syncLDAPUser LDAP 验证通过后同步本地记录:不存在则创建(user 角色,密码占位不可本地登录), // 存在则同步昵称/邮箱 func (s *AuthService) syncLDAPUser(entry *ldapUserEntry) (*models.User, error) { db := repository.GetDB() // 已存在 → 同步昵称并标记 LDAP 来源 db.Lock() for i := range db.Users { if db.Users[i].Username == entry.Username { db.Users[i].Nickname = entry.DisplayName db.Users[i].AuthSource = "ldap" db.RequestSave() u := db.Users[i] db.Unlock() return &u, nil } } db.Unlock() // 不存在 → 创建(CreateUser 自管锁与 ID 分配) hash, _ := crypto.HashPassword("ldap-user-" + entry.Username + "-no-local-login") user := &models.User{ Username: entry.Username, Password: hash, Nickname: entry.DisplayName, Role: "user", AuthSource: "ldap", } if err := db.CreateUser(user); err != nil { return nil, err } return user, nil } func (s *AuthService) Register(username, password, nickname, role string) (*models.User, error) { db := repository.GetDB() db.RLock() for i := range db.Users { if db.Users[i].Username == username { db.RUnlock() return nil, utils.ErrUsernameExists } } db.RUnlock() hash, err := crypto.HashPassword(password) if err != nil { return nil, err } user := &models.User{ ID: repository.GetDB().NextID("users"), Username: username, Password: hash, Nickname: nickname, Role: role, } if err := repository.GetDB().CreateUser(user); err != nil { return nil, err } return user, nil } func (s *AuthService) GetUserByID(id uint) (*models.User, error) { db := repository.GetDB() db.RLock() defer db.RUnlock() for i := range db.Users { if db.Users[i].ID == id { u := db.Users[i] return &u, nil } } return nil, utils.ErrNotFound } func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword string) error { db := repository.GetDB() db.Lock() defer db.Unlock() for i := range db.Users { if db.Users[i].ID == userID { if db.Users[i].AuthSource == "ldap" { return fmt.Errorf("LDAP 用户请在 LDAP 目录中修改密码") } if !crypto.VerifyPassword(oldPassword, db.Users[i].Password) { return utils.ErrInvalidCredentials } hash, err := crypto.HashPassword(newPassword) if err != nil { return err } db.Users[i].Password = hash db.Users[i].TokenVersion++ db.RequestSave() return nil } } return utils.ErrNotFound } // ListUsers 分页返回用户列表(按 ID 升序),pageSize 上限 100 func (s *AuthService) ListUsers(page, pageSize int) ([]models.User, int64) { db := repository.GetDB() db.RLock() defer db.RUnlock() total := int64(len(db.Users)) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } start := (page - 1) * pageSize if start >= int(total) { return []models.User{}, total } end := start + pageSize if end > int(total) { end = int(total) } users := make([]models.User, end-start) copy(users, db.Users[start:end]) return users, total } // DeleteUser 删除用户;不能删除自己,不能删除最后一个管理员 func (s *AuthService) DeleteUser(id, requesterID uint) error { db := repository.GetDB() db.Lock() defer db.Unlock() idx := -1 adminCount := 0 for i := range db.Users { if db.Users[i].ID == id { idx = i } if db.Users[i].Role == "admin" { adminCount++ } } if idx == -1 { return utils.ErrNotFound } if id == requesterID { return utils.ErrCannotDeleteSelf } if db.Users[idx].Role == "admin" && adminCount <= 1 { return utils.ErrLastAdmin } db.Users = append(db.Users[:idx], db.Users[idx+1:]...) for i := range db.LogSources { kept := db.LogSources[i].Access[:0] for _, rule := range db.LogSources[i].Access { if rule.UserID != id { kept = append(kept, rule) } } db.LogSources[i].Access = kept } groups := db.LogPolicyGroups[:0] for _, group := range db.LogPolicyGroups { members := group.UserIDs[:0] for _, member := range group.UserIDs { if member != id { members = append(members, member) } } group.UserIDs = members if len(members) > 0 { groups = append(groups, group) } } db.LogPolicyGroups = groups db.RequestSave() return nil } // ResetPassword 管理员重置用户密码,并递增 TokenVersion 吊销其全部旧会话 func (s *AuthService) ResetPassword(userID uint, newPassword string) error { db := repository.GetDB() db.Lock() defer db.Unlock() for i := range db.Users { if db.Users[i].ID == userID { if db.Users[i].AuthSource == "ldap" { return fmt.Errorf("LDAP 用户密码由目录服务管理,不能在此重置") } hash, err := crypto.HashPassword(newPassword) if err != nil { return err } db.Users[i].Password = hash db.Users[i].TokenVersion++ db.RequestSave() return nil } } return utils.ErrNotFound }