# Auth — Requirements

> Produced by the Reversa **Writer** (phase: generation) · doc_level: `complete`
> Generated on 2026-09-19
> Unit granularity: `endpoint` · Legacy routes: `Admin::registerAuthRoutes()` (login / logout / password-setting)

**Confidence scale:** 🟢 CONFIRMED (read directly from code) · 🟡 INFERRED (pattern-based, may be wrong) · 🔴 GAP (needs human validation)

## Overview

The `auth` unit is the single gate into TinyPOS (`tnx-pos`). It authenticates a back-office **Administrator** against the `admin_users` table using the Encore\Admin `admin` session guard, exposes login / logout, and lets the authenticated user edit their own profile and password. There is no customer-facing login — customers are CRM records, not accounts. Access control across the whole application collapses to a single decision: *authenticated admin, or not.* 🟢 (`routes/web.php:20`, `config/admin.php:23-64`, `_reversa_sdd/permissions.md`)

## Responsibilities

- Register and serve the admin authentication routes via `Admin::registerAuthRoutes()` (`routes/web.php:20`). 🟢
- Show the login page and reject anonymous access to every application route, redirecting to the login page. 🟢
- Validate credentials (`username`, `password`) and start an authenticated session on the `admin` guard. 🟢
- Log out: destroy the guard session and invalidate the underlying session. 🟢
- Let the logged-in administrator view and update their own account (`name`, `avatar`, `password`) through the self-setting form. 🟢
- Bootstrap post-login navigation: the effective landing page after login is `/pos` (root `/` → 301 → `/pos`). 🟢 (`routes/web.php:22`)
- **Out of scope for this unit (do not implement here):** the Encore\Admin RBAC management screens (`auth/users`, `auth/roles`, `auth/permissions`, `auth/menu`, `auth/logs`) are also registered by `registerAuthRoutes()` but belong to the (unenforced) RBAC subsystem, not to login/logout/password. They are decorative in this app. 🟢 (`vendor/salipropham/laravel55-admin/src/Admin.php:240-251`, `_reversa_sdd/permissions.md`)

## Business Rules

- **BR-01 — Single user class.** Only the back-office Administrator exists; there is no self-registration and no customer login. 🟢 (`_reversa_sdd/permissions.md`, `_reversa_sdd/adrs/0009-authentication-only-access-control.md`)
- **BR-02 — Authentication ≠ authorization.** The RBAC tables (roles/permissions) are seeded but the application route group runs only the `admin` **auth** middleware, not the package's `admin.permission` middleware; therefore every authenticated administrator can reach every screen and action. 🟢 (`config/admin.php:29`, `_reversa_sdd/permissions.md`)
- **BR-03 — Login identity field is `username`** (not email). Both `username` and `password` are required; validation failure returns to the form with errors. 🟢 (`vendor/…/AuthController.php:41-51,186-189`)
- **BR-04 — Failed-credentials message** is `trans('auth.failed')` when available, else `"These credentials do not match our records."` — the same message is used whether the user is unknown or the password is wrong (no user enumeration). 🟢 (`vendor/…/AuthController.php:57-59,144-149`)
- **BR-05 — Passwords are bcrypt-hashed.** On self-setting update, the password is re-hashed with `bcrypt()` only when it actually changed. 🟢 (`vendor/…/AuthController.php:127-131`)
- **BR-06 — Session hygiene.** The session ID is regenerated on successful login and the session is fully invalidated on logout (fixation protection). 🟢 (`vendor/…/AuthController.php:71,176`)
- **BR-07 — Already-authenticated login is a no-op redirect.** Hitting `GET auth/login` while authenticated redirects to the post-login path instead of re-showing the form. 🟢 (`vendor/…/AuthController.php:25-27`)
- **BR-08 — Post-login destination** is `redirect()->intended(redirectPath())`; `redirectPath()` resolves to `config('admin.route.prefix')` which is `''` (root), and root 301-redirects to `/pos`. 🟢 (`vendor/…/AuthController.php:156-163,178`, `config/admin.php:24`, `routes/web.php:22`)
- **BR-09 — Default seeded credential `admin`/`admin`.** Created by `AdminTablesSeeder`; confirmed rotated in production, so the residual risk is only on a fresh seed / DB reset. 🟢 (`_reversa_sdd/permissions.md`, `_reversa_sdd/adrs/0009-authentication-only-access-control.md`)
- **BR-10 — Dead framework auth stack is intentional.** `app/Http/Controllers/Auth/{Login,Register,ForgotPassword,ResetPassword}Controller.php` and the `web` guard → `App\User` are stock Laravel scaffolding that is never wired (`App\User` does not exist, no `Auth::routes()`). It is dead by design and must NOT be reimplemented. 🟢 (`config/auth.php:71`, `app/Http/Controllers/Auth/*`, `_reversa_sdd/code-analysis.md#module-auth`)

## Functional Requirements

| ID | Requirement | Priority | Acceptance Criterion |
|----|-------------|----------|----------------------|
| RF-01 | Serve `GET auth/login` showing the admin login page to anonymous users. | Must | Anonymous request returns the `admin::login` view with a `username`/`password` form. 🟢 |
| RF-02 | Authenticate `POST auth/login` with `username`+`password` against the `admin` guard. | Must | Valid credentials start a session and redirect to the intended/`/pos` page; invalid credentials return to the form with an error and preserved input. 🟢 |
| RF-03 | Require both `username` and `password`; validate before attempting login. | Must | Missing either field returns to the form with field errors, without a login attempt. 🟢 |
| RF-04 | Redirect an already-authenticated user away from the login form. | Should | `GET auth/login` while logged in issues a redirect to the post-login path. 🟢 |
| RF-05 | Serve `GET auth/logout` that logs the user out and invalidates the session, then redirects to the admin root. | Must | After logout the session is destroyed and the next protected request redirects to login. 🟢 |
| RF-06 | Protect every application route with the `admin` auth middleware. | Must | An anonymous request to any app route (e.g. `/pos`, `/orders`) is redirected to `auth/login`. 🟢 |
| RF-07 | Serve `GET auth/setting` — the self-service account form (`name`, `avatar`, `password`). | Should | Authenticated user sees a form pre-filled with their own record (username read-only). 🟢 |
| RF-08 | Handle `PUT auth/setting` — update the current user's own account; re-hash password only if changed. | Should | Submitting a new name/avatar/password persists to `admin_users`; unchanged password is not re-hashed; success shows a toast and redirects back to the setting page. 🟢 |
| RF-09 | Use one generic failed-login message regardless of failure cause. | Must | Wrong username and wrong password yield the identical error text. 🟢 |
| RF-10 | Regenerate the session on login and invalidate it on logout. | Should | Session identifier differs before/after login; session is empty after logout. 🟢 |

## Non-Functional Requirements

| Type | Inferred requirement | Evidence in code | Confidence |
|------|----------------------|------------------|------------|
| Security | All application routes require an authenticated admin session (`admin` guard). | `config/admin.php:29` (`middleware => ['web','admin']`), `routes/web.php:23-27` | 🟢 |
| Security | HTTPS is expected in production (secure cookies / secure admin). | `config/admin.php` `'secure' => env('ADMIN_SECURE', true)` | 🟢 |
| Security | Credentials stored as bcrypt hashes; never in plaintext. | `vendor/…/AuthController.php:129` | 🟢 |
| Security | CSRF protection on state-changing requests (POST login, PUT setting). | `app/Http/Middleware/VerifyCsrfToken.php` in the `web` group | 🟢 |
| Security | Session fixation mitigated (regenerate on login, invalidate on logout). | `vendor/…/AuthController.php:71,176` | 🟢 |
| Security | No user enumeration: identical message for unknown user vs wrong password. | `vendor/…/AuthController.php:57-59` | 🟢 |
| Availability | Session driver only (no external auth provider); auth is available whenever the app + DB are up. | `config/admin.php:53-57` (driver `session`) | 🟡 |

> Inferred from the code. Validate the HTTPS assumption (`ADMIN_SECURE`) and session driver with the operations team.

## Acceptance Criteria

```gherkin
Feature: Administrator authentication

  Scenario: Successful login
    Given an administrator account exists in admin_users
    And I am not authenticated
    When I POST auth/login with a valid username and password
    Then a session is started on the admin guard
    And the session id is regenerated
    And I am redirected to the intended page (defaulting to /pos)

  Scenario: Failed login with wrong password
    Given an administrator account exists
    When I POST auth/login with the correct username and a wrong password
    Then I am returned to the login form
    And I see the generic "credentials do not match" error
    And my username input is preserved
    And no session is started

  Scenario: Missing credentials
    Given I am on the login form
    When I POST auth/login without a password
    Then validation fails before any login attempt
    And I am returned to the form with a "password is required" error

  Scenario: Anonymous access to a protected route
    Given I am not authenticated
    When I request GET /pos
    Then I am redirected to auth/login

  Scenario: Logout
    Given I am authenticated
    When I request GET auth/logout
    Then my session is invalidated
    And I am redirected to the admin root
    And a subsequent request to a protected route redirects me to login

  Scenario: Change own password
    Given I am authenticated
    When I PUT auth/setting with a new password and matching confirmation
    Then the new password is bcrypt-hashed and stored
    And I see a success toast

  Scenario: Already authenticated hitting the login page
    Given I am authenticated
    When I request GET auth/login
    Then I am redirected to the post-login path instead of seeing the form
```

## Priority (MoSCoW)

| Requirement | MoSCoW | Rationale |
|-------------|--------|-----------|
| Login / credential verification (RF-01, RF-02, RF-03) | Must | The single entry point for the whole system; no fallback. |
| Route protection via `admin` guard (RF-06) | Must | Every other unit depends on this middleware being in place. |
| Logout + session invalidation (RF-05, RF-10) | Must | Required to end a session on shared terminals. |
| Generic failure message (RF-09) | Must | Security control against user enumeration. |
| Self-service account/password change (RF-07, RF-08) | Should | Important for credential rotation but not on the critical sales path. |
| Redirect already-authenticated user (RF-04) | Should | UX nicety; app still works without it. |
| RBAC role/permission enforcement | Won't | Present in tables but intentionally not enforced (single-operator shop, ADR-0009). |
| Framework `web`-guard / self-registration stack | Won't | Dead scaffolding; `App\User` missing, never routed. |

> Priority inferred from call frequency and position in the dependency chain (every unit sits behind `auth`).

## Code Traceability

| File | Function / Class | Coverage |
|------|------------------|----------|
| `routes/web.php:20` | `Admin::registerAuthRoutes()` invocation | 🟢 |
| `routes/web.php:22-27` | root→`/pos` redirect + protected route group middleware | 🟢 |
| `vendor/salipropham/laravel55-admin/src/Admin.php:232-259` | `registerAuthRoutes()` route definitions | 🟢 |
| `vendor/salipropham/laravel55-admin/src/Controllers/AuthController.php` | `getLogin/postLogin/getLogout/getSetting/putSetting/username/guard` | 🟢 |
| `config/admin.php:23-64` | route prefix/namespace/middleware + `admin` guard/provider | 🟢 |
| `app/Http/Middleware/RedirectIfAuthenticated.php` | `handle` (redirect authed → `/home`, dead path) | 🟢 |
| `app/Http/Controllers/Auth/*` | stock Laravel auth controllers (dead) | 🟢 |
| `config/auth.php:15-72` | default `web` guard → `App\User` (dead) | 🟢 |
