diff --git a/lib/clients/api_client.dart b/lib/clients/api_client.dart index 43f6a5a..586886c 100644 --- a/lib/clients/api_client.dart +++ b/lib/clients/api_client.dart @@ -15,6 +15,7 @@ class ApiClient { static const String machineIdKey = "machineId"; static const String tokenKey = "token"; static const String passwordKey = "password"; + static const String uidKey = "uid"; ApiClient({required AuthenticationData authenticationData, required this.onLogout}) : _authenticationData = authenticationData; @@ -48,12 +49,12 @@ class ApiClient { "rememberMe": rememberMe, "secretMachineId": const Uuid().v4(), }; + final uid = const Uuid().v4().replaceAll("-", ""); final response = await http.post( buildFullUri("/userSessions"), headers: { "Content-Type": "application/json", - "SecretClientAccessKey": Config.secretClientKey, - "UID":"2cde2bd72c104d1785af28ae77c29fc2", + "UID": uid, if (oneTimePad != null) totpKey: oneTimePad, }, body: jsonEncode(body), @@ -65,8 +66,9 @@ class ApiClient { throw "Invalid Credentials"; } checkResponseCode(response); - - final authData = AuthenticationData.fromMap(jsonDecode(response.body)); + final data = jsonDecode(response.body); + data["entity"]["uid"] = uid; + final authData = AuthenticationData.fromMap(data); if (authData.isAuthenticated) { const FlutterSecureStorage storage = FlutterSecureStorage( aOptions: AndroidOptions(encryptedSharedPreferences: true), @@ -74,6 +76,7 @@ class ApiClient { await storage.write(key: userIdKey, value: authData.userId); await storage.write(key: machineIdKey, value: authData.secretMachineIdHash); await storage.write(key: tokenKey, value: authData.token); + await storage.write(key: uidKey, value: authData.uid); if (rememberPass) await storage.write(key: passwordKey, value: password); } return authData; @@ -87,19 +90,25 @@ class ApiClient { String? machineId = await storage.read(key: machineIdKey); String? token = await storage.read(key: tokenKey); String? password = await storage.read(key: passwordKey); + String? uid = await storage.read(key: uidKey); - if (userId == null || machineId == null) { + if (userId == null || machineId == null || uid == null) { return AuthenticationData.unauthenticated(); } if (token != null) { final response = await http.patch(buildFullUri("/userSessions"), headers: { "Authorization": "res $userId:$token", - "UID":"2cde2bd72c104d1785af28ae77c29fc2", - "SecretClientAccessKey": Config.secretClientKey, + "UID": uid, }); if (response.statusCode < 300) { - return AuthenticationData(userId: userId, token: token, secretMachineIdHash: machineId, isAuthenticated: true); + return AuthenticationData( + userId: userId, + token: token, + secretMachineIdHash: machineId, + isAuthenticated: true, + uid: uid, + ); } } diff --git a/lib/config.dart b/lib/config.dart index 6f5a576..9d33194 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -2,7 +2,6 @@ class Config { static const String apiBaseUrl = "https://api.resonite.com"; static const String skyfrostAssetsUrl = "https://assets.resonite.com"; static const String resoniteHubUrl = "$apiBaseUrl/hub"; - static const String secretClientKey = ""; static const int messageCacheValiditySeconds = 90; diff --git a/lib/models/authentication_data.dart b/lib/models/authentication_data.dart index 1f24e32..fc37e76 100644 --- a/lib/models/authentication_data.dart +++ b/lib/models/authentication_data.dart @@ -1,40 +1,53 @@ import 'package:contacts_plus_plus/config.dart'; +import 'package:uuid/uuid.dart'; class AuthenticationData { - static const _unauthenticated = AuthenticationData(userId: "", token: "", secretMachineIdHash: "", isAuthenticated: false); + static const _unauthenticated = AuthenticationData( + userId: "", + token: "", + secretMachineIdHash: "", + isAuthenticated: false, + uid: "", + ); final String userId; final String token; final String secretMachineIdHash; final bool isAuthenticated; + final String uid; const AuthenticationData({ - required this.userId, required this.token, required this.secretMachineIdHash, required this.isAuthenticated + required this.userId, + required this.token, + required this.secretMachineIdHash, + required this.isAuthenticated, + required this.uid, }); - + factory AuthenticationData.fromMap(Map map) { map = map["entity"]; final userId = map["userId"]; final token = map["token"]; final machineId = map["secretMachineIdHash"]; - if (userId == null || token == null || machineId == null) { + final uid = map["uid"]; + if (userId == null || token == null || machineId == null || uid == null) { return _unauthenticated; } - return AuthenticationData(userId: userId, token: token, secretMachineIdHash: machineId, isAuthenticated: true); + return AuthenticationData(userId: userId, token: token, secretMachineIdHash: machineId, isAuthenticated: true, uid: uid); } factory AuthenticationData.unauthenticated() => _unauthenticated; Map get authorizationHeader => { - "Authorization": "res $userId:$token", - "SecretClientAccessKey": Config.secretClientKey, - "UID":"2cde2bd72c104d1785af28ae77c29fc2", - }; + "Authorization": "res $userId:$token", + "UID": uid, + }; Map toMap() { return { "userId": userId, "token": token, "secretMachineId": secretMachineIdHash, + "uid": uid, }; } -} \ No newline at end of file +} diff --git a/lib/models/session.dart b/lib/models/session.dart index b501731..e05d9c3 100644 --- a/lib/models/session.dart +++ b/lib/models/session.dart @@ -60,7 +60,7 @@ class Session { id: map["sessionId"], name: map["name"], sessionUsers: (map["sessionUsers"] as List? ?? []).map((entry) => SessionUser.fromMap(entry)).toList(), - thumbnail: map["thumbnail"] ?? "", + thumbnail: map["thumbnailUrl"] ?? "", maxUsers: map["maxUsers"] ?? 0, hasEnded: map["hasEnded"] ?? false, isValid: map["isValid"] ?? true, diff --git a/lib/widgets/login_screen.dart b/lib/widgets/login_screen.dart index ac147d6..bd1b3ce 100644 --- a/lib/widgets/login_screen.dart +++ b/lib/widgets/login_screen.dart @@ -1,5 +1,6 @@ import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/models/authentication_data.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:contacts_plus_plus/client_holder.dart'; @@ -73,7 +74,7 @@ class _LoginScreenState extends State { _isLoading = false; }); await loginSuccessful(authData); - } catch (e) { + } catch (e, s) { setState(() { if (e == ApiClient.totpKey) { if (_needsTotp == false) { @@ -81,9 +82,7 @@ class _LoginScreenState extends State { _totpFocusNode.requestFocus(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { _scrollController.animateTo(_scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 400), - curve: Curves.easeOutCirc - ); + duration: const Duration(milliseconds: 400), curve: Curves.easeOutCirc); }); } else { _error = "The given 2FA code is not valid."; @@ -92,15 +91,19 @@ class _LoginScreenState extends State { } else { _error = "Login unsuccessful: $e."; } + if (kDebugMode) { + FlutterError.reportError(FlutterErrorDetails( + exception: e, + stack: s, + )); + } _isLoading = false; }); } } Future loginSuccessful(AuthenticationData authData) async { - final settingsClient = ClientHolder - .of(context) - .settingsClient; + final settingsClient = ClientHolder.of(context).settingsClient; final notificationManager = FlutterLocalNotificationsPlugin(); if (settingsClient.currentSettings.notificationsDenied.value == null) { if (context.mounted) { @@ -114,19 +117,19 @@ class _LoginScreenState extends State { TextButton( onPressed: () async { Navigator.of(context).pop(); - await settingsClient.changeSettings( - settingsClient.currentSettings.copyWith(notificationsDenied: true)); + await settingsClient + .changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: true)); }, child: const Text("No"), ), TextButton( onPressed: () async { Navigator.of(context).pop(); - final requestResult = await notificationManager.resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() + final requestResult = await notificationManager + .resolvePlatformSpecificImplementation() ?.requestPermission(); - await settingsClient.changeSettings(settingsClient.currentSettings.copyWith( - notificationsDenied: requestResult == null ? false : !requestResult)); + await settingsClient.changeSettings(settingsClient.currentSettings + .copyWith(notificationsDenied: requestResult == null ? false : !requestResult)); }, child: const Text("Yes"), ) @@ -145,95 +148,84 @@ class _LoginScreenState extends State { appBar: AppBar( title: const Text("Contacts++"), ), - body: Builder( - builder: (context) { - return ListView( - controller: _scrollController, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 64), - child: Center( - child: Text("Sign In", style: Theme - .of(context) - .textTheme - .headlineMedium), + body: Builder(builder: (context) { + return ListView( + controller: _scrollController, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 64), + child: Center( + child: Text("Sign In", style: Theme.of(context).textTheme.headlineMedium), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), + child: TextField( + controller: _usernameController, + onEditingComplete: () => _passwordFocusNode.requestFocus(), + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32), + ), + labelText: 'Username', + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), + child: TextField( + controller: _passwordController, + focusNode: _passwordFocusNode, + onEditingComplete: submit, + obscureText: true, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(32)), + labelText: 'Password', + ), + ), + ), + if (_needsTotp) + Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), + child: TextField( + controller: _totpController, + focusNode: _totpFocusNode, + onEditingComplete: submit, + obscureText: false, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32), + ), + labelText: '2FA Code', ), ), - Padding( + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: _isLoading + ? const Center(child: CircularProgressIndicator()) + : TextButton.icon( + onPressed: submit, + icon: const Icon(Icons.login), + label: const Text("Login"), + ), + ), + Center( + child: AnimatedOpacity( + opacity: _errorOpacity, + duration: const Duration(milliseconds: 200), + child: Padding( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), - child: TextField( - controller: _usernameController, - onEditingComplete: () => _passwordFocusNode.requestFocus(), - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(32), - ), - labelText: 'Username', - ), - ), + child: Text(_error, style: Theme.of(context).textTheme.labelMedium?.copyWith(color: Colors.red)), ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), - child: TextField( - controller: _passwordController, - focusNode: _passwordFocusNode, - onEditingComplete: submit, - obscureText: true, - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(32) - ), - labelText: 'Password', - ), - ), - ), - if (_needsTotp) - Padding( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), - child: TextField( - controller: _totpController, - focusNode: _totpFocusNode, - onEditingComplete: submit, - obscureText: false, - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(32), - ), - labelText: '2FA Code', - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 16), - child: _isLoading ? - const Center(child: CircularProgressIndicator()) : - TextButton.icon( - onPressed: submit, - icon: const Icon(Icons.login), - label: const Text("Login"), - ), - ), - Center( - child: AnimatedOpacity( - opacity: _errorOpacity, - duration: const Duration(milliseconds: 200), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), - child: Text(_error, style: Theme - .of(context) - .textTheme - .labelMedium - ?.copyWith(color: Colors.red)), - ), - ), - ) - ], - ); - } - ), + ), + ) + ], + ); + }), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/messages/messages_list.dart b/lib/widgets/messages/messages_list.dart index 3e5b8a1..b8deda8 100644 --- a/lib/widgets/messages/messages_list.dart +++ b/lib/widgets/messages/messages_list.dart @@ -6,6 +6,7 @@ import 'package:contacts_plus_plus/widgets/friends/friend_online_status_indicato import 'package:contacts_plus_plus/widgets/messages/message_input_bar.dart'; import 'package:contacts_plus_plus/widgets/messages/messages_session_header.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'message_bubble.dart'; @@ -60,6 +61,9 @@ class _MessagesListState extends State with SingleTickerProviderSt return Scaffold( appBar: AppBar( + systemOverlayStyle: SystemUiOverlayStyle( + systemNavigationBarColor: Theme.of(context).colorScheme.background, + ), title: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [