Initial commit: site monorepo with API, web, and infra.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
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.jwt_denylist import bump_auth_epoch
|
||||
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":
|
||||
bump_auth_epoch(target.id)
|
||||
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)
|
||||
bump_auth_epoch(target.id)
|
||||
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}
|
||||
Reference in New Issue
Block a user