Auth
Аутентификация: JWT, OTP, token refresh, nginx auth_request.
Компоненты¶
| Класс | Файл | Назначение |
|---|---|---|
EmailIdpEndpoint |
src/auth/email_idp_endpoint.dart |
Переопределяет хуки email IDP: логирует события, шлёт push-уведомления |
AuthValidateEndpoint |
src/endpoints/auth/auth_validate_endpoint.dart |
Валидирует JWT при refresh, возвращает ValidateResult |
NginxAuthRoute |
src/web/routes/nginx_auth_route.dart |
HTTP-эндпоинт /nginx/auth для auth_request nginx (мобильный пользователь) |
NginxAdminAuthRoute |
src/web/routes/nginx_auth_route.dart |
HTTP-эндпоинт /nginx/auth/admin для Admin Panel |
JwksRoute |
src/web/routes/jwks_route.dart |
Отдаёт JWKS для верификации launch-JWT мини-приложениями |
AuthEventService |
src/services/auth_event_service.dart |
Запись auth-событий в audit log |
JWT Flow¶
sequenceDiagram
App->>AC: login(email, password)
AC->>App: accessToken (TTL 30 мин) + refreshToken (TTL 14 дней)
Note over App: хранится в flutter_secure_storage
App->>AC: любой RPC (JWT в заголовке)
AC->>AC: verify JWT → resolve userId
App->>AC: refreshTokens() (по истечении accessToken)
AC->>App: новые токены
Значения TTL из server.dart:
- accessTokenLifetime: Duration(minutes: 30) — явно задан (Serverpod default: 10 мин)
- refreshTokenLifetime — не задан, используется default Serverpod 14 дней
AuthValidateEndpoint¶
Вызывается при каждом refreshTokens() со стороны клиента. Последовательность:
session.authenticated.userIdentifier→authUserId(UUID)- Поиск
UserпоauthUserId, фильтрisArchived=false - Блокирующие статусы:
registration_expired/blocked→NotAuthorizedException(insufficientAccess) - Загружает
KycVerification(дляkycStatus) - Загружает
UserProfile(дляdisplayNameиз расшифрованногоencryptedPii) - Обновляет
lastActivityAt→DateTime.now() - Загружает admin-роли через
admin_user_rolesJOINadmin_roles(graceful fallback[]до миграции) - Логирует
session_refreshсобытие черезAuthEventService - Возвращает
ValidateResult(userId, status, kycStatus, email, displayName, roles)
NginxAuthRoute¶
Путь: GET /nginx/auth. Используется nginx как auth_request.
| Код | Условие | Заголовки в ответе |
|---|---|---|
| 200 | JWT валиден, пользователь активен и не заблокирован | X-User-Id, X-User-Status, X-User-Email |
| 401 | JWT отсутствует / невалиден / пользователь не найден | — |
| 403 | status=registration_expired или user_profile.isLocked=true |
— |
Блокировка проверяется через UserProfile.isLocked, а не через поле User.
NginxAdminAuthRoute¶
Путь: GET /nginx/auth/admin. Требует admin-роль из admin_user_roles.
| Код | Условие | Заголовки в ответе |
|---|---|---|
| 200 | JWT валиден + роль из разрешённого набора | X-User-Id, X-User-Email, X-User-Role |
| 401 | JWT отсутствует / невалиден / пользователь не найден | — |
| 403 | status=registration_expired / нет роли / роль не в whitelist |
— |
Разрешённые роли: superadmin, operator, finance, support.
Graceful fallback: если таблицы admin_roles/admin_user_roles ещё не созданы (до миграции A-1), возвращает 403.
JwksRoute¶
- Путь:
GET /jwks.json - Содержимое: результат
LaunchJwtService.jwks()— публичный JWKS для верификации launch-JWT Cache-Control: public, max-age=86400(24 часа)- Мини-приложения кешируют ключи 24 часа, используют
kidиз заголовка токена при ротации ключей
EmailIdpEndpoint¶
Переопределяет следующие хуки базового класса EmailIdpBaseEndpoint:
| Хук | Действия |
|---|---|
login(email, password) |
Вызывает super.login(), затем: логирует sign_in событие (success/fail), шлёт push "Вход в аккаунт" (канал security, локаль из Accept-Language) |
finishRegistration(registrationToken, password) |
Вызывает super.finishRegistration(), логирует register событие |
startPasswordReset(email) |
Вызывает super.startPasswordReset(), логирует password_reset_request |
finishPasswordReset(token, newPassword) |
Вызывает super.finishPasswordReset(), логирует password_reset_complete, шлёт push "Пароль изменён" |
Push при login: MVP без детектора нового устройства — детектор запланирован на Phase 2.
При ошибке login: EmailLoginServerException перехватывается, событие логируется с success=false и failureReason, после чего исключение rethrow.