From 3e9d7d822c179f816f49ea6795188bec58896385 Mon Sep 17 00:00:00 2001 From: klockky Date: Sun, 29 Mar 2026 10:39:02 +0300 Subject: [PATCH] =?UTF-8?q?=D1=81=D1=83=D0=BA=D0=B0=20=D0=B6=D0=B8=D0=B6?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=B8=D0=B4=D0=BE=D1=80=D0=B0=D1=81=20=D0=B2?= =?UTF-8?q?=D0=BE=D1=82=20=D1=82=D0=B5=D0=B1=D0=B5=20=D0=BF=D0=B5=D0=B9?= =?UTF-8?q?=D0=BB=D0=BE=D0=B0=D0=B4=D1=8B=20=D1=81=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=BE=D0=BC=20=D0=BD=D0=B0=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BF=D0=BE=D0=B4=D1=82=D0=B2=D0=B5=D1=80=D0=B6=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/backend/modules/account.dart | 141 +++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/lib/backend/modules/account.dart b/lib/backend/modules/account.dart index e69de29..1baf5e8 100644 --- a/lib/backend/modules/account.dart +++ b/lib/backend/modules/account.dart @@ -0,0 +1,141 @@ +import '../api.dart'; +import '../../core/protocol/opcode_map.dart'; +import '../../core/protocol/packet.dart'; +import '../../core/utils/logger.dart'; + +enum AuthRequestType { + startAuth('START_AUTH'), + resend('RESEND'), + checkCode('CHECK_CODE'), + register('REGISTER'); + + const AuthRequestType(this.value); + final String value; +} + +class RequestCodeResult { + final String token; + + const RequestCodeResult({required this.token}); +} + +class VerifyCodeResult { + final Map payload; + + const VerifyCodeResult({required this.payload}); + + String? get loginToken => _nestedToken('LOGIN'); + + String? get registerToken => _nestedToken('REGISTER'); + + bool get requiresPassword => payload['passwordChallenge'] != null; + + Map? get passwordChallenge { + final c = payload['passwordChallenge']; + return c is Map ? c.cast() : null; + } + + String? _nestedToken(String key) { + final attrs = payload['tokenAttrs']; + if (attrs is! Map) return null; + final entry = attrs[key]; + if (entry is! Map) return null; + return entry['token'] as String?; + } +} + +/// Поток авторизации по номеру телефона: +/// 1. [requestCode] → сервер шлёт SMS, возвращает временный токен. +/// 2. [verifyCode] → клиент отправляет код + токен, получает токен сессии. +/// +/// При необходимости повторной отправки SMS используйте [resendCode]. +class AccountModule { + final Api _api; + + AccountModule(this._api); + + Future requestCode( + String phone, { + String language = 'ru', + }) => + _requestCodeInternal(phone, AuthRequestType.startAuth, language); + + Future resendCode( + String phone, { + String language = 'ru', + }) => + _requestCodeInternal(phone, AuthRequestType.resend, language); + + Future verifyCode(String code, String token) async { + _ensureOnline(); + + final payload = { + 'token': token, + 'verifyCode': code, + 'authTokenType': AuthRequestType.checkCode.value, + }; + + logger.i('Отправка OTP-кода (opcode=${Opcode.auth})'); + + final packet = await _api.sendRequest(Opcode.auth, payload); + + _checkPacketError(packet, 'verifyCode'); + + final data = packet.payload; + if (data is! Map) { + throw Exception('verifyCode: неожиданный тип payload: ${data.runtimeType}'); + } + + return VerifyCodeResult(payload: data.cast()); + } + + Future _requestCodeInternal( + String phone, + AuthRequestType type, + String language, + ) async { + _ensureOnline(); + + final payload = { + 'phone': phone, + 'type': type.value, + 'language': language, + }; + + logger.i('Запрос OTP-кода: phone=$phone type=${type.value}'); + + final packet = await _api.sendRequest(Opcode.authRequest, payload); + + _checkPacketError(packet, 'requestCode'); + + final data = packet.payload; + if (data is! Map) { + throw Exception('requestCode: неожиданный тип payload: ${data.runtimeType}'); + } + + final token = data['token']; + if (token is! String || token.isEmpty) { + throw Exception('requestCode: отсутствует token в ответе сервера'); + } + + logger.i('OTP-код запрошен, получен временный токен'); + return RequestCodeResult(token: token); + } + + void _ensureOnline() { + if (_api.state != SessionState.online) { + throw StateError( + 'AccountModule: сессия не онлайн (текущее состояние: ${_api.state.name})', + ); + } + } + + void _checkPacketError(Packet packet, String method) { + if (packet.isError) { + final errMsg = packet.payload is Map + ? (packet.payload as Map)['message'] ?? packet.payload.toString() + : packet.payload?.toString() ?? 'unknown error'; + throw Exception('$method: ошибка от сервера — $errMsg'); + } + } +}