147 lines
8.1 KiB
Markdown
147 lines
8.1 KiB
Markdown
# PRD: Telegram Phone-Number Authentication
|
|
|
|
## Problem
|
|
|
|
The current Telegram integration treats Telegram as a *secondary identity layer*: a user must already hold an Amanat email-password (or Google OAuth) account before they can link their Telegram identity. This creates unnecessary friction for users who arrive from Telegram, have never heard of Amanat, and reasonably expect the Mini App to just work — as every other well-built Telegram Mini App does.
|
|
|
|
Telegram accounts are phone-number-verified by Telegram itself. When the backend successfully verifies Mini App `initData`, it has already established that the requester holds a specific Telegram account tied to a phone number. That is sufficient identity to create and authenticate an Amanat account without asking for an email or password.
|
|
|
|
## Goal
|
|
|
|
Allow users to sign in to Amanat using their Telegram identity as the primary credential — with zero separate signup step. This applies to:
|
|
|
|
1. **Telegram Mini App context** — `initData` is auto-available and already cryptographically verified.
|
|
2. **Telegram Login Widget** — the standard web widget that lets users authenticate via Telegram on any browser page; returns a signed payload verifiable against the bot token.
|
|
|
|
In both cases the user's phone-verification is owned by Telegram; Amanat trusts the signed assertion and does not re-verify the phone number itself.
|
|
|
|
## Platform assumptions
|
|
|
|
- Telegram Mini App `initData` is signed with HMAC-SHA-256 keyed on `HMAC-SHA-256("WebAppData", BOT_TOKEN)`. Server-side verification is already implemented in `telegramService.ts`.
|
|
- Telegram Login Widget returns `{id, first_name, last_name, username, photo_url, auth_date, hash}`. Hash is `HMAC-SHA-256(data_check_string, SHA256(BOT_TOKEN))`. The same bot token covers both flows.
|
|
- Telegram user IDs are stable, globally unique integers that do not change even if the user changes username or phone number.
|
|
- Telegram does not expose the raw phone number to third-party apps — the ID is the stable identity anchor.
|
|
|
|
Reference docs:
|
|
- Telegram Login Widget: https://core.telegram.org/widgets/login
|
|
- Mini Apps initData: https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
|
|
|
|
## What changes
|
|
|
|
### Backend
|
|
|
|
**New endpoint: `POST /auth/telegram`**
|
|
|
|
Accepts one of:
|
|
- `{ initData: string }` — from Mini App context
|
|
- `{ id, first_name, last_name, username, photo_url, auth_date, hash }` — from Login Widget
|
|
|
|
Steps:
|
|
1. Verify signature (reuse `verifyMiniAppInitData` for initData path; add `verifyTelegramLoginWidget` for widget path).
|
|
2. Extract `telegramUserId` (stable integer).
|
|
3. Look up `TelegramLink` by `telegramUserId`.
|
|
4. **If link found and active** → load the linked Amanat user → issue JWT + refresh token (same format as `POST /auth/login`).
|
|
5. **If no link found** → auto-provision a new Amanat user:
|
|
- `email`: `null` (nullable; add index exclusion for null values)
|
|
- `firstName` / `lastName`: from Telegram profile
|
|
- `username`: `tg_<telegramUserId>` as stable internal handle
|
|
- `role`: `buyer` (default; user can change)
|
|
- `isEmailVerified`: `false` — set a flag `telegramVerified: true` instead
|
|
- `status`: `active`
|
|
- `authProvider`: `telegram`
|
|
- Create `TelegramLink` record in the same transaction.
|
|
- Issue JWT + refresh token.
|
|
6. On success, always upsert `TelegramLink.lastSeenAt` and update name/username fields from latest Telegram profile.
|
|
7. Return: `{ token, refreshToken, user, isNewUser: boolean }` — `isNewUser: true` signals the frontend to show an onboarding nudge (optional email capture, preferred language, currency).
|
|
|
|
**User model changes:**
|
|
|
|
- `email`: make nullable (`type: String, sparse: true` — allows multiple null values in a unique sparse index)
|
|
- Add `telegramVerified: Boolean` (default `false`)
|
|
- Add `authProvider: 'email' | 'google' | 'telegram'` field
|
|
- Existing email-based users are unaffected; their `authProvider` defaults to `'email'`
|
|
|
|
**Rate limiting and security:**
|
|
|
|
- Apply the same replay protection already in `telegramService.ts` to this endpoint.
|
|
- Rate limit: 10 requests per IP per minute, 5 per Telegram user ID per minute.
|
|
- Log all auto-provisioning events as audit records.
|
|
- Reject `auth_date` older than `TELEGRAM_MINIAPP_MAX_AGE_MS` (already configurable).
|
|
|
|
**Blocked account handling:**
|
|
|
|
- If `TelegramLink.status === 'blocked'` → return 403 with `ACCOUNT_BLOCKED` code.
|
|
- If the linked Amanat user is suspended/deleted → return 403 with `ACCOUNT_SUSPENDED`.
|
|
|
|
### Frontend
|
|
|
|
**Mini App auto-login (inside Telegram):**
|
|
|
|
When `window.Telegram?.WebApp?.initData` is non-empty:
|
|
1. Skip the login page entirely.
|
|
2. POST `initData` to `/auth/telegram`.
|
|
3. Store the returned JWT in the existing auth context.
|
|
4. If `isNewUser === true`, show a lightweight onboarding overlay inside the Mini App to capture optional email and preferred settings before routing to the main app.
|
|
|
|
**Web login page — "Continue with Telegram" button:**
|
|
|
|
- Add a "Continue with Telegram" button alongside the existing Google button.
|
|
- Clicking it opens the Telegram Login Widget in a popup (`window.open` or inline script tag method).
|
|
- On callback, POST the widget payload to `/auth/telegram`.
|
|
- This works on any browser, not only inside Telegram.
|
|
|
|
**Auth types update:**
|
|
|
|
```typescript
|
|
// auth/types.ts additions
|
|
export interface User {
|
|
// ... existing fields ...
|
|
telegramVerified: boolean;
|
|
authProvider: 'email' | 'google' | 'telegram';
|
|
telegramUsername?: string;
|
|
}
|
|
```
|
|
|
|
**Onboarding nudge (post-Telegram-auth):**
|
|
|
|
New users created via Telegram auth should be shown a non-blocking screen:
|
|
- Optional: "Add an email for account recovery" (not required)
|
|
- Optional: preferred language and currency
|
|
- Skippable — routing to the main app without completing it is fine
|
|
|
|
## Non-goals
|
|
|
|
- Do not expose the user's raw phone number.
|
|
- Do not require email for Telegram-authenticated users.
|
|
- Do not use Telegram auth as a bypass for high-risk actions (release, payout address change, dispute resolution still require step-up confirmation as per task 5.6/5.8 policy).
|
|
- Do not auto-merge a Telegram-provisioned account with an existing email account unless the user explicitly initiates account linking from settings.
|
|
|
|
## Account merge and collision handling
|
|
|
|
| Scenario | Behavior |
|
|
|---|---|
|
|
| Telegram user arrives, no existing account | Auto-provision, create TelegramLink, return `isNewUser: true` |
|
|
| Telegram user arrives, TelegramLink already exists | Auth as linked user, update last-seen |
|
|
| Email user opens Mini App, not yet linked | Show "Link your Telegram" prompt (existing task 5.2 flow) — do NOT auto-merge |
|
|
| Two Telegram accounts try to link to same email user | Reject second; return 409 DUPLICATE_TELEGRAM_LINK |
|
|
| Same Telegram user tries to auth with two different app accounts | Impossible by design — TelegramLink.telegramUserId is unique |
|
|
|
|
## Acceptance criteria
|
|
|
|
1. A new Telegram user can complete authentication inside the Mini App without entering an email or password.
|
|
2. A returning Telegram user gets the same JWT session whether they authenticate via Mini App `initData` or the web Login Widget.
|
|
3. `POST /auth/telegram` rejects replayed `initData` within the existing replay window.
|
|
4. `POST /auth/telegram` rejects `auth_date` older than the configured max-age.
|
|
5. Blocked Telegram accounts receive a 403 response; they cannot circumvent it by unlinking and re-linking.
|
|
6. Auto-provisioned users have `authProvider: 'telegram'` and no email; existing email users are unaffected.
|
|
7. Admin UI (task 5.7) can distinguish `authProvider: telegram` users and shows `telegramVerified` status.
|
|
8. A Telegram-authed user who later adds an email can then also log in via email — both paths converge to the same user record.
|
|
9. High-risk actions still require the step-up policy regardless of auth provider.
|
|
10. The "Continue with Telegram" button is visible on the web login page in non-Mini-App contexts.
|
|
|
|
## Dependencies
|
|
|
|
- Task 5.2 (identity linking model) — TelegramLink model and verifyMiniAppInitData are already done; this task extends the auth path, not the linking model.
|
|
- Task 5.8 (security controls) — replay protection, rate limits, and audit logging from that task apply here too.
|
|
- Task 5.6 (high-risk action policy) — must not be weakened.
|