Files
Proxmox-VPS-Panel/backend/app/manage_users.py
T

244 lines
9.2 KiB
Python

"""CLI для управления пользователями панели.
Публичная регистрация в панели закрыта — пользователей создаёт администратор
либо через этот скрипт, либо через эндпоинт /admin/users.
Запуск внутри контейнера backend:
docker compose exec backend python -m app.manage_users <команда> [аргументы]
Команды:
list — показать всех пользователей
create <email> <password> [role] — создать пользователя
role: admin | client (по умолчанию client)
activate <email> — активировать аккаунт (is_active=True)
deactivate <email> — деактивировать аккаунт (is_active=False)
set-role <email> <role> — сменить роль (admin | client)
reset-password <email> <new_password> — задать новый пароль
delete <email> — удалить пользователя (осторожно)
Примеры:
docker compose exec backend python -m app.manage_users list
docker compose exec backend python -m app.manage_users create user@example.com 'Str0ng!Pass' client
docker compose exec backend python -m app.manage_users set-role user@example.com admin
docker compose exec backend python -m app.manage_users deactivate user@example.com
"""
import argparse
import getpass
import sys
from typing import Iterable
from app import models
from app.database import SessionLocal
from app.security import hash_password
VALID_ROLES = ("admin", "client")
def _normalize_email(email: str) -> str:
return email.lower().strip()
def list_users() -> Iterable[models.User]:
db = SessionLocal()
try:
users = db.query(models.User).order_by(models.User.created_at.desc()).all()
if not users:
print("В базе нет пользователей.")
return
print(f"{'ID':>4} {'EMAIL':<32} {'ROLE':<7} {'ACTIVE':<6} CREATED_AT")
print("-" * 78)
for u in users:
print(
f"{u.id:>4} {u.email:<32} {u.role.value:<7} "
f"{'да' if u.is_active else 'нет':<6} {u.created_at:%Y-%m-%d %H:%M}"
)
finally:
db.close()
def create_user(email: str, password: str, role: str) -> None:
if role not in VALID_ROLES:
print(f"❌ Неверная роль: {role}. Допустимо: {', '.join(VALID_ROLES)}")
sys.exit(1)
email = _normalize_email(email)
if len(password) < 6:
print("❌ Пароль слишком короткий (минимум 6 символов).")
sys.exit(1)
db = SessionLocal()
try:
if db.query(models.User).filter(models.User.email == email).first():
print(f"❌ Пользователь {email} уже существует.")
sys.exit(1)
user = models.User(
email=email,
password_hash=hash_password(password),
role=models.Role(role),
is_active=True,
)
db.add(user)
db.commit()
db.refresh(user)
print(f"✅ Создан пользователь {email} (role={role}, id={user.id})")
finally:
db.close()
def set_active(email: str, active: bool) -> None:
email = _normalize_email(email)
db = SessionLocal()
try:
user = db.query(models.User).filter(models.User.email == email).first()
if not user:
print(f"❌ Пользователь {email} не найден.")
sys.exit(1)
user.is_active = active
db.commit()
state = "активирован" if active else "деактивирован"
print(f"✅ {email}: {state}")
finally:
db.close()
def set_role(email: str, role: str) -> None:
if role not in VALID_ROLES:
print(f"❌ Неверная роль: {role}. Допустимо: {', '.join(VALID_ROLES)}")
sys.exit(1)
email = _normalize_email(email)
db = SessionLocal()
try:
user = db.query(models.User).filter(models.User.email == email).first()
if not user:
print(f"❌ Пользователь {email} не найден.")
sys.exit(1)
# Защита от случайного понижения единственного админа.
if user.role == models.Role.admin and role == "client":
admins = (
db.query(models.User)
.filter(models.User.role == models.Role.admin, models.User.is_active.is_(True))
.count()
)
if admins <= 1:
print("❌ Нельзя понизить единственного активного администратора. Сначала назначьте другого admin.")
sys.exit(1)
user.role = models.Role(role)
db.commit()
print(f"✅ {email}: роль изменена на {role}")
finally:
db.close()
def reset_password(email: str, new_password: str) -> None:
if len(new_password) < 6:
print("❌ Пароль слишком короткий (минимум 6 символов).")
sys.exit(1)
email = _normalize_email(email)
db = SessionLocal()
try:
user = db.query(models.User).filter(models.User.email == email).first()
if not user:
print(f"❌ Пользователь {email} не найден.")
sys.exit(1)
user.password_hash = hash_password(new_password)
db.commit()
print(f"✅ {email}: пароль изменён")
finally:
db.close()
def delete_user(email: str) -> None:
email = _normalize_email(email)
db = SessionLocal()
try:
user = db.query(models.User).filter(models.User.email == email).first()
if not user:
print(f"❌ Пользователь {email} не найден.")
sys.exit(1)
# Защита от удаления единственного админа.
if user.role == models.Role.admin:
admins = (
db.query(models.User)
.filter(models.User.role == models.Role.admin, models.User.is_active.is_(True))
.count()
)
if admins <= 1:
print("❌ Нельзя удалить единственного активного администратора.")
sys.exit(1)
db.delete(user)
db.commit()
print(f"✅ Пользователь {email} удалён")
finally:
db.close()
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
prog="manage_users",
description="Управление пользователями Proxmox VPS Panel.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
sub = p.add_subparsers(dest="command", required=True)
sub.add_parser("list", help="Список всех пользователей")
pc = sub.add_parser("create", help="Создать пользователя")
pc.add_argument("email")
pc.add_argument("password", nargs="?", help="Если не задан — спросит интерактивно")
pc.add_argument("role", nargs="?", default="client", choices=VALID_ROLES)
pa = sub.add_parser("activate", help="Активировать аккаунт")
pa.add_argument("email")
pd = sub.add_parser("deactivate", help="Деактивировать аккаунт")
pd.add_argument("email")
pr = sub.add_parser("set-role", help="Сменить роль пользователя")
pr.add_argument("email")
pr.add_argument("role", choices=VALID_ROLES)
pp = sub.add_parser("reset-password", help="Сменить пароль пользователя")
pp.add_argument("email")
pp.add_argument("new_password", nargs="?")
pdel = sub.add_parser("delete", help="Удалить пользователя")
pdel.add_argument("email")
return p
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
if args.command == "list":
list_users()
elif args.command == "create":
password = args.password or getpass.getpass("Пароль для нового пользователя: ")
create_user(args.email, password, args.role)
elif args.command == "activate":
set_active(args.email, True)
elif args.command == "deactivate":
set_active(args.email, False)
elif args.command == "set-role":
set_role(args.email, args.role)
elif args.command == "reset-password":
password = args.new_password or getpass.getpass("Новый пароль: ")
reset_password(args.email, password)
elif args.command == "delete":
delete_user(args.email)
else:
parser.print_help()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())