Initial commit: site monorepo with API, web, and infra.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
влад
2026-07-16 10:11:54 +03:00
co-authored by Cursor
commit 016910ffb7
447 changed files with 73972 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Admin module."""
+171
View File
@@ -0,0 +1,171 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from app.core.dependencies import require_admin, require_superuser
from app.modules.admin.schemas import (
AdminSettingsPatchIn,
AdminRevealSecretIn,
AdminUiActivityIn,
AdminUserCreateIn,
AdminUserPasswordPatchIn,
AdminUserPatchIn,
)
from app.modules.admin.service import (
create_admin_user,
delete_admin_user,
get_admin_settings,
get_admin_summary,
get_install_secrets,
get_diagnostics_report,
get_server_log_tail,
list_activity_feed,
list_admin_users,
patch_admin_settings,
patch_user,
record_ui_activity,
reveal_secret,
reset_user_password,
)
from app.modules.analytics.service import get_admin_dashboard_metrics
router = APIRouter()
@router.get("/users")
async def list_users_route(
page: int = Query(default=1, ge=1),
limit: int = Query(default=20, ge=1, le=100),
_admin=Depends(require_admin),
):
return list_admin_users(page, limit)
@router.patch("/users/{user_id}")
async def patch_user_route(
user_id: str,
payload: AdminUserPatchIn,
admin=Depends(require_admin),
):
if payload.is_superuser is not None and not admin.is_superuser:
raise HTTPException(status_code=403, detail="SUPERUSER_ONLY")
try:
return patch_user(admin, user_id, payload.role, payload.status, payload.is_superuser)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.post("/users")
async def create_user_route(payload: AdminUserCreateIn, admin=Depends(require_superuser)):
try:
return create_admin_user(
admin,
email=payload.email,
password=payload.password,
role=payload.role,
is_superuser=payload.is_superuser,
status=payload.status,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.patch("/users/{user_id}/password")
async def reset_user_password_route(
user_id: str, payload: AdminUserPasswordPatchIn, admin=Depends(require_superuser)
):
try:
return reset_user_password(admin, user_id, payload.password)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.delete("/users/{user_id}")
async def delete_user_route(user_id: str, admin=Depends(require_superuser)):
try:
return delete_admin_user(admin, user_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.get("/summary")
async def summary_route(_admin=Depends(require_admin)):
return get_admin_summary()
@router.get("/stats")
async def stats_route(_admin=Depends(require_admin)):
return get_admin_dashboard_metrics()
@router.get("/settings")
async def admin_settings_route(_admin=Depends(require_superuser)):
return get_admin_settings()
@router.patch("/settings")
async def admin_settings_patch_route(payload: AdminSettingsPatchIn, admin=Depends(require_superuser)):
return patch_admin_settings(admin, payload.values)
@router.get("/diagnostics/report")
async def diagnostics_route(_admin=Depends(require_superuser)):
return get_diagnostics_report()
@router.get("/activity-feed")
async def activity_feed_route(limit: int = Query(default=200, ge=1, le=1000), _admin=Depends(require_admin)):
return list_activity_feed(limit=limit)
@router.post("/ui-activity")
async def ui_activity_route(payload: AdminUiActivityIn, admin=Depends(require_admin)):
return record_ui_activity(admin, payload.event, payload.meta)
@router.get("/server-log")
async def server_log_route(
lines: int = Query(default=200, ge=1, le=1000),
_admin=Depends(require_superuser),
):
return get_server_log_tail(lines=lines)
@router.get("/secrets")
async def install_secrets_route(_admin=Depends(require_superuser)):
return get_install_secrets()
@router.post("/secrets/reveal")
async def reveal_secret_route(payload: AdminRevealSecretIn, admin=Depends(require_superuser)):
try:
return reveal_secret(admin, payload.key)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.get("/enterprises")
async def admin_enterprises(_admin=Depends(require_superuser)):
from sqlalchemy import select
from app.core.database import session_scope
from app.modules.sync.models import Enterprise
with session_scope() as db:
rows = list(db.scalars(select(Enterprise).order_by(Enterprise.name)))
return [{"id": r.id, "name": r.name, "slug": r.slug, "status": r.status} for r in rows]
@router.get("/sync-metrics")
async def admin_sync_metrics(_admin=Depends(require_superuser)):
from sqlalchemy import select
from app.core.database import session_scope
from app.modules.sync.models import Enterprise
from app.modules.sync.service import get_sync_metrics
with session_scope() as db:
enterprises = list(db.scalars(select(Enterprise).order_by(Enterprise.name)))
rows = [(e.id, e.name, e.slug) for e in enterprises]
return [
{"enterprise_id": enterprise_id, "name": name, "slug": slug, **get_sync_metrics(enterprise_id)}
for enterprise_id, name, slug in rows
]
+46
View File
@@ -0,0 +1,46 @@
from typing import Literal
from pydantic import BaseModel, EmailStr, Field, field_validator
from app.core.password_policy import validate_password_strength
class AdminUserPatchIn(BaseModel):
role: Literal["user", "admin"] | None = None
status: Literal["pending", "active", "blocked"] | None = None
is_superuser: bool | None = None
class AdminUserCreateIn(BaseModel):
email: EmailStr
password: str = Field(min_length=8)
role: Literal["user", "admin"] = "user"
is_superuser: bool = False
status: Literal["pending", "active", "blocked"] = "active"
@field_validator("password")
@classmethod
def password_policy(cls, value: str) -> str:
return validate_password_strength(value)
class AdminUserPasswordPatchIn(BaseModel):
password: str = Field(min_length=8)
@field_validator("password")
@classmethod
def password_policy(cls, value: str) -> str:
return validate_password_strength(value)
class AdminSettingsPatchIn(BaseModel):
values: dict
class AdminUiActivityIn(BaseModel):
event: str
meta: dict | None = None
class AdminRevealSecretIn(BaseModel):
key: Literal["database_password", "jwt_access_secret", "jwt_refresh_pepper", "s3_secret_key"]
@@ -0,0 +1,94 @@
from __future__ import annotations
from urllib.parse import parse_qs, urlparse
from app.core.config import settings
from app.core.install_secrets import read_install_secrets
from app.core.password_denylist import load_denylist
from app.core.redis import get_redis_client
from app.core.database import session_scope
from app.modules.auth.models import RefreshToken
from app.modules.users import repository
from sqlalchemy import func, select
def build_security_diagnostics_report() -> dict:
redis_client = get_redis_client()
db = urlparse(settings.database_url)
sslmode = parse_qs(db.query).get("sslmode", [""])[0]
install_secrets = read_install_secrets()
with session_scope() as session:
refresh_count = session.scalar(select(func.count()).select_from(RefreshToken)) or 0
checks = [
{
"id": "jwt_access_secret",
"status": "ok" if len(settings.jwt_access_secret) >= 32 else "fail",
"message": "JWT access secret configured",
},
{
"id": "jwt_refresh_pepper",
"status": "ok" if len(settings.jwt_refresh_pepper) >= 32 else "fail",
"message": "JWT refresh pepper configured",
},
{
"id": "cookie_secure",
"status": "ok" if settings.cookie_secure else "warn",
"message": "Refresh cookie Secure flag is enabled",
},
{
"id": "rate_limit_backend",
"status": "ok" if redis_client is not None else "warn",
"message": "Rate limiter uses Redis backend",
},
{
"id": "password_denylist",
"status": "ok" if len(load_denylist()) >= 1000 else "warn",
"message": "Password denylist has strong coverage",
},
{
"id": "cors_wildcard",
"status": "ok" if "*" not in settings.cors_origins else "fail",
"message": "CORS does not include wildcard origin",
},
{
"id": "superuser_exists",
"status": "ok" if repository.count_superusers() > 0 else "fail",
"message": "At least one superuser exists",
},
{
"id": "docs_production",
"status": "warn" if settings.enable_docs else "ok",
"message": "API docs are disabled in production",
},
{
"id": "db_default_credentials",
"status": "fail" if db.username == "user" and db.password == "pass" else "ok",
"message": "Database does not use default credentials",
},
{
"id": "db_localhost_exposed",
"status": "fail" if settings.app_env.lower() == "production" and db.hostname in {"localhost", "127.0.0.1"} else "ok",
"message": "Production database host is not localhost",
},
{
"id": "db_ssl_mode",
"status": "ok" if settings.app_env.lower() != "production" or sslmode == "require" else "warn",
"message": "Production database URL uses sslmode=require",
},
{
"id": "db_refresh_token_table_size",
"status": "warn" if refresh_count > 10000 else "ok",
"message": "Refresh token table size is under threshold",
},
{
"id": "install_secrets_initialized",
"status": "ok" if bool(install_secrets) else "fail",
"message": "Install secrets file is initialized",
},
{
"id": "install_secrets_locked",
"status": "ok" if install_secrets.get("SECRETS_LOCKED") == "true" else "warn",
"message": "Install secrets are locked after bootstrap",
},
]
return {"checks": checks}
+209
View File
@@ -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}