chore(07.3-02): wire JWT env vars in docker-compose, README key-gen, .env.example, bump to 0.1.2
- docker-compose.yml: add JWT_PRIVATE_KEY + JWT_PUBLIC_KEY to backend and celery-worker service env blocks - README.md: add JWT_PRIVATE_KEY/JWT_PUBLIC_KEY to required env vars table; add JWT Key Generation section with Python one-liner and warning about session revocation - .env.example: add JWT_PRIVATE_KEY= and JWT_PUBLIC_KEY= adjacent to SECRET_KEY - backend/main.py: version bump 0.1.1 -> 0.1.2 - frontend/package.json: version bump 0.1.1 -> 0.1.2
This commit is contained in:
@@ -31,6 +31,11 @@ REDIS_URL=redis://:changeme_redis@redis:6379/0
|
|||||||
# JWT signing secret — generate with: python3 -c "import secrets; print(secrets.token_hex(64))"
|
# JWT signing secret — generate with: python3 -c "import secrets; print(secrets.token_hex(64))"
|
||||||
SECRET_KEY=CHANGEME-replace-with-64-char-random-hex
|
SECRET_KEY=CHANGEME-replace-with-64-char-random-hex
|
||||||
|
|
||||||
|
# ── JWT Key Pair (Phase 7.3 — ES256) ─────────────────────────────────────────
|
||||||
|
# Generated by running the Python snippet in README.md JWT Key Generation section
|
||||||
|
JWT_PRIVATE_KEY=
|
||||||
|
JWT_PUBLIC_KEY=
|
||||||
|
|
||||||
# ── Admin Bootstrap (Phase 2 — D-04) ─────────────────────────────────────────
|
# ── Admin Bootstrap (Phase 2 — D-04) ─────────────────────────────────────────
|
||||||
# First admin account created on startup if users table is empty.
|
# First admin account created on startup if users table is empty.
|
||||||
# Both vars must be set; if missing, a WARNING is logged but app starts normally.
|
# Both vars must be set; if missing, a WARNING is logged but app starts normally.
|
||||||
|
|||||||
@@ -141,6 +141,8 @@ Copy `.env.example` to `.env`. Only the fields marked **Required** must be set b
|
|||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `SECRET_KEY` | JWT signing secret — generate with `openssl rand -hex 32` |
|
| `SECRET_KEY` | JWT signing secret — generate with `openssl rand -hex 32` |
|
||||||
|
| `JWT_PRIVATE_KEY` | Base64-encoded PEM private key for ES256 JWT signing (required) — see [JWT Key Generation](#jwt-key-generation) |
|
||||||
|
| `JWT_PUBLIC_KEY` | Base64-encoded PEM public key for ES256 JWT verification (required) — see [JWT Key Generation](#jwt-key-generation) |
|
||||||
| `CLOUD_CREDS_KEY` | Master key for cloud credential encryption — generate with `openssl rand -hex 16` (pad to 32 chars) |
|
| `CLOUD_CREDS_KEY` | Master key for cloud credential encryption — generate with `openssl rand -hex 16` (pad to 32 chars) |
|
||||||
| `ADMIN_EMAIL` | Bootstrap admin email |
|
| `ADMIN_EMAIL` | Bootstrap admin email |
|
||||||
| `ADMIN_PASSWORD` | Bootstrap admin password (must pass strength check) |
|
| `ADMIN_PASSWORD` | Bootstrap admin password (must pass strength check) |
|
||||||
@@ -159,6 +161,29 @@ Copy `.env.example` to `.env`. Only the fields marked **Required** must be set b
|
|||||||
| `GOOGLE_CLIENT_ID/SECRET` | *(unset)* | Required only if using Google Drive backend |
|
| `GOOGLE_CLIENT_ID/SECRET` | *(unset)* | Required only if using Google Drive backend |
|
||||||
| `ONEDRIVE_CLIENT_ID/SECRET` | *(unset)* | Required only if using OneDrive backend |
|
| `ONEDRIVE_CLIENT_ID/SECRET` | *(unset)* | Required only if using OneDrive backend |
|
||||||
|
|
||||||
|
### JWT Key Generation
|
||||||
|
|
||||||
|
Phase 7.3 uses ES256 (ECDSA P-256) for JWT signing. Unlike HS256, ES256 is asymmetric — the private key signs tokens and the public key verifies them. A leaked public key cannot forge tokens.
|
||||||
|
|
||||||
|
Generate the key pair with this Python one-liner (requires `cryptography`, already in `requirements.txt`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c "
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
import base64
|
||||||
|
k = ec.generate_private_key(ec.SECP256R1())
|
||||||
|
priv = base64.b64encode(k.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption())).decode()
|
||||||
|
pub = base64.b64encode(k.public_key().public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo)).decode()
|
||||||
|
print(f'JWT_PRIVATE_KEY={priv}')
|
||||||
|
print(f'JWT_PUBLIC_KEY={pub}')
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
Paste the two output lines into your `.env` file at the project root.
|
||||||
|
|
||||||
|
> **Warning:** Rotating these keys invalidates every active session — the startup rotation hook will bulk-revoke all refresh tokens on the next boot.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|||||||
+1
-1
@@ -244,7 +244,7 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
# ── Application factory ───────────────────────────────────────────────────────
|
# ── Application factory ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
app = FastAPI(title="Document Scanner API", version="0.1.1", lifespan=lifespan)
|
app = FastAPI(title="Document Scanner API", version="0.1.2", lifespan=lifespan)
|
||||||
|
|
||||||
# Rate limiter state (slowapi)
|
# Rate limiter state (slowapi)
|
||||||
app.state.limiter = auth_limiter
|
app.state.limiter = auth_limiter
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ services:
|
|||||||
- MINIO_PUBLIC_ENDPOINT=${MINIO_PUBLIC_ENDPOINT:-localhost:9000}
|
- MINIO_PUBLIC_ENDPOINT=${MINIO_PUBLIC_ENDPOINT:-localhost:9000}
|
||||||
- REDIS_URL=${REDIS_URL}
|
- REDIS_URL=${REDIS_URL}
|
||||||
- SECRET_KEY=${SECRET_KEY}
|
- SECRET_KEY=${SECRET_KEY}
|
||||||
|
- JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY}
|
||||||
|
- JWT_PUBLIC_KEY=${JWT_PUBLIC_KEY}
|
||||||
- ADMIN_EMAIL=${ADMIN_EMAIL}
|
- ADMIN_EMAIL=${ADMIN_EMAIL}
|
||||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||||
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173}
|
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173}
|
||||||
@@ -101,6 +103,8 @@ services:
|
|||||||
- REDIS_URL=${REDIS_URL}
|
- REDIS_URL=${REDIS_URL}
|
||||||
- CLOUD_CREDS_KEY=${CLOUD_CREDS_KEY}
|
- CLOUD_CREDS_KEY=${CLOUD_CREDS_KEY}
|
||||||
- SECRET_KEY=${SECRET_KEY}
|
- SECRET_KEY=${SECRET_KEY}
|
||||||
|
- JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY}
|
||||||
|
- JWT_PUBLIC_KEY=${JWT_PUBLIC_KEY}
|
||||||
- PYTHONDONTWRITEBYTECODE=1
|
- PYTHONDONTWRITEBYTECODE=1
|
||||||
- PYTHONPATH=/app
|
- PYTHONPATH=/app
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "document-scanner-frontend",
|
"name": "document-scanner-frontend",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Reference in New Issue
Block a user