contact accept

This commit is contained in:
Christien Rioux 2023-08-05 21:01:27 -04:00
parent b12cbcf684
commit 6f525843ff
4 changed files with 125 additions and 30 deletions

View File

@ -88,7 +88,9 @@
"paste": "Paste", "paste": "Paste",
"message_from_contact": "Message from contact", "message_from_contact": "Message from contact",
"validating": "Validating...", "validating": "Validating...",
"invalid_invitation": "Invalid invitation" "invalid_invitation": "Invalid invitation",
"failed_to_accept": "Failed to accept contact invite",
"failed_to_reject": "Failed to reject contact invite"
}, },
"enter_pin_dialog": { "enter_pin_dialog": {
"enter_pin": "Enter PIN", "enter_pin": "Enter PIN",

View File

@ -34,7 +34,7 @@
3. Set ContactRequest unicastinbox DHT record writer subkey with SignedContactResponse, encrypted with writer secret 3. Set ContactRequest unicastinbox DHT record writer subkey with SignedContactResponse, encrypted with writer secret
## Receiving an accept/reject ## Receiving an accept/reject
1. Open and get SignedContactResponse from ContactRequest unicaseinbox DHT record 1. Open and get SignedContactResponse from ContactRequest unicastinbox DHT record
2. Decrypt with writer secret 2. Decrypt with writer secret
3. Get DHT record for contact's AccountMaster 3. Get DHT record for contact's AccountMaster
4. Validate the SignedContactResponse signature 4. Validate the SignedContactResponse signature
@ -42,8 +42,10 @@
If accept == false: If accept == false:
1. Announce rejection 1. Announce rejection
2. Delete local invitation from table 2. Delete local invitation from table
3. Overwrite and delete ContactRequest inbox
If accept == true: If accept == true:
1. Add a local contact with the remote chat dht record, updating from the remote profile in it. 1. Add a local contact with the remote chat dht record, updating from the remote profile in it.
2. Delete local invitation from table 2. Delete local invitation from table
3. Overwrite and delete ContactRequest inbox

View File

@ -111,7 +111,24 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
} }
final validInvitation = _validInvitation; final validInvitation = _validInvitation;
if (validInvitation != null) { if (validInvitation != null) {
await acceptContactInvitation(activeAccountInfo, validInvitation); final acceptedContact =
await acceptContactInvitation(activeAccountInfo, validInvitation);
if (acceptedContact != null) {
await createContact(
activeAccountInfo: activeAccountInfo,
profile: acceptedContact.profile,
remoteIdentity: acceptedContact.remoteIdentity,
remoteConversation: acceptedContact.remoteConversation,
localConversation: acceptedContact.localConversation,
);
ref
..invalidate(fetchContactInvitationRecordsProvider)
..invalidate(fetchContactListProvider);
} else {
if (context.mounted) {
showErrorToast(context, 'paste_invite_dialog.failed_to_accept');
}
}
} }
setState(() { setState(() {
_isAccepting = false; _isAccepting = false;
@ -135,7 +152,13 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
} }
final validInvitation = _validInvitation; final validInvitation = _validInvitation;
if (validInvitation != null) { if (validInvitation != null) {
await rejectContactInvitation(activeAccountInfo, validInvitation); if (await rejectContactInvitation(activeAccountInfo, validInvitation)) {
// do nothing right now
} else {
if (context.mounted) {
showErrorToast(context, 'paste_invite_dialog.failed_to_reject');
}
}
} }
setState(() { setState(() {
_isAccepting = false; _isAccepting = false;

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart';
@ -330,46 +331,79 @@ Future<ValidContactInvitation> validateContactInvitation(Uint8List inviteData,
return out; return out;
} }
Future<void> acceptContactInvitation(ActiveAccountInfo activeAccountInfo, class AcceptedContact {
AcceptedContact({
required this.profile,
required this.remoteIdentity,
required this.remoteConversation,
required this.localConversation,
});
proto.Profile profile;
IdentityMaster remoteIdentity;
TypedKey remoteConversation;
OwnedDHTRecordPointer localConversation;
}
Future<AcceptedContact?> acceptContactInvitation(
ActiveAccountInfo activeAccountInfo,
ValidContactInvitation validContactInvitation) async { ValidContactInvitation validContactInvitation) async {
final pool = await DHTRecordPool.instance(); final pool = await DHTRecordPool.instance();
await (await pool.openWrite(validContactInvitation.contactRequestInboxKey, final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
return (await pool.openWrite(validContactInvitation.contactRequestInboxKey,
validContactInvitation.writer)) validContactInvitation.writer))
.deleteScope((contactRequestInbox) async { .deleteScope((contactRequestInbox) async {
final cs = await pool.veilid final cs = await pool.veilid
.getCryptoSystem(validContactInvitation.contactRequestInboxKey.kind); .getCryptoSystem(validContactInvitation.contactRequestInboxKey.kind);
// xxx // Create local conversation key for this
final contactResponse = ContactResponse() // contact and send via contact response
..accept = false return (await pool.create(parent: accountRecordKey))
..identityMasterRecordKey = activeAccountInfo .deleteScope((localConversation) async {
.localAccount.identityMaster.masterRecordKey final contactResponse = ContactResponse()
.toProto(); ..accept = true
final contactResponseBytes = contactResponse.writeToBuffer(); ..remoteConversationKey = localConversation.key.toProto()
..identityMasterRecordKey = activeAccountInfo
.localAccount.identityMaster.masterRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final identitySignature = await cs.sign( final identitySignature = await cs.sign(
activeAccountInfo.localAccount.identityMaster.identityPublicKey, activeAccountInfo.localAccount.identityMaster.identityPublicKey,
activeAccountInfo.userLogin.identitySecret.value, activeAccountInfo.userLogin.identitySecret.value,
contactResponseBytes); contactResponseBytes);
final signedContactResponse = SignedContactResponse() final signedContactResponse = SignedContactResponse()
..contactResponse = contactResponseBytes ..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto(); ..identitySignature = identitySignature.toProto();
// Write the rejection to the invox // Write the acceptance to the inbox
if (await contactRequestInbox.tryWriteProtobuf( if (await contactRequestInbox.tryWriteProtobuf(
SignedContactResponse.fromBuffer, signedContactResponse, SignedContactResponse.fromBuffer, signedContactResponse,
subkey: 1) != subkey: 1) !=
null) { null) {
log.error('failed to accept contact invitation'); log.error('failed to accept contact invitation');
} await localConversation.delete();
await contactRequestInbox.delete();
return null;
}
return AcceptedContact(
profile: validContactInvitation.contactRequestPrivate.profile,
remoteIdentity: validContactInvitation.contactIdentityMaster,
remoteConversation: proto.TypedKeyProto.fromProto(
validContactInvitation.contactRequestPrivate.chatRecordKey),
localConversation: localConversation.ownedDHTRecordPointer,
);
});
}); });
} }
Future<void> rejectContactInvitation(ActiveAccountInfo activeAccountInfo, Future<bool> rejectContactInvitation(ActiveAccountInfo activeAccountInfo,
ValidContactInvitation validContactInvitation) async { ValidContactInvitation validContactInvitation) async {
final pool = await DHTRecordPool.instance(); final pool = await DHTRecordPool.instance();
await (await pool.openWrite(validContactInvitation.contactRequestInboxKey, return (await pool.openWrite(validContactInvitation.contactRequestInboxKey,
validContactInvitation.writer)) validContactInvitation.writer))
.deleteScope((contactRequestInbox) async { .deleteScope((contactRequestInbox) async {
final cs = await pool.veilid final cs = await pool.veilid
@ -397,6 +431,40 @@ Future<void> rejectContactInvitation(ActiveAccountInfo activeAccountInfo,
subkey: 1) != subkey: 1) !=
null) { null) {
log.error('failed to reject contact invitation'); log.error('failed to reject contact invitation');
return false;
}
return true;
});
}
Future<void> createContact({
required ActiveAccountInfo activeAccountInfo,
required proto.Profile profile,
required IdentityMaster remoteIdentity,
required TypedKey remoteConversation,
required OwnedDHTRecordPointer localConversation,
}) async {
final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
// Create Contact
final contact = Contact()
..editedProfile = profile
..remoteProfile = profile
..remoteIdentity = jsonEncode(remoteIdentity.toJson())
..remoteConversationKey = remoteConversation.toProto()
..localConversation = localConversation.toProto()
..showAvailability = false;
// Add Contact to account's list
// if this fails, don't keep retrying, user can try again later
await (await DHTShortArray.openOwned(
proto.OwnedDHTRecordPointerProto.fromProto(
activeAccountInfo.account.contactList),
parent: accountRecordKey))
.scope((contactList) async {
if (await contactList.tryAddItem(contact.writeToBuffer()) == false) {
throw StateError('Failed to add contact');
} }
}); });
} }