Files
Agent-System/internal/auth/store.go
T
weichen 88b7c11a23 fix(auth): reseed standard roles and reject duplicate usernames as 400
Registration always writes role-guest; a deleted standard role became a masked INTERNAL. Username unique violations are now a 400 instead of an infrastructure 500.
2026-09-28 01:12:07 +00:00

334 lines
9.2 KiB
Go

package auth
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"chat-agent-system/internal/config"
)
// ErrUsernameExists 用户名或 account_id 唯一约束冲突。调用方应返回 400,而不是基础设施 500。
var ErrUsernameExists = errors.New("用户名已存在")
// AuthStore 用户与认证配置存储接口
type AuthStore interface {
GetUserByUsername(ctx context.Context, username string) (*UserAccount, error)
GetUserByID(ctx context.Context, id int64) (*UserAccount, error)
CreateUser(ctx context.Context, user *UserAccount) (*UserAccount, error)
UpdateUser(ctx context.Context, user *UserAccount, actorID int64) (*UserAccount, error)
UpdateUserRole(ctx context.Context, id int64, roleID, position string) (*UserAccount, error)
UpdateUserStatus(ctx context.Context, id int64, status AccountStatus) (*UserAccount, error)
UpdateUserPassword(ctx context.Context, id int64, passwordHash string, mustChangePassword bool) (*UserAccount, error)
RecordPasswordResetAudit(ctx context.Context, targetUserID, actorUserID int64, requestID string) error
ListUsers(ctx context.Context) ([]*UserAccount, error)
ListActiveUserDirectory(ctx context.Context) ([]UserDirectoryEntry, error)
GetConfig(ctx context.Context) (SystemAuthConfig, error)
UpdateConfig(ctx context.Context, cfg SystemAuthConfig) error
}
// MemoryStore 内存与并发安全存储实现
type MemoryStore struct {
mu sync.RWMutex
users map[int64]*UserAccount
nextID int64
cfg SystemAuthConfig
}
// NewMemoryStore 初始化并载入初始系统管理员与基线用户
func NewMemoryStore(cfg *config.Config) *MemoryStore {
store := &MemoryStore{
users: make(map[int64]*UserAccount),
nextID: 1,
cfg: SystemAuthConfig{
AllowRegistration: true,
RequireApproval: false,
},
}
if cfg != nil {
store.cfg.AllowRegistration = cfg.AuthRegistrationEnabled
store.cfg.RequireApproval = cfg.AuthRequireApproval
}
now := time.Now().Format("2006-01-02 15:04:05")
// 1. 初始化 .env 系统超级管理员
adminUser := "admin"
adminPass := "admin-secret-password-123"
if cfg != nil && cfg.AdminUsername != "" {
adminUser = cfg.AdminUsername
}
if cfg != nil && cfg.AdminPassword != "" {
adminPass = cfg.AdminPassword
}
adminHash, _ := HashPassword(adminPass)
store.users[1] = &UserAccount{
ID: 1,
Username: adminUser,
Name: "系统管理员",
PasswordHash: adminHash,
Status: StatusActive,
MustChangePassword: false,
RoleID: "role-admin",
Position: "系统管理员",
Department: "系统运维部",
EmployeeID: "SYS-000",
CreatedAt: now,
UpdatedAt: now,
}
store.nextID = 2
// 2. 标准测试用户仅 dev/test 环境种子化;生产不得携带固定密码演示账号
env := ""
if cfg != nil {
env = cfg.Env
}
if env == "dev" || env == "test" || env == "" {
defaultPassHash, _ := HashPassword("123456")
seedUsers := []struct {
id int64
username string
name string
roleID string
position string
dept string
employeeID string
}{
{2, "zhanggong", "张工", "role-tech-lead", "技术主管", "骨科耗材研发组", "RD-001"},
{3, "lizhuguan", "李主管", "role-tech-lead", "技术主管", "骨科耗材研发组", "RD-002"},
{4, "wangzong", "王总监", "role-director", "研发总监", "研发中心", "RD-003"},
{5, "guest", "访客", "role-guest", "待指派", "外协中心", "EXT-999"},
}
for _, su := range seedUsers {
store.users[su.id] = &UserAccount{
ID: su.id,
Username: su.username,
Name: su.name,
PasswordHash: defaultPassHash,
Status: StatusActive,
MustChangePassword: false,
RoleID: su.roleID,
Position: su.position,
Department: su.dept,
EmployeeID: su.employeeID,
CreatedAt: now,
UpdatedAt: now,
}
if su.id >= store.nextID {
store.nextID = su.id + 1
}
}
}
return store
}
// GetUserByUsername 根据用户名查询
func (s *MemoryStore) GetUserByUsername(ctx context.Context, username string) (*UserAccount, error) {
s.mu.RLock()
defer s.mu.RUnlock()
for _, u := range s.users {
if strings.EqualFold(u.Username, username) {
copy := *u
return &copy, nil
}
}
return nil, fmt.Errorf("user not found: %s", username)
}
// GetUserByID 根据用户ID查询
func (s *MemoryStore) GetUserByID(ctx context.Context, id int64) (*UserAccount, error) {
s.mu.RLock()
defer s.mu.RUnlock()
u, exists := s.users[id]
if !exists {
return nil, fmt.Errorf("user not found: %d", id)
}
copy := *u
return &copy, nil
}
// CreateUser 创建新用户账号
func (s *MemoryStore) CreateUser(ctx context.Context, user *UserAccount) (*UserAccount, error) {
s.mu.Lock()
defer s.mu.Unlock()
if user == nil || strings.TrimSpace(user.PasswordHash) == "" {
return nil, fmt.Errorf("password hash required")
}
for _, existing := range s.users {
if strings.EqualFold(existing.Username, user.Username) {
return nil, ErrUsernameExists
}
}
user.ID = s.nextID
s.nextID++
now := time.Now().Format("2006-01-02 15:04:05")
if user.CreatedAt == "" {
user.CreatedAt = now
}
user.UpdatedAt = now
copied := *user
s.users[user.ID] = &copied
result := *user
return &result, nil
}
// RecordPasswordResetAudit records an admin password reset in memory (no persistence).
func (s *MemoryStore) RecordPasswordResetAudit(ctx context.Context, targetUserID, actorUserID int64, requestID string) error {
return nil
}
// UpdateUser 更新用户基础资料;actorID==user.ID 时清除管理员代改标记。
func (s *MemoryStore) UpdateUser(ctx context.Context, user *UserAccount, actorID int64) (*UserAccount, error) {
s.mu.Lock()
defer s.mu.Unlock()
existing, ok := s.users[user.ID]
if !ok {
return nil, fmt.Errorf("user not found: %d", user.ID)
}
existing.Name = user.Name
existing.Department = user.Department
existing.EmployeeID = user.EmployeeID
existing.Email = user.Email
existing.Avatar = user.Avatar
existing.Locale = user.Locale
existing.Timezone = user.Timezone
now := time.Now().Format("2006-01-02 15:04:05")
existing.UpdatedAt = now
if actorID > 0 && actorID != user.ID {
existing.ProfileUpdatedBy = actorID
existing.ProfileUpdatedAt = now
} else {
existing.ProfileUpdatedBy = 0
existing.ProfileUpdatedAt = ""
}
copy := *existing
return &copy, nil
}
// UpdateUserRole 更新用户职位与全局角色
func (s *MemoryStore) UpdateUserRole(ctx context.Context, id int64, roleID, position string) (*UserAccount, error) {
s.mu.Lock()
defer s.mu.Unlock()
existing, ok := s.users[id]
if !ok {
return nil, fmt.Errorf("user not found: %d", id)
}
existing.RoleID = roleID
existing.Position = position
existing.UpdatedAt = time.Now().Format("2006-01-02 15:04:05")
copy := *existing
return &copy, nil
}
// UpdateUserStatus 更新用户账号状态
func (s *MemoryStore) UpdateUserStatus(ctx context.Context, id int64, status AccountStatus) (*UserAccount, error) {
s.mu.Lock()
defer s.mu.Unlock()
existing, ok := s.users[id]
if !ok {
return nil, fmt.Errorf("user not found: %d", id)
}
existing.Status = status
existing.UpdatedAt = time.Now().Format("2006-01-02 15:04:05")
copy := *existing
return &copy, nil
}
// UpdateUserPassword 更新用户密码并置位/复位首次强制改密
func (s *MemoryStore) UpdateUserPassword(ctx context.Context, id int64, passwordHash string, mustChangePassword bool) (*UserAccount, error) {
s.mu.Lock()
defer s.mu.Unlock()
existing, ok := s.users[id]
if !ok {
return nil, fmt.Errorf("user not found: %d", id)
}
existing.PasswordHash = passwordHash
existing.MustChangePassword = mustChangePassword
existing.UpdatedAt = time.Now().Format("2006-01-02 15:04:05")
copy := *existing
return &copy, nil
}
// ListActiveUserDirectory 返回 active 用户的轻量目录。
func (s *MemoryStore) ListActiveUserDirectory(ctx context.Context) ([]UserDirectoryEntry, error) {
s.mu.RLock()
defer s.mu.RUnlock()
out := make([]UserDirectoryEntry, 0, len(s.users))
for _, u := range s.users {
if u.Status != StatusActive {
continue
}
out = append(out, UserDirectoryEntry{
ID: u.ID,
Username: u.Username,
Name: u.Name,
Department: u.Department,
Position: u.Position,
})
}
// 稳定排序:按姓名再按 id
for i := 0; i < len(out); i++ {
for j := i + 1; j < len(out); j++ {
if out[j].Name < out[i].Name || (out[j].Name == out[i].Name && out[j].ID < out[i].ID) {
out[i], out[j] = out[j], out[i]
}
}
}
return out, nil
}
// ListUsers 列出所有用户
func (s *MemoryStore) ListUsers(ctx context.Context) ([]*UserAccount, error) {
s.mu.RLock()
defer s.mu.RUnlock()
list := make([]*UserAccount, 0, len(s.users))
for _, u := range s.users {
copy := *u
// 脱敏密码哈希
copy.PasswordHash = ""
list = append(list, &copy)
}
return list, nil
}
// GetConfig 获取准入配置
func (s *MemoryStore) GetConfig(ctx context.Context) (SystemAuthConfig, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.cfg, nil
}
// UpdateConfig 更新准入配置
func (s *MemoryStore) UpdateConfig(ctx context.Context, cfg SystemAuthConfig) error {
s.mu.Lock()
defer s.mu.Unlock()
s.cfg = cfg
return nil
}