Files
Agent-System/internal/auth/service.go
T
2026-09-13 02:50:20 +00:00

291 lines
7.7 KiB
Go

package auth
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"strings"
"sync"
"chat-agent-system/internal/config"
)
// RegisterRequest 注册入参 (仅用户名、密码、姓名为必填)
type RegisterRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
EmployeeID string `json:"employee_id,omitempty"`
Department string `json:"department,omitempty"`
Email string `json:"email,omitempty"`
}
// RegisterResponse 注册响应
type RegisterResponse struct {
Status AccountStatus `json:"status"`
Message string `json:"message"`
UserID int64 `json:"user_id"`
}
// CreateUserRequest 管理员手动创建账号入参
type CreateUserRequest struct {
Username string `json:"username"`
Name string `json:"name"`
InitialPassword string `json:"initial_password"`
RoleID string `json:"role_id"`
Position string `json:"position"`
Department string `json:"department,omitempty"`
EmployeeID string `json:"employee_id,omitempty"`
}
// Service 认证与账号治理业务逻辑
type Service struct {
store AuthStore
cfg *config.Config
tokensMu sync.RWMutex
tokens map[string]int64 // token -> userID
}
// NewService 实例化认证服务
func NewService(store AuthStore, cfg *config.Config) *Service {
if store == nil {
store = NewMemoryStore(cfg)
}
return &Service{
store: store,
cfg: cfg,
tokens: make(map[string]int64),
}
}
// generateToken 生成随机安全会话 Token
func generateToken() string {
b := make([]byte, 24)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}
// Login 登录鉴权
func (s *Service) Login(ctx context.Context, username, password string) (*SessionInfo, error) {
username = strings.TrimSpace(username)
if username == "" || password == "" {
return nil, errors.New("用户名或密码不能为空")
}
user, err := s.store.GetUserByUsername(ctx, username)
if err != nil {
return nil, errors.New("用户名或密码错误")
}
if !CheckPassword(user.PasswordHash, password) {
return nil, errors.New("用户名或密码错误")
}
if user.Status == StatusPendingApproval {
return nil, errors.New("账号正在等待管理员审核中,暂无法登录")
}
if user.Status == StatusDisabled {
return nil, errors.New("账号已被停用,禁止登录")
}
token := generateToken()
s.tokensMu.Lock()
s.tokens[token] = user.ID
s.tokensMu.Unlock()
// 敏感信息脱敏
user.PasswordHash = ""
env := "dev"
if s.cfg != nil && s.cfg.Env != "" {
env = s.cfg.Env
}
return &SessionInfo{
Token: token,
User: user,
MustChangePassword: user.MustChangePassword,
Env: env,
AllowDevSwitch: env == "dev",
}, nil
}
// Register 用户自主注册 (职位严格由系统固定为访客待分配)
func (s *Service) Register(ctx context.Context, req RegisterRequest) (*RegisterResponse, error) {
req.Username = strings.TrimSpace(req.Username)
req.Name = strings.TrimSpace(req.Name)
if req.Username == "" || req.Password == "" || req.Name == "" {
return nil, errors.New("用户名、密码与真实姓名均为必填项")
}
sysCfg, err := s.store.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("读取系统准入策略失败: %w", err)
}
if !sysCfg.AllowRegistration {
return nil, errors.New("系统当前已暂停自主注册,请联系系统管理员分配账号")
}
passHash, err := HashPassword(req.Password)
if err != nil {
return nil, fmt.Errorf("密码哈希处理失败: %w", err)
}
status := StatusActive
msg := "注册成功,已激活,请返回登录"
if sysCfg.RequireApproval {
status = StatusPendingApproval
msg = "注册成功!您的账号正在等待管理员审核与职位指派,审核通过后方可登录。"
}
account := &UserAccount{
Username: req.Username,
Name: req.Name,
PasswordHash: passHash,
Status: status,
MustChangePassword: false,
RoleID: "role-guest",
Position: "访客/待指派",
EmployeeID: req.EmployeeID,
Department: req.Department,
Email: req.Email,
}
created, err := s.store.CreateUser(ctx, account)
if err != nil {
return nil, err
}
return &RegisterResponse{
Status: status,
Message: msg,
UserID: created.ID,
}, nil
}
// ChangePassword 修改密码 (处理普通改密与首次登录强制改密)
func (s *Service) ChangePassword(ctx context.Context, userID int64, oldPass, newPass string) error {
if strings.TrimSpace(newPass) == "" {
return errors.New("新密码不能为空")
}
user, err := s.store.GetUserByID(ctx, userID)
if err != nil {
return fmt.Errorf("用户不存在: %w", err)
}
if !CheckPassword(user.PasswordHash, oldPass) {
return errors.New("原密码不正确")
}
newHash, err := HashPassword(newPass)
if err != nil {
return fmt.Errorf("新密码加密失败: %w", err)
}
_, err = s.store.UpdateUserPassword(ctx, userID, newHash, false)
return err
}
// GetUserByToken 根据会话 Token 查找登录用户
func (s *Service) GetUserByToken(ctx context.Context, token string) (*UserAccount, error) {
s.tokensMu.RLock()
userID, ok := s.tokens[token]
s.tokensMu.RUnlock()
if !ok {
return nil, errors.New("会话已过期或未登录")
}
user, err := s.store.GetUserByID(ctx, userID)
if err != nil {
return nil, err
}
user.PasswordHash = ""
return user, nil
}
// Logout 注销会话
func (s *Service) Logout(token string) {
s.tokensMu.Lock()
defer s.tokensMu.Unlock()
delete(s.tokens, token)
}
// ListUsers 列出所有用户
func (s *Service) ListUsers(ctx context.Context) ([]*UserAccount, error) {
return s.store.ListUsers(ctx)
}
// CreateUserByAdmin 管理员手动开通账号 (自动标记 mustChangePassword = true)
func (s *Service) CreateUserByAdmin(ctx context.Context, req CreateUserRequest) (*UserAccount, error) {
if req.Username == "" || req.Name == "" || req.InitialPassword == "" {
return nil, errors.New("用户名、姓名与初始密码为必填")
}
hash, err := HashPassword(req.InitialPassword)
if err != nil {
return nil, err
}
roleID := req.RoleID
if roleID == "" {
roleID = "role-engineer"
}
position := req.Position
if position == "" {
position = "方案工程师"
}
account := &UserAccount{
Username: req.Username,
Name: req.Name,
PasswordHash: hash,
Status: StatusActive,
MustChangePassword: true, // 核心约束:管理员创建的账号首次登录必须修改密码
RoleID: roleID,
Position: position,
Department: req.Department,
EmployeeID: req.EmployeeID,
}
created, err := s.store.CreateUser(ctx, account)
if err != nil {
return nil, err
}
created.PasswordHash = ""
return created, nil
}
// UpdateUserRole 管理员为用户指定职位与角色
func (s *Service) UpdateUserRole(ctx context.Context, userID int64, roleID, position string) (*UserAccount, error) {
u, err := s.store.UpdateUserRole(ctx, userID, roleID, position)
if err != nil {
return nil, err
}
u.PasswordHash = ""
return u, nil
}
// UpdateUserStatus 管理员审批通过/停用账号
func (s *Service) UpdateUserStatus(ctx context.Context, userID int64, status AccountStatus) (*UserAccount, error) {
u, err := s.store.UpdateUserStatus(ctx, userID, status)
if err != nil {
return nil, err
}
u.PasswordHash = ""
return u, nil
}
// GetConfig 获取准入策略
func (s *Service) GetConfig(ctx context.Context) (SystemAuthConfig, error) {
return s.store.GetConfig(ctx)
}
// UpdateConfig 更新准入策略
func (s *Service) UpdateConfig(ctx context.Context, cfg SystemAuthConfig) error {
return s.store.UpdateConfig(ctx, cfg)
}