Files
veilix.app/docs/05-auth.md
T
andreypr207andClaude Opus 5.5 7e331c2c9d Этап 1a: фундамент
- Next 16.4 и Payload 3.91 в одном приложении, PostgreSQL с UUID,
  TypeScript 6, ESLint 9, Prettier, Vitest
- Payload: администраторы с ролями, multi-tenant по сайтам, локализация
  en/ru/de/fr/es, интерфейс админки ru/en, языки сайта (sites.locales)
- Коллекции sites, domains, currencies, countries, locations, tags,
  tariffs, payment-systems (manual), media и начальные данные (pnpm seed)
- Proxy: хост → домен, язык и валюта, внутренние маршруты по хосту;
  /admin и /api закрыты на доменах брендов
- Токены и темы брендов, оболочки сайта и кабинета, переключатели языка,
  валюты и темы, заглушки страниц
- /svc/health, юнит systemd veilix-dev.service
- Документация: решения D-022…D-024, команды в CLAUDE.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:08:05 +02:00

104 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 05. Вход, сессии, аккаунты
## 1. Аккаунты
1. Аккаунты раздельные по сайтам ([D-011](decisions/D-011-accounts.md)). Уникальность: (`site`, `email`) и (`site`, `telegramId`).
2. Один человек на двух сайтах — два разных пользователя с разными балансами и ключами.
3. Администраторы — отдельная коллекция `admins` с паролем, к пользователям отношения не имеют.
## 2. Вход по коду из письма
Одно поле email: если аккаунта нет — он создаётся, если есть — выполняется вход. Ответ всегда одинаковый, по нему нельзя узнать, есть ли аккаунт.
### 2.1 Запрос кода
1. Пользователь вводит email и проходит ALTCHA.
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 (этап 3)
### 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 или токен в памяти) — решаем на этапе 3.
### 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 (этап 1h) |
| `payload-token` | хост админки | сессия админки | вход администратора |
Тема (светлая, тёмная, как в системе) хранится в localStorage, как в макете, и применяется скриптом до отрисовки, без мигания.