# WP4 — Auth (login · register · forgot/reset password · guards)

**Repo:** FE only — `/home/moonui4/public_html/moon-erp` (branch `hazemdev4`)
**Depends on:** WP0 ✅ · WP1 ✅ `05744239` · WP2 ✅ `64742cfa` · WP3 (cart — **must be committed before this WP starts**; same repo, one writer at a time)
**Review:** code-reviewer · **[FIN]:** no · **Migration:** no

## Goal

Give the storefront real accounts: a customer can register, log in, stay logged in across reloads, recover a forgotten password, and log out. This is also what makes WP3's merge-on-login replay reachable from the UI for the first time, and what WP5's checkout depends on.

## Routes — verified against `Modules/WebStore/routes/public.php` on 2026-07-18

All under `POST /api/store/auth/`, all public (`throttle` applies):

| Endpoint | Purpose |
|---|---|
| `register` | create account — **returns a token immediately** |
| `login` | `username` (email **or** mobile) + `password` |
| `social-login` | out of scope for the MVP |
| `forgot-password` | starts the reset flow |
| **`verify-code`** | verify the emailed/SMS'd code |
| **`resend-code`** | resend it |
| `reset-password` | set the new password |

⚠️ **The plan and the KB both call these `verify-otp`. That is wrong** — the real route names are **`verify-code`** and **`resend-code`**. Verified in the route file. Do not copy the old names from any doc.

## Contract facts (verified — these override the theme and the analysis)

| # | Fact | Consequence |
|---|---|---|
| **A4** | **Registration has NO OTP step.** A token is issued immediately on register. The code flow belongs **only** to forgot-password. | The signup journey is one screen, not two. Much smaller than the plan implied |
| **A3** | The token is a **top-level `token` key, sibling of `data`** — not inside `data` | — |
| **A2** | Header is `Authorization: Bearer <token>` (the admin app's `X-Authorization` is only a host fallback) | WP0's interceptor already does this |
| **A11** | Register **requires `branch_id`**; `company_id` is derived server-side. **Never send `company_id`** | fetch `GET /branches` |
| **A7** | **No logout endpoint** — logout is a client-side token drop | WP1's header already wires this; make sure the cart decision from WP3 is honoured |
| **A10** | Three different 422 shapes; the error interceptor branches on `Array.isArray(body.errors)` | field-level errors must land **on the right form fields**, not just in a toast |

**Live data check (2026-07-18):** `GET /branches` returns exactly **one** branch — `id: 1, "Main Branch" / "الفرع الرئيسي"`. **OPEN-1's recommendation applies: auto-select when there is exactly one branch, and only render a picker when there is more than one.** Build both paths; the picker is simply not shown today.

## Theme

Read-only: `/tmp/claude-0/-home-moonui4-public-html/714ac5d7-2ce4-45d7-bc22-9a935e3e9336/scratchpad/theme/theme/`
Pages: `login.html` · `register.html` · `forgot-password.html` · `verify-otp.html` · `reset-password.html`

Two theme problems you must handle:

- **T6: the theme's register form has no confirm-password field**, but the API requires `password_confirmation`. Add it — **the smallest possible visual addition**, matching the theme's existing input styling exactly.
- **T10: branding is inconsistent** — the header says `صيدليتي`, these auth pages say `الصيدلاني`/`صيدليات`. Use the existing `BRAND.NAME` i18n key everywhere (WP1 created it). ⚠️ Its **English value `Saydaliyti` is an earlier agent's own transliteration and has never been approved** — leave it as-is, do not invent a second variant.

The theme's login form uses `username + branch`, which **matches** the backend — no structural redesign needed.

## 🔴 Honesty constraint — the forgot-password flow is not really usable

`otp_bypass_enabled` defaults to **`true`** with bypass code **`123456`**, and **the code is only written to the log — there is no SMS sender**. So on a real install, "forgot password" cannot complete for an actual customer.

Build the screens correctly against the API, but **do not imply delivery that does not happen**. Do not write copy like «تم إرسال الكود إلى هاتفك» unless the backend genuinely sends it. Use neutral wording, and report exactly what you observed the backend do. Both the bypass and the missing sender are already recorded as pre-launch deferrals — **do not "fix" them here, and do not paper over them in the UI.**

## Guards & session

- A route guard for authenticated-only routes. Nothing in the MVP strictly needs it yet except checkout (WP5) — build it and apply it where it belongs, don't over-apply it to browsing (**catalog must stay public**).
- A "already logged in" redirect away from login/register.
- Token persists across reload (WP0's `auth.state` already does this — verify it, don't rebuild it).
- **401 → clear the session and redirect to login** (WP0's error interceptor). Verify it end to end with a deliberately invalidated token, and make sure it cannot fire a redirect loop on the login page itself.
- Preserve the intended destination: a user sent to login from checkout should return to checkout after logging in.

## Acceptance criteria

- [ ] `npx ng build storefront` green **and** `npx ng build` (admin) still green. Nothing under `src/` or in `angular.json`'s `moon-erp` block touched.
- [ ] Register → account created on the **live** API, token stored, header flips to the account variant. Paste the observed response shape (proving the top-level `token`).
- [ ] Login with **both** an email and a mobile as `username`.
- [ ] Field-level validation errors from a 422 render **on the correct fields** — show a real example (e.g. duplicate mobile, mismatched confirmation).
- [ ] Login persists across a full reload; logout clears it and returns to the logged-out header.
- [ ] An invalid/expired token triggers exactly one redirect to login, with no loop.
- [ ] **WP3's merge-on-login replay now works through the real UI** — guest adds items → registers/logs in → items appear in the server cart. This is the first true end-to-end test of that path; report what actually happened, including anything that broke.
- [ ] Forgot-password screens work against the API, and the copy does not promise delivery that does not occur. State plainly what the backend did (e.g. code visible only in the log).
- [ ] Branch auto-selects because only one exists; the picker path is implemented and reachable if a second branch appears.
- [ ] Zero hardcoded user-facing strings — grep your diff and paste the result. `I18N_VERSION` bumped.

## Out of scope

No checkout/addresses/shipping (WP5). No account pages, wishlist, prescriptions, loyalty, notifications. No social login. Do not touch the backend, `src/`, or `angular.json`'s `moon-erp` block. Do not change `otp_bypass_enabled` or add an SMS sender.

⛔ **Do not push, deploy, run `/fullpush`, or merge to `main`.**

## Commit

Conventional commit on `hazemdev4` + **one bilingual bullet** under `## [Unreleased]` in `/home/moonui4/moon-erp-be/docs/moonstack/CHANGELOG.md`, then verify the file is still well-formed. Keep the bullet honest about the password-reset limitation rather than overselling it.

## Report back

Report what you **verified with evidence** versus what you assumed, and flag anything in this brief that contradicts what you find. **Every WP so far has found real errors in its own brief — including route names I had wrong until I checked.** Treat these tables as claims to verify.
