mycoralhealth / mycoral-patient

Mobile app for patients to interact with Coral Health blockchain
17 stars 7 forks source link

Inbound shared records #47

Closed grcevski closed 6 years ago

grcevski commented 6 years ago

This PR should implement importing other people's records in the app. To clarify how the sharing works, I'll document it here in this PR and we can decide to move this into a READ.me once we have everything finalized.

Sharing of contacts

The contacts sharing info is created the following way:

The QR code with default error checking parameters allows us to store up to around 10,000 ASCII characters which should suffice for this purpose.

When we import the contact we do the following:

Sharing of the QR code

Since we don't have corald yet or dapp, the transfer of contacts/records is done manually, either through scanning the QR codes or sharing the QR code information through other means, e.g. email, text message, Telegram...

When the user hits the share button on the QR display screen a link to our app is created with the following URL structure:

coralhealth://applink?type={contact|record}&data={the qr code displayed hash}

Most of the code for scanning a contact QR is in:

store.qrCodeContactHelper()

Creation of the contact info and uploading the public key to IPFS is done in:

SharedRecordsScreen onPressHandler for 'My Record Sharing Information' 

Sharing of records

Sharing of records follows similar procedure as the sharing of contacts above, with one minor exception: The record metadata info is too large to be displayed in the QR code with maintaining the default error correction and it would be too large to be shared in an URL. To mitigate this we do one more wrap in IPFS.

Here's how the shared record information is created:

Most of the code for creating the shared metadata info is in DelegateAccessScreen.js

onContactSelected(contact)

Here's an annotated code extract (I removed all JavaScript UI responsiveness code for better clarity)

// We take our encrypted IPFS hash which is stored on the record in on-device storage
ipfs.cat(record.hash)
            .then(async (dataUri) => {
              // We just downloaded the file from IPFS, so we decrypt the record data with our key
              let { decryptedUri } = await cryptoHelpers.decryptFile(dataUri, record.encryptionInfo.key, record.encryptionInfo.iv);

             // We load it as a string (base64 encoded for images) and delete the temp file
              let data = await FileSystem.readAsStringAsync(decryptedUri);
              await FileSystem.deleteAsync(decryptedUri, { idempotent: true });

              // We now encrypt the record by using the third-party public key
              let encryptedInfo = await cryptoHelpers.encryptFile(data, record.metadata, publicKeyPem);

              // We add the data part of the record to IPFS (to match what we do for our records)
              let hash = await ipfs.add(encryptedInfo.uri);

              // Clean-up the temp file
              await FileSystem.deleteAsync(encryptedInfo.uri, { idempotent: true });

              // We create the shared record data with the original id, the data IPFS hash, the encrypted metadata and the encrypted decryption symmetric keys
              let sharedRecord = { 
                id: record.id, 
                hash, 
                metadata: encryptedInfo.encryptedMetadata, 
                encryptionInfo: { key: encryptedInfo.encryptedKey, iv: encryptedInfo.encryptedIv }
              };

              // This just does JSON.stringify() and encodes in base64 
              let sharedInfo = store.thirdPartySharedRecordInfo(sharedRecord);

              // We add the shared record information to IPFS so we can generate the final sharing hash
              let sharedRecordHash = await ipfs.add(sharedInfo);

              sharedRecord.sharedHash = sharedRecordHash;

              // Finally we store the shared record in our on-device storage. This is what's used to populate the Shared Record Tab screen.
              await store.shareRecord(contact.name, sharedRecord);

              resolve(sharedRecordHash);
            });