# Security Notes — Phase 1 Foundation

You asked for "no backdoors or bypasses." That's not a single setting — it's a set of
habits enforced consistently everywhere. Here's exactly what Phase 1 does and why,
so Phase 2+ code stays consistent with it.

## 1. No hardcoded credentials, anywhere
`AdminUserSeeder` generates a random 16-character password at seed time and prints it
once — it is never committed to the repo. Grep your own codebase periodically for
literal passwords/API keys; a hardcoded `admin/admin123` is the #1 real-world backdoor
in student/freelance Laravel projects.

## 2. Roles can never be self-assigned
`User::$fillable` deliberately excludes any role/permission field. `Register.php`
hardcodes `assignRole('student')` — there is no form field a user can manipulate
(even via browser devtools/Postman) to grant themselves Teacher or Admin. Elevated
roles are only ever assigned by an already-authenticated Admin through a Policy-gated
admin action (built in Phase 2).

## 3. Defense in depth on every protected route
Two independent layers, both must pass:
- **Middleware** (`role:admin` etc.) — blocks the request before it reaches your code.
- **Policies** (Phase 2+, e.g. `ExamPolicy::update`) — blocks the *specific* action on
  the *specific* record. This is what stops IDOR (a Teacher editing another Teacher's
  exam by changing the ID in the URL) — middleware alone cannot catch that, only an
  ownership check on the actual model can.

**Rule going forward: every new controller/Livewire method that reads or writes a
specific record must call `$this->authorize(...)` or a Policy check — no exceptions,
even for "internal" admin tools.**

## 4. Brute-force protection on login
Two independent limiters:
- Livewire-level (`RateLimiter` in `Login.php`) — 5 attempts per email+IP before lockout.
- Route-level (`RateLimiter::for('login', ...)` in `AppServiceProvider`) — a backstop
  in case an endpoint is ever called outside the Livewire component.

Failed logins return an identical, generic message regardless of whether the email
exists — this prevents user enumeration.

## 5. Session hardening
- `request()->session()->regenerate()` on every successful login/registration —
  prevents session fixation attacks.
- `.env`: `SESSION_SECURE_COOKIE=true` in production (cookie only sent over HTTPS),
  `SESSION_HTTP_ONLY=true` (JavaScript cannot read the session cookie, blocking a
  large class of XSS-driven session theft).

## 6. Mass assignment protection
Every model in this package has an explicit `$fillable` allow-list — never `$guarded
= []`. This is what stops an attacker from adding unexpected fields (like `is_active`
or `created_by`) to a form submission and having them silently saved.

## 7. HTTP security headers
`SecurityHeaders` middleware sets `X-Frame-Options: DENY` (blocks clickjacking —
someone iframing your login page to steal credentials), `X-Content-Type-Options:
nosniff`, and a `Permissions-Policy` that denies camera/mic access everywhere except
the exam-taking screen itself (where proctoring legitimately needs it).

## 8. Soft deletes on Users
Users are soft-deleted, not hard-deleted. This preserves exam history/audit trail
integrity — a deleted account's past exam attempts and grades remain intact for
academic record-keeping and dispute resolution.

## 9. What Phase 1 does NOT yet cover (coming in later phases)
- CSRF is on by default in Laravel/Livewire — no action needed, but don't disable it
  anywhere for "convenience."
- Two-factor authentication (recommend Laravel Fortify's 2FA for Admin/Teacher roles
  specifically, before Phase 7).
- Rate limiting on the exam-submission endpoint itself (Phase 4, alongside the
  server-authoritative timer).
- Judge0 sandbox isolation for coding questions (Phase 5) — student code must never
  execute on your main application server.
- Signed/expiring URLs for any exam-related email links (Phase 8, notifications).

## 10. A note on "no bypass" and client-side checks
Anything enforced only in JavaScript (fullscreen lock, copy-paste blocking, the exam
timer shown to the student) is a **deterrent**, not a security boundary — a
determined user can always manipulate their own browser. The actual boundary is
always server-side: the real submission deadline is checked against
`exam_attempts.started_at` on the server, not the JS countdown. Keep this principle
in mind for every proctoring/anti-cheat feature in later phases — client-side checks
improve the *experience* and catch casual attempts; server-side checks are what
actually can't be bypassed.
