feat: 支持管理员自助修改密码 (#57)
This commit is contained in:
@@ -12,11 +12,12 @@ import (
|
||||
|
||||
var (
|
||||
// ErrUsersAlreadyExist 表示首次初始化入口已经永久关闭。
|
||||
ErrUsersAlreadyExist = errors.New("管理员已经初始化")
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
ErrSessionNotFound = errors.New("网页登录会话不存在")
|
||||
ErrUsernameExists = errors.New("用户名已经存在")
|
||||
ErrLastActiveAdmin = errors.New("不能禁用最后一个有效管理员")
|
||||
ErrUsersAlreadyExist = errors.New("管理员已经初始化")
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
ErrSessionNotFound = errors.New("网页登录会话不存在")
|
||||
ErrUsernameExists = errors.New("用户名已经存在")
|
||||
ErrLastActiveAdmin = errors.New("不能禁用最后一个有效管理员")
|
||||
ErrUserPasswordChanged = errors.New("用户密码已经变更")
|
||||
)
|
||||
|
||||
// CountUsers 返回所有用户数量,disabled 也算。即使所有账号被禁用,
|
||||
@@ -29,6 +30,39 @@ func CountUsers(q Execer) (int, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ChangePasswordAndRevokeSessions 只在旧哈希仍匹配时更新密码,并在同一事务中
|
||||
// 撤销该用户的全部 Session。旧哈希条件用于阻止两个并发改密请求互相覆盖。
|
||||
func ChangePasswordAndRevokeSessions(db *sql.DB, userID, expectedHash, passwordHash, updatedAt string) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("开始修改密码事务失败: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
result, err := tx.Exec(`
|
||||
UPDATE users
|
||||
SET password_hash = ?, password_changed_at = ?, updated_at = ?
|
||||
WHERE user_id = ? AND password_hash = ? AND role = 'admin' AND status = 'active'`,
|
||||
passwordHash, updatedAt, updatedAt, userID, expectedHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("修改管理员密码失败: %w", err)
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("确认修改密码结果失败: %w", err)
|
||||
}
|
||||
if affected == 0 {
|
||||
return ErrUserPasswordChanged
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM web_sessions WHERE user_id = ?`, userID); err != nil {
|
||||
return fmt.Errorf("撤销管理员 Session 失败: %w", err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("提交修改密码事务失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateInitialAdmin 在写事务里再次确认 users 为空后创建首位管理员。
|
||||
// Open 的 _txlock=immediate 让并发事务从一开始就串行,最多一个能成功。
|
||||
func CreateInitialAdmin(db *sql.DB, user model.User) error {
|
||||
|
||||
Reference in New Issue
Block a user