Browse Source

Fix login check on initial load, add failed to send message

pull/1/head
Tovi Jaeschke-Rogers 2 years ago
parent
commit
66810ac55e
11 changed files with 213 additions and 109 deletions
  1. +9
    -0
      Backend/Api/Auth/Check.go
  2. +2
    -0
      Backend/Api/Routes.go
  3. +5
    -1
      mobile/lib/models/messages.dart
  4. +50
    -46
      mobile/lib/utils/storage/conversations.dart
  5. +2
    -1
      mobile/lib/utils/storage/database.dart
  6. +36
    -31
      mobile/lib/utils/storage/friends.dart
  7. +37
    -21
      mobile/lib/utils/storage/messages.dart
  8. +1
    -1
      mobile/lib/views/authentication/login.dart
  9. +28
    -4
      mobile/lib/views/main/conversation_detail.dart
  10. +40
    -3
      mobile/lib/views/main/home.dart
  11. +3
    -1
      mobile/lib/views/main/profile.dart

+ 9
- 0
Backend/Api/Auth/Check.go View File

@ -0,0 +1,9 @@
package Auth
import (
"net/http"
)
func Check(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}

+ 2
- 0
Backend/Api/Routes.go View File

@ -59,6 +59,8 @@ func InitApiEndpoints(router *mux.Router) {
authApi = api.PathPrefix("/auth/").Subrouter() authApi = api.PathPrefix("/auth/").Subrouter()
authApi.Use(authenticationMiddleware) authApi.Use(authenticationMiddleware)
authApi.HandleFunc("/check", Auth.Check).Methods("GET")
// Define routes for friends and friend requests // Define routes for friends and friend requests
authApi.HandleFunc("/friend_requests", Friends.EncryptedFriendRequestList).Methods("GET") authApi.HandleFunc("/friend_requests", Friends.EncryptedFriendRequestList).Methods("GET")
authApi.HandleFunc("/friend_request", Friends.CreateFriendRequest).Methods("POST") authApi.HandleFunc("/friend_request", Friends.CreateFriendRequest).Methods("POST")


+ 5
- 1
mobile/lib/models/messages.dart View File

@ -1,6 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:uuid/uuid.dart';
import 'package:Envelope/models/conversation_users.dart'; import 'package:Envelope/models/conversation_users.dart';
import 'package:Envelope/models/conversations.dart'; import 'package:Envelope/models/conversations.dart';
import 'package:pointycastle/export.dart'; import 'package:pointycastle/export.dart';
@ -23,6 +22,7 @@ class Message {
String senderUsername; String senderUsername;
String associationKey; String associationKey;
String createdAt; String createdAt;
bool failedToSend;
Message({ Message({
required this.id, required this.id,
required this.symmetricKey, required this.symmetricKey,
@ -32,6 +32,7 @@ class Message {
required this.senderUsername, required this.senderUsername,
required this.associationKey, required this.associationKey,
required this.createdAt, required this.createdAt,
required this.failedToSend,
}); });
@ -65,6 +66,7 @@ class Message {
senderUsername: 'Unknown', senderUsername: 'Unknown',
associationKey: json['association_key'], associationKey: json['association_key'],
createdAt: json['created_at'], createdAt: json['created_at'],
failedToSend: false,
); );
} }
@ -151,6 +153,7 @@ class Message {
'sender_username': senderUsername, 'sender_username': senderUsername,
'association_key': associationKey, 'association_key': associationKey,
'created_at': createdAt, 'created_at': createdAt,
'failed_to_send': failedToSend ? 1 : 0,
}; };
} }
@ -179,6 +182,7 @@ Future<List<Message>> getMessagesForThread(Conversation conversation) async {
senderUsername: maps[i]['sender_username'], senderUsername: maps[i]['sender_username'],
associationKey: maps[i]['association_key'], associationKey: maps[i]['association_key'],
createdAt: maps[i]['created_at'], createdAt: maps[i]['created_at'],
failedToSend: maps[i]['failed_to_send'] == 1,
); );
}); });


+ 50
- 46
mobile/lib/utils/storage/conversations.dart View File

@ -13,63 +13,64 @@ import '/utils/encryption/aes_helper.dart';
Future<void> updateConversations() async { Future<void> updateConversations() async {
RSAPrivateKey privKey = await getPrivateKey(); RSAPrivateKey privKey = await getPrivateKey();
var resp = await http.get(
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversations'),
headers: {
'cookie': await getSessionCookie(),
}
);
if (resp.statusCode != 200) {
try {
var resp = await http.get(
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversations'),
headers: {
'cookie': await getSessionCookie(),
}
);
if (resp.statusCode != 200) {
throw Exception(resp.body); throw Exception(resp.body);
}
}
List<Conversation> conversations = [];
List<String> conversationsDetailIds = [];
List<Conversation> conversations = [];
List<String> conversationsDetailIds = [];
List<dynamic> conversationsJson = jsonDecode(resp.body);
List<dynamic> conversationsJson = jsonDecode(resp.body);
for (var i = 0; i < conversationsJson.length; i++) {
for (var i = 0; i < conversationsJson.length; i++) {
Conversation conversation = Conversation.fromJson( Conversation conversation = Conversation.fromJson(
conversationsJson[i] as Map<String, dynamic>,
privKey,
conversationsJson[i] as Map<String, dynamic>,
privKey,
); );
conversations.add(conversation); conversations.add(conversation);
conversationsDetailIds.add(conversation.conversationDetailId); conversationsDetailIds.add(conversation.conversationDetailId);
}
}
Map<String, String> params = {};
params['conversation_detail_ids'] = conversationsDetailIds.join(',');
var uri = Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversation_details');
uri = uri.replace(queryParameters: params);
Map<String, String> params = {};
params['conversation_detail_ids'] = conversationsDetailIds.join(',');
var uri = Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversation_details');
uri = uri.replace(queryParameters: params);
resp = await http.get(
uri,
headers: {
'cookie': await getSessionCookie(),
}
);
resp = await http.get(
uri,
headers: {
'cookie': await getSessionCookie(),
}
);
if (resp.statusCode != 200) {
if (resp.statusCode != 200) {
throw Exception(resp.body); throw Exception(resp.body);
}
}
final db = await getDatabaseConnection();
final db = await getDatabaseConnection();
List<dynamic> conversationsDetailsJson = jsonDecode(resp.body);
for (var i = 0; i < conversationsDetailsJson.length; i++) {
List<dynamic> conversationsDetailsJson = jsonDecode(resp.body);
for (var i = 0; i < conversationsDetailsJson.length; i++) {
var conversationDetailJson = conversationsDetailsJson[i] as Map<String, dynamic>; var conversationDetailJson = conversationsDetailsJson[i] as Map<String, dynamic>;
var conversation = findConversationByDetailId(conversations, conversationDetailJson['id']); var conversation = findConversationByDetailId(conversations, conversationDetailJson['id']);
conversation.name = AesHelper.aesDecrypt( conversation.name = AesHelper.aesDecrypt(
base64.decode(conversation.symmetricKey),
base64.decode(conversationDetailJson['name']),
base64.decode(conversation.symmetricKey),
base64.decode(conversationDetailJson['name']),
); );
await db.insert( await db.insert(
'conversations',
conversation.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
'conversations',
conversation.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
); );
List<dynamic> usersData = json.decode( List<dynamic> usersData = json.decode(
@ -80,16 +81,19 @@ Future<void> updateConversations() async {
); );
for (var i = 0; i < usersData.length; i++) { for (var i = 0; i < usersData.length; i++) {
ConversationUser conversationUser = ConversationUser.fromJson(
usersData[i] as Map<String, dynamic>,
conversation.id,
);
await db.insert(
'conversation_users',
conversationUser.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
ConversationUser conversationUser = ConversationUser.fromJson(
usersData[i] as Map<String, dynamic>,
conversation.id,
);
await db.insert(
'conversation_users',
conversationUser.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
} }
}
} catch (SocketException) {
return;
} }
} }

+ 2
- 1
mobile/lib/utils/storage/database.dart View File

@ -65,7 +65,8 @@ Future<Database> getDatabaseConnection() async {
sender_id TEXT, sender_id TEXT,
sender_username TEXT, sender_username TEXT,
association_key TEXT, association_key TEXT,
created_at TEXT
created_at TEXT,
failed_to_send INTEGER
); );
'''); ''');


+ 36
- 31
mobile/lib/utils/storage/friends.dart View File

@ -10,17 +10,18 @@ import '/utils/storage/session_cookie.dart';
import '/utils/encryption/aes_helper.dart'; import '/utils/encryption/aes_helper.dart';
Future<void> updateFriends() async { Future<void> updateFriends() async {
RSAPrivateKey privKey = await getPrivateKey();
RSAPrivateKey privKey = await getPrivateKey();
try {
var resp = await http.get( var resp = await http.get(
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/friend_requests'),
headers: {
'cookie': await getSessionCookie(),
}
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/friend_requests'),
headers: {
'cookie': await getSessionCookie(),
}
); );
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception(resp.body);
throw Exception(resp.body);
} }
List<Friend> friends = []; List<Friend> friends = [];
@ -29,14 +30,14 @@ Future<void> updateFriends() async {
List<dynamic> friendsRequestJson = jsonDecode(resp.body); List<dynamic> friendsRequestJson = jsonDecode(resp.body);
for (var i = 0; i < friendsRequestJson.length; i++) { for (var i = 0; i < friendsRequestJson.length; i++) {
friends.add(
Friend.fromJson(
friendsRequestJson[i] as Map<String, dynamic>,
privKey,
)
);
friendIds.add(friends[i].friendId);
friends.add(
Friend.fromJson(
friendsRequestJson[i] as Map<String, dynamic>,
privKey,
)
);
friendIds.add(friends[i].friendId);
} }
Map<String, String> params = {}; Map<String, String> params = {};
@ -45,35 +46,39 @@ Future<void> updateFriends() async {
uri = uri.replace(queryParameters: params); uri = uri.replace(queryParameters: params);
resp = await http.get( resp = await http.get(
uri,
headers: {
'cookie': await getSessionCookie(),
}
uri,
headers: {
'cookie': await getSessionCookie(),
}
); );
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception(resp.body);
throw Exception(resp.body);
} }
final db = await getDatabaseConnection(); final db = await getDatabaseConnection();
List<dynamic> friendsJson = jsonDecode(resp.body); List<dynamic> friendsJson = jsonDecode(resp.body);
for (var i = 0; i < friendsJson.length; i++) { for (var i = 0; i < friendsJson.length; i++) {
var friendJson = friendsJson[i] as Map<String, dynamic>;
var friend = findFriendByFriendId(friends, friendJson['id']);
var friendJson = friendsJson[i] as Map<String, dynamic>;
var friend = findFriendByFriendId(friends, friendJson['id']);
friend.username = AesHelper.aesDecrypt(
base64.decode(friend.friendSymmetricKey),
base64.decode(friendJson['username']),
);
friend.username = AesHelper.aesDecrypt(
base64.decode(friend.friendSymmetricKey),
base64.decode(friendJson['username']),
);
friend.asymmetricPublicKey = friendJson['asymmetric_public_key'];
friend.asymmetricPublicKey = friendJson['asymmetric_public_key'];
await db.insert(
'friends',
friend.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
await db.insert(
'friends',
friend.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
} }
} catch (SocketException) {
return;
}
} }

+ 37
- 21
mobile/lib/utils/storage/messages.dart View File

@ -52,12 +52,16 @@ Future<void> updateMessageThread(Conversation conversation, {RSAPrivateKey? priv
} }
Future<void> updateMessageThreads({List<Conversation>? conversations}) async { Future<void> updateMessageThreads({List<Conversation>? conversations}) async {
RSAPrivateKey privKey = await getPrivateKey();
try {
RSAPrivateKey privKey = await getPrivateKey();
conversations ??= await getConversations();
conversations ??= await getConversations();
for (var i = 0; i < conversations.length; i++) {
await updateMessageThread(conversations[i], privKey: privKey);
for (var i = 0; i < conversations.length; i++) {
await updateMessageThread(conversations[i], privKey: privKey);
}
} catch(SocketException) {
return;
} }
} }
@ -74,7 +78,6 @@ Future<void> sendMessage(Conversation conversation, String data) async {
ConversationUser currentUser = await getConversationUserByUsername(conversation, username); ConversationUser currentUser = await getConversationUserByUsername(conversation, username);
Message message = Message( Message message = Message(
id: messageDataId, id: messageDataId,
symmetricKey: '', symmetricKey: '',
@ -82,31 +85,44 @@ Future<void> sendMessage(Conversation conversation, String data) async {
senderId: userId, senderId: userId,
senderUsername: username, senderUsername: username,
data: data, data: data,
createdAt: DateTime.now().toIso8601String(),
associationKey: currentUser.associationKey, associationKey: currentUser.associationKey,
createdAt: DateTime.now().toIso8601String(),
failedToSend: false,
); );
final db = await getDatabaseConnection(); final db = await getDatabaseConnection();
print(await db.query('messages'));
await db.insert( await db.insert(
'messages', 'messages',
message.toMap(), message.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace, conflictAlgorithm: ConflictAlgorithm.replace,
); );
String messageJson = await message.toJson(conversation, messageDataId);
final resp = await http.post(
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/message'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'cookie': await getSessionCookie(),
},
body: messageJson,
);
// TODO: If statusCode not successfull, mark as needing resend
print(resp.statusCode);
String sessionCookie = await getSessionCookie();
message.toJson(conversation, messageDataId)
.then((messageJson) {
return http.post(
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/message'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'cookie': sessionCookie,
},
body: messageJson,
);
})
.then((resp) {
if (resp.statusCode != 200) {
throw Exception('Unable to send message');
}
})
.catchError((exception) {
message.failedToSend = true;
db.update(
'messages',
message.toMap(),
where: 'id = ?',
whereArgs: [message.id],
);
});
} }

+ 1
- 1
mobile/lib/views/authentication/login.dart View File

@ -67,7 +67,7 @@ Future<LoginResponse> login(context, String username, String password) async {
setPrivateKey(rsaPriv); setPrivateKey(rsaPriv);
final preferences = await SharedPreferences.getInstance(); final preferences = await SharedPreferences.getInstance();
preferences.setBool('islogin', true);
preferences.setString('logged_in_at', (DateTime.now()).toIso8601String());
preferences.setString('userId', response.userId); preferences.setString('userId', response.userId);
preferences.setString('username', response.username); preferences.setString('username', response.username);
preferences.setString('asymmetricPublicKey', response.asymmetricPublicKey); preferences.setString('asymmetricPublicKey', response.asymmetricPublicKey);


+ 28
- 4
mobile/lib/views/main/conversation_detail.dart View File

@ -54,6 +54,32 @@ class _ConversationDetailState extends State<ConversationDetail> {
setState(() {}); setState(() {});
} }
Widget usernameOrFailedToSend(int index) {
if (messages[index].senderUsername != username) {
return Text(messages[index].senderUsername);
}
if (messages[index].failedToSend) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: const <Widget>[
Icon(
Icons.warning_rounded,
color: Colors.red,
size: 20,
),
Text(
'Failed to send',
style: TextStyle(color: Colors.red, fontSize: 12),
textAlign: TextAlign.right,
),
],
);
}
return const SizedBox.shrink();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -102,7 +128,7 @@ class _ConversationDetailState extends State<ConversationDetail> {
reverse: true, reverse: true,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return Container( return Container(
padding: const EdgeInsets.only(left: 14,right: 14,top: 0,bottom: 0),
padding: const EdgeInsets.only(left: 14,right: 14,top: 0,bottom: 10),
child: Align( child: Align(
alignment: ( alignment: (
messages[index].senderUsername == username ? messages[index].senderUsername == username ?
@ -126,9 +152,7 @@ class _ConversationDetailState extends State<ConversationDetail> {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: Text(messages[index].data, style: const TextStyle(fontSize: 15)), child: Text(messages[index].data, style: const TextStyle(fontSize: 15)),
), ),
messages[index].senderUsername != username ?
Text(messages[index].senderUsername) :
const SizedBox.shrink(),
usernameOrFailedToSend(index),
Text( Text(
convertToAgo(messages[index].createdAt), convertToAgo(messages[index].createdAt),
textAlign: TextAlign.left, textAlign: TextAlign.left,


+ 40
- 3
mobile/lib/views/main/home.dart View File

@ -1,11 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '/views/main/conversation_list.dart'; import '/views/main/conversation_list.dart';
import '/views/main/friend_list.dart'; import '/views/main/friend_list.dart';
import '/views/main/profile.dart'; import '/views/main/profile.dart';
import '/utils/storage/friends.dart'; import '/utils/storage/friends.dart';
import '/utils/storage/conversations.dart'; import '/utils/storage/conversations.dart';
import '/utils/storage/messages.dart'; import '/utils/storage/messages.dart';
import '/utils/storage/session_cookie.dart';
import '/models/conversations.dart'; import '/models/conversations.dart';
import '/models/friends.dart'; import '/models/friends.dart';
@ -53,12 +56,46 @@ class _HomeState extends State<Home> {
}); });
} }
// TODO: Do server GET check here
Future checkLogin() async {
Future<void> checkLogin() async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
if (preferences.getBool('islogin') != true) {
var loggedInTime = preferences.getString('logged_in_at');
if (loggedInTime == null) {
preferences.remove('logged_in_at');
preferences.remove('username');
preferences.remove('userId');
preferences.remove('asymmetricPublicKey');
Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing'));
return;
}
DateTime loggedInAt = DateTime.parse(loggedInTime);
bool isAfter = loggedInAt.isAfter((DateTime.now()).add(const Duration(hours: 12)));
int statusCode = 200;
try {
var resp = await http.get(
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/check'),
headers: {
'cookie': await getSessionCookie(),
}
);
statusCode = resp.statusCode;
} catch(SocketException) {
if (!isAfter) {
return;
}
} }
if (!isAfter && statusCode == 200) {
return;
}
preferences.remove('logged_in_at');
preferences.remove('username');
preferences.remove('userId');
preferences.remove('asymmetricPublicKey');
Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing'));
} }
void _onItemTapped(int index) { void _onItemTapped(int index) {


+ 3
- 1
mobile/lib/views/main/profile.dart View File

@ -37,7 +37,9 @@ class _ProfileState extends State<Profile> {
onTap: () async { onTap: () async {
deleteDb(); deleteDb();
final preferences = await SharedPreferences.getInstance(); final preferences = await SharedPreferences.getInstance();
preferences.setBool('islogin', false);
preferences.remove('logged_in_at');
preferences.remove('username');
preferences.remove('userId');
preferences.remove(rsaPrivateKeyName); preferences.remove(rsaPrivateKeyName);
Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing'));
}, },


Loading…
Cancel
Save