This commit is contained in:
Christien Rioux 2023-08-05 13:50:31 -04:00
parent c047ae05c5
commit a5a45e2492
5 changed files with 133 additions and 18 deletions

View File

@ -86,7 +86,8 @@
"paste_invite_dialog": {
"paste_invite_here": "Paste your contact invite here:",
"paste": "Paste",
"message_from_contact": "Message from contact"
"message_from_contact": "Message from contact",
"validating": "Validating..."
},
"enter_pin_dialog": {
"enter_pin": "Enter PIN",

View File

@ -23,13 +23,13 @@
## Accepting an invitation
1. Create a Local Chat DHT record (no content yet, will be encrypted with DH of contact identity key)
2. Create ContactAccept with chat dht record and account master
2. Create ContactResponse with chat dht record and account master
3. Create SignedContactResponse with accept=true signed with identity
4. Set ContactRequest unicastinbox DHT record writer subkey with SignedContactResponse, encrypted with writer secret
5. Add a local contact with the remote chat dht record, updating from the remote profile in it
## Rejecting an invitation
1. Create ContactReject with account master
1. Create ContactResponse with account master
2. Create SignedContactResponse with accept=false signed with identity
3. Set ContactRequest unicastinbox DHT record writer subkey with SignedContactResponse, encrypted with writer secret

View File

@ -33,6 +33,7 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
Timestamp? _expiration;
ValidContactInvitation? _validInvitation;
bool _validatingPaste = false;
bool _isAccepting = false;
@override
void initState() {
@ -95,17 +96,51 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
// }
Future<void> _onAccept() async {
Navigator.of(context).pop();
if (_validInvitation != null) {
return acceptContactInvitation(_validInvitation);
final navigator = Navigator.of(context);
setState(() {
_isAccepting = true;
});
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
if (activeAccountInfo == null) {
setState(() {
_isAccepting = false;
});
navigator.pop();
return;
}
final validInvitation = _validInvitation;
if (validInvitation != null) {
await acceptContactInvitation(activeAccountInfo, validInvitation);
}
setState(() {
_isAccepting = false;
});
navigator.pop();
}
Future<void> _onReject() async {
Navigator.of(context).pop();
if (_validInvitation != null) {
return rejectContactInvitation(_validInvitation);
final navigator = Navigator.of(context);
setState(() {
_isAccepting = true;
});
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
if (activeAccountInfo == null) {
setState(() {
_isAccepting = false;
});
navigator.pop();
return;
}
final validInvitation = _validInvitation;
if (validInvitation != null) {
await rejectContactInvitation(activeAccountInfo, validInvitation);
}
setState(() {
_isAccepting = false;
});
navigator.pop();
}
Future<void> _onPasteChanged(String text) async {
@ -178,6 +213,9 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
final textTheme = theme.textTheme;
//final height = MediaQuery.of(context).size.height;
if (_isAccepting) {
return SizedBox(height: 400, child: waitingPage(context));
}
return SizedBox(
height: 400,
child: SingleChildScrollView(
@ -207,6 +245,11 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
//labelText: translate('paste_invite_dialog.paste')
),
).paddingAll(8)),
if (_validatingPaste)
Column(children: [
Text(translate('paste_invite_dialog.validating')),
buildProgressIndicator(context),
]),
if (_validInvitation != null && !_validatingPaste)
Column(children: [
ProfileWidget(
@ -232,7 +275,7 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
],
),
),
);
).withModalHUD(context, _isAccepting);
}
@override

View File

@ -321,7 +321,7 @@ message ContactResponse {
bool accept = 1;
// Account master record key
TypedKey account_master_record_key = 2;
// Local chat DHT record key if accepted
// Remote chat DHT record key if accepted
TypedKey remote_conversation_key = 3;
}

View File

@ -14,7 +14,10 @@ import '../entities/proto.dart'
ContactInvitationRecord,
ContactRequest,
ContactRequestPrivate,
SignedContactInvitation;
SignedContactInvitation,
ContactResponse,
SignedContactResponse;
import '../log/loggy.dart';
import '../tools/tools.dart';
import '../veilid_support/veilid_support.dart';
import 'account.dart';
@ -163,7 +166,8 @@ class ValidContactInvitation {
required this.contactRequestInboxKey,
required this.contactRequest,
required this.contactRequestPrivate,
required this.contactIdentityMaster});
required this.contactIdentityMaster,
required this.writer});
SignedContactInvitation signedContactInvitation;
ContactInvitation contactInvitation;
@ -171,6 +175,7 @@ class ValidContactInvitation {
ContactRequest contactRequest;
ContactRequestPrivate contactRequestPrivate;
IdentityMaster contactIdentityMaster;
KeyPair writer;
}
typedef GetEncryptionKeyCallback = Future<SecretKey> Function(
@ -221,26 +226,92 @@ Future<ValidContactInvitation> validateContactInvitation(Uint8List inviteData,
await cs.verify(contactIdentityMaster.identityPublicKey,
contactInvitationBytes, signature);
final writer = KeyPair(
key: proto.CryptoKeyProto.fromProto(contactRequestPrivate.writerKey),
secret: writerSecret);
out = ValidContactInvitation(
signedContactInvitation: signedContactInvitation,
contactInvitation: contactInvitation,
contactRequestInboxKey: contactRequestInboxKey,
contactRequest: contactRequest,
contactRequestPrivate: contactRequestPrivate,
contactIdentityMaster: contactIdentityMaster);
contactIdentityMaster: contactIdentityMaster,
writer: writer);
});
return out;
}
Future<void> acceptContactInvitation(
Future<void> acceptContactInvitation(ActiveAccountInfo activeAccountInfo,
ValidContactInvitation validContactInvitation) async {
//
final pool = await DHTRecordPool.instance();
await (await pool.openWrite(validContactInvitation.contactRequestInboxKey,
validContactInvitation.writer))
.deleteScope((contactRequestInbox) async {
final cs = await pool.veilid
.getCryptoSystem(validContactInvitation.contactRequestInboxKey.kind);
// xxx
final contactResponse = ContactResponse()
..accept = false
..accountMasterRecordKey = activeAccountInfo
.localAccount.identityMaster.masterRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final identitySignature = await cs.sign(
activeAccountInfo.localAccount.identityMaster.identityPublicKey,
activeAccountInfo.userLogin.identitySecret.value,
contactResponseBytes);
final signedContactResponse = SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the rejection to the invox
if (await contactRequestInbox.tryWriteProtobuf(
SignedContactResponse.fromBuffer, signedContactResponse,
subkey: 1) !=
null) {
log.error('failed to accept contact invitation');
}
});
}
Future<void> rejectContactInvitation(
Future<void> rejectContactInvitation(ActiveAccountInfo activeAccountInfo,
ValidContactInvitation validContactInvitation) async {
//
final pool = await DHTRecordPool.instance();
await (await pool.openWrite(validContactInvitation.contactRequestInboxKey,
validContactInvitation.writer))
.deleteScope((contactRequestInbox) async {
final cs = await pool.veilid
.getCryptoSystem(validContactInvitation.contactRequestInboxKey.kind);
final contactResponse = ContactResponse()
..accept = false
..accountMasterRecordKey = activeAccountInfo
.localAccount.identityMaster.masterRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final identitySignature = await cs.sign(
activeAccountInfo.localAccount.identityMaster.identityPublicKey,
activeAccountInfo.userLogin.identitySecret.value,
contactResponseBytes);
final signedContactResponse = SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the rejection to the invox
if (await contactRequestInbox.tryWriteProtobuf(
SignedContactResponse.fromBuffer, signedContactResponse,
subkey: 1) !=
null) {
log.error('failed to reject contact invitation');
}
});
}
/// Get the active account contact invitation list