Files
docs/features/authentication.md
T

83 lines
7.6 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.
# Authentication
Текущее объяснение локальной identity и сессий WealthLedger. Auth связывает
[User](../architecture/domain-glossary.md#user) с browser session; доступ к
брокеру принадлежит отдельному [T-Bank connection](tbank-connection.md).
Credentials и ротация принадлежат backend, auth state и route decisions — frontend.
## User flow
Пользователь регистрируется или входит по email и паролю. Регистрация сразу создаёт первую сессию. Внешние пробелы email удаляются, буквы приводятся к нижнему регистру. Backend требует пароль не короче 12 символов и не длиннее 72 UTF-8 байтов.
**Требует ручного решения:** сохранённое требование регистрации и проверка
в форме frontend считают нижнюю границу в UTF-8 байтах, а backend — в символах.
Не-ASCII пароль может пройти форму и получить отказ API. Это описание
наблюдаемого рассогласования, а не принятое изменение password policy;
исправление требует отдельного согласованного security решения и HTTP-проверки.
Access token живёт 15 минут только в памяти браузера. Refresh token живёт 7 дней в cookie `refresh_token` с `httpOnly` и `SameSite=Lax`; при восстановлении сессии он заменяется. После перезагрузки приложение восстанавливает access token через refresh-cookie до показа защищённых маршрутов. Пока результат восстановления неизвестен, frontend показывает нейтральный structural skeleton каркаса без public или protected route content; после завершения bootstrap сохраняются прежние decisions и redirects.
Все маршруты, кроме `/login` и `/register`, требуют сессию. После входа пользователь возвращается на исходный внутренний маршрут, если он был. Logout доступен в настройках, очищает локальную сессию независимо от состояния сети и отзывает серверную сессию при доступном refresh-cookie.
## Восстановление сессии
```mermaid
sequenceDiagram
participant F as React SPA
participant B as NestJS Auth
participant D as SQLite Session
Note over F: Reload: access token в памяти отсутствует
F->>B: Refresh с httpOnly cookie
B->>D: Проверить Session и текущий hash token
alt Действительная сессия, ротация выиграна
B->>D: Атомарно заменить hash и срок
B-->>F: Access token, профиль, новая refresh-cookie
F->>B: Protected request с Bearer
else Cookie нет, token истёк или уже заменён
B-->>F: 401 и очистка cookie
Note over F: Готовое неавторизованное состояние
end
```
В одном экземпляре frontend одновременные refresh используют один незавершённый запрос;
между вкладками используется Web Locks, когда browser его поддерживает.
Backend всё равно защищает ротацию атомарно: browser coordination не заменяет
server-side invariant. Обычный protected запрос с `401 UNAUTHORIZED` проходит
refresh и один повтор через общий transport; auth endpoints из этого цикла
исключены. Сам по себе истёкший access token не запускает фоновый таймер refresh.
## API
Endpoints, статусы и формы публичных ответов определяет backend через OpenAPI;
проверка snapshot описана в [contract workflow](../development/runtime-openapi-verification.md).
Причины хранения и сроков tokens — [ADR 006](../adr/006-auth-session-policy.md).
Refresh-cookie ограничена auth API. Признак Secure зависит от режима окружения:
локальный HTTP предполагает development mode, см.
[локальное окружение](../development/local-workspace.md).
Форма ошибок и граница её HTTP-проверки описаны у владельца
[HTTP API](../architecture/http-api.md#error-contract).
HTTP- и транспортная ошибка refresh одинаково завершают cold start готовым неавторизованным состоянием; при refresh после protected `401` frontend очищает in-memory session и React Query cache, сохраняя исходный `401` для существующего login flow.
## Limitations
MVP не включает подтверждение email, восстановление или смену пароля, смену email, удаление аккаунта, список устройств, logout всех устройств или rate limit попыток входа. Access token может оставаться действительным до 15 минут после logout.
Если несколько клиентов одновременно предъявляют один действительный refresh token, сервер завершает одну ротацию успешно, а остальные запросы получают `401`; сессия победившего запроса остаётся действительной. Уже заменённый refresh token также даёт `401`, но сам по себе не отзывает сессию: MVP не хранит историю token или grace-window, чтобы отличить конкурентный запрос от replay.
Это ограничение и его rationale приняты в
[ADR 009](../adr/009-concurrent-refresh-rotation-policy.md). Refresh token имеет
server-side срок, но cookie не задаёт собственные `maxAge`/`expires`: сохранение
cookie после закрытия браузера не является обещанием семидневного входа.
## Ответственность и проверка
Backend проверяет credentials и tokens, хранит состояние Session и атомарно
выполняет ротацию. Frontend управляет состоянием входа, восстановлением после
reload и доступностью экранов; общий HTTP-транспорт передаёт Bearer token
и выполняет описанный выше refresh/retry.
Проверки охватывают конкурентную ротацию, состояние входа в браузере и поведение
транспорта. Изолированные проверки этих частей не заменяют полный HTTP-поток
и реальную базу при проверке нового изменения безопасности.