apps/user-service · Техническая документация
реализовано, e2e зелёный: 113+77+31

Единый identity-флоу вместо парных резолверов

Парные резолверы registerByPhone/registerByEmail и аналогичные им удалены полностью и заменены единым контрактом: register/confirmRegister/login/ requestPasswordRecovery/confirmPasswordRecovery/verificationPeek. Канал (PHONE/EMAIL) определяется бэкендом по формату поля identity.

verificationId

Ядро решения — непрозрачный подписанный JWT (JWT_EMAIL_CONFIRM_KEY), ничего не хранящий на сервере. Для email-канала это тот же артефакт, что раньше назывался token (ссылка в письме); для phone-канала — новый JWT, оборачивающий номер телефона. Сам SMS-код по-прежнему хранится в Redis notify-service по номеру телефона — это не менялось. Claims: purpose, channel, ровно одно из email/phone, опционально name/redirect/userId, iat/exp.

Схема мутаций

Было (2 резолвера)Стало (1 резолвер)Канал определяет
registerByPhone / registerByEmailregisteridentity
confirmRegisterByPhone / ByEmailconfirmRegisterverificationId
loginByPhone / loginByEmailloginidentity
requestPasswordRecoveryBy*requestPasswordRecoveryidentity
confirmPasswordRecoveryBy*confirmPasswordRecoveryverificationId
phoneCodeIsValid + emailTokenGetStateverificationPeekverificationId

Старые резолверы удалены полностью, без deprecated-обёрток — стиль проекта: чистый рефакторинг, не shim'ы.

VerificationService — аддитивные изменения

Три новых публичных метода, построенных поверх существующих sendPhoneCode/confirmPhoneCode/ peekPhoneCode/confirmEmailLink:

Карта файлов

Backend (apps/user-service)

Frontend (apps/shared, apps/front, apps/admin)

Безопасность

Найдено попутно, не в рамках задачи

SMS-коды в Redis notify-service хранятся по номеру телефона без TTL и без purpose-метки — код от REGISTER теоретически может подтвердить RECOVER для того же номера в окне до перезаписи. Не исправлено сейчас (notify-service вне рамок), зафиксировано как рекомендация на будущее.

OAuth (соцвход) намеренно не встроен в verificationId/VerificationChannel — это закрытый union EMAIL|PHONE. Под соцвход зарезервирована отдельная будущая мутация loginWithProvider(provider, authorizationCode, redirect), переиспользующая только loginAndSetToken.

Не в рамках задачи

secured-fields.resolver.ts (смена телефона/почты уже авторизованным пользователем, requestPhoneChange/confirmPhoneChange/requestEmailChange/changeMyPass) не переделывался — публичные методы VerificationService, которыми он пользуется, сохранили сигнатуры.

Связанные документы