# Auth Design

## Data Model

### `admin_users` table

The credential and profile store for all administrators. Sourced from `database/migrations/2016_01_04_173148_create_admin_tables.php`.

| Column | Type | Notes |
|--------|------|-------|
| `id` | PK | |
| `username` | string | Login identity; unique; never email |
| `password` | string | bcrypt hash |
| `name` | string | Display name; mutable via `PUT auth/setting` |
| `avatar` | string/null | Optional profile image path |
| `remember_token` | string/null | Standard Laravel remember-me token |

Model: `Encore\Admin\Auth\Database\Administrator` (eloquent driver).  
Guard: `admin` session guard (`config/admin.php:59-63`) — the only active auth stack.  
The framework's default `web` guard → `App\User` is dead; `App\User` does not exist.

### Session state

The unit itself is stateless request-to-request. Persisted state is exactly:

- **Server-side session** — holds the authenticated `admin` guard identity; created on login, regenerated on login, destroyed on logout.
- **`admin_users` row** — mutated only by `PUT auth/setting` (name, avatar, password hash).

---

## Internal Flows

### Login (`POST auth/login`)

```mermaid
flowchart TD
    A[POST auth/login] --> B{Validate username+password required}
    B -- fail --> C[redirect back\nwithInput + withErrors\nvalidation bag]
    B -- pass --> D[guard-attempt credentials]
    D -- success --> E[emit login_successful toast]
    E --> F[session regenerate]
    F --> G[redirect intended / redirectPath]
    G --> H[root / → 301 → /pos]
    D -- fail --> I[redirect back\nwithInput + withErrors\nauth.failed message]
```

Source: `AuthController::postLogin` (`:39-60`), `sendLoginResponse` (`:172-179`).

Key invariant: `redirectPath()` = `config('admin.route.prefix')` = `''` (root), which is 301-redirected to `/pos` by `routes/web.php:22`. The controller never hard-codes `/pos`.

### Login page (`GET auth/login`)

```mermaid
flowchart TD
    A[GET auth/login] --> B{guard check}
    B -- authenticated --> C[redirect to redirectPath]
    B -- anonymous --> D[render admin::login view]
```

Source: `AuthController::getLogin` (`:23-30`).

### Logout (`GET auth/logout`)

```mermaid
flowchart TD
    A[GET auth/logout] --> B[guard logout]
    B --> C[session invalidate]
    C --> D[redirect to admin root /]
```

Source: `AuthController::getLogout` (`:67-74`).  
Logout is a `GET` (not `POST`) — CSRF-unprotected by the Encore\Admin package design.

### Route protection (application-wide)

```mermaid
flowchart TD
    A[Any app request] --> B{admin middleware}
    B -- session valid --> C[proceed to controller]
    B -- no session --> D[redirect to auth/login]
```

Source: `routes/web.php:23-27`, `config/admin.php:29`.  
Middleware stack per route group: `['web', 'admin']`. The `admin.permission` middleware is **not** wired — every authenticated administrator reaches every screen (BR-02).

### Self-setting update (`GET`/`PUT auth/setting`)

```mermaid
flowchart TD
    A[GET auth/setting] --> B[build Administrator form\nbound to Admin::user id\nusername read-only]
    B --> C[render form]

    D[PUT auth/setting] --> E[settingForm update Admin::user id]
    E --> F{saving hook:\npassword changed?}
    F -- yes --> G[bcrypt password]
    F -- no --> H[skip re-hash]
    G --> I[drop password_confirmation\n via form ignore]
    H --> I
    I --> J[persist to admin_users]
    J --> K[saved hook: success toast]
    K --> L[redirect back to auth/setting]
```

Source: `AuthController::getSetting` / `settingForm` (`:81-139`).  
The `saving` hook compares the incoming `password` value against the stored hash before deciding whether to re-hash — unchanged passwords are never re-bcrypted.

---

## Technical Decisions

| Decision | Detail | Confidence |
|----------|--------|------------|
| Single Encore\Admin session guard as the sole auth stack | `routes/web.php:20`, `config/admin.php:50-64`, `config/auth.php:71`; framework `web` guard left dead by design | 🟢 |
| Authentication-only access control (no per-route authorization) | `admin.permission` middleware absent from route group; RBAC tables seeded but decorative (ADR-0009) | 🟢 |
| Login identity is `username`, not email | `AuthController::username()` returns the literal `'username'` (`:186-189`) | 🟢 |
| bcrypt hashing; re-hash only on change | `saving` hook at `:127-131`; avoids unnecessary hash churn | 🟢 |
| Session regenerate on login / invalidate on logout | Fixation protection; `:71,176` | 🟢 |
| Post-login landing delegated to a root 301 redirect | Controller resolves to `''` (root); `routes/web.php:22` owns the `/pos` redirect — decoupled from the auth unit | 🟢 |
| Logout via `GET` (not `POST`) | Encore\Admin package convention; CSRF-unprotected by design | 🟡 |
| No brute-force protection or failed-login logging | Confirmed intentional 2026-09-25 (`questions.md#question-15`); not needed in reimplementation | 🟢 |
| Dead framework auth stack must not be reimplemented | `app/Http/Controllers/Auth/*`, `web` guard, `RedirectIfAuthenticated` are stock scaffolding; `App\User` missing, no `Auth::routes()` call | 🟢 |

---

## Notes

### Guard / provider symbol reference

| Symbol | Signature | Returns |
|--------|-----------|---------|
| `AuthController::guard()` | `()` | `Auth::guard('admin')` — session `StatefulGuard` |
| `AuthController::username()` | `()` | `'username'` |
| `AuthController::redirectPath()` | `()` | `config('admin.route.prefix')` = `''` |

### Constraints and pitfalls

- **Redirect-based validation errors, not JSON 422.** The legacy returns HTTP 302 back to the form with a Laravel error bag; `422` appears in design tables only as a semantic label. A REST reimplementation must decide whether to keep redirect-based errors or return JSON 422 (open gap).
- **HTTPS posture is environment-dependent.** Secure-cookie behavior is controlled by `ADMIN_SECURE` (default `true`); the effective value per environment is not in the repo — validate with ops.
- **Default seed credential `admin`/`admin`.** Created by `AdminTablesSeeder`; must be rotated before production use; residual risk on a fresh seed or DB reset only.
- **RBAC management screens are out of scope.** `auth/users`, `auth/roles`, `auth/permissions`, `auth/menu`, `auth/logs` are registered by the same `registerAuthRoutes()` call but belong to the unenforced RBAC subsystem (`_reversa_sdd/permissions.md`), not to this unit.
- **Operation logging is disabled.** `admin_operation_log` table exists but `operation_log.enable = false` in `config/admin.php`; auth events (login, logout, setting changes) are not audit-logged.
