Align the project baseline with the latest admin interface styling and layout structure while documenting setup and usage updates in README.
207 lines
6.7 KiB
Python
207 lines
6.7 KiB
Python
from pathlib import Path
|
|
|
|
from app.core.app_settings import apply_settings_to_app, get_settings_payload, write_settings
|
|
from app.core.audit_log import read_audit_events, write_audit_event
|
|
from app.core.config import settings
|
|
from app.core.install_secrets import install_secrets_payload, reveal_install_secret
|
|
from app.core.security import hash_password
|
|
from app.modules.admin.security_diagnostics import build_security_diagnostics_report
|
|
from app.modules.auth.service import revoke_user_refresh_family
|
|
from app.modules.users import repository
|
|
|
|
|
|
def list_admin_users(page: int, limit: int) -> dict:
|
|
users, total = repository.list_users(page, limit)
|
|
return {
|
|
"data": [
|
|
{
|
|
"id": user.id,
|
|
"email": user.email,
|
|
"role": user.role,
|
|
"is_superuser": user.is_superuser,
|
|
"status": user.status,
|
|
}
|
|
for user in users
|
|
],
|
|
"meta": {"total": total, "page": page, "limit": limit},
|
|
}
|
|
|
|
|
|
def _ensure_last_superuser_protection(target, role: str | None, is_superuser: bool | None) -> None:
|
|
super_count = repository.count_superusers()
|
|
role_becomes_admin = target.role if role is None else role
|
|
super_becomes_true = target.is_superuser if is_superuser is None else is_superuser
|
|
is_losing_super = target.role == "admin" and target.is_superuser and (
|
|
role_becomes_admin != "admin" or not super_becomes_true
|
|
)
|
|
if is_losing_super and super_count <= 1:
|
|
raise ValueError("LAST_SUPERUSER_PROTECTED")
|
|
|
|
|
|
def patch_user(
|
|
admin_user, target_user_id: str, role: str | None, status: str | None, is_superuser: bool | None = None
|
|
) -> dict:
|
|
target = repository.get_user_by_id(target_user_id)
|
|
if not target:
|
|
raise ValueError("USER_NOT_FOUND")
|
|
if admin_user.id == target_user_id and role and role != "admin":
|
|
raise ValueError("SELF_DEMOTION_FORBIDDEN")
|
|
if admin_user.id == target_user_id and is_superuser is False:
|
|
raise ValueError("SELF_DEMOTION_FORBIDDEN")
|
|
if admin_user.id == target_user_id and status == "blocked":
|
|
raise ValueError("SELF_BLOCK_FORBIDDEN")
|
|
|
|
admin_count = repository.count_admins()
|
|
if target.role == "admin" and role and role != "admin" and admin_count <= 1:
|
|
raise ValueError("LAST_ADMIN_PROTECTED")
|
|
_ensure_last_superuser_protection(target, role, is_superuser)
|
|
|
|
if role:
|
|
target.role = role
|
|
if is_superuser is not None:
|
|
target.is_superuser = bool(is_superuser)
|
|
if status:
|
|
target.status = status
|
|
if status == "blocked":
|
|
revoke_user_refresh_family(target.id)
|
|
repository.update_user(target)
|
|
write_audit_event(
|
|
action="admin.user.patch",
|
|
actor_user_id=admin_user.id,
|
|
actor_email=admin_user.email,
|
|
details={"target_user_id": target.id},
|
|
)
|
|
return {
|
|
"id": target.id,
|
|
"email": target.email,
|
|
"role": target.role,
|
|
"is_superuser": target.is_superuser,
|
|
"status": target.status,
|
|
}
|
|
|
|
|
|
def create_admin_user(admin_user, email: str, password: str, role: str, is_superuser: bool, status: str) -> dict:
|
|
if repository.get_user_by_email(email):
|
|
raise ValueError("USER_EXISTS")
|
|
user = repository.create_user(
|
|
email=email,
|
|
password_hash=hash_password(password),
|
|
role=role,
|
|
is_superuser=is_superuser,
|
|
status=status,
|
|
)
|
|
write_audit_event(
|
|
action="admin.user.create",
|
|
actor_user_id=admin_user.id,
|
|
actor_email=admin_user.email,
|
|
details={"target_user_id": user.id},
|
|
)
|
|
return {
|
|
"id": user.id,
|
|
"email": user.email,
|
|
"role": user.role,
|
|
"is_superuser": user.is_superuser,
|
|
"status": user.status,
|
|
}
|
|
|
|
|
|
def reset_user_password(admin_user, target_user_id: str, password: str) -> dict:
|
|
target = repository.get_user_by_id(target_user_id)
|
|
if not target:
|
|
raise ValueError("USER_NOT_FOUND")
|
|
target.password_hash = hash_password(password)
|
|
repository.update_user(target)
|
|
revoke_user_refresh_family(target.id)
|
|
write_audit_event(
|
|
action="admin.user.reset_password",
|
|
actor_user_id=admin_user.id,
|
|
actor_email=admin_user.email,
|
|
details={"target_user_id": target.id},
|
|
)
|
|
return {"status": "ok"}
|
|
|
|
|
|
def delete_admin_user(admin_user, target_user_id: str) -> dict:
|
|
target = repository.get_user_by_id(target_user_id)
|
|
if not target:
|
|
raise ValueError("USER_NOT_FOUND")
|
|
if admin_user.id == target.id:
|
|
raise ValueError("SELF_DELETE_FORBIDDEN")
|
|
_ensure_last_superuser_protection(target, "user", False)
|
|
if target.role == "admin" and repository.count_admins() <= 1:
|
|
raise ValueError("LAST_ADMIN_PROTECTED")
|
|
repository.delete_user(target.id)
|
|
revoke_user_refresh_family(target.id)
|
|
write_audit_event(
|
|
action="admin.user.delete",
|
|
actor_user_id=admin_user.id,
|
|
actor_email=admin_user.email,
|
|
details={"target_user_id": target.id},
|
|
)
|
|
return {"status": "deleted"}
|
|
|
|
|
|
def get_admin_summary() -> dict:
|
|
return {
|
|
"users_count": repository.count_users(),
|
|
"registrations_day": repository.count_users_registered_today(),
|
|
"admins_count": repository.count_admins(),
|
|
"superusers_count": repository.count_superusers(),
|
|
}
|
|
|
|
|
|
def get_admin_settings() -> dict:
|
|
return get_settings_payload()
|
|
|
|
|
|
def patch_admin_settings(admin_user, values: dict) -> dict:
|
|
merged = write_settings(values)
|
|
apply_settings_to_app(merged)
|
|
write_audit_event(
|
|
action="admin.settings.patch",
|
|
actor_user_id=admin_user.id,
|
|
actor_email=admin_user.email,
|
|
details={"updated_keys": sorted(values.keys())},
|
|
)
|
|
return get_settings_payload()
|
|
|
|
|
|
def get_diagnostics_report() -> dict:
|
|
return build_security_diagnostics_report()
|
|
|
|
|
|
def list_activity_feed(limit: int = 200) -> dict:
|
|
return {"events": read_audit_events(limit=limit)}
|
|
|
|
|
|
def record_ui_activity(admin_user, event: str, meta: dict | None) -> dict:
|
|
write_audit_event(
|
|
action=f"ui.{event}",
|
|
actor_user_id=admin_user.id,
|
|
actor_email=admin_user.email,
|
|
details=meta or {},
|
|
)
|
|
return {"status": "ok"}
|
|
|
|
|
|
def get_server_log_tail(lines: int = 200) -> dict:
|
|
path = Path(settings.server_log_path)
|
|
if not path.exists():
|
|
return {"lines": []}
|
|
return {"lines": path.read_text(encoding="utf-8", errors="ignore").splitlines()[-lines:]}
|
|
|
|
|
|
def get_install_secrets() -> dict:
|
|
return install_secrets_payload()
|
|
|
|
|
|
def reveal_secret(admin_user, key: str) -> dict:
|
|
value = reveal_install_secret(key)
|
|
write_audit_event(
|
|
action="admin.secrets.reveal",
|
|
actor_user_id=admin_user.id,
|
|
actor_email=admin_user.email,
|
|
details={"key": key},
|
|
)
|
|
return {"key": key, "value": value}
|