From fbee31841dcc24d1fdfabc6a6f6bfb705ad42291 Mon Sep 17 00:00:00 2001 From: Shivering Ocelot Date: Wed, 21 May 2025 22:27:35 -0500 Subject: [PATCH] Multiple improvements in authentication functionality --- frontend/lib/app_menu.dart | 19 +++ frontend/lib/backend.dart | 31 +++- frontend/lib/login_screen.dart | 28 +++- frontend/lib/user_checker.dart | 32 +++- .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 4 + frontend/pubspec.lock | 151 +++++++++++++++++- frontend/pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 11 files changed, 260 insertions(+), 17 deletions(-) diff --git a/frontend/lib/app_menu.dart b/frontend/lib/app_menu.dart index d8ba687..3c6133a 100644 --- a/frontend/lib/app_menu.dart +++ b/frontend/lib/app_menu.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'chat_list_screen.dart'; +import 'user_checker.dart'; +import 'backend.dart'; class AppMenu extends StatelessWidget { const AppMenu({super.key}); @@ -21,6 +24,22 @@ class AppMenu extends StatelessWidget { ); }, ), + ListTile( + title: const Text('Logout'), + onTap: () async { + final storage = const FlutterSecureStorage(); + await storage.delete(key: 'username'); + await storage.delete(key: 'password'); + Backend.disconnect(); + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => UserChecker(), + ), + ); + }, + ), // Add other settings options here ], ), diff --git a/frontend/lib/backend.dart b/frontend/lib/backend.dart index 5fca77d..2eda9d2 100644 --- a/frontend/lib/backend.dart +++ b/frontend/lib/backend.dart @@ -6,26 +6,46 @@ import 'dart:async'; // Import for handling Completers class Backend { static WebSocket? _socket; static String? _url; - static String? _username; static Map _pendingActions = {}; static Map>> _chatControllers = {}; static int _nextNonce = 0; // Tracking nonce for each action static StreamController> _packetStreamController = StreamController.broadcast(); + + // Authentication + static ({String username, String password})? _credentials; + static Completer? _loginCompleter; + static Completer? _credentialsCompleter; static void setup(String url) { _url = url; connect(); } - static void auth(String username, String password) { + static Future auth(String username, String password) { _socket!.send(json.encode({'username': username, 'password': password})); - _username = username; + _credentials = (username: username, password: password); + if (_credentialsCompleter != null) { + _credentialsCompleter!.complete(_credentials); + _credentialsCompleter = null; + } + _loginCompleter = Completer(); + return _loginCompleter!.future; } static void connect() { _socket = WebSocket(_url!); _socket!.onOpen.listen((event) { print('Connected to the WebSocket server'); + print('Logging in...'); + if (_credentials != null) { + _socket!.send(json.encode({'username': _credentials!.username, 'password': _credentials!.password})); + } else { + _credentialsCompleter = Completer(); + _credentialsCompleter!.future.then((credentials) { + _socket!.send(json.encode({'username': credentials.username, 'password': credentials.password})); + }); + } + _loginCompleter?.complete(); }); _socket!.onError.listen((event) { print('Error: $event'); @@ -40,6 +60,11 @@ class Backend { }); } + static void disconnect() { + _socket?.close(); + _loginCompleter = null; + } + static void _reconnect() { Future.delayed(Duration(seconds: 5), () { connect(); diff --git a/frontend/lib/login_screen.dart b/frontend/lib/login_screen.dart index 5be703d..edbbbd4 100644 --- a/frontend/lib/login_screen.dart +++ b/frontend/lib/login_screen.dart @@ -8,6 +8,13 @@ class LoginScreen extends StatelessWidget { final TextEditingController usernameController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); + final FocusNode usernameFocusNode = FocusNode(); + final FocusNode passwordFocusNode = FocusNode(); + + void _handleLogin() { + onLogin(usernameController.text, passwordController.text); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -18,18 +25,29 @@ class LoginScreen extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - TextField(controller: usernameController, decoration: InputDecoration(labelText: 'Username')), + TextField( + controller: usernameController, + focusNode: usernameFocusNode, + decoration: InputDecoration(labelText: 'Username'), + textInputAction: TextInputAction.next, + onSubmitted: (_) { + FocusScope.of(context).requestFocus(passwordFocusNode); + }, + ), SizedBox(height: 20), TextField( controller: passwordController, + focusNode: passwordFocusNode, decoration: InputDecoration(labelText: 'Password'), - obscureText: true, // Hide password characters + obscureText: true, + textInputAction: TextInputAction.done, + onSubmitted: (_) { + _handleLogin(); + }, ), SizedBox(height: 20), ElevatedButton( - onPressed: () { - onLogin(usernameController.text, passwordController.text); - }, + onPressed: _handleLogin, child: Text('Login'), ), ], diff --git a/frontend/lib/user_checker.dart b/frontend/lib/user_checker.dart index 31de775..b8e79ab 100644 --- a/frontend/lib/user_checker.dart +++ b/frontend/lib/user_checker.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'backend.dart'; import 'login_screen.dart'; import 'chat_screen.dart'; @@ -11,12 +12,26 @@ class UserChecker extends StatefulWidget { class _UserCheckerState extends State { String? username; - bool isLoading = false; + bool isLoading = true; + final _storage = const FlutterSecureStorage(); @override void initState() { super.initState(); - username = null; + _checkSavedCredentials(); + } + + Future _checkSavedCredentials() async { + final savedUsername = await _storage.read(key: 'username'); + final savedPassword = await _storage.read(key: 'password'); + + if (savedUsername != null && savedPassword != null) { + _handleLogin(savedUsername, savedPassword); + } else { + setState(() { + isLoading = false; + }); + } } static void fetchChats(Function(List) onSuccess) { @@ -28,13 +43,15 @@ class _UserCheckerState extends State { }); } - void _handleLogin(String user, String password) { - Backend.auth(user, password); - + void _handleLogin(String user, String password) async { setState(() { username = user; - isLoading = true; // Show loading spinner + isLoading = true; }); + + await Backend.auth(user, password); + await _storage.write(key: 'username', value: user); + await _storage.write(key: 'password', value: password); fetchChats((chatIds) { if (chatIds.isEmpty) { @@ -49,14 +66,13 @@ class _UserCheckerState extends State { ); } }); - } @override Widget build(BuildContext context) { print("Build UserChecker: $username $isLoading"); if (isLoading) { - return Center(child: CircularProgressIndicator()); // Loading state UI + return Center(child: CircularProgressIndicator()); } return LoginScreen(onLogin: _handleLogin); diff --git a/frontend/linux/flutter/generated_plugin_registrant.cc b/frontend/linux/flutter/generated_plugin_registrant.cc index e71a16d..d0e7f79 100644 --- a/frontend/linux/flutter/generated_plugin_registrant.cc +++ b/frontend/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); } diff --git a/frontend/linux/flutter/generated_plugins.cmake b/frontend/linux/flutter/generated_plugins.cmake index 2e1de87..b29e9ba 100644 --- a/frontend/linux/flutter/generated_plugins.cmake +++ b/frontend/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..15a1671 100644 --- a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,10 @@ import FlutterMacOS import Foundation +import flutter_secure_storage_macos +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index d993b91..6942cc2 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" flutter: dependency: "direct main" description: flutter @@ -70,11 +78,72 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" leak_tracker: dependency: transitive description: @@ -139,6 +208,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -208,6 +341,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + url: "https://pub.dev" + source: hosted + version: "5.13.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.27.0" diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 3480227..de1483f 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -35,6 +35,9 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + # For user credentials and sensitive data + flutter_secure_storage: ^9.2.4 + dev_dependencies: flutter_test: sdk: flutter diff --git a/frontend/windows/flutter/generated_plugin_registrant.cc b/frontend/windows/flutter/generated_plugin_registrant.cc index 8b6d468..0c50753 100644 --- a/frontend/windows/flutter/generated_plugin_registrant.cc +++ b/frontend/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); } diff --git a/frontend/windows/flutter/generated_plugins.cmake b/frontend/windows/flutter/generated_plugins.cmake index b93c4c3..4fc759c 100644 --- a/frontend/windows/flutter/generated_plugins.cmake +++ b/frontend/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST