17 KiB
user-authentication Specification
Purpose
Позволяет пользователю создать и восстановить личную сессию, чтобы его данные и будущие подключения к брокеру были доступны только ему.
Requirements
Requirement: Регистрация пользователя
Система SHALL позволять зарегистрировать User по уникальному нормализованному email и паролю. Email MUST нормализоваться удалением внешних пробелов и приведением к нижнему регистру; он MUST содержать не более 254 символов, ровно один @, непустые local и domain части и не содержать пробельных или управляющих символов. Пароль MUST передаваться без изменения, содержать от 12 до 72 UTF-8 байтов и не иметь требований к составу символов. Успешная регистрация MUST создать первую Session и вернуть профиль { id, email } и access token.
Scenario: Успешная регистрация
- WHEN пользователь отправляет новый email и валидный пароль
- THEN система создаёт User и Session, устанавливает refresh-cookie и возвращает access token с нормализованным профилем
Scenario: Нормализация email при регистрации
- WHEN пользователь регистрирует
Anna@Example.com - THEN система сохраняет и возвращает email
anna@example.com
Scenario: Повторная регистрация
- WHEN пользователь регистрирует email, уже принадлежащий User после нормализации
- THEN система отвечает
409Problem Details с codeEMAIL_ALREADY_REGISTERED
Scenario: Конкурентная регистрация
- WHEN два запроса одновременно регистрируют один нормализованный email
- THEN ровно один запрос создаёт User и Session, а второй отвечает
409Problem Details с codeEMAIL_ALREADY_REGISTERED
Scenario: Невалидные регистрационные данные
- WHEN пользователь отправляет email или пароль, не соответствующий правилам регистрации
- THEN система отвечает
400Problem Details с codeVALIDATION_ERRORи указателями на невалидные поля
Requirement: Вход пользователя
Система SHALL позволять User начать новую независимую Session по нормализованному email и паролю. Успешный вход MUST вернуть { id, email }, access token и установить refresh-cookie. Неверный пароль и неизвестный email MUST иметь одинаковый ответ, не раскрывающий наличие User.
Scenario: Успешный вход
- WHEN пользователь отправляет зарегистрированный email и правильный пароль
- THEN система создаёт новую Session, устанавливает refresh-cookie и возвращает access token с профилем
Scenario: Неверные учётные данные
- WHEN пользователь отправляет неизвестный email или неверный пароль
- THEN система отвечает
401Problem Details с codeINVALID_CREDENTIALS
Requirement: Access и refresh сессии
Система SHALL выдавать access token сроком 15 минут и refresh token сроком 7 дней. Access token MUST передаваться клиенту только в ответе API и использоваться как Bearer token. Refresh token MUST передаваться только в httpOnly, SameSite=Lax, host-only cookie refresh_token с path /api/v1/auth; в HTTPS-среде cookie MUST иметь атрибут Secure. Каждый новый refresh token MUST иметь уникальный JWT ID и ротироваться при успешном обновлении Session; Session MUST оставаться активной 7 дней после последнего успешного обновления. Одновременные Session одного User MUST быть независимы и не иметь числового лимита.
Scenario: Успешное восстановление сессии
- WHEN клиент отправляет действительный refresh-cookie
- THEN система заменяет refresh-cookie и возвращает новый access token и профиль Session
Scenario: Конкурентное восстановление сессии
- WHEN два запроса одновременно отправляют один действительный refresh-cookie одной Session
- THEN ровно один запрос заменяет refresh-cookie и возвращает новый access token и профиль, второй отвечает
401Problem Details с codeUNAUTHORIZED, а Session победителя остаётся активной
Scenario: Истёкшая сессия
- WHEN клиент отправляет refresh-cookie Session, не обновлявшейся более 7 дней
- THEN система удаляет refresh-cookie и отвечает
401Problem Details с codeUNAUTHORIZED
Scenario: Повторное использование refresh token
- WHEN клиент отправляет refresh token, заменённый предыдущим успешным refresh
- THEN система удаляет refresh-cookie и отвечает
401Problem Details с codeUNAUTHORIZED, не отзывая связанную Session только из-за этого запроса
Requirement: Завершение сессии
Система SHALL позволять завершить текущую Session через logout. Logout MUST отозвать Session, если refresh-cookie соответствует активной Session, и MUST удалить refresh-cookie. Logout MUST быть идемпотентным и не раскрывать существование Session.
Scenario: Logout действующей сессии
- WHEN клиент вызывает logout с действительным refresh-cookie
- THEN система отзывает текущую Session, удаляет refresh-cookie и отвечает
204
Scenario: Logout без refresh-cookie
- WHEN клиент вызывает logout без refresh-cookie или с невалидным refresh-cookie
- THEN система удаляет refresh-cookie и отвечает
204
Requirement: Доступ к текущему пользователю и защищённым API
Система SHALL предоставлять GET /api/v1/auth/me, возвращающий { id, email } текущего User. Этот маршрут и все прикладные API вне /api/v1/auth MUST требовать действительный неистёкший access token. Refresh token MUST NOT предоставлять доступ к защищённому API. Logout и refresh MUST NOT требовать access token.
Scenario: Получение текущего профиля
- WHEN User вызывает
GET /api/v1/auth/meс действительным access token - THEN система возвращает
{ id, email }этого User
Scenario: Запрос без access token
- WHEN клиент вызывает защищённый API без действительного access token
- THEN система отвечает
401Problem Details с codeUNAUTHORIZED
Scenario: Refresh token как Bearer token
- WHEN клиент передаёт refresh token в Authorization header защищённого API
- THEN система отвечает
401Problem Details с codeUNAUTHORIZED
Requirement: Стандартные ответы об ошибках
Система SHALL возвращать все API-ошибки как application/problem+json. Каждый Problem Details MUST содержать поле status и extension code. Ошибки валидации MUST использовать 400 и code VALIDATION_ERROR, а также extension errors как массив объектов { pointer, detail }, где pointer является JSON Pointer поля запроса. Ошибки, не требующие специальной обработки клиентом, MUST использовать один из code BAD_REQUEST, NOT_FOUND, UNSUPPORTED_MEDIA_TYPE или INTERNAL_SERVER_ERROR согласно HTTP status.
Scenario: Ошибка DTO-валидации
- WHEN API получает тело запроса, не проходящее валидацию
- THEN API возвращает
application/problem+jsonсstatus400, codeVALIDATION_ERRORиerrorsдля невалидных полей
Scenario: Внутренняя ошибка
- WHEN API сталкивается с необработанной внутренней ошибкой
- THEN API возвращает
application/problem+jsonсstatus500 и codeINTERNAL_SERVER_ERRORбез внутренних деталей
Requirement: Клиентский поток аутентификации
Клиент SHALL предоставлять формы входа и регистрации. Обе формы MUST валидировать email и пароль до отправки; форма регистрации MUST дополнительно требовать совпадающее подтверждение пароля, не передавая его API. Все поля, ссылки и submit MUST быть отключены во время отправки. Клиент MUST хранить access token и профиль только в памяти, восстанавливать их вызовом refresh при старте и передавать cookie при API-вызовах.
Scenario: Несовпадающее подтверждение пароля
- WHEN пользователь отправляет регистрацию с несовпадающим подтверждением пароля
- THEN клиент показывает ошибку поля и не вызывает API
Scenario: Восстановление после перезагрузки
- WHEN приложение запускается с действительным refresh-cookie
- THEN клиент получает новый access token и профиль до отображения защищённого маршрута
Scenario: Сбой отправки формы
- WHEN login или registration API недоступен или возвращает неожиданную ошибку
- THEN клиент завершает pending-состояние, сохраняет введённые поля и показывает локализованную общую ошибку
Requirement: Защищённая клиентская навигация
Клиент SHALL ограничивать все маршруты, кроме /login и /register, активной access-сессией. Неавторизованного пользователя MUST перенаправлять на login и после успешной аутентификации MUST возвращать на исходный внутренний pathname и search либо на /accounts. Авторизованного пользователя на auth-маршруте MUST перенаправлять на /accounts. При UNAUTHORIZED от защищённого API клиент MUST выполнить один сериализованный refresh, повторить исходный запрос один раз и при неуспехе очистить сессию и cache перед переходом на login.
Scenario: Возврат на исходный маршрут
- WHEN неавторизованный пользователь открывает
/accounts/123?tab=positionsи затем успешно входит - THEN клиент направляет его на
/accounts/123?tab=positions
Scenario: Потеря сессии при защищённом запросе
- WHEN защищённый API отвечает
401с codeUNAUTHORIZEDи refresh не восстанавливает сессию - THEN клиент очищает access token, профиль и cache, затем направляет пользователя на
/login
Scenario: Выход из настроек
- WHEN пользователь нажимает logout на
/settings - THEN клиент очищает локальную сессию и cache и направляет пользователя на
/loginнезависимо от результата сетевого запроса
Requirement: Единый HTTP contract успешных auth endpoints
Успешные POST /api/v1/auth/register, POST /api/v1/auth/login и
POST /api/v1/auth/refresh MUST отвечать HTTP 200; опубликованный
OpenAPI-документ, зафиксированный frontend snapshot и сгенерированные
transport types MUST описывать этот же status. Каждая операция сохраняет свои
существующие payload, refresh-cookie и session semantics. Logout MUST
продолжать отвечать 204.
Scenario: Успешная регистрация возвращает контрактный status
- WHEN пользователь отправляет новую валидную регистрацию
- THEN система устанавливает refresh-cookie, возвращает профиль и access
token с HTTP
200
Scenario: Успешный login или refresh возвращает контрактный status
- WHEN пользователь успешно входит либо восстанавливает Session
- THEN система устанавливает или заменяет refresh-cookie, возвращает
профиль и access token с HTTP
200
Requirement: Клиент контролируемо завершает неуспешный refresh
Frontend MUST обрабатывать HTTP и транспортную ошибку общего refresh flow без необработанного отклонения Promise. При неуспешном cold-start refresh frontend MUST завершить инициализацию как неавторизованный, не сохраняя access token или профиль. При неуспешном refresh после защищённого запроса frontend MUST очистить in-memory session и query cache перед existing navigation to login.
Scenario: Cold-start refresh завершается транспортной ошибкой
- WHEN приложение запускается без access token и refresh запрос не может получить транспортный response
- THEN frontend завершается в готовом неавторизованном состоянии без необработанного отклонения Promise и показывает обычный public/login flow
Scenario: Повторный refresh после protected 401 завершается транспортной ошибкой
- WHEN защищённый API отвечает
401, а единственный serialised refresh завершается транспортной ошибкой - THEN frontend очищает in-memory session и query cache и направляет User в существующий login flow без необработанного отклонения Promise