chore: initial project setup with backend, frontend, Android app, and CI/CD
Add complete NexaMFA push MFA system with: - FastAPI backend with PostgreSQL, Redis, OIDC provider, and Prometheus metrics - React TypeScript admin console - Android Kotlin/Jetpack Compose app with biometric authentication - Docker Compose deployment configuration - Gitea CI workflow for backend, frontend, and Android builds - Environment configuration template with security settings - Documentation for security model, deployment
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from sqlalchemy import desc, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_db, require_admin
|
||||
from app.core.security import now_utc
|
||||
from app.models.audit import AuditLog
|
||||
from app.models.challenge import Challenge
|
||||
from app.models.device import Device
|
||||
from app.models.user import User
|
||||
from app.schemas.api import AuditOut, ChallengeOut, DeviceOut, UserOut
|
||||
from app.services.audit import audit
|
||||
|
||||
router = APIRouter(prefix="/api/admin", tags=["admin"], dependencies=[Depends(require_admin)])
|
||||
|
||||
|
||||
@router.get("/users", response_model=list[UserOut])
|
||||
async def users(session: AsyncSession = Depends(get_db)):
|
||||
return (await session.scalars(select(User).order_by(User.username))).all()
|
||||
|
||||
|
||||
@router.get("/devices", response_model=list[DeviceOut])
|
||||
async def devices(session: AsyncSession = Depends(get_db)):
|
||||
return (await session.scalars(select(Device).order_by(desc(Device.created_at)))).all()
|
||||
|
||||
|
||||
@router.post("/devices/{device_id}/revoke")
|
||||
async def revoke_device(device_id: UUID, request: Request, session: AsyncSession = Depends(get_db)):
|
||||
device = await session.get(Device, device_id)
|
||||
if not device:
|
||||
raise HTTPException(status_code=404, detail="Device not found")
|
||||
device.is_revoked = True
|
||||
device.revoked_at = now_utc()
|
||||
await audit(
|
||||
session,
|
||||
"device.revoked",
|
||||
actor="admin",
|
||||
target_type="device",
|
||||
target_id=str(device.id),
|
||||
ip_address=request.client.host if request.client else None,
|
||||
)
|
||||
await session.commit()
|
||||
return {"status": "revoked"}
|
||||
|
||||
|
||||
@router.get("/audit", response_model=list[AuditOut])
|
||||
async def audit_logs(limit: int = 100, session: AsyncSession = Depends(get_db)):
|
||||
limit = min(max(limit, 1), 500)
|
||||
return (await session.scalars(select(AuditLog).order_by(desc(AuditLog.created_at)).limit(limit))).all()
|
||||
|
||||
|
||||
@router.get("/challenges", response_model=list[ChallengeOut])
|
||||
async def challenges(limit: int = 100, session: AsyncSession = Depends(get_db)):
|
||||
limit = min(max(limit, 1), 500)
|
||||
return (await session.scalars(select(Challenge).order_by(desc(Challenge.created_at)).limit(limit))).all()
|
||||
Reference in New Issue
Block a user