- Новый порядок этапов (D-026): фаза 1 — концепт на dev без внешних сервисов (1b сайт, 1c вход, 1d ключи, 1e счёт и демо-оплата, 1f демо-выдача, 1g продления, 1h рефералы); фаза 2 — Telegram; фаза 3 — ALTCHA, ЮKassa, почта, Twenty, Chatwoot, Umami, выдача через API и публичный запуск; фаза 4 — звёзды и эквайринги. - Решения: демо-оплата по списку IP (D-027), выдача ключей через адаптер manual/demo/api (D-028), страницы сайта из блоков Payload (D-029), пересборка через revalidatePath (D-030), RU-адрес у mass (D-031), счётчик свободных серверов (D-032), переводы на 5 языков в каждом этапе (D-033). - Тексты описывают работающий продукт; Вейликс — выделенный IP на общем сервере; Telegram поддержки — @veilix_support; сроки ответа не обещаем; VPN-серверы не ведут журналов. - Ссылки на этапы во всех документах, Q-12 решён (ЮKassa для RUB), R-19 (библиотеки для headless Chromium) сделано. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
11 KiB
Markdown
104 lines
11 KiB
Markdown
# 05. Вход, сессии, аккаунты
|
||
|
||
## 1. Аккаунты
|
||
|
||
1. Аккаунты раздельные по сайтам ([D-011](decisions/D-011-accounts.md)). Уникальность: (`site`, `email`) и (`site`, `telegramId`).
|
||
2. Один человек на двух сайтах — два разных пользователя с разными балансами и ключами.
|
||
3. Администраторы — отдельная коллекция `admins` с паролем, к пользователям отношения не имеют.
|
||
|
||
## 2. Вход по коду из письма
|
||
|
||
Одно поле email: если аккаунта нет — он создаётся, если есть — выполняется вход. Ответ всегда одинаковый, по нему нельзя узнать, есть ли аккаунт.
|
||
|
||
### 2.1 Запрос кода
|
||
|
||
1. Пользователь вводит email и проходит ALTCHA (с этапа 3a).
|
||
2. Сервер нормализует email (обрезает пробелы, переводит в нижний регистр) и проверяет лимиты:
|
||
a. по email — не чаще раза в 60 секунд и не больше 5 кодов в час;
|
||
b. по IP — не больше 20 запросов в час.
|
||
3. Прошлые неиспользованные коды для этого email и сайта аннулируются.
|
||
4. Создаётся код: 6 цифр, действует 10 минут. В базе хранится только HMAC-SHA256 (секрет из `.env`, сайт, email, код).
|
||
5. Письмо `login_code` на языке текущей страницы. Если в настройках почты включено «Записывать коды входа в журнал», код пишется в журнал писем (раздел 4).
|
||
6. Ответ: «Код отправлен на {email}. Он действует 10 минут». Кнопка «Отправить ещё раз» — через 60 секунд, «Другая почта» — назад.
|
||
|
||
### 2.2 Проверка кода
|
||
|
||
1. Берётся последний действующий код для email и сайта. Счётчик попыток +1; после 5 неудачных код аннулируется.
|
||
2. Сравнение — за постоянное время.
|
||
3. Код верный:
|
||
a. найти пользователя (сайт, email) или создать: `registeredVia = email`, язык — текущий, валюта — выбранная (Q-04), реферальная привязка из cookie `vx_ref`, хэш IP регистрации;
|
||
b. пользователь `blocked` — отказ: «Аккаунт заблокирован, напишите в поддержку»;
|
||
c. проставить `emailVerifiedAt` и `lastLoginAt`, создать сессию, вернуть на страницу, с которой пришёл (кабинет или оформление заказа).
|
||
|
||
## 3. Сессии
|
||
|
||
1. Cookie `vx_session`: случайные 32 байта, только для хоста кабинета (`my.veilix.app` и т.д., без атрибута Domain), `HttpOnly`, `Secure`, `SameSite=Lax`.
|
||
2. В базе (`customer-sessions`) — только SHA-256 токена. Срок — 30 дней, продлевается при активности (не чаще раза в сутки).
|
||
3. «Выйти» отзывает текущую сессию, «Выйти на всех устройствах» — все.
|
||
4. Payload: коллекция `customers` — коллекция авторизации с `disableLocalStrategy: true` и своей стратегией. Стратегия читает cookie, находит сессию и возвращает пользователя. Поэтому правила доступа Payload работают и для пользователей.
|
||
5. Защита от CSRF: `SameSite=Lax`; server actions Next.js сверяют Origin и Host; свои POST-эндпоинты проверяют Origin.
|
||
6. Cookie хостовые, поэтому dev и prod не пересекаются: `dev.my.veilix.app` и `my.veilix.app` не видят cookie друг друга.
|
||
|
||
## 4. Коды в журнале для отладки
|
||
|
||
1. Глобальная настройка «Почта» → «Записывать коды входа в журнал».
|
||
2. Включено: в записи журнала писем видно поле `debugCode` с кодом открытым текстом. В dev-логе процесса код тоже печатается.
|
||
3. Видят только роли owner и admin. Пока настройка включена, в админке висит предупреждение. Записи с кодом удаляются через сутки.
|
||
4. Вместе с транспортом «только журнал» (письма не отправляются, только пишутся в журнал) это позволяет тестировать вход без настроенной почты.
|
||
5. В prod по умолчанию выключено: с кодами из журнала можно войти в любой аккаунт.
|
||
|
||
## 5. Оформление заказа
|
||
|
||
Шаг «Почта» на странице заказа — тот же вход по коду. После подтверждения пользователь остаётся на шаге «Оплата».
|
||
|
||
## 6. Telegram (этапы 2a–2c)
|
||
|
||
### 6.1 Вход из MiniApp
|
||
|
||
1. MiniApp передаёт серверу `initData` от Telegram.
|
||
2. Сервер проверяет подпись: HMAC-SHA256 с ключом `HMAC_SHA256("WebAppData", токен основного бота)`, `auth_date` не старше суток.
|
||
3. Ищем пользователя (сайт, telegramId). Нет — создаём: `registeredVia = telegram`, язык — из `language_code` Telegram (если он среди включённых), валюта — по умолчанию домена.
|
||
4. Сессия `kind = miniapp`. Как её хранить (cookie внутри WebView Telegram или токен в памяти) — решаем на этапе 2a.
|
||
|
||
### 6.2 Привязка почты из Telegram
|
||
|
||
1. Пользователь вводит email в боте или MiniApp и получает код с назначением `link_email`.
|
||
2. Код верный — три случая:
|
||
a. на этом сайте нет пользователя с такой почтой — почта привязывается, теперь работает вход на сайте по коду;
|
||
b. есть пользователь с этой почтой без Telegram — аккаунты объединяются (раздел 6.4);
|
||
c. у пользователя с этой почтой привязан другой Telegram — автоматически не объединяем: «Эта почта привязана к другому Telegram-аккаунту, напишите в поддержку».
|
||
|
||
### 6.3 Привязка Telegram с сайта и вход через Telegram
|
||
|
||
1. «Подключить Telegram» в кабинете создаёт одноразовый токен (10 минут) и открывает `t.me/<основной бот>?start=link_<токен>`. Бот проверяет токен и привязывает Telegram к аккаунту. Если этот Telegram уже у другого пользователя сайта — правила раздела 6.4.
|
||
2. «Войти через Telegram» на сайте — официальный виджет Telegram Login. Для основного бота в BotFather указывается домен кабинета. Нет пользователя с этим Telegram — создаётся новый.
|
||
|
||
### 6.4 Объединение аккаунтов (в пределах сайта)
|
||
|
||
1. Основным остаётся аккаунт с почтой, к нему переходит Telegram второго.
|
||
2. Ключи, заказы, платежи, сохранённые карты, заявки переназначаются на основной аккаунт.
|
||
3. Баланс переносится парой движений `adjustment` с комментарием «Объединение аккаунтов»: минус на втором, плюс на основном. Сами движения не меняются.
|
||
4. Реферальная привязка: если у обоих есть пригласивший — остаётся более ранний. Реферальный код — основного аккаунта.
|
||
5. Второй аккаунт блокируется со ссылкой «объединён с …». Всё пишется в журнал действий.
|
||
6. Конфликты переводят объединение в ручной режим, его делает администратор кнопкой «Объединить аккаунты»: разные валюты при ненулевых балансах, у обоих привязан Telegram.
|
||
|
||
## 7. Администраторы
|
||
|
||
1. Стандартная авторизация Payload: email и пароль, cookie только на хосте админки.
|
||
2. Админка открывается только на `plc.veilix.cc` и `dev.plc.veilix.cc`; nginx пускает туда только с разрешённых IP.
|
||
3. 5 неудачных попыток — блокировка на 15 минут.
|
||
4. Первый администратор каждого окружения создаётся на экране первого входа Payload и автоматически получает роль owner.
|
||
|
||
## 8. Cookies
|
||
|
||
| Cookie | Где | Срок | Зачем |
|
||
|---|---|---|---|
|
||
| `vx_session` | хост кабинета | 30 дней | сессия пользователя |
|
||
| `vx_ref` | хост кабинета | 60 дней | реферальный код |
|
||
| `NEXT_LOCALE` | каждый хост | 1 год | выбранный язык |
|
||
| `vx_currency` | хост сайта и кабинета | 1 год | выбранная валюта (у вошедшего — валюта аккаунта) |
|
||
| `vx_consent` | хост сайта и кабинета | 1 год | согласие на cookies (этап 3f) |
|
||
| `payload-token` | хост админки | сессия админки | вход администратора |
|
||
|
||
Тема (светлая, тёмная, как в системе) хранится в localStorage, как в макете, и применяется скриптом до отрисовки, без мигания.
|