Направление работ: Mobile Код раздела: 1.4 Авторизация > Восстановление пароля Статус: Готов Ответственный: Панкова Л.А. Версия: 0.1 Обновлено: 14.07.26


1. Назначение

Раздел описывает восстановление пароля пользователем мобильного приложения, если пользователь не может войти по текущим учетным данным.

Интерфейс должен провести пользователя от ввода телефона или почты до подтверждения владения аккаунтом кодом и установки нового пароля.

Документ предназначен для проектирования frontend-сценария. Он не задает backend-логику поиска аккаунта, отправки и проверки кода или сохранения нового пароля.

Раздел зависит от Back: 1.1 Организация аккаунтов и Back: 1.1.3 Восстановление.

2. Ключевые понятия

Восстановление доступа — процесс подтверждения владения аккаунтом через данные для сброса пароля и последующей смены пароля.

Восстановление пароля — пользовательский сценарий в мобильном приложении, в котором пользователь подтверждает доступ к аккаунту и задает новый пароль.

Способ восстановления — выбор идентификатора: телефон или почта.

Идентификатор — телефон или подтвержденный email, по которому пользователь инициирует восстановление пароля.

Код сброса пароля — цифровой код из 6 цифр, который пользователь вводит для подтверждения права задать новый пароль.

Маскирование контакта — скрытие части номера телефона или адреса электронной почты при отображении канала, на который отправлен код.

3. Scope

Входит

  • S1: Инициация восстановления пароля: открытие экрана восстановления, выбор способа по телефону или почте, ввод идентификатора, отправка запроса и отображение ошибки, если пользователь не найден.
  • S2: Подтверждение восстановления кодом: ввод кода из письма или СМС, отображение замаскированного контакта, таймера повторной отправки и действия для запроса нового кода.
  • S3: Установка нового пароля: ввод нового пароля и повтора пароля, управление видимостью значений, сохранение пароля и переход к следующему продуктовому сценарию после успешного восстановления.
  • S4: Состояния восстановления: неактивные действия до заполнения обязательных полей, ошибки ввода идентификатора/кода/пароля, ограничения повторной отправки и возврат назад в рамках flow.

Не входит

  • Mobile: 1.2 — Регистрация
  • Mobile: 1.3 — Вход
  • Mobile: 1.3.1 — Вход по код-паролю
  • Back: 1.1.3 — backend-логика поиска аккаунта, отправки/проверки кода, лимитов и сохранения нового пароля
  • WebLk: 1.3 / Widget: 3.3 / Catalog: 3.3 — восстановление пароля в других интерфейсах
  • Mobile: 2.1 — Поиск > Список сфер

4. BR

Указаны в пункте 4 раздела Back: 1.1.3 Восстановление.

5. Функциональные требования (UC + FR)

FR — functional requirement: что интерфейс должен уметь делать в рамках конкретного пользовательского сценария.

IDСценарийФункциональное требованиеBRВесСтатус
S1: #FR1.4-1Пользователь открывает восстановление пароляИнтерфейс показывает экран восстановления с выбором способа восстановления по телефону или по почте.1Реализовано
S1: #FR1.4-2Пользователь выбирает способ восстановленияИнтерфейс меняет поле идентификатора и доступный тип ввода в зависимости от выбранного способа: телефон или почта.1Реализовано
S1: #FR1.4-3Пользователь вводит телефонИнтерфейс принимает телефон, применяет маску ввода и не позволяет продолжить, пока номер не заполнен до допустимого формата.1Реализовано
S1: #FR1.4-4Пользователь вводит почтуИнтерфейс принимает email и не позволяет продолжить, пока значение не заполнено в допустимом формате.1Реализовано
S1: #FR1.4-5Пользователь отправляет запрос восстановленияИнтерфейс отправляет выбранный идентификатор и источник Mobile на восстановление пароля, после успешного ответа переводит пользователя на шаг подтверждения кодом.1Реализовано
S1: #FR1.4-6По введенному телефону пользователь не найденИнтерфейс показывает ошибку под полем телефона и оставляет пользователя на экране восстановления с возможностью исправить значение.2Частично \ #OQ1.4-2
S1: #FR1.4-7По введенной почте пользователь не найденИнтерфейс показывает ошибку под полем почты и оставляет пользователя на экране восстановления с возможностью исправить значение.2Частично \ #OQ1.4-2
S2: #FR1.4-8Пользователь попадает на подтверждение кодом из письмаИнтерфейс показывает модальное окно подтверждения, замаскированный email, поле ввода кода и таймер до возможности запросить новый код.1Реализовано
S2: #FR1.4-9Пользователь попадает на подтверждение кодом из СМСИнтерфейс показывает модальное окно подтверждения, замаскированный номер телефона, поле ввода кода и таймер до возможности запросить новый код.1Реализовано
S2: #FR1.4-10Пользователь вводит код сброса пароляИнтерфейс принимает код в поле подтверждения и отправляет его на проверку после заполнения требуемого количества символов.1Частично \ #OQ1.4-1
S2: #FR1.4-11Код сброса пароля подтвержденИнтерфейс закрывает модальное окно подтверждения и переводит пользователя на экран установки нового пароля.1Реализовано
S2: #FR1.4-12Пользователь ввел неверный кодИнтерфейс показывает ошибку проверки кода и позволяет повторить ввод.2Частично \ #OQ1.4-5
S2: #FR1.4-13Срок действия кода истекИнтерфейс сообщает, что код устарел, и предлагает запросить новый код после доступности повторной отправки.2Не реализовано \ #OQ1.4-6
S2: #FR1.4-14Пользователь ожидает повторную отправку кодаИнтерфейс показывает таймер до повторной отправки и не позволяет запросить новый код до завершения таймера.2Реализовано
S2: #FR1.4-15Пользователь запрашивает новый кодИнтерфейс позволяет запросить новый код после завершения таймера и обновляет состояние подтверждения после ответа.2Реализовано
S3: #FR1.4-16Пользователь попадает на установку нового пароляИнтерфейс показывает экран установки пароля с полями нового пароля и повтора пароля.1Реализовано
S3: #FR1.4-17Пользователь вводит новый парольИнтерфейс скрывает значение пароля по умолчанию и позволяет пользователю показать или снова скрыть введенное значение.2Реализовано
S3: #FR1.4-18Пользователь подтверждает новый парольИнтерфейс сравнивает пароль и повтор пароля и не позволяет сохранить пароль, если значения не совпадают.1Реализовано
S3: #FR1.4-19Пользователь сохраняет новый парольИнтерфейс отправляет новый пароль на сохранение только после успешной проверки кода и заполнения обязательных полей.1Реализовано \ #OQ1.4-4
S3: #FR1.4-20Новый пароль успешно сохраненИнтерфейс завершает восстановление пароля и переводит пользователя к следующему продуктовому сценарию: Mobile 2.1 Поиск > Список сфер.1Реализовано
S4: #FR1.4-21Пользователь не заполнил обязательные поляИнтерфейс оставляет основное действие неактивным до заполнения обязательных полей на текущем шаге.1Реализовано
S4: #FR1.4-22Пользователь возвращается на предыдущий шагИнтерфейс позволяет вернуться назад из экранов и модального окна восстановления без перехода в соседние сценарии.2Реализовано
S4: #FR1.4-23Превышено ограничение на повторные запросы кодаИнтерфейс показывает состояние временного ограничения и не позволяет повторно инициировать восстановление до истечения ограничения, если такая блокировка была применена.2Частично \ #OQ1.4-3
S4: #FR1.4-24Во время восстановления нет интернет-соединения или получена техническая ошибкаИнтерфейс показывает общее сообщение о проблеме и позволяет повторить действие без потери актуального контекста шага.2Частично \ #OQ1.4-7
S4: #FR1.4-25Пользователь повторно нажимает действие во время запросаИнтерфейс предотвращает дублирование запроса: повторное нажатие недоступно или игнорируется до получения ответа.2Реализовано

6. Открытые вопросы

IDИсточникВопросКомуСтатусРекомендацииИтоговое решение
#OQ1.4-1Код FR1.4-10Требование говорит: код сброса — цифровой код из 6 цифр (см. п.2), отправка на проверку после ввода требуемого числа символов. В коде длина зашита _kCodeLength = 4; Pinput на 4 ячейки с авто-отправкой при 4 символах — расходится с «6 цифр» из глоссария. \ forgot_password_screen.dart:23,780,538-548Бизнес/РазработкаOpenКритично: если бэкенд шлёт 6-значные коды, поле на 4 ячейки авто-отправит первые 4 символа и валидация не пройдёт никогда. Фактическую длину — согласовать с Back: 1.1.3. (А, рекоменд.) привести _kCodeLength к реальной длине, идеально — не хардкодить, а брать из ответа SendRestorePasswordCodeMobile; (Б) если 4 верно — поправить глоссарий BRD (правит владелец BRD).привести _kCodeLength к реальной длине и захардкодить
#OQ1.4-2Код FR1.4-6, FR1.4-7Требование говорит: при «пользователь не найден» — ошибка под полем телефона/почты, пользователь остаётся на экране. В коде ветка accountNotFound пишет текст в единую центрированную область под обоими полями (не под конкретным полем); при пустом shortMessage показывает generic «Не удалось отправить код…», а не «пользователь не найден». \ forgot_password_screen.dart:147-154,207-241Бизнес/РазработкаOpenКопирайт и размещение ошибки — под вопросом. (А) показывать явный «Пользователь не найден» под активным полем; (Б) оставить как есть, если продукт согласен, что случай покрывается серверным shortMessage без привязки к полю. Текст согласовать с Back: 1.1.3.От бизнеса: доработка, вес 2.
#OQ1.4-3Код FR1.4-23Требование говорит: при превышении лимита повторных запросов — состояние временного ограничения, нельзя повторно инициировать до истечения. В коде на экране инициации tooManyRequests только показывает текст; кнопка «Продолжить» остаётся активной, кулдаун-блокировки нет (таймер есть лишь в модалке повторной отправки). \ forgot_password_screen.dart:147-154 (в модалке — :492-497)Бизнес/РазработкаOpenРешение: требуется ли лок на экране инициации. (А, рекоменд.) ввести кулдаун-блокировку кнопки по timeout из ответа, как в модалке; (Б) оставить как есть, если продукт согласен на текст без блокировки. Значение лимита/окна — с Back: 1.1.3.оставить как есть, кулдаун на сервере
#OQ1.4-4Код вне BRDЧто нашли: клиент сам enforce-ит минимум 6 символов пароля (_minPasswordLength = 6, текст «Минимум 6 символов»), иных правил сложности нет. BRD 1.4 отдаёт правила пароля бэкенду (п.1 «Не входит»). \ set_password_screen.dart:43,230-231Бизнес/РазработкаOpenНужно подтверждение, что клиентский порог совпадает с политикой Back; согласовать с Back: 1.1.3. (А) синхронизировать минимум/правила с Back и вынести в конфиг; (Б) убрать клиентский порог, полагаясь на ответ SetNewPassword. FR1.4-19 функционально работает — вопрос про полноту правил.согласовать с Back: 1.1.3. (А) синхронизировать минимум/правила с Back и вынести в конфиг
#OQ1.4-5Код FR1.4-12Требование говорит: при неверном коде — ошибка и повтор, если лимит попыток не был исчерпан. В коде invalidCode → тряска + снек, поле очищается, повтор разрешён бесконечно; состояния «лимит попыток исчерпан» на валидации нет (enum: ok/invalidCode/invalidSource/expiredCode/accountNotFound). Условие «если лимит не исчерпан» не реализовано. \ restore_password_result.dart:55-61; forgot_password_screen.dart:558-565Бизнес/РазработкаOpen(А, рекоменд.) согласовать с Back (1.1.3) поведение при исчерпании попыток (доп. состояние/ошибка) и обработать отдельно — блокировать ввод или предлагать новый код; (Б) если лимит целиком на бэкенде — убрать оговорку «если лимит не исчерпан» из BRD (правит владелец BRD).От бизнеса: уточнили БРД.
#OQ1.4-6Код FR1.4-13Требование говорит: при истёкшем коде — «код устарел» и предложить запросить новый после доступности повторной отправки. В коде expiredCode обрабатывается идентично invalidCode (тряска + серверный shortMessage); отдельного «код устарел» и проактивного предложения нет — повторная отправка по-прежнему заблокирована таймером. Доп.: invalidSource не разбирается → generic «Ошибка проверки кода». \ forgot_password_screen.dart:558-580Бизнес/РазработкаOpen(А, рекоменд.) развести ветки — для expiredCode показывать «Код устарел, запросите новый» и разблокировать/подсветить повторную отправку; для invalidSource — осмысленный текст; (Б) подтвердить, что серверный shortMessage покрывает оба случая. Связать с OQ1.4-5.Доработка: привести код к требованию, вес 1.
#OQ1.4-7Код FR1.4-24Требование говорит: при отсутствии сети/техошибке — общее сообщение, повтор без потери контекста шага. В коде контекст шага сохраняется (остаёмся на экране/в модалке), но пользователю показывается сырой текст исключения (shortMessage/description с бэкенда), без общего «проблема с соединением, повторите». \ forgot_password_screen.dart:55-60,150-154,513-528РазработкаOpenКонтекст шага не теряется, вопрос только в тексте — продуктового выбора нет. Закрываю сам: маппить транспортные/технические ошибки в единое дружелюбное сообщение, детали — в лог/Sentry → тикет в Bitrix.Доработка.
#OQ1.4-8Код вне BRDЧто нашли: в репозитории два флоу восстановления — активный auth/forgot_password и мёртвый restore_password (RestorePasswordScreen/RestorePasswordRoute зарегистрирован в роутере, но нигде не пушится). Дубль-реализация, риск расхождения при доработках. \ restore_password/widget/resotre_password_screen.dart:11; core/utils/app_router.dart:305РазработкаOpenМёртвый код, продуктового вопроса нет. Закрываю сам: удалить restore_password и снять RestorePasswordRoute из роутера → тикет в Bitrix. Если ветка планируется — альтернатива: задокументировать назначение.Доработка.
#OQ1.4-9Код вне BRDЧто нашли: print-логи в потоке восстановления печатают логин (PII) и полный JSON ответов, включая JWT-токены (ValidateRestorePasswordCodeMobile/SetNewPassword) — в проде утечка чувствительных данных в логи. \ auth_repository.dart:235,264,306,338РазработкаOpenЯвный риск безопасности, продуктового вопроса нет. Закрываю сам (приоритет): удалить debug-print (или загейтить за kDebugMode), из логов исключить токены и логин → тикет в Bitrix.Доработка.

Вопросы по сверке с кодом

BRD 1.4 v0.1 · booking.mobile.flutter · commit dad0c65/dev · 2026-07-10 · якорь lib/src/feature/auth/forgot_password/ (вход: login_screen.dart:441ForgotPasswordRouteSetPasswordRoute; restore_password/* — мёртвая ветка) Сводка: вопросов 9 (бизнесовых 4 / программных 5) · подтверждено без вопросов 18 (FR1.4-1, FR1.4-2, FR1.4-3, FR1.4-4, FR1.4-5, FR1.4-8, FR1.4-9, FR1.4-11, FR1.4-14, FR1.4-15, FR1.4-16, FR1.4-17, FR1.4-18, FR1.4-19, FR1.4-20, FR1.4-21, FR1.4-22, FR1.4-25) · отрезано находок обратного прохода 4 (аналитика AppMetrica, хаптик-фидбек, shake-анимация при неверном коде, нормализация вставленного 11-значного номера) Под особым вниманием: длина кода 4 vs «6 цифр» в BRD (Б1); отсутствие обработки лимита попыток на валидации кода (П1); tooManyRequests без блокировки на экране инициации (Б3)

Бизнесовые

Связано сЧто в BRDЧто в кодеГде в коде
Б1FR1.4-10 (+ п.2 «Код сброса пароля»)Код сброса пароля — цифровой код из 6 цифр; отправка на проверку после ввода требуемого числа символовДлина зашита _kCodeLength = 4; Pinput на 4 ячейки с авто-отправкой при 4 символах — расходится с «6 цифр» из глоссария. Нужно решение: 4 или 6 (клиент обязан совпасть с длиной кода бэкенда, иначе валидация не отправится/сорвётся)forgot_password_screen.dart:23, :780, :538-548
Б2FR1.4-6, FR1.4-7При «пользователь не найден» — ошибка под полем телефона/почты, пользователь остаётся на экранеВетка accountNotFound пишет текст в единую центрированную область под обоими полями (не под конкретным полем); при пустом shortMessage показывает generic «Не удалось отправить код…», а не «пользователь не найден» — нужно подтверждение копирайта и размещенияforgot_password_screen.dart:147-154, :207-241
Б3FR1.4-23При превышении лимита повторных запросов — состояние временного ограничения, нельзя повторно инициировать до истеченияНа экране инициации tooManyRequests только показывает текст; кнопка «Продолжить» остаётся активной, кулдаун-блокировки нет (таймер есть лишь в модалке повторной отправки) — нужно решение: требуется ли лок на экране инициацииforgot_password_screen.dart:147-154 (в модалке — :492-497)
Б4вне BRD (п.1: сохранение пароля — Back)Клиент сам enforce-ит минимум 6 символов пароля (_minPasswordLength = 6, текст «Минимум 6 символов»); иных правил сложности нет. BRD 1.4 отдаёт правила пароля бэкенду — нужно подтверждение, что клиентский порог совпадает с политикой Backset_password_screen.dart:43, :230-231

Рекомендация / варианты решения

  • Длина кода 4 vs 6 (Б1) — критично: если бэкенд шлёт 6-значные коды, Pinput на 4 ячейки авто-отправит первые 4 символа и валидация не пройдёт никогда. Вариант А (реком.): уточнить фактическую длину у Back (1.1.3) и привести _kCodeLength к ней, идеально — не хардкодить, а брать из ответа SendRestorePasswordCodeMobile. Вариант Б: если 4 верно — поправить глоссарий BRD (правит владелец BRD, не мы).
  • Копирайт и размещение ошибок (Б2, Б3) — Вариант А: для accountNotFound показывать явный «Пользователь не найден» под активным полем; для tooManyRequests на экране инициации ввести кулдаун-блокировку кнопки по timeout из ответа, как в модалке. Вариант Б: оставить как есть, если продукт согласен, что «не найден» и «лимит» покрываются серверным shortMessage без блокировки.
  • Клиентская политика пароля (Б4) — Вариант А: синхронизировать минимум/правила с Back и вынести в конфиг. Вариант Б: убрать клиентский порог, полагаясь на ответ SetNewPassword.

Программные

Связано сЧто в BRDЧто в кодеГде в коде
П1FR1.4-12При неверном коде — ошибка и повтор, если лимит попыток не был исчерпанinvalidCode → тряска + снек, поле очищается, повтор разрешён бесконечно; состояния «лимит попыток исчерпан» на валидации нет (enum содержит только ok/invalidCode/invalidSource/expiredCode/accountNotFound). Условие «если лимит не исчерпан» не реализованоrestore_password_result.dart:55-61; forgot_password_screen.dart:558-565
П2FR1.4-13При истёкшем коде — сообщить «код устарел» и предложить запросить новый после доступности повторной отправкиexpiredCode обрабатывается идентично invalidCode (тряска + серверный shortMessage); отдельного «код устарел» и проактивного предложения запросить новый нет — повторная отправка по-прежнему заблокирована таймером. Доп.: invalidSource не разбирается → generic «Ошибка проверки кода»forgot_password_screen.dart:558-580
П3FR1.4-24При отсутствии сети/техошибке — общее сообщение, повтор без потери контекста шагаКонтекст шага сохраняется (остаёмся на экране/в модалке), но пользователю показывается «очеловеченный», но сырой текст исключения (shortMessage/description с бэкенда), без общего «проблема с соединением, повторите»forgot_password_screen.dart:55-60, :150-154, :513-528
П4вне BRDВ репозитории два флоу восстановления: активный auth/forgot_password и мёртвый restore_passwordRestorePasswordScreen/RestorePasswordRoute зарегистрирован в роутере, но нигде не пушится. Дубль-реализация, риск расхождения при доработкахrestore_password/widget/resotre_password_screen.dart:11; core/utils/app_router.dart:305
П5вне BRDprint-логи в потоке восстановления печатают логин (PII) и полный JSON ответов, включающий JWT-токены (ValidateRestorePasswordCodeMobile/SetNewPassword) — в проде это утечка чувствительных данных в логиauth_repository.dart:235, :264, :306, :338

Рекомендация / варианты решения

  • Лимит попыток на валидации (П1) — Вариант А (реком.): согласовать с Back поведение при исчерпании попыток (доп. состояние/ошибка) и обработать его отдельно — блокировать ввод или предлагать новый код. Вариант Б: если лимит целиком на бэкенде — убрать оговорку «если лимит не исчерпан» из BRD (правит владелец BRD).
  • Истёкший код и invalidSource (П2) — Вариант А: развести ветки — для expiredCode показывать «Код устарел, запросите новый» и разблокировать/подсвечивать повторную отправку; для invalidSource — осмысленный текст. Вариант Б: подтвердить, что серверный shortMessage покрывает оба случая.
  • Текст техошибки (П3) — Вариант А: маппить транспортные/технические ошибки в единое дружелюбное сообщение, детали — в лог/Sentry. Вариант Б: оставить серверный shortMessage как есть.
  • Дубль-флоу (П4) — Вариант А (реком.): удалить мёртвый restore_password и снять RestorePasswordRoute из роутера. Вариант Б: оставить, если ветка планируется к использованию (тогда задокументировать назначение).
  • Логирование токенов/PII (П5) — Вариант А (реком.): удалить debug-print или загейтить за kDebugMode, из логов исключить токены и логин. Вариант Б: заменить на Sentry-breadcrumb без чувствительных полей.

0 items under this folder.