# 01 — Admin Authentication

## Overview

Session-based authentication for the **OOTBO ops team**. Backed by the `admins` table (NOT the shared `users` table). Built on Laravel's stock `AuthenticatesUsers` trait scaffolded by `laravel/ui`.

**Status:** Live

- Login + logout + password-reset views auto-generated by `php artisan ui`.
- Sanctum is installed (composer dependency + migration) but only used as scaffolding; admin sessions are pure web cookies, not API tokens.
- Session lifetime is **480 minutes (8 hours)** — `SESSION_LIFETIME=480` in `.env`.

---

## User Stories

| ID | As a | I want to | So that |
|----|------|-----------|---------|
| AUTH-01 | Admin user | Log in with my email + password | I can access the admin panel |
| AUTH-02 | Admin user | Be redirected to my role-appropriate landing page | I see the work I'm responsible for |
| AUTH-03 | Admin user | Request a password reset by email | I can recover access without bothering an admin |
| AUTH-04 | Admin user | Be logged out after 8h of inactivity | My account isn't hijacked from an unattended browser |
| AUTH-05 | Admin user | See a friendly redirect to `/login` when my session expires (even mid-XHR) | I don't get a confusing 403/419 page |
| AUTH-06 | New visitor | Hit `/` and be sent to `/login` | I don't see a blank Laravel welcome page |

---

## Screens & Flows

```
                                  ┌──────────────┐
                  GET  /          │  redirect    │
                 ────────────────▶│  to /login   │
                                  └──────┬───────┘
                                         │
                                         ▼
┌────────────────┐ POST /login  ┌──────────────────┐
│  /login        │─────────────▶│  Auth::attempt() │
│  (login.blade) │              │  on `admins`     │
└────────────────┘              └────────┬─────────┘
                                         │ success
                                         ▼
                                  ┌──────────────┐
                                  │  /welcome    │
                                  │ (router by   │
                                  │  superadmin) │
                                  └──────┬───────┘
                                         │
              ┌──────────────────────────┼────────────────────────────┐
              │ superadmin=1             │ superadmin=2               │ regular
              ▼                          ▼                            ▼
       ┌────────────┐            ┌────────────────┐         ┌────────────────────┐
       │ /dashboard │            │ /coordinator   │         │ first allowed menu │
       │ (full)     │            │ (coordinator   │         │ via getRedirection │
       └────────────┘            │   only)        │         └────────────────────┘
                                 └────────────────┘
```

### Routes & Actions

| Route | Method | Handler | Description |
|-------|--------|---------|-------------|
| `/` | GET | inline closure (`web.php:16`) | Redirects to `/login` |
| `/login` | GET/POST | `Auth\LoginController` (trait) | Show / submit login form |
| `/logout` | POST | `Auth\LoginController::logout()` | Destroy session |
| `/password/email` | POST | `ForgotPasswordController` | Send password-reset email |
| `/password/reset/{token}` | GET | `ResetPasswordController` | Show reset form |
| `/password/reset` | POST | `ResetPasswordController` | Apply new password |
| `/password/confirm` | GET/POST | `ConfirmPasswordController` | Re-confirm for sensitive ops |
| `/email/verify` | GET | `VerificationController` | Stock Laravel email verification (not actively used) |
| `/register` | GET/POST | `RegisterController` | **Disabled in practice** — admins are created via `/user/admin` |
| `/welcome` | GET | `WelcomeController::index()` | Role-based landing redirect |
| `/dashboard` | GET | `HomeController::index()` | Superadmin / standard landing |

### Blade Views

- `resources/views/auth/login.blade.php` (and an unused `login1.blade.php` variant)
- `resources/views/auth/register.blade.php`
- `resources/views/auth/verify.blade.php`
- `resources/views/auth/passwords/email.blade.php`
- `resources/views/auth/passwords/reset.blade.php`
- `resources/views/auth/passwords/confirm.blade.php`

---

## Data Model

### `admins` table (auth source)

```php
// App\Models\User (NOTE: model maps to `admins` table)
protected $table = 'admins';
protected $fillable = ['name', 'email', 'password'];
protected $hidden   = ['password', 'remember_token'];

// Columns observed in code:
{
  id:             int,
  uuid:           string,
  name:           string,
  email:          string,            // login key
  phone:          string|null,
  password:       string,            // bcrypt
  role_id:        int|null,          // FK roles.id (1..8)
  superadmin:     0 | 1 | 2,         // 0=normal, 1=full, 2=coordinator
  status:         'active'|'inactive',
  country_code:   string|null,       // used by RM scoping
  permission:     string|null,       // legacy comma-sep per-user permissions
  dor:            string,            // "date of request" — added by 2022 migration
  created_at:     datetime,
  updated_at:     datetime,
  remember_token: string|null,
  email_verified_at: datetime|null,
}
```

### `password_resets` table (Laravel default)

```php
{
  email:      string,
  token:      string,
  created_at: datetime,
}
```

---

## Validations & Business Rules

| Rule | Detail |
|------|--------|
| Login uses `email` field | Stock `AuthenticatesUsers::username() = 'email'` |
| Passwords are bcrypt | `Hash::make($pwd)` |
| Session lifetime 8 hours | `SESSION_LIFETIME=480` in `.env` |
| Idle session → friendly redirect | `UserPermission::handle()` returns 401 JSON for XHR, `redirect()->guest('/login')` for HTML — never raw 403/419 |
| `Auth::routes()` enables `/register` | But there is no link to it in the UI; admins are created only via `/user/admin` |
| Logout requires POST | Default Laravel CSRF token enforced |
| Remember-me cookie | Standard Laravel behaviour — works because `Authenticatable` trait is intact on `User` |

---

## API Endpoints

VisoAdmin authentication is **session-based** — there are no auth tokens to issue. The only API endpoint is the stock:

| Method | Path | Auth | Request | Response | Consumer |
|--------|------|------|---------|----------|----------|
| GET | `/api/user` | `auth:sanctum` | — | Current user (JSON) | Unused (scaffold) |

`api.php` contains only this one route.

---

## Upstream Impact (what feeds INTO Auth)

- **`admins` table seed/CRUD** — comes from Feature 03 (Admin User Management).
- **`role_permissions` table** — written by Feature 02 (RBAC); read by `User::getHaspermissionAttribute()` immediately after login to compute `has_permission`.
- **`.env` config**: `SESSION_LIFETIME`, `APP_URL`, mailer settings.

---

## Downstream Impact (what Auth feeds INTO)

Every other feature. The `auth` middleware is on every meaningful route group in `web.php`:

```php
Route::group(['middleware' => ['auth']], function () { ... });
```

- All 19 other features become inaccessible if Auth is broken.
- Logout invalidates ALL state in middleware-protected pages (RM lists, vendor lists, dashboards).

---

## Impact of Changes

| If you change... | Risk to... | Level | Type |
|-----------------|------------|-------|------|
| `User::$table` (the `admins` mapping) | Every feature (all auth lookups break) | Critical | Data |
| `UserPermission::handle()` redirect logic | All XHR error UX in jQuery code | High | Guard |
| `WelcomeController::index()` superadmin branching | Coordinator landing flow | High | Navigation |
| Session driver (file → redis etc.) | All open admin sessions invalidated on deploy | Medium | Service |
| `SESSION_LIFETIME` | Idle-timeout expectations across team | Medium | Service |
| Login blade markup | Login submission JS in custom assets | Low | UI |
| Adding new `Auth::routes()` features (e.g. email verify) | Stock `RegisterController` becomes accessible — risk of unauthorised admin creation | Critical | Guard |

---

## Known Issues

- `register.blade.php` exists and `Auth::routes()` enables `/register`, so anyone hitting `/register` can theoretically create an admin row. The link is not exposed in the UI but the route is live. **Mitigation owed**.
- The `admins.password = bcrypt($phone)` pattern (used when an admin is created and when a vendor is created from VisoAdmin) means default-password = phone-number — predictable. There's no "force change password on first login".
