merge: B2B into main
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -115,6 +115,33 @@ Storage: `localStorage['ed25519AdminToken']` (access), `localStorage['ed25519Adm
|
|||||||
|
|
||||||
**Open decision (business, not technical — ask a human):** whether Mechanism A is retired outright in favor of Mechanism B at cutover, or both run in parallel gated by role/tenant config.
|
**Open decision (business, not technical — ask a human):** whether Mechanism A is retired outright in favor of Mechanism B at cutover, or both run in parallel gated by role/tenant config.
|
||||||
|
|
||||||
|
### 2c. Email/phone OTP login — customer (NOT IMPLEMENTED, proposed)
|
||||||
|
|
||||||
|
**Gap:** customer storefront login/checkout requires Telegram (Mechanism A) — shoppers without Telegram have no way to identify themselves. Raised as a real usability problem, not a hypothetical.
|
||||||
|
|
||||||
|
**Ask:** a third, independent auth mechanism (coexists with 2a/2b, replaces neither):
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /auth/otp/request
|
||||||
|
Body: { "identifier": "user@example.com" } // or E.164 phone, e.g. "+79991234567"
|
||||||
|
Response: { "requestId": "...", "expiresAt": "2026-08-15T10:15:00Z" }
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /auth/otp/verify
|
||||||
|
Body: { "requestId": "...", "code": "482913" }
|
||||||
|
Response (on success): {
|
||||||
|
"sessionId": "...", "userId": 8823771, "username": null,
|
||||||
|
"displayName": "user@example.com", "active": true, "expires": "2026-08-15T11:15:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Bootstrap — the runtime config document
|
## 3. Bootstrap — the runtime config document
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Email/phone customer login (OTP) — design
|
||||||
|
|
||||||
|
**Status:** Approved
|
||||||
|
**Date:** 2026-08-15
|
||||||
|
**Related backlog item:** #4 (Telegram-only identification)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Customer storefront login/checkout requires Telegram today (`src/app/services/auth.service.ts`, `TelegramSessionApiService`) — shoppers without Telegram have no way to identify themselves. User asked for email/phone as an alternative.
|
||||||
|
|
||||||
|
Backend has zero email/phone/OTP/password infrastructure — only Telegram session polling exists (`BACKEND-API-REFERENCE.md` §2a). Building real authentication client-side is not possible; this is fundamentally a backend feature. Per user's explicit choice, this round produces the design + backend spec only — no client UI/code, since there is no real backend to build a working feature against yet (mirrors the currency-FX and admin-notifications backend-dependency pattern already documented this session).
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
**Mechanism: OTP code (email or SMS)**, chosen over magic link (email-only, extra click) and password (heaviest backend lift — storage, hashing, reset flow). Passwordless matches the feel of the existing Telegram QR flow.
|
||||||
|
|
||||||
|
**A third, independent auth mechanism** — coexists with Telegram QR (§2a) and admin Ed25519 (§2b, still unimplemented) exactly the way those two already coexist. Does not replace or modify either.
|
||||||
|
|
||||||
|
### Proposed backend endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /auth/otp/request
|
||||||
|
Body: { "identifier": "user@example.com" } // or E.164 phone: "+79991234567"
|
||||||
|
Response: { "requestId": "...", "expiresAt": "2026-08-15T10:15:00Z" }
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /auth/otp/verify
|
||||||
|
Body: { "requestId": "...", "code": "482913" }
|
||||||
|
Response (on success): {
|
||||||
|
"sessionId": "...",
|
||||||
|
"userId": 8823771,
|
||||||
|
"username": null,
|
||||||
|
"displayName": "user@example.com",
|
||||||
|
"active": true,
|
||||||
|
"expires": "2026-08-15T11:15:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The success response is shaped identically to the existing `AuthSession` model (`src/app/models/auth.model.ts`) — `sessionId`, `userId`, `username`, `displayName`, `active`, `expires`. This is deliberate: every downstream consumer (auth guards, session signals, cart/checkout) already works against `AuthSession` regardless of which mechanism produced it, so wiring this in later requires no changes to guards or session state — only a new "request/verify" UI flow that ends by populating the same session shape Telegram QR already produces.
|
||||||
|
|
||||||
|
**Rate limiting / expiry (backend-enforced, not left implicit):**
|
||||||
|
- Resend cooldown: 60s between `POST /auth/otp/request` calls for the same identifier.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### Future client UI (not built this round)
|
||||||
|
|
||||||
|
A "Login with email or phone" option next to the existing Telegram QR button: identifier entry → code entry → session established. Deferred until the backend endpoints above exist — no client code to write against a 404.
|
||||||
|
|
||||||
|
## Backend doc update
|
||||||
|
|
||||||
|
New `BACKEND-API-REFERENCE.md` §2c ("Email/phone OTP login — customer (NOT IMPLEMENTED)"), following the same Gap/Ask format as the existing §12.x entries, documenting the two endpoints, the response-shape compatibility requirement, and the rate-limit/expiry asks above.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Admin backoffice login — user confirmed this round is customer-storefront only (item 4 was split into two potential specs during brainstorming; admin auth is a separate future spec if wanted).
|
||||||
|
- Magic link and password mechanisms — considered, OTP chosen.
|
||||||
|
- Any client-side UI or session-handling code — explicitly deferred; nothing to build against a non-existent backend.
|
||||||
|
- Account merging (e.g. a shopper who later links Telegram + email to the same identity) — not raised, not designed.
|
||||||
Reference in New Issue
Block a user