# Security Architecture — CipherGuard

This document explains every security control implemented in the project, where it
lives in the codebase, and the specific threat it defends against. Written to double
as the security-rationale section of a coursework report.

---

## 1. Password hashing — bcrypt

**Where:** `backend/models/User.js`, `pre("save")` hook.

Passwords are never stored in plaintext. Before saving, the password is hashed with
bcrypt using a configurable cost factor (`BCRYPT_SALT_ROUNDS`, default 12). bcrypt
automatically generates and stores a unique salt per password, which defeats
rainbow-table attacks and ensures two users with the same password get different
hashes. The cost factor makes brute-forcing computationally expensive even if the
database is leaked.

## 2. JWT authentication (access + refresh tokens)

**Where:** `backend/utils/generateToken.js`, `backend/controllers/authController.js`.

- A short-lived **access token** (15 min) authorizes API requests.
- A long-lived **refresh token** (7–30 days) is used only to silently obtain a new
  access token, via a hash stored server-side so it can be revoked.
- Tokens are signed with HS256 using two distinct, high-entropy secrets so that
  compromising one token type doesn't compromise the other.
- The refresh token is **rotated** on every use (`backend/controllers/authController.js
  → refresh`), limiting the window in which a stolen refresh token is useful.

## 3. Secure cookies (not localStorage)

**Where:** `setAuthCookies()` in `authController.js`.

Both tokens are set as **httpOnly** (inaccessible to JavaScript, defeating token
theft via XSS), **secure** in production (HTTPS-only transmission), and
**SameSite=strict** (the browser won't attach them to cross-site requests, which is
the primary defense against CSRF). This is a deliberately stronger choice than the
common but insecure pattern of storing JWTs in `localStorage`.

## 4. Protected routes & route guarding

**Where:** `backend/middleware/auth.js` (`protect`), `frontend/src/components/ProtectedRoute.jsx`.

Server-side, `protect` verifies the JWT on every request to a guarded route and
attaches the user document to `req.user`; requests without a valid token receive
`401`. Client-side, `ProtectedRoute` redirects unauthenticated users to `/login`.
**The client-side guard is a UX convenience only** — the real enforcement is always
the server-side middleware, since client code can be bypassed.

## 5. Role-based access control (RBAC)

**Where:** `backend/middleware/roleCheck.js` (`authorize`), used in `userRoutes.js`.

Routes can require specific roles (e.g. `authorize("admin")`). The role is stored
server-side on the `User` document and is **never accepted from client input** —
`authController.js` always hard-codes `role: "user"` on registration, so a user
cannot self-promote to admin by tampering with a request body.

## 6. Input validation — express-validator

**Where:** `backend/middleware/validators.js`.

Every auth endpoint validates and normalizes input before it touches business logic:
required fields, email format, full-name character whitelist, and a strong password
policy (8+ characters, upper + lower case, number, special character). Validation
runs server-side regardless of what the React forms already check client-side,
because client-side checks can be bypassed by calling the API directly.

## 7. Rate limiting

**Where:** `backend/middleware/rateLimiter.js`.

- **Login:** 10 attempts / 15 min per IP — slows credential-stuffing and brute-force
  attacks.
- **Registration:** 20 / hour per IP — slows automated mass account creation.
- **Password reset:** 5 / hour per IP — prevents email-bombing a victim or
  brute-forcing reset tokens.
- **Global baseline:** 300 requests / 15 min on all routes.

This is layered with **account-level lockout**: 5 failed password attempts locks
the specific account for 15 minutes (`authController.js`), independent of IP.

## 8. XSS protection

**Where:** Helmet's Content-Security-Policy (`server.js`), `xss-clean` middleware,
httpOnly cookies, React's default output escaping.

Three layers: (1) React escapes all rendered content by default, so user-supplied
strings can't inject HTML/script unless `dangerouslySetInnerHTML` is used (it isn't,
anywhere in this codebase); (2) `xss-clean` sanitizes incoming request data
server-side; (3) a strict CSP header blocks inline scripts and restricts script
sources to `'self'`, so even if a payload were reflected, the browser would refuse
to execute it; (4) tokens live in httpOnly cookies, so even a successful XSS can't
exfiltrate the session.

## 9. CSRF protection

**Where:** `server.js`, double-submit cookie pattern via `csrf-csrf`; `frontend/src/services/api.js`.

Defense-in-depth on top of `SameSite=strict` cookies: the frontend fetches a CSRF
token from `GET /api/csrf-token` and must echo it back in the `x-csrf-token` header
on every state-changing request. A cross-site page cannot read this token (it's not
embedded in a URL or accessible via cookies alone) and so cannot forge a valid
request even if it tricks a logged-in user's browser into submitting one.

## 10. MongoDB / NoSQL injection prevention

**Where:** `express-mongo-sanitize` in `server.js`.

Strips any request keys containing `$` or `.` before they reach Mongoose, which
neutralizes operator-injection payloads like `{ "email": { "$gt": "" } }` that
would otherwise let an attacker bypass a login query. Combined with the fact that
Mongoose schemas only ever cast/whitelist expected field types.

## 11. Additional hardening

- **`hpp`** — prevents HTTP Parameter Pollution (duplicate query keys).
- **`helmet`** — sets HSTS, `X-Content-Type-Options: nosniff`, frameguard
  (clickjacking defense), and removes the `X-Powered-By` header that leaks
  framework fingerprint information.
- **CORS** — locked to a single configured origin (`CLIENT_URL`) with
  `credentials: true`, instead of a permissive wildcard.
- **Body size limits** — `express.json({ limit: "10kb" })` mitigates large-payload
  denial-of-service attempts.
- **Generic auth error messages** — login and password-reset endpoints return the
  same message whether an email exists or not, preventing account enumeration.
- **No secrets in source control** — `.env` is git-ignored; `.env.example`
  documents required variables without real values.
- **Centralized error handler** — never leaks stack traces outside development mode.

## 12. HTTPS

**Where:** `README.md` §5, `server.js` (`secure: true` cookie flag gated on
`NODE_ENV=production`, `trust proxy` set for reverse-proxy deployments).

TLS termination is intentionally left to the deployment layer (reverse proxy or
direct Node `https` server) rather than hard-coded, since certificate management is
environment-specific. The application is written so that once HTTPS is in front of
it in production, cookies automatically become HTTPS-only and HSTS is enforced.

---

## Production hardening checklist

- [ ] Replace all placeholder secrets in `.env` with high-entropy random values
- [ ] Set `NODE_ENV=production`
- [ ] Terminate TLS (reverse proxy or direct HTTPS) — see README §5
- [ ] Point `MONGO_URI` at an authenticated, network-restricted MongoDB instance
- [ ] Wire `forgotPassword` to a real transactional email provider (currently logs
      the reset token to the console for development only)
- [ ] Add structured logging/alerting on repeated 401s and lockout events
- [ ] Consider adding MFA (TOTP) as a second factor for admin accounts
