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_dialog": {
"paste_invite_here": "Paste your contact invite here:", "paste_invite_here": "Paste your contact invite here:",
"paste": "Paste", "paste": "Paste",
"message_from_contact": "Message from contact" "message_from_contact": "Message from contact",
"validating": "Validating..."
}, },
"enter_pin_dialog": { "enter_pin_dialog": {
"enter_pin": "Enter PIN", "enter_pin": "Enter PIN",

View file

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

View file

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

View file

@ -321,7 +321,7 @@ message ContactResponse {
bool accept = 1; bool accept = 1;
// Account master record key // Account master record key
TypedKey account_master_record_key = 2; 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; TypedKey remote_conversation_key = 3;
} }

View file

@ -14,7 +14,10 @@ import '../entities/proto.dart'
ContactInvitationRecord, ContactInvitationRecord,
ContactRequest, ContactRequest,
ContactRequestPrivate, ContactRequestPrivate,
SignedContactInvitation; SignedContactInvitation,
ContactResponse,
SignedContactResponse;
import '../log/loggy.dart';
import '../tools/tools.dart'; import '../tools/tools.dart';
import '../veilid_support/veilid_support.dart'; import '../veilid_support/veilid_support.dart';
import 'account.dart'; import 'account.dart';
@ -163,7 +166,8 @@ class ValidContactInvitation {
required this.contactRequestInboxKey, required this.contactRequestInboxKey,
required this.contactRequest, required this.contactRequest,
required this.contactRequestPrivate, required this.contactRequestPrivate,
required this.contactIdentityMaster}); required this.contactIdentityMaster,
required this.writer});
SignedContactInvitation signedContactInvitation; SignedContactInvitation signedContactInvitation;
ContactInvitation contactInvitation; ContactInvitation contactInvitation;
@ -171,6 +175,7 @@ class ValidContactInvitation {
ContactRequest contactRequest; ContactRequest contactRequest;
ContactRequestPrivate contactRequestPrivate; ContactRequestPrivate contactRequestPrivate;
IdentityMaster contactIdentityMaster; IdentityMaster contactIdentityMaster;
KeyPair writer;
} }
typedef GetEncryptionKeyCallback = Future<SecretKey> Function( typedef GetEncryptionKeyCallback = Future<SecretKey> Function(
@ -221,26 +226,92 @@ Future<ValidContactInvitation> validateContactInvitation(Uint8List inviteData,
await cs.verify(contactIdentityMaster.identityPublicKey, await cs.verify(contactIdentityMaster.identityPublicKey,
contactInvitationBytes, signature); contactInvitationBytes, signature);
final writer = KeyPair(
key: proto.CryptoKeyProto.fromProto(contactRequestPrivate.writerKey),
secret: writerSecret);
out = ValidContactInvitation( out = ValidContactInvitation(
signedContactInvitation: signedContactInvitation, signedContactInvitation: signedContactInvitation,
contactInvitation: contactInvitation, contactInvitation: contactInvitation,
contactRequestInboxKey: contactRequestInboxKey, contactRequestInboxKey: contactRequestInboxKey,
contactRequest: contactRequest, contactRequest: contactRequest,
contactRequestPrivate: contactRequestPrivate, contactRequestPrivate: contactRequestPrivate,
contactIdentityMaster: contactIdentityMaster); contactIdentityMaster: contactIdentityMaster,
writer: writer);
}); });
return out; return out;
} }
Future<void> acceptContactInvitation( Future<void> acceptContactInvitation(ActiveAccountInfo activeAccountInfo,
ValidContactInvitation validContactInvitation) async { 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 { 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 /// Get the active account contact invitation list