import 'package:chepuhagram/data/datasources/local_db_service.dart'; import 'package:flutter/material.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; import 'dart:io'; import '/core/constants.dart'; import 'package:http/http.dart' as http; import 'package:chepuhagram/domain/services/api_service.dart'; import 'package:chepuhagram/data/datasources/ws_client.dart'; import 'package:chepuhagram/domain/services/crypto_service.dart'; import 'package:chepuhagram/logic/contact_provider.dart'; import 'package:provider/provider.dart'; import 'package:path/path.dart' as p; import 'package:chepuhagram/data/models/session_model.dart'; import 'package:chepuhagram/presentation/screens/splash_screen.dart'; import 'package:chepuhagram/presentation/screens/login_screen.dart'; import 'package:chepuhagram/main.dart'; class AuthProvider extends ChangeNotifier { bool _isLoading = false; bool get isLoading => _isLoading; String? _error; String? get error => _error; int? _currentUserId; int? get currentUserId => _currentUserId; String? _username; String? get username => _username; String? _firstName; String? get firstName => _firstName; String? _lastName; String? get lastName => _lastName; String? _phone; String? get phone => _phone; String? _email; String? get email => _email; String? _about; String? get about => _about; String? _avatarPath; String? get avatarPath => _avatarPath; String? _avatarUrl; String? get avatarUrl => _avatarUrl; // Privacy settings bool? _showEmail; bool? get showEmail => _showEmail; bool? _showPhone; bool? get showPhone => _showPhone; bool? _showAvatar; bool? get showAvatar => _showAvatar; bool? _showAbout; bool? get showAbout => _showAbout; bool? _showUsername; bool? get showUsername => _showUsername; List _sessions = []; List get sessions => _sessions; String get displayName { final full = '${_firstName ?? ''} ${_lastName ?? ''}'.trim(); if (full.isNotEmpty) return full; if ((_username ?? '').isNotEmpty) return _username!; return 'User'; } // Флаги для определения пути пользователя bool _needsSetup = false; bool get needsSetup => _needsSetup; bool _needsKeyRecovery = false; bool get needsKeyRecovery => _needsKeyRecovery; bool _hasPublicKeyOnServer = false; bool get hasPublicKeyOnServer => _hasPublicKeyOnServer; final _storage = const FlutterSecureStorage(); FlutterSecureStorage get storage => _storage; final _client = http.Client(); final ApiService _apiService = ApiService(); final SocketService _socketService = SocketService(); final CryptoService _cryptoService = CryptoService(); void clearError() { _error = null; notifyListeners(); } Future initRealtime() async { try { await _socketService.connect(_apiService); _socketService.messages.listen((message) async { if (message['type'] == 'session_terminated') { print('Сессия завершена'); await logout(); } }); } catch (e) { throw Exception(e); } } Future logout() async { if (_isLoading) return; _isLoading = true; notifyListeners(); // 1. Отзываем токен на сервере try { await ApiService().logoutCurrentUser(); } catch (e) { print("Сервер не ответил на logout: $e"); } // 2. Отключаем WebSocket connection try { SocketService().disconnect(); } catch (e) { print("Ошибка при отключении сокета: $e"); } String? mode; String? color; try { mode = await _storage.read(key: 'theme_mode'); color = await _storage.read(key: 'accent_color'); } catch (e) { print("Ошибка чтения настроек темы: $e"); } try { await _storage.deleteAll(); } catch (e) { print("Ошибка очистки SecureStorage: $e"); // Пытаемся удалить хотя бы авторизационные токены try { await _storage.delete(key: 'access_token'); await _storage.delete(key: 'refresh_token'); await _storage.delete(key: 'private_key'); } catch (ex) { print("Ошибка удаления отдельных токенов: $ex"); } } try { CryptoService.clearCache(); } catch (e) { print("Ошибка очистки кэша криптографии: $e"); } final context = navigatorKey.currentContext; if (context != null) { try { Provider.of(context, listen: false).clearCache(); } catch (e) { print("Error clearing contact provider cache: $e"); } } try { final prefs = await SharedPreferences.getInstance(); await prefs.clear(); } catch (e) { print("Ошибка очистки SharedPreferences: $e"); } try { await LocalDbService().closeDatabase(); } catch (e) { print("Ошибка закрытия локальной базы данных: $e"); } _currentUserId = null; _username = null; _firstName = null; _lastName = null; _phone = null; _email = null; _about = null; _avatarPath = null; _avatarUrl = null; try { if (mode != null) { await _storage.write(key: 'theme_mode', value: mode); } if (color != null) { await _storage.write(key: 'accent_color', value: color); } } catch (e) { print("Ошибка записи настроек темы: $e"); } // 3. Уничтожаем локальную базу данных переписки (Drift) try { final dbFolder = await databaseFactory.getDatabasesPath(); final dbFile = File(p.join(dbFolder, 'chat_app.db')); final journalFile = File(p.join(dbFolder, 'chat_app.db-journal')); final walFile = File(p.join(dbFolder, 'chat_app.db-wal')); final shmFile = File(p.join(dbFolder, 'chat_app.db-shm')); // Даем операционной системе немного времени на освобождение дескрипторов после закрытия Drift await Future.delayed(const Duration(milliseconds: 300)); Future safeDelete(File file) async { if (await file.exists()) { for (int i = 0; i < 5; i++) { try { await file.delete(); print("Файл ${file.path} успешно удален"); return; } catch (e) { if (i == 4) { // Если не удалось за 5 попыток, пробуем через native deleteDatabase try { await databaseFactory.deleteDatabase(file.path); print("Файл ${file.path} удален через databaseFactory"); return; } catch (ex) { print("Не удалось удалить файл после 5 попыток: $ex"); rethrow; } } await Future.delayed(const Duration(milliseconds: 200)); } } } } await safeDelete(dbFile); await safeDelete(journalFile); await safeDelete(walFile); await safeDelete(shmFile); print("БАЗА ДАННЫХ УСПЕШНО УНИЧТОЖЕНА В ЦЕЛЯХ БЕЗОПАСНОСТИ"); } catch (e) { print("Ошибка удаления файлов БД: $e"); } _isLoading = false; notifyListeners(); // Перенаправляем напрямую на LoginScreen во избежание циклического автологина navigatorKey.currentState?.pushAndRemoveUntil( PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => const LoginScreen(), transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition(opacity: animation, child: child); }, ), (route) => false, ); } Future fetchSessions({bool silent = false}) async { if (!silent) { _isLoading = true; _error = null; notifyListeners(); } try { final data = await _apiService.getActiveSessions(); print('Fetched sessions: $data'); _sessions = data.map((json) => Session.fromJson(json)).toList(); _error = null; } catch (e) { if (!silent) { _error = 'Произошла ошибка при загрузке сессий: $e'; } print('Ошибка при загрузке сессий: $e'); } finally { if (!silent) { _isLoading = false; } notifyListeners(); } } Future revokeSession(int sessionId) async { try { final success = await _apiService.revokeSession(sessionId); if (success) { await fetchSessions(); return true; } else { _error = 'Не удалось завершить сессию.'; notifyListeners(); return false; } } catch (e) { _error = 'Произошла ошибка при завершении сессии: $e'; notifyListeners(); return false; } } Future revokeAllOtherSessions() async { try { final success = await _apiService.clearOtherSessions(); if (success) { await fetchSessions(); return true; } else { _error = 'Не удалось завершить другие сессии.'; notifyListeners(); return false; } } catch (e) { _error = 'Произошла ошибка при завершении других сессий: $e'; notifyListeners(); return false; } } void closeRealtime() { _socketService.disconnect(); } SocketService get socketService => _socketService; Future login( String username, String password, { String? totpCode, }) async { _isLoading = true; notifyListeners(); try { final deviceName = await _apiService.getDeviceName(); final body = { 'username': username, 'password': password, 'device_name': deviceName, }; if (totpCode != null) { body['totp_code'] = totpCode; } final response = await _client.post( Uri.parse('${AppConstants.baseUrl}/auth/login'), headers: { 'Content-Type': 'application/json', 'X-Device-Name': deviceName, }, body: jsonEncode(body), ); final decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; if (response.statusCode == 200) { await _storage.write( key: 'access_token', value: decodedResponse['access_token'], ); await _storage.write( key: 'refresh_token', value: decodedResponse['refresh_token'], ); await _storage.write( key: 'user_id', value: decodedResponse['user_id'].toString(), ); _currentUserId = decodedResponse['user_id']; // Проверяем статус аккаунта (нужна ли настройка или восстановление) await _checkAccountStatus(); _isLoading = false; notifyListeners(); return true; } else { _isLoading = false; notifyListeners(); final error = decodedResponse['detail'] ?? 'Ошибка запроса'; throw Exception(error); } } catch (e) { _isLoading = false; notifyListeners(); rethrow; } } Future loginWithQrData({ required String accessToken, required String refreshToken, required String userId, required String privateKey, }) async { _isLoading = true; notifyListeners(); try { await _storage.write(key: 'access_token', value: accessToken); await _storage.write(key: 'refresh_token', value: refreshToken); await _storage.write(key: 'user_id', value: userId); await _storage.write(key: 'private_key', value: privateKey); _currentUserId = int.tryParse(userId); await _checkAccountStatus(); } catch (e) { print("Ошибка авторизации по QR: $e"); rethrow; } finally { _isLoading = false; notifyListeners(); } } Future tryAutoLogin() async { String? token; try { token = await _apiService.getAccessToken(forceRefresh: true); } catch (e) { throw Exception('$e+_aup_tal_1'); } if (token == null) return false; // Загружаем currentUserId из хранилища /*final userIdStr = await _storage.read(key: 'user_id'); if (userIdStr != null) { _currentUserId = int.tryParse(userIdStr); } try { final response = await _client .get( Uri.parse('${AppConstants.baseUrl}/users/me'), headers: {'Authorization': 'Bearer $token'}, ) .timeout(const Duration(seconds: 5)); if (response.statusCode == 200) { // Проверяем статус аккаунта для определения дальнейшего пути await _checkAccountStatus(); return true; } else if (response.statusCode == 401) { bool isUpdated = await _apiService.refreshToken(); if (isUpdated) { // После обновления токена проверяем статус await _checkAccountStatus(); } return isUpdated; } else { return false; } } catch (e) { // Если сервер недоступен, позволяем offline mode return true; }*/ return true; } Future setupAccount( String firstName, String? lastName, String masterPassword, { String? phone, String? email, String? about, }) async { _isLoading = true; notifyListeners(); try { final token = await _apiService.getAccessToken(); // Генерируем ключи и шифруем приватный final keys = await _cryptoService.initAccountSecurity(masterPassword); final response = await _client.post( Uri.parse('${AppConstants.baseUrl}/auth/setup-account'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $token', }, body: jsonEncode({ 'first_name': firstName, 'last_name': lastName, 'public_key': keys['public_key'], 'encrypted_private_key': keys['encrypted_private_key'], }), ); if (response.statusCode == 200) { final String currentUsername = _username ?? ''; await _apiService.updateMe( username: currentUsername, firstName: firstName, lastName: lastName ?? '', phone: phone, email: email, about: about, ); _needsSetup = false; await _checkAccountStatus(); notifyListeners(); return true; } else { print("Ошибка настройки профиля: ${response.body}"); return false; } } catch (e) { print("Ошибка сети: $e"); return false; } finally { _isLoading = false; notifyListeners(); } } // Приватный метод для проверки статуса аккаунта Future _checkAccountStatus() async { try { final token = await _apiService.getAccessToken(); final response = await _client.get( Uri.parse('${AppConstants.baseUrl}/users/me'), headers: {'Authorization': 'Bearer $token'}, ); if (response.statusCode == 200) { final data = jsonDecode(utf8.decode(response.bodyBytes)) as Map; _currentUserId = data['id'] as int?; _username = data['username']?.toString(); _firstName = data['first_name']?.toString(); _lastName = data['last_name']?.toString(); _phone = data['phone']?.toString(); _email = data['email']?.toString(); _about = data['about']?.toString(); final avatarFileId = data['avatar_file_id']?.toString(); _avatarUrl = avatarFileId != null ? '${AppConstants.baseUrl}/media/$avatarFileId' : null; // Загружаем локальные настройки _avatarPath = await _storage.read(key: 'avatar_path'); // Проверяем наличие публичного ключа на сервере _hasPublicKeyOnServer = data['public_key'] != null && data['public_key'].isNotEmpty; // Проверяем наличие приватного ключа локально final hasLocalPrivateKey = await _storage.read(key: 'private_key') != null; if (!_hasPublicKeyOnServer) { // Путь А: Первая настройка - нужно создать ключи и профиль _needsSetup = true; _needsKeyRecovery = false; } else if (!hasLocalPrivateKey) { // Путь В: Переустановка - ключ на сервере, но его нет локально _needsKeyRecovery = true; _needsSetup = false; } else { // Путь Б: Нормальный вход - все в порядке _needsSetup = false; _needsKeyRecovery = false; } } // Загружаем настройки конфиденциальности try { final privacyData = await _apiService.getPrivacySettings(); _showEmail = privacyData['show_email'] as bool?; _showPhone = privacyData['show_phone'] as bool?; _showAvatar = privacyData['show_avatar'] as bool?; _showAbout = privacyData['show_about'] as bool?; _showUsername = privacyData['show_username'] as bool?; } catch (e) { print("Ошибка загрузки настроек конфиденциальности: $e"); // Устанавливаем значения по умолчанию _showEmail = true; _showPhone = true; _showAvatar = true; _showAbout = true; _showUsername = true; } } catch (e) { print("Ошибка проверки статуса: $e"); _needsSetup = false; _needsKeyRecovery = false; } notifyListeners(); } Future refreshMe() async { await _checkAccountStatus(); } // Метод для начала с чистого листа (новые ключи) Future resetKeys() async { try { await ApiService().clearOtherSessions(); } catch (e) { print("Error clearing other sessions on key reset: $e"); } await _storage.delete(key: 'private_key'); try { final allKeys = await _storage.readAll(); for (final key in allKeys.keys) { if (key.startsWith('contact_shared_key_') || key.startsWith('contact_public_key_')) { await _storage.delete(key: key); } } } catch (e) { print("Error clearing cached contact keys in secure storage: $e"); } CryptoService.clearCache(); final context = navigatorKey.currentContext; if (context != null) { try { Provider.of(context, listen: false).clearCache(); } catch (_) {} } _needsKeyRecovery = false; notifyListeners(); } void updateAvatarPath(String? path) { _avatarPath = path; if (path != null) { _storage.write(key: 'avatar_path', value: path); } else { _storage.delete(key: 'avatar_path'); } notifyListeners(); } Future updateAvatar(String path) async { try { final bytes = await File(path).readAsBytes(); final fileId = await _apiService.uploadFile(bytes, purpose: 'avatar'); if (fileId != null) { final success = await _apiService.updateAvatar(fileId); if (success) { updateAvatarPath(path); await refreshMe(); // Обновить данные профиля, включая avatarUrl return true; } } return false; } catch (e) { print('Ошибка обновления аватарки: $e'); return false; } } Future updateAvatarForUser(String path, int userId) async { try { final bytes = await File(path).readAsBytes(); final fileId = await _apiService.uploadFile(bytes, purpose: 'avatar'); if (fileId != null) { final success = await _apiService.updateAvatarForUser(fileId, userId); if (success) { updateAvatarPath(path); await refreshMe(); // Обновить данные профиля, включая avatarUrl return true; } } return false; } catch (e) { print('Ошибка обновления аватарки: $e'); return false; } } }