Security Requirements
Six security domains covering authorization, rate limiting, JWT revocation, input validation, secrets management, and audit logging.
These requirements must be addressed before production deployment. Each domain covers what currently exists, the specific gaps, and the required fix.
1. Authorization — School Isolation & IDOR
- getSchoolFilter() must return { school: { $in: req.user.schools } } for super_admin (not empty {})
- Every controller query that accepts a URL param ID must include school: req.activeSchoolId in the MongoDB filter — never fetch by _id alone
- requireParentOfStudent() helper must validate that studentId is in parent's parentOf array before any parent accesses student data
- belongsToSchool() must be updated to check schools array (not just school field) for multi-school users
2. Rate Limiting
| Tier | Limit | Routes |
|---|---|---|
| Auth (strict) | 5 req / 15 min per IP | /auth/login, /auth/forgot-password, /magic-link/verify |
| Mutation (standard) | 30 req / 1 min per user | POST/PATCH/DELETE on most resources |
| Read (relaxed) | 120 req / 1 min per user | GET routes |
| AI batch (device) | 60 req / 1 min per device token | /ai-signals/batch |
| WhatsApp webhook (IP) | 100 req / 1 min per IP | /whatsapp/webhook |
3. JWT Revocation
- Every JWT contains a unique jti (JWT ID) claim generated at login
- On logout, the jti is stored in a BlacklistedToken MongoDB collection with a TTL index that auto-expires at the token's own exp time
- protect middleware checks the blacklist on every request — O(1) lookup via indexed jti
- Password reset and forced logout endpoints call revokeAllTokens(userId) — inserts all active jtis for that user into the blacklist
4. Input Validation & Injection
- express-mongo-sanitize middleware strips $ and . from request bodies — prevents NoSQL injection
- Helmet.js sets HTTP security headers on all responses
- File uploads validated with magic bytes (not just extension) — prevents MIME spoofing
- Max file sizes enforced per upload type: profile photos 2 MB, course materials 50 MB, assignment submissions 10 MB
- All user-supplied strings trimmed and length-capped at the schema level
5. Secrets Management
- All API keys and connection strings stored in environment variables — never committed to the repo
- JWT_SECRET minimum 256-bit random string
- Webhook secrets (DodoPayments, SendZen) validated via HMAC-SHA256 on every inbound webhook
- Production error responses never expose stack traces or internal details
- Dependency vulnerability scanning (npm audit) in CI pipeline
6. Audit Logging
All write operations by admin-level roles are recorded in an AuditLog collection: actor (user ID + role), action (create/update/delete), resource type + ID, timestamp, IP address, and a JSON diff of the change. Logs are append-only and stored for 90 days.