Remove client secret dependecy and fix session thumbnails

This commit is contained in:
Nutcake 2023-09-30 11:15:17 +02:00
parent e3837066bb
commit 6e83ce32f8
6 changed files with 136 additions and 119 deletions

View file

@ -15,6 +15,7 @@ class ApiClient {
static const String machineIdKey = "machineId"; static const String machineIdKey = "machineId";
static const String tokenKey = "token"; static const String tokenKey = "token";
static const String passwordKey = "password"; static const String passwordKey = "password";
static const String uidKey = "uid";
ApiClient({required AuthenticationData authenticationData, required this.onLogout}) ApiClient({required AuthenticationData authenticationData, required this.onLogout})
: _authenticationData = authenticationData; : _authenticationData = authenticationData;
@ -48,12 +49,12 @@ class ApiClient {
"rememberMe": rememberMe, "rememberMe": rememberMe,
"secretMachineId": const Uuid().v4(), "secretMachineId": const Uuid().v4(),
}; };
final uid = const Uuid().v4().replaceAll("-", "");
final response = await http.post( final response = await http.post(
buildFullUri("/userSessions"), buildFullUri("/userSessions"),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"SecretClientAccessKey": Config.secretClientKey, "UID": uid,
"UID":"2cde2bd72c104d1785af28ae77c29fc2",
if (oneTimePad != null) totpKey: oneTimePad, if (oneTimePad != null) totpKey: oneTimePad,
}, },
body: jsonEncode(body), body: jsonEncode(body),
@ -65,8 +66,9 @@ class ApiClient {
throw "Invalid Credentials"; throw "Invalid Credentials";
} }
checkResponseCode(response); checkResponseCode(response);
final data = jsonDecode(response.body);
final authData = AuthenticationData.fromMap(jsonDecode(response.body)); data["entity"]["uid"] = uid;
final authData = AuthenticationData.fromMap(data);
if (authData.isAuthenticated) { if (authData.isAuthenticated) {
const FlutterSecureStorage storage = FlutterSecureStorage( const FlutterSecureStorage storage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true), aOptions: AndroidOptions(encryptedSharedPreferences: true),
@ -74,6 +76,7 @@ class ApiClient {
await storage.write(key: userIdKey, value: authData.userId); await storage.write(key: userIdKey, value: authData.userId);
await storage.write(key: machineIdKey, value: authData.secretMachineIdHash); await storage.write(key: machineIdKey, value: authData.secretMachineIdHash);
await storage.write(key: tokenKey, value: authData.token); 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); if (rememberPass) await storage.write(key: passwordKey, value: password);
} }
return authData; return authData;
@ -87,19 +90,25 @@ class ApiClient {
String? machineId = await storage.read(key: machineIdKey); String? machineId = await storage.read(key: machineIdKey);
String? token = await storage.read(key: tokenKey); String? token = await storage.read(key: tokenKey);
String? password = await storage.read(key: passwordKey); 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(); return AuthenticationData.unauthenticated();
} }
if (token != null) { if (token != null) {
final response = await http.patch(buildFullUri("/userSessions"), headers: { final response = await http.patch(buildFullUri("/userSessions"), headers: {
"Authorization": "res $userId:$token", "Authorization": "res $userId:$token",
"UID":"2cde2bd72c104d1785af28ae77c29fc2", "UID": uid,
"SecretClientAccessKey": Config.secretClientKey,
}); });
if (response.statusCode < 300) { 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,
);
} }
} }

View file

@ -2,7 +2,6 @@ class Config {
static const String apiBaseUrl = "https://api.resonite.com"; static const String apiBaseUrl = "https://api.resonite.com";
static const String skyfrostAssetsUrl = "https://assets.resonite.com"; static const String skyfrostAssetsUrl = "https://assets.resonite.com";
static const String resoniteHubUrl = "$apiBaseUrl/hub"; static const String resoniteHubUrl = "$apiBaseUrl/hub";
static const String secretClientKey = "";
static const int messageCacheValiditySeconds = 90; static const int messageCacheValiditySeconds = 90;

View file

@ -1,40 +1,53 @@
import 'package:contacts_plus_plus/config.dart'; import 'package:contacts_plus_plus/config.dart';
import 'package:uuid/uuid.dart';
class AuthenticationData { 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 userId;
final String token; final String token;
final String secretMachineIdHash; final String secretMachineIdHash;
final bool isAuthenticated; final bool isAuthenticated;
final String uid;
const AuthenticationData({ 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) { factory AuthenticationData.fromMap(Map map) {
map = map["entity"]; map = map["entity"];
final userId = map["userId"]; final userId = map["userId"];
final token = map["token"]; final token = map["token"];
final machineId = map["secretMachineIdHash"]; 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 _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; factory AuthenticationData.unauthenticated() => _unauthenticated;
Map<String, String> get authorizationHeader => { Map<String, String> get authorizationHeader => {
"Authorization": "res $userId:$token", "Authorization": "res $userId:$token",
"SecretClientAccessKey": Config.secretClientKey, "UID": uid,
"UID":"2cde2bd72c104d1785af28ae77c29fc2", };
};
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
"userId": userId, "userId": userId,
"token": token, "token": token,
"secretMachineId": secretMachineIdHash, "secretMachineId": secretMachineIdHash,
"uid": uid,
}; };
} }
} }

View file

@ -60,7 +60,7 @@ class Session {
id: map["sessionId"], id: map["sessionId"],
name: map["name"], name: map["name"],
sessionUsers: (map["sessionUsers"] as List? ?? []).map((entry) => SessionUser.fromMap(entry)).toList(), sessionUsers: (map["sessionUsers"] as List? ?? []).map((entry) => SessionUser.fromMap(entry)).toList(),
thumbnail: map["thumbnail"] ?? "", thumbnail: map["thumbnailUrl"] ?? "",
maxUsers: map["maxUsers"] ?? 0, maxUsers: map["maxUsers"] ?? 0,
hasEnded: map["hasEnded"] ?? false, hasEnded: map["hasEnded"] ?? false,
isValid: map["isValid"] ?? true, isValid: map["isValid"] ?? true,

View file

@ -1,5 +1,6 @@
import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/clients/api_client.dart';
import 'package:contacts_plus_plus/models/authentication_data.dart'; import 'package:contacts_plus_plus/models/authentication_data.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:contacts_plus_plus/client_holder.dart'; import 'package:contacts_plus_plus/client_holder.dart';
@ -73,7 +74,7 @@ class _LoginScreenState extends State<LoginScreen> {
_isLoading = false; _isLoading = false;
}); });
await loginSuccessful(authData); await loginSuccessful(authData);
} catch (e) { } catch (e, s) {
setState(() { setState(() {
if (e == ApiClient.totpKey) { if (e == ApiClient.totpKey) {
if (_needsTotp == false) { if (_needsTotp == false) {
@ -81,9 +82,7 @@ class _LoginScreenState extends State<LoginScreen> {
_totpFocusNode.requestFocus(); _totpFocusNode.requestFocus();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_scrollController.animateTo(_scrollController.position.maxScrollExtent, _scrollController.animateTo(_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 400), duration: const Duration(milliseconds: 400), curve: Curves.easeOutCirc);
curve: Curves.easeOutCirc
);
}); });
} else { } else {
_error = "The given 2FA code is not valid."; _error = "The given 2FA code is not valid.";
@ -92,15 +91,19 @@ class _LoginScreenState extends State<LoginScreen> {
} else { } else {
_error = "Login unsuccessful: $e."; _error = "Login unsuccessful: $e.";
} }
if (kDebugMode) {
FlutterError.reportError(FlutterErrorDetails(
exception: e,
stack: s,
));
}
_isLoading = false; _isLoading = false;
}); });
} }
} }
Future<void> loginSuccessful(AuthenticationData authData) async { Future<void> loginSuccessful(AuthenticationData authData) async {
final settingsClient = ClientHolder final settingsClient = ClientHolder.of(context).settingsClient;
.of(context)
.settingsClient;
final notificationManager = FlutterLocalNotificationsPlugin(); final notificationManager = FlutterLocalNotificationsPlugin();
if (settingsClient.currentSettings.notificationsDenied.value == null) { if (settingsClient.currentSettings.notificationsDenied.value == null) {
if (context.mounted) { if (context.mounted) {
@ -114,19 +117,19 @@ class _LoginScreenState extends State<LoginScreen> {
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
await settingsClient.changeSettings( await settingsClient
settingsClient.currentSettings.copyWith(notificationsDenied: true)); .changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: true));
}, },
child: const Text("No"), child: const Text("No"),
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
final requestResult = await notificationManager.resolvePlatformSpecificImplementation< final requestResult = await notificationManager
AndroidFlutterLocalNotificationsPlugin>() .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.requestPermission(); ?.requestPermission();
await settingsClient.changeSettings(settingsClient.currentSettings.copyWith( await settingsClient.changeSettings(settingsClient.currentSettings
notificationsDenied: requestResult == null ? false : !requestResult)); .copyWith(notificationsDenied: requestResult == null ? false : !requestResult));
}, },
child: const Text("Yes"), child: const Text("Yes"),
) )
@ -145,95 +148,84 @@ class _LoginScreenState extends State<LoginScreen> {
appBar: AppBar( appBar: AppBar(
title: const Text("Contacts++"), title: const Text("Contacts++"),
), ),
body: Builder( body: Builder(builder: (context) {
builder: (context) { return ListView(
return ListView( controller: _scrollController,
controller: _scrollController, children: [
children: [ Padding(
Padding( padding: const EdgeInsets.symmetric(vertical: 64),
padding: const EdgeInsets.symmetric(vertical: 64), child: Center(
child: Center( child: Text("Sign In", style: Theme.of(context).textTheme.headlineMedium),
child: Text("Sign In", style: Theme ),
.of(context) ),
.textTheme Padding(
.headlineMedium), 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), padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64),
child: TextField( child: Text(_error, style: Theme.of(context).textTheme.labelMedium?.copyWith(color: Colors.red)),
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: 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)),
),
),
)
],
);
}
),
); );
} }
} }

View file

@ -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/message_input_bar.dart';
import 'package:contacts_plus_plus/widgets/messages/messages_session_header.dart'; import 'package:contacts_plus_plus/widgets/messages/messages_session_header.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'message_bubble.dart'; import 'message_bubble.dart';
@ -60,6 +61,9 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
systemOverlayStyle: SystemUiOverlayStyle(
systemNavigationBarColor: Theme.of(context).colorScheme.background,
),
title: Row( title: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [