Skip to content

Commit cb35b6f

Browse files
committed
docs: update README for auth, uploads, admin dashboard, and CI changes
1 parent 339db2b commit cb35b6f

1 file changed

Lines changed: 27 additions & 3 deletions

File tree

README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ generation, helmet + compression + express-rate-limit for hardening.
4444
```
4545
backend/
4646
├── config/ db.js
47-
├── controllers/ auth, document, ai, flashcard, quiz, dashboard
47+
├── controllers/ auth, document, ai, flashcard, quiz, dashboard, admin
4848
├── middlewares/ auth, upload, error, rate limiter
4949
├── models/ User, Document, Flashcard, Quiz, ChatHistory
5050
├── routes/ one router per resource, mounted under /api/*
@@ -85,12 +85,14 @@ npm run dev # http://localhost:8000
8585
PORT=8000
8686
MONGO_URI=<your MongoDB connection string>
8787
JWT_SECRET=<long random string>
88-
JWT_EXPIRES_IN=7d
8988
OPENAI_API_KEY=<your OpenAI API key>
9089
OPENAI_MODEL=gpt-4o-mini
9190
CLIENT_URL=http://localhost:5173
9291
```
9392

93+
See `backend/.env.example` for the optional variables (access/refresh token
94+
lifetimes, account lockout thresholds, AI budget cap) and their defaults.
95+
9496
### Frontend
9597

9698
```bash
@@ -126,7 +128,29 @@ Then open `http://localhost:5173`, register an account, and upload a PDF.
126128
be capped per user per month with `MONTHLY_AI_BUDGET_USD` (unset = no cap).
127129
- Quiz answer keys are never sent to the client until a quiz is submitted.
128130
Grading happens on the server.
131+
- Login/register/refresh are rate limited to 20 requests per 15 minutes per
132+
IP, and an account locks itself out for 15 minutes after 5 consecutive
133+
wrong passwords — both blunt email enumeration and credential stuffing.
134+
Locked-out and nonexistent-user logins return the identical "Invalid email
135+
or password" response so neither leaks which emails are registered.
136+
- Auth uses a short-lived (15 min) access token returned in the response body
137+
plus a 7-day refresh token in an httpOnly cookie; `POST /api/auth/refresh`
138+
rotates both. A stolen access token is only useful for minutes; the refresh
139+
token never touches JavaScript-readable storage. There's no server-side
140+
revocation list yet, so a compromised refresh token is still valid until it
141+
expires — full rotation-with-reuse-detection is still open (Phase 31).
129142
- Uploaded files are stored on local disk under `backend/uploads/`. For a
130-
production deploy with an ephemeral filesystem, swap in S3 or Cloudinary.
143+
production deploy with an ephemeral filesystem, swap in S3 or Cloudinary —
144+
until then, a document whose file was wiped by a redeploy shows a "file no
145+
longer available" banner instead of a broken viewer (chat/flashcards/quiz
146+
still work since the extracted text is stored in MongoDB, not on disk).
131147
- `GET /health` is a liveness check; `GET /ready` also verifies MongoDB is
132148
connected — point an orchestrator's readiness probe at the latter.
149+
- Set `ADMIN_EMAILS` (comma-separated) to unlock a read-only cost dashboard at
150+
`/admin/costs` — spend and token usage per user per day, from the `LlmCall`
151+
ledger. It's an allowlist check on every request, not a stored role, so
152+
granting/revoking access is just an env var change.
153+
- CI runs backend/frontend tests, `npm audit --audit-level=high` on both,
154+
CodeQL static analysis, and the Playwright E2E suite against a real
155+
backend + ephemeral in-memory MongoDB — see
156+
[.github/workflows/ci.yml](.github/workflows/ci.yml).

0 commit comments

Comments
 (0)