Files
nessi f925009977 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
2026-06-28 09:37:51 +02:00

117 lines
3.4 KiB
Markdown

# NexaMFA
NexaMFA is an open-source, self-hosted Push MFA system similar to Duo Push. It provides a FastAPI backend, PostgreSQL persistence, Redis-ready queue/cache integration, a React TypeScript admin console, an Android Kotlin/Jetpack Compose app, Firebase Cloud Messaging push notifications, Docker Compose deployment, Prometheus metrics, and OIDC provider mode for authentik.
## Security Model
Push delivery is only a wake-up signal. NexaMFA never treats a push notification as approval. Each Android device generates an asymmetric keypair in Android Keystore, stores only the public key on the server, and signs the exact challenge payload after local BiometricPrompt confirmation. The backend verifies the signature, checks challenge expiry, blocks replay by allowing only one terminal state, and rejects revoked devices immediately.
Push notifications contain only `challenge_id`.
## Quick Start
1. Copy the environment file:
```bash
cp .env.example .env
```
2. Replace all secrets in `.env`, set `PUBLIC_BASE_URL`, `OIDC_ISSUER`, `OIDC_REDIRECT_URIS`, and Firebase settings.
3. Start the stack:
```bash
docker compose up --build
```
4. Open the admin UI at `http://localhost:8080` and sign in with `ADMIN_TOKEN`.
5. Create an enrollment from the backend API:
```bash
curl -H 'Content-Type: application/json' \
-d '{"username":"alice","display_name":"Alice","email":"alice@example.com"}' \
http://localhost:8000/api/enroll/start
```
Scan the returned QR code with the Android app.
## Backend API
Public/device endpoints:
- `POST /api/enroll/start`
- `POST /api/enroll/finish`
- `POST /api/challenges`
- `GET /api/challenges/{id}`
- `POST /api/challenges/{id}/approve`
- `POST /api/challenges/{id}/deny`
- `GET /health`
- `GET /metrics`
Admin endpoints require `Authorization: Bearer $ADMIN_TOKEN`:
- `GET /api/admin/users`
- `GET /api/admin/devices`
- `POST /api/admin/devices/{id}/revoke`
- `GET /api/admin/challenges`
- `GET /api/admin/audit`
OIDC endpoints:
- `/.well-known/openid-configuration`
- `/oauth/authorize`
- `/oauth/token`
- `/oauth/userinfo`
- `/oauth/jwks`
## Zoraxy Reverse Proxy
Expose the backend public hostname, for example `https://mfa.example.com`, to container `backend:8000`. Enable HTTPS in Zoraxy and forward:
- `/.well-known/openid-configuration`
- `/oauth/*`
- `/api/*`
- `/health`
- `/metrics` if Prometheus is remote and authorized by your network policy
Expose the admin frontend separately, for example `https://mfa-admin.example.com`, to container `frontend:80`. Set `CORS_ORIGINS=https://mfa-admin.example.com`.
## Development
Backend:
```bash
cd backend
pip install -e '.[test]'
pytest
uvicorn app.main:app --reload
```
Frontend:
```bash
cd frontend
npm install
npm run dev
```
Android:
```bash
cd android
./gradlew testDebugUnitTest assembleDebug
```
Add your Firebase `google-services.json` at `android/app/google-services.json`.
## Production Notes
- Use long random values for `ADMIN_TOKEN`, `POSTGRES_PASSWORD`, and `OIDC_CLIENT_SECRET`.
- Persist `OIDC_SIGNING_KEY_PEM`; changing it invalidates token verification until clients refresh JWKS.
- Restrict admin UI and metrics at the reverse proxy or network layer.
- Use HTTPS only. Android enrollment and challenge approval should never be sent over cleartext.
- Configure Firebase service account credentials for real push delivery.
See [SECURITY.md](SECURITY.md) and [docs/authentik.md](docs/authentik.md).