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:
2026-06-28 09:37:51 +02:00
commit f925009977
57 changed files with 2776 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from prometheus_client import CONTENT_TYPE_LATEST, Counter, Histogram, generate_latest
from slowapi import Limiter
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from slowapi.util import get_remote_address
from starlette.responses import JSONResponse
from app.api import admin, oidc, public
from app.core.config import get_settings
from app.core.database import create_all
from app.core.security import load_or_create_oidc_key
settings = get_settings()
REQUESTS = Counter("nexamfa_http_requests_total", "HTTP requests", ["method", "path", "status"])
LATENCY = Histogram("nexamfa_http_request_duration_seconds", "HTTP request latency", ["method", "path"])
limiter = Limiter(key_func=get_remote_address, default_limits=[settings.rate_limit_default])
@asynccontextmanager
async def lifespan(app: FastAPI):
oidc.set_oidc_private_key(load_or_create_oidc_key(settings.oidc_signing_key_pem))
await create_all()
yield
app = FastAPI(title="NexaMFA", version="0.1.0", lifespan=lifespan)
app.state.limiter = limiter
app.add_middleware(SlowAPIMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origin_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.exception_handler(RateLimitExceeded)
async def rate_limit_handler(_: Request, exc: RateLimitExceeded):
return JSONResponse({"detail": "Rate limit exceeded"}, status_code=429)
@app.middleware("http")
async def metrics_middleware(request: Request, call_next):
with LATENCY.labels(request.method, request.url.path).time():
response = await call_next(request)
REQUESTS.labels(request.method, request.url.path, str(response.status_code)).inc()
return response
@app.get("/health")
async def health():
return {"status": "ok", "service": "NexaMFA"}
@app.get("/metrics")
async def metrics():
return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)
app.include_router(public.router)
app.include_router(admin.router)
app.include_router(oidc.router)