handleToDeviceEvent method

Future<void> handleToDeviceEvent(
  1. ToDeviceEvent event
)

Implementation

Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
  if (event.type == EventTypes.SecretRequest) {
    // got a request to share a secret
    Logs().i('[SSSS] Received sharing request...');
    if (event.sender != client.userID ||
        !client.userDeviceKeys.containsKey(client.userID)) {
      Logs().i('[SSSS] Not sent by us');
      return; // we aren't asking for it ourselves, so ignore
    }
    if (event.content['action'] != 'request') {
      Logs().i('[SSSS] it is actually a cancelation');
      return; // not actually requesting, so ignore
    }
    final device = client.userDeviceKeys[client.userID]!
        .deviceKeys[event.content['requesting_device_id']];
    if (device == null || !device.verified || device.blocked) {
      Logs().i('[SSSS] Unknown / unverified devices, ignoring');
      return; // nope....unknown or untrusted device
    }
    // alright, all seems fine...let's check if we actually have the secret they are asking for
    final type = event.content.tryGet<String>('name');
    if (type == null) {
      Logs().i('[SSSS] Wrong data type for type param, ignoring');
      return;
    }
    final secret = await getCached(type);
    if (secret == null) {
      Logs()
          .i('[SSSS] We don\'t have the secret for $type ourself, ignoring');
      return; // seems like we don't have this, either
    }
    // okay, all checks out...time to share this secret!
    Logs().i('[SSSS] Replying with secret for $type');
    await client.sendToDeviceEncrypted(
        [device],
        EventTypes.SecretSend,
        {
          'request_id': event.content['request_id'],
          'secret': secret,
        });
  } else if (event.type == EventTypes.SecretSend) {
    // receiving a secret we asked for
    Logs().i('[SSSS] Received shared secret...');
    final encryptedContent = event.encryptedContent;
    if (event.sender != client.userID ||
        !pendingShareRequests.containsKey(event.content['request_id']) ||
        encryptedContent == null) {
      Logs().i('[SSSS] Not by us or unknown request');
      return; // we have no idea what we just received
    }
    final request = pendingShareRequests[event.content['request_id']]!;
    // alright, as we received a known request id, let's check if the sender is valid
    final device = request.devices.firstWhereOrNull(
      (d) =>
          d.userId == event.sender &&
          d.curve25519Key == encryptedContent['sender_key'],
    );
    if (device == null) {
      Logs().i('[SSSS] Someone else replied?');
      return; // someone replied whom we didn't send the share request to
    }
    final secret = event.content.tryGet<String>('secret');
    if (secret == null) {
      Logs().i('[SSSS] Secret wasn\'t a string');
      return; // the secret wasn't a string....wut?
    }
    // let's validate if the secret is, well, valid
    if (_validators.containsKey(request.type) &&
        !(await _validators[request.type]!(secret))) {
      Logs().i('[SSSS] The received secret was invalid');
      return; // didn't pass the validator
    }
    pendingShareRequests.remove(request.requestId);
    if (request.start.add(Duration(minutes: 15)).isBefore(DateTime.now())) {
      Logs().i('[SSSS] Request is too far in the past');
      return; // our request is more than 15min in the past...better not trust it anymore
    }
    Logs().i('[SSSS] Secret for type ${request.type} is ok, storing it');
    final db = client.database;
    if (db != null) {
      final keyId = keyIdFromType(request.type);
      if (keyId != null) {
        final ciphertext = (client.accountData[request.type]!.content
                .tryGetMap<String, Object?>('encrypted'))
            ?.tryGetMap<String, Object?>(keyId)
            ?.tryGet<String>('ciphertext');
        if (ciphertext == null) {
          Logs().i('[SSSS] Ciphertext is empty or not a String');
          return;
        }
        await db.storeSSSSCache(request.type, keyId, ciphertext, secret);
        if (_cacheCallbacks.containsKey(request.type)) {
          _cacheCallbacks[request.type]!(secret);
        }
        onSecretStored.add(keyId);
      }
    }
  }
}