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,116 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user