2 Commits

Author SHA1 Message Date
sdarbinyan
65ce2ef23c merge: B2B into main
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 18:53:03 +04:00
sdarbinyan
e818dc6fc0 docs: add admin method toggle + error handling to email/phone login spec
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 18:52:52 +04:00
2 changed files with 35 additions and 2 deletions

View File

@@ -138,7 +138,19 @@ Response (on success): {
The success response must be shaped identically to the existing `AuthSession` (`sessionId, userId, username, displayName, active, expires`, §2a's client model) — this lets every existing downstream consumer (guards, session signals, cart/checkout) work unchanged regardless of which mechanism produced the session. The success response must be shaped identically to the existing `AuthSession` (`sessionId, userId, username, displayName, active, expires`, §2a's client model) — this lets every existing downstream consumer (guards, session signals, cart/checkout) work unchanged regardless of which mechanism produced the session.
Rate limiting/expiry, explicit so nothing is left to guesswork: 60s resend cooldown per identifier between `/request` calls; code expires 10 minutes after issuance; `requestId` is single-use (a `/verify` call consumes it on success or failure — a fresh `/request` is needed after either). Rate limiting/expiry, explicit so nothing is left to guesswork: 60s resend cooldown per identifier between `/request` calls; code expires 10 minutes after issuance; `requestId` allows up to 5 verify attempts before it's invalidated (consumed on success, on the 5th wrong attempt, or on expiry) — not single-use-per-attempt, so one mistyped digit doesn't force a full 60s wait for a new code.
**Error responses must use the existing envelope** (§5), with these codes on `/verify` (the client maps each to distinct UX — see the design doc):
| `error.code` | HTTP status | Meaning |
|---|---|---|
| `VALIDATION_FAILED` | 422 | Malformed identifier (`error.details[0]` names the field). |
| `RATE_LIMITED` | 429 | Resend cooldown not yet elapsed. |
| `CODE_EXPIRED` | 410 | 10-minute window passed. |
| `CODE_INVALID` | 401 | Wrong code, attempts remain on this `requestId`. |
| `REQUEST_NOT_FOUND` | 404 | `requestId` unknown, exhausted (5 wrong attempts), or expired. |
Admin can toggle which login methods (Telegram/Email/Phone) are shown to shoppers — this is a client-only UI gate (Admin Settings, `LocalStorageService`-persisted), not a backend flag; all endpoints stay available regardless of the toggle state.
See `docs/superpowers/specs/2026-08-15-email-phone-login-design.md` for the full design. No client code exists yet — nothing to build against a 404. See `docs/superpowers/specs/2026-08-15-email-phone-login-design.md` for the full design. No client code exists yet — nothing to build against a 404.

View File

@@ -42,7 +42,28 @@ The success response is shaped identically to the existing `AuthSession` model (
**Rate limiting / expiry (backend-enforced, not left implicit):** **Rate limiting / expiry (backend-enforced, not left implicit):**
- Resend cooldown: 60s between `POST /auth/otp/request` calls for the same identifier. - Resend cooldown: 60s between `POST /auth/otp/request` calls for the same identifier.
- Code expiry: 10 minutes from issuance. - Code expiry: 10 minutes from issuance.
- `requestId` is single-use — a verify call consumes it regardless of success/failure; a new request is needed after either a wrong code or expiry. - `requestId` allows up to 5 verify attempts before it's invalidated — consumed on success, on the 5th wrong attempt, or on expiry, whichever comes first. (Revised from an earlier single-use-per-attempt draft: burning the whole request on one typo is bad UX — a shopper should be able to correct a mistyped digit without waiting out a fresh 60s cooldown.)
### Admin-configurable login methods
Admin can enable/disable each login method independently — Telegram QR, Email OTP, Phone OTP — via three checkboxes in Admin Settings, same section/pattern as the existing currency-rates and notification-interval settings (`admin-settings-page.component.ts`, `LocalStorageService`-persisted signal).
- Default: all three enabled — a settings change must never silently lock shoppers out.
- A new `AuthMethodsService` (or an extension of the existing settings service) exposes `enabledMethods: Signal<('telegram' | 'email' | 'phone')[]>`. The storefront login screen reads it and only renders buttons for enabled methods; if exactly one is enabled, skip the method-picker screen entirely and go straight to it.
- Purely a client-side UI gate — the backend OTP endpoints stay unconditionally available; disabling "Email OTP" in admin just hides the button, it doesn't need a corresponding backend flag. (Same category of client-only gate as the existing `adminAuthGuard`/permission checks — real enforcement, if ever needed, would be a separate backend concern.)
### Error handling
The codebase has an established error envelope (`BACKEND-API-REFERENCE.md` §5: `error.code`, `error.message`, `error.status`, `error.details`) explicitly flagged as "recommended for new endpoints, not wired anywhere yet." Since the OTP endpoints are new, this is the natural first real adopter — every response maps to a specific code, not just an HTTP status:
| `error.code` | HTTP status | UX |
|---|---|---|
| `VALIDATION_FAILED` | 422 | Inline field error under the identifier input, sourced from `error.details[0].message` — same pattern the client already uses for local validation errors (`cart.component.ts`'s email/phone inline errors), so a 422 slots into the existing inline-error UI without inventing a second display mechanism. |
| `RATE_LIMITED` | 429 | "Too many attempts — try again in Ns," countdown derived from `error.details`/`Retry-After` if present, otherwise a flat 60s. Resend button stays disabled until the countdown ends. |
| `CODE_EXPIRED` | 410 | "This code expired — request a new one." Auto-focuses/enables the resend action; does not silently re-send. |
| `CODE_INVALID` | 401 | "Wrong code, try again" — stays on the code-entry screen (does not consume the whole flow; see the 5-attempt allowance above). Shows the remaining-attempts count once ≤2 remain. |
| `REQUEST_NOT_FOUND` | 404 | `requestId` unknown/already invalidated (5 wrong attempts, expiry, or a stale reload) — "This login attempt is no longer valid, start again," returns to the identifier-entry step. |
| Anything else / network error / 5xx | — | Generic fallback: "Something went wrong. Try again, or use a different login method" — the second half of that sentence is a real, populated action, not filler text: it surfaces whichever other methods are currently enabled per the admin toggle above (e.g. falls back to the Telegram QR button), not just a dead-end retry link. |
**Identifier validation:** email vs. phone format is auto-detected client-side. Extract the validation logic already written inline in `cart.component.ts` (`validateEmail`/`validatePhone`, currently only used for post-purchase contact capture) into a shared utility rather than duplicating it when the client UI is eventually built — the same email/phone shape-checking applies to both use cases. **Identifier validation:** email vs. phone format is auto-detected client-side. Extract the validation logic already written inline in `cart.component.ts` (`validateEmail`/`validatePhone`, currently only used for post-purchase contact capture) into a shared utility rather than duplicating it when the client UI is eventually built — the same email/phone shape-checking applies to both use cases.