feat: add system admin account management with RBAC
- AdminUser model: multi-admin accounts with bcrypt hashing (admin_users table) - Three roles: superadmin / admin / viewer with granular permission bits - auth.py: login migrated from env-var single admin to DB-backed accounts; seed_initial_admin() auto-creates first superadmin from SM_ADMIN_PASSWORD - Web UI: /system/admins page (add/edit role/toggle/reset pwd/delete) + change-my-password; sidebar entry; permission-guarded routes - REST API: /api/admins CRUD with protection checks - Protections: cannot delete/disable self; keep >=1 enabled superadmin; disabled accounts fail permission checks immediately - README: document roles, permission bits, API
This commit is contained in:
@@ -4,6 +4,67 @@ from database import db
|
||||
import bcrypt
|
||||
|
||||
|
||||
# ── 后台管理员 ──────────────────────────────────────────────────
|
||||
class AdminUser(db.Model):
|
||||
"""Web 后台管理员账户(存数据库,支持多账户、角色权限)。"""
|
||||
__tablename__ = "admin_users"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(64), nullable=False, unique=True, index=True)
|
||||
password = db.Column(db.String(128), nullable=False) # bcrypt 哈希
|
||||
role = db.Column(db.String(16), default="viewer") # superadmin | admin | viewer
|
||||
enabled = db.Column(db.Boolean, default=True)
|
||||
last_login_at = db.Column(db.DateTime, nullable=True)
|
||||
last_login_ip = db.Column(db.String(64), nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc),
|
||||
onupdate=lambda: datetime.now(timezone.utc))
|
||||
|
||||
ROLE_LABELS = {
|
||||
"superadmin": "超级管理员",
|
||||
"admin": "管理员",
|
||||
"viewer": "只读用户",
|
||||
}
|
||||
|
||||
# 角色权限位(可扩展)
|
||||
ROLE_PERMISSIONS = {
|
||||
"superadmin": {"users:read", "users:write", "instances:read", "instances:write",
|
||||
"system:read", "system:write", "admins:read", "admins:write",
|
||||
"logs:read", "stats:read"},
|
||||
"admin": {"users:read", "users:write", "instances:read", "instances:write",
|
||||
"system:read", "system:write", "admins:read",
|
||||
"logs:read", "stats:read"},
|
||||
"viewer": {"users:read", "instances:read", "system:read",
|
||||
"logs:read", "stats:read"},
|
||||
}
|
||||
|
||||
def set_password(self, password):
|
||||
self.password = bcrypt.hashpw(
|
||||
password.encode("utf-8"), bcrypt.gensalt()
|
||||
).decode("utf-8")
|
||||
|
||||
def check_password(self, password):
|
||||
if not self.password:
|
||||
return False
|
||||
try:
|
||||
return bcrypt.checkpw(
|
||||
password.encode("utf-8"), self.password.encode("utf-8")
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@property
|
||||
def role_label(self):
|
||||
return self.ROLE_LABELS.get(self.role, self.role)
|
||||
|
||||
def has_permission(self, perm):
|
||||
perms = self.ROLE_PERMISSIONS.get(self.role, set())
|
||||
return perm in perms
|
||||
|
||||
def __repr__(self):
|
||||
return f"<AdminUser {self.username} ({self.role})>"
|
||||
|
||||
|
||||
# ── SOCKS5 实例 ──────────────────────────────────────────────────
|
||||
class Instance(db.Model):
|
||||
__tablename__ = "instances"
|
||||
|
||||
Reference in New Issue
Block a user