Направление работ: Mobile Код раздела: 1.3 Авторизация > Вход Статус: Готов Ответственный: Панкова Л.А. Версия: 0.1 Обновлено: 14.07.26
1. Назначение
Раздел описывает вход пользователя в мобильное приложение по телефону или email и паролю.
Интерфейс должен провести пользователя по единому сценарию: выбрать способ входа, ввести учетные данные, отправить их на проверку и получить результат.
Документ предназначен для проектирования макета и фронтового сценария. Он не задает backend-логику аутентификации.
Раздел зависит от Back: 1.1 Организация аккаунтов и Back: 1.1.2 Вход в систему.
2. Ключевые понятия
Вход — пользовательский сценарий авторизации в мобильном приложении.
Способ входа — выбор идентификатора: телефон или email.
Идентификатор — телефон или подтвержденный email, по которому пользователь входит в аккаунт.
Ошибка входа — состояние, при котором пользователь остается на экране входа и может исправить данные и повторить действие.
3. Scope
Входит
- Открытие экрана входа
- Выбор способа входа: телефон или почта
- Ввод идентификатора и пароля
- Отправка данных для входа
- Состояния входа
- Переход к регистрации или к восстановлению пароля и следующему сценарию
Не входит
Mobile: 1.2 Регистрация
Mobile: 1.4 Восстановление пароля
Mobile: 1.3.1 Вход по код-паролю или биометрии
4. BR
Бизнес-правила и ограничения указаны в Back: 1.1 Организация аккаунтов и Back: 1.1.2 Вход в систему.
В этом документе связи с BR указаны только при прямом соответствии фронтового требования backend-правилу.
5. Функциональные требования (UC + FR)
FR – functional requirements, что интерфейс должен уметь делать по определенному UC (триггеру).
| ID | Сценарий | Функциональное требование | BR | Вес | Статус |
|---|---|---|---|---|---|
| FR-1 | Пользователь открывает приложение без активизированной сессии | Интерфейс показывает экран входа при запуске приложения. | Back: 1.1 Back: 1.1.2 | 1 | Частично \ #OQ1.3-6 |
| FR-2 | Пользователь выбирает способ входа | Интерфейс позволяет выбрать вход по телефону или по почте. При выборе способа меняется поле идентификатора и доступный тип клавиатуры. | Back: 1.1.2 | 1 | Реализовано |
| FR-3 | Пользователь вводит идентификатор и пароль | Интерфейс принимает телефон или email и пароль, применяет соответствующую маску/формат поля, скрывает пароль по умолчанию и позволяет показать пароль. Длина телефона ограничивается 10 цифрами. | Back: 1.1 Back: 1.1.2 | 1 | Частично \ #OQ1.3-1 \ #OQ1.3-10 |
| FR-4 | Пользователь отправляет данные для входа | Интерфейс активирует продолжение после заполнения обязательных полей и отправляет выбранный идентификатор вместе с паролем на проверку наличия Аккаунта. | Back: 1.1.2 | 1 | Частично \ #OQ1.3-7 |
| FR-5 | Вход выполнен успешно | Интерфейс переводит пользователя к следующему продуктовому сценарию после успешной проверки данных. По макету следующий шаг — 2.1 Поиск > Список сфер. | Back: 1.1 Back: 1.1.2 | 1 | Частично \ #OQ1.3-4 |
| FR-6 | Пользователь ввел неверные данные | Интерфейс показывает единую ошибку входа без уточнения, какое поле неверно, и позволяет исправить идентификатор или пароль. | Back: 1.1.2 | 2 | Частично \ #OQ1.3-2 \ #OQ1.3-8 |
| FR-7 | Возникла ошибка соединения | Интерфейс показывает ошибку соединения, сохраняет введенные данные и позволяет повторить отправку. | — | 2 | Частично \ #OQ1.3-3 \ #OQ1.3-9 |
| FR-8 | Пользователь переходит к регистрации или к восстановлению пароля | Интерфейс переводит пользователя из входа в сценарий регистрации или восстановления пароля. Пользователь может перейти к этому шагу до начала авторизации или после ошибки. | — | 2 | Реализовано |
| FR-9 | Шаг входа успешно завершен | Интерфейс переводит пользователя к следующему продуктовому сценарию: Mobile 2.1 Поиск сфер. | 1 | Частично \ #OQ1.3-4 |
6. Открытые вопросы
| ID | Источник | Вопрос | Кому | Статус | Рекомендации | Итоговое решение |
|---|---|---|---|---|---|---|
| #OQ1.3-1 | Код FR-3 | Требование говорит: интерфейс применяет соответствующую маску/формат к идентификатору — телефону или email. В коде маска/формат есть только для телефона; для email ни маски, ни проверки формата — кнопка активируется по непустому полю, идентификатор уходит на бэкенд как есть. \ login_screen.dart:110; auth_text_field.dart:204-206 | Бизнес/Разработка | Open | Кто валидирует формат email — фронт или бэкенд; согласовать с Back: 1.1.2. (А, рекоменд.) зафиксировать в FR-3, что маска только для телефона, а формат email проверяет бэкенд; (Б) добавить клиентскую проверку формата email до отправки. | (Б) добавить клиентскую проверку формата email до отправки. |
| #OQ1.3-2 | Код FR-6 | Требование говорит: единая ошибка входа без указания, какое поле неверно. В коде UI показывает один блок ошибки без привязки к полю, но текст берётся из бэкенда (error.shortMessage) без пост-обработки — фиксированной клиентской строки нет, гарантия «не раскрывать поле» зависит от бэкенда. \ login_screen.dart:343; auth_repository.dart:111 | Бизнес/Разработка | Open | (А, рекоменд.) задать в BRD фиксированную клиентскую строку («Неверный логин или пароль») поверх бэкенд-сообщения, чтобы гарантировать «без указания поля»; (Б) сознательно доверять тексту бэкенда — зафиксировать это в FR-6. Текст ошибки согласовать с Back: 1.1.2. | (Б) сознательно доверять тексту бэкенда — зафиксировать это в FR-6. Текст ошибки согласовать с Back: 1.1.2. |
| #OQ1.3-3 | Код FR-7 | Требование говорит: отдельный сценарий «ошибка соединения», в отличие от ошибки входа (FR-6). В коде клиент не различает сетевой сбой и неверные данные — обе ветки сводятся в один AuthStateError с текстом e.toString(); при обрыве связи пользователь увидит то же, что при неверном пароле. \ auth_bloc.dart:300-311 | Бизнес/Разработка | Open | (А, рекоменд.) развести состояния (сетевой сбой → отдельный тип ошибки со своим UI и «Повторить»); (Б) сознательно объединить и убрать FR-7 как отдельный сценарий, оговорив в BRD. Связать с OQ1.3-9 (повтор отправки). | (А, рекоменд.) развести состояния (сетевой сбой → отдельный тип ошибки со своим UI и «Повторить»); |
| #OQ1.3-4 | Код FR-5, FR-9 | Требование говорит: после успеха — переход к «2.1 Поиск > Список сфер». В коде явного перехода на список сфер нет: после успеха управление возвращается на изначально запрошенный маршрут (resolver.next()), а «Список сфер» становится экраном лишь как первая вкладка стартового HomeRoute; плюс guard может увести на код-пароль/биометрию до сфер. \ app_router.dart:165,170-196; home_screen.dart:42 | Бизнес/Разработка | Open | (А, рекоменд.) переформулировать FR-5/FR-9 как «стартовая вкладка = Список сфер» и оговорить возможные вставки код-пароля/биометрии (шаг 1.3.1); (Б) закрепить явный переход на экран сфер. Вставки код-пароля/биометрии — тот же узел, что 1.3.1. | (А, рекоменд.) переформулировать FR-5/FR-9 как «стартовая вкладка = Список сфер» и оговорить возможные вставки код-пароля/биометрии (шаг 1.3.1); |
| #OQ1.3-5 | Код вне BRD | Что нашли: код обрабатывает вход по временному паролю (IsTemporaryPassword) — пользователь помечается authenticated, но guard блокирует любые переходы до повторной авторизации. В BRD 1.3 этого сценария нет. \ auth_repository.dart:130; auth_bloc.dart:290-298; app_router.dart:139-154 | Разработка | Open | Гайдбука у фронта нет, правило — на стороне продукта/Back. (А, рекоменд.) добавить отдельный UC/FR «вход по временному паролю»; (Б) явно вынести в scope-out «Не входит». Решение продукта; при необходимости сверить с Back: 1.1.2. | От бизнеса: подняли дискуссию в чате задачи. |
| #OQ1.3-6 | Код FR-1 | Требование говорит: интерфейс показывает экран входа при запуске приложения (без активной сессии). В коде экран входа появляется не «при запуске», а когда guard перехватывает запрос защищённого маршрута и видит unauthenticated (начальный статус — по наличию валидного токена); перед авторизацией срабатывает онбординг — при непройденном онбординге сначала он. \ app_router.dart:114-166; initialization_processor.dart:317-320,128 | Бизнес/Разработка | Open | Функционально вход показывается при отсутствии сессии, но формулировка «при запуске» неточна. (А, рекоменд.) переформулировать FR-1 под guard + начальный статус по токену + приоритет онбординга; (Б) подтвердить, что «при запуске» трактуется именно так, и оставить формулировку. | (А, рекоменд.) переформулировать FR-1 под guard + начальный статус по токену + приоритет онбординга; (это и есть при запуске) |
| #OQ1.3-7 | Код FR-4 | Требование говорит: отправка выбранного идентификатора вместе с паролем на проверку. В коде regionCode вычисляется, но его передача в вызов входа закомментирована → используется дефолт +7; поле кода региона readOnly, в UI есть FIXME про другие регионы. Вход фактически поддерживает только +7. \ login_screen.dart:383-389; auth_scope.dart:91; auth_repository.dart:71 | Бизнес/Разработка | Open | (А, рекоменд.) зафиксировать ограничение +7 в FR-4 (или scope-out мультирегиона); (Б) раскомментировать передачу regionCode и снять readOnly, если мультирегион нужен. Тот же корень, что в 1.2 (формат телефона, OQ1.2-2). | (А, рекоменд.) зафиксировать ограничение +7 в FR-4 (или scope-out мультирегиона) |
| #OQ1.3-8 | Код FR-6 | Требование говорит: после ошибки — «позволяет исправить идентификатор или пароль». В коде красный текст ошибки рендерится, только пока state is AuthStateError; сброса ошибки при редактировании поля нет — она исчезает лишь при новой отправке (которая сначала эмитит AuthStateProcessing). \ login_screen.dart:340-347; auth_bloc.dart:267 | Разработка | Open | Мелкий UX-выбор. (А) сбрасывать AuthStateError в onChanged полей («ошибка исчезает при правке»); (Б, рекоменд.) зафиксировать текущее — ошибка снимается при повторной отправке. Исправление данных не блокируется. | (Б, рекоменд.) зафиксировать текущее — ошибка снимается при повторной отправке. Исправление данных не блокируется. |
| #OQ1.3-9 | Код FR-7 | Требование говорит: при ошибке соединения — «сохраняет введённые данные и позволяет повторить отправку». В коде контроллеры живут в State экрана и при ошибке не очищаются (данные сохраняются), но при пересоздании экрана сбросятся; отдельной кнопки «Повторить» нет — повтор = повторное нажатие «Продолжить». \ login_screen.dart:66-69; auth_bloc.dart:300-311 | Бизнес/Разработка | Open | Завязано на разделение ошибок (OQ1.3-3). (А) развести сетевую ошибку в отдельное состояние и добавить кнопку «Повторить»; (Б, рекоменд.) задокументировать «повтор = повторное нажатие Продолжить» и сохранение данных в рамках сессии экрана. Связать с OQ1.3-3. | (Б, рекоменд.) задокументировать «повтор = повторное нажатие Продолжить» и сохранение данных в рамках сессии экрана. Связать с OQ1.3-3. |
| #OQ1.3-10 | Код FR-3 | Проблема: заданный для поля телефона maxLength: 10 игнорируется (в TextField при phone выставляется maxLength: null); длину ограничивает сам форматтер (сброс ведущих 7/8, обрезка до 10 цифр), храня «сырые» цифры в отдельном контроллере. \ auth_text_field.dart:201,204-206; login_screen.dart:275 | Разработка | Open | Деталь реализации, продуктового вопроса нет. Закрываю сам: зафиксировать в BRD, что длину телефона ограничивает маска (10 цифр), а maxLength поля не используется → правка BRD. Правки кода не требуется. | От бизнеса: внесли изменения в БРД. |
| #OQ1.3-11 | Код вне BRD | Что нашли: репозиторий печатает в лог логин и пароль в открытом виде при каждой попытке входа — риск утечки в логи/Sentry. \ auth_repository.dart:73-74 | Разработка | Open | Явный риск безопасности, продуктового вопроса нет. Закрываю сам, приоритетно: убрать print логина/пароля (логировать только обезличенные данные) → тикет в Bitrix. | От бизнеса: доработать, согласовать с Back. Доработка, вес 1. |
| #OQ1.3-12 | Код вне BRD | Что нашли: побочные эффекты входа, не отражённые в BRD — перед попыткой очищаются все токены; из ответа сохраняется InviteCode; AuthBloc подписан на очистку токенов и делает авто-logout при их сбросе (напр. по refresh-failure). \ auth_repository.dart:78,143-168; auth_bloc.dart:101-116 | Бизнес/Разработка | Open | Решение: отражать ли invite-code / авто-logout / предочистку токенов в требованиях 1.3 (как UC/техданные) или считать техническими деталями вне BRD. Продукт + разработка. | считать техническими деталями вне BRD |
Подстраницы
Вопросы по сверке с кодом
BRD 1.3 v0.1 · booking.mobile.flutter · commit dad0c65/dev · 2026-07-10 · якорь
lib/src/feature/auth/widget/login_screen.dart(экран входа),auth/bloc/auth_bloc.dart,auth/data/auth_repository.dart,core/utils/widgets/auth_text_field.dart,core/utils/app_router.dart(guard),feature/initialization/logic/initialization_processor.dart(стартовый статус),feature/home/screen/home_screen.dart(вкладка «Список сфер»). Сводка: вопросов 12 (бизнесовых 5 / программных 7) · подтверждено без вопросов 2 (FR-2, FR-8) · отрезано находок обратного прохода 1 (аналитические события AppMetrica на каждое поле/клик — вне BRD, требований не затрагивают). Под особым вниманием: FR-3 (email без валидации формата), FR-5/FR-9 (приземление на дефолтную вкладку, а не гарантированный экран), FR-6/FR-7 (ошибка входа и ошибка соединения не разделены).
Бизнесовые
| № | Связано с | Что в BRD | Что в коде | Где в коде |
|---|---|---|---|---|
| Б1 | FR-3 | Интерфейс применяет «соответствующую маску/формат поля» к идентификатору (телефон или email). | Маска/формат есть только для телефона (_PhoneNumberFormatter); для email не применяется ни маска, ни проверка формата — единственное условие активации кнопки — непустое поле, идентификатор уходит на бэкенд как есть — нужно решение, кто валидирует формат email. | login_screen.dart:110, auth_text_field.dart:204-206 |
| Б2 | FR-6 | «Единая ошибка входа без уточнения, какое поле неверно». | UI показывает один блок ошибки без привязки к полю, но текст берётся из бэкенда (error.shortMessage) и выводится без пост-обработки — фиксированной клиентской строки нет, гарантия «не раскрывать поле» зависит от бэкенда — нужно решение. | login_screen.dart:343, auth_repository.dart:111 |
| Б3 | FR-7 | Отдельный сценарий «ошибка соединения» — в отличие от ошибки входа (FR-6). | Клиент не различает сетевой сбой и неверные данные: обе ветки сводятся в один AuthStateError с текстом e.toString(); при обрыве связи пользователь увидит то же сообщение, что и при неверном пароле — нужно решение, разделять ли эти состояния. | auth_bloc.dart:300-311 |
| Б4 | FR-5, FR-9 | После успеха — переход к «2.1 Поиск > Список сфер». | Явного перехода на список сфер нет: после успеха управление возвращается на изначально запрошенный маршрут через resolver.next(); «Список сфер» становится экраном лишь как первая вкладка стартового HomeRoute. Плюс guard может увести на код-пароль/биометрию до сфер — нужно решение о формулировке «приземления». | app_router.dart:165, home_screen.dart:42, app_router.dart:170-196 |
| Б5 | вне BRD | — | Код обрабатывает вход по временному паролю (IsTemporaryPassword): пользователь помечается authenticated, но guard блокирует любые переходы до повторной авторизации. В BRD 1.3 этого сценария нет — нужно решение: описать как UC или явно вынести в scope-out. | auth_repository.dart:130, auth_bloc.dart:290-298, app_router.dart:139-154 |
Рекомендация / варианты решения
- Валидация идентификатора (Б1) — Вариант А (рекоменд.): зафиксировать в FR-3, что маска применяется только к телефону, а формат email проверяет бэкенд (клиент шлёт непустое значение). Вариант Б: добавить клиентскую проверку формата email до отправки (под-пункт FR-3/FR-4).
- Текст ошибки входа (Б2) — Вариант А (рекоменд.): задать в BRD фиксированную клиентскую строку («Неверный логин или пароль») поверх бэкенд-сообщения, чтобы гарантировать «без указания поля». Вариант Б: сознательно доверять тексту бэкенда — тогда зафиксировать это в FR-6 как решение.
- Ошибка входа vs соединения (Б3) — Вариант А (рекоменд.): развести состояния (сетевой сбой → отдельный тип ошибки со своим UI и «Повторить»). Вариант Б: сознательно объединить и убрать FR-7 как отдельный сценарий, оговорив это в BRD.
- Приземление после входа (Б4) — Вариант А (рекоменд.): переформулировать FR-5/FR-9 как «стартовая вкладка = Список сфер» и оговорить возможные вставки код-пароля/биометрии. Вариант Б: закрепить явный переход на экран сфер (см. П-рекомендации).
- Временный пароль (Б5) — Вариант А (рекоменд.): добавить отдельный UC/FR «вход по временному паролю». Вариант Б: явная строка в scope-out «Не входит».
Программные
| № | Связано с | Что в BRD | Что в коде | Где в коде |
|---|---|---|---|---|
| П1 | FR-1 | «Интерфейс показывает экран входа при запуске приложения» (без активной сессии). | Экран входа появляется не «при запуске», а когда guard перехватывает запрос защищённого маршрута и видит unauthenticated (начальный статус — по наличию валидного токена в хранилище). Перед авторизацией срабатывает проверка онбординга — при непройденном онбординге сначала показывается он. | app_router.dart:114-166, initialization_processor.dart:317-320, :128 |
| П2 | FR-4 | Отправка выбранного идентификатора вместе с паролем на проверку. | regionCode вычисляется, но в вызов signInWithLoginAndPassword его передача закомментирована → используется дефолт +7; поле кода региона readOnly, в UI есть FIXME про «другие регионы». Вход фактически поддерживает только +7. | login_screen.dart:383-389, auth_scope.dart:91, auth_repository.dart:71 |
| П3 | FR-6 | «Позволяет исправить идентификатор или пароль» после ошибки. | Красный текст ошибки рендерится, только пока state is AuthStateError; сброса ошибки при редактировании поля нет — она исчезает лишь при новой отправке (которая сначала эмитит AuthStateProcessing). | login_screen.dart:340-347, auth_bloc.dart:267 |
| П4 | FR-7 | «Сохраняет введённые данные и позволяет повторить отправку». | Контроллеры живут в State экрана и при ошибке не очищаются (данные сохраняются), но при пересоздании экрана сбросятся. Отдельной кнопки «Повторить» нет — повтор = повторное нажатие «Продолжить». | login_screen.dart:66-69, auth_bloc.dart:300-311 |
| П5 | FR-3 | Маска/формат поля телефона. | maxLength: 10, заданный для поля телефона, игнорируется (в TextField при phone выставляется maxLength: null); длину ограничивает сам форматтер (сброс ведущих 7/8, обрезка до 10 цифр), храня «сырые» цифры в отдельном контроллере. | auth_text_field.dart:201, :204-206, login_screen.dart:275 |
| П6 | вне BRD | — | Репозиторий печатает в лог логин и пароль в открытом виде при каждой попытке входа — программный риск безопасности (утечка в логи/Sentry). | auth_repository.dart:73-74 |
| П7 | вне BRD | — | Побочные эффекты входа, не отражённые в BRD: перед попыткой очищаются все токены; из ответа сохраняется InviteCode; AuthBloc подписан на очистку токенов и делает авто-logout при их сбросе (напр. по refresh-failure). | auth_repository.dart:78, :143-168, auth_bloc.dart:101-116 |
Рекомендация / варианты решения
- FR-1 vs фактическая архитектура (П1) — Вариант А (рекоменд.): переформулировать FR-1 под guard + начальный статус по токену + приоритет онбординга («вход = первый защищённый переход после онбординга при отсутствии сессии»). Вариант Б: подтвердить, что «при запуске» трактуется именно так, и оставить формулировку.
- Регион/
+7(П2) — Вариант А (рекоменд.): зафиксировать ограничение+7в FR-4 (или scope-out мультирегиона). Вариант Б: раскомментировать передачуregionCodeи снятьreadOnly, если мультирегион нужен. - Сброс ошибки (П3) — Вариант А: добавить сброс
AuthStateErrorвonChangedполей («ошибка исчезает при правке»). Вариант Б (рекоменд., если поведение устраивает): зафиксировать текущее — ошибка снимается при повторной отправке. - Повтор отправки (П4) — Вариант А: развести сетевую ошибку в отдельное состояние и добавить кнопку «Повторить». Вариант Б (рекоменд.): задокументировать «повтор = повторное нажатие Продолжить» и сохранение данных в рамках сессии экрана.
- maxLength телефона (П5) — деталь реализации: зафиксировать в BRD, что длину телефона ограничивает маска (10 цифр), а
maxLengthполя не используется. Правки кода не требуется. - Безопасность (П6) — приоритетно: убрать
printлогина/пароля изauth_repository.dart:73-74(логировать только обезличенные данные). - Побочные эффекты (П7) — решить, отражать ли invite-code / авто-logout / предочистку токенов в требованиях 1.3 или считать их техническими деталями вне BRD.