security(admin): harden admin API surface — role guard, secret fail-closed, env-based credentials

Removes the unauthenticated admin API hole verified live in the 2026-09-26 audit:

- Add require_admin dependency; apply to ALL 17 /api/admin/* endpoints.
  8 of them previously had NO auth dependency at all (bulk register/delete,
  student toggle, test upload, coupons); the rest accepted any logged-in
  student's JWT via get_current_user.
- Remove the 'super-secret-pragativeer-key' JWT fallback: backend now
  refuses to start without JWT_SECRET_KEY (fail-closed, no insecure default).
- Admin login no longer returns the opaque 5-char token 'admin'; it now
  verifies ADMIN_PASSWORD from env and returns a real JWT for the DB admin
  user. The opaque token shim (get_current_user/tier_checker) now only
  accepts the env-configured ADMIN_TOKEN (backwards-compat until frontend
  tokens are rotated).
- Replace all hardcoded 'Pv9#kL2$mQ8!vX4@' literals with env-derived
  DEMO_PASSWORD (login, change-password, ensure_demo_user seed hash) so
  rotation is a .env edit, not a code change.
- Frontend login: stop special-casing cookie token='admin'; store the real
  JWT for admin logins too.
- Add backend/.env.example documenting JWT_SECRET_KEY, ADMIN_PASSWORD,
  ADMIN_TOKEN, DEMO_PASSWORD.

Verified: AST parse clean; student-facing routes untouched; /api/tests and
profile/billing endpoints still authenticate via get_current_user.

Ops (deploy checklist, NOT code): set JWT_SECRET_KEY + ADMIN_PASSWORD +
ADMIN_TOKEN in backend/.env before restarting, or the backend will fail
closed. Old issued JWTs remain valid until the secret is rotated.
This commit is contained in:
2026-09-26 07:19:33 +00:00
parent 8a5ea57feb
commit 0c2a2275ce
3 changed files with 102 additions and 30 deletions

30
backend/.env.example Normal file
View File

@@ -0,0 +1,30 @@
# PragatiVeer backend — required & recommended environment variables.
# Copy to backend/.env and fill in real values. NEVER commit the real .env.
# --- SECURITY (required in production) ---
# Secret used to sign JWTs. Generate: python3 -c "import secrets; print(secrets.token_hex(32))"
# The backend REFUSES TO START if this is unset (no insecure fallback).
# Rotating this invalidates every existing token (all users must log in again).
JWT_SECRET_KEY=
# Admin login password for admin@pragativeer.com (used by /api/auth/login).
# Generate a long random value; rotate whenever it may have leaked.
# Leave unset to disable the env-based admin login entirely (then admin must
# authenticate via the DB password hash only).
ADMIN_PASSWORD=
# Opaque admin bearer token accepted by /api/admin/* alongside real JWTs.
# Kept for backwards compatibility with the pre-hardening frontend; set it to
# a long random value. Once the frontend sends real JWTs everywhere, rotate
# it to a random string or remove the shim in main.py.
ADMIN_TOKEN=admin
# Demo accounts password (demo@pragativeer.com etc.). Rotate in .env, never in code.
DEMO_PASSWORD=
# --- EXISTING SETTINGS (unchanged by the security hardening) ---
# DATABASE_URL=postgresql://user:pass@host:port/db
# RAZORPAY_KEY_ID= / RAZORPAY_KEY_SECRET=
# MinIO / S3 credentials for PDF storage
# SMTP settings for welcome emails