working encryption at rest.

Medicare storage isnt working on first init - but Sync works fine.
This commit is contained in:
Jason Kulatunga 2022-10-16 12:09:22 -07:00
parent 1b0bfab2ac
commit 75e858d0f2
5 changed files with 169 additions and 14 deletions

View File

@ -33,7 +33,9 @@
"chart.js": "2.9.4",
"crypto-pouch": "^4.0.1",
"fhirclient": "^2.5.1",
"garbados-crypt": "^3.0.0-beta",
"humanize-duration": "^3.27.3",
"idb": "^7.1.0",
"moment": "^2.29.4",
"ng2-charts": "^2.3.0",
"ngx-dropzone": "^3.1.0",
@ -45,6 +47,7 @@
"pouchdb-find": "^7.3.0",
"pouchdb-upsert": "^2.2.0",
"rxjs": "~6.5.4",
"transform-pouch": "^2.0.0",
"tslib": "^2.0.0",
"uuid": "^9.0.0",
"zone.js": "~0.11.8"

View File

@ -0,0 +1,123 @@
// This is a Typescript module that recreates the functionality defined in https://github.com/calvinmetcalf/crypto-pouch/blob/master/index.js
// This file only exists because the PouchDB crypto plugin must work in both the browser and web-worker environment (where `window` is
// undefined and causes errors).
// Also, crypto-pouch does not support storing encrypted data in the remote database by default, which I'm attempting to do by commenting out the
// NO_COUCH error.
//
// We've attempted to use the Typescript Module Plugin/Augmentation pattern to modify the global `pouchdb` object, however that
// failed for a variety of reasons, so instead we're using a PouchdbCrypto class with static methods to re-implement the crypto logic
//
//
// See:
// - https://github.com/calvinmetcalf/crypto-pouch/blob/master/index.js
// - https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-plugin-d-ts.html
// - https://www.typescriptlang.org/docs/handbook/declaration-merging.html
// - https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-plugin-d-ts.html
// - https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html
// - https://stackoverflow.com/questions/35074713/extending-typescript-global-object-in-node-js
// - https://github.com/Microsoft/TypeScript/issues/15818
import * as Crypt from 'garbados-crypt';
import { openDB, deleteDB, wrap, unwrap } from 'idb';
// const Crypt = require()
const LOCAL_ID = '_local/crypto'
const IGNORE = ['_id', '_rev', '_deleted', '_conflicts']
// const NO_COUCH = 'crypto-pouch does not work with pouchdb\'s http adapter. Use a local adapter instead.'
export class PouchdbCryptoOptions {
password?: string
ignore?: string[]
}
export class PouchdbCrypto {
public static async localIdb(){
const dbPromise = openDB('crypto-store', 1, {
upgrade(db) {
db.createObjectStore('crypto');
},
});
return await dbPromise
}
public static async crypto(db, password, options: PouchdbCryptoOptions = {}) {
// if (db.adapter === 'http') {
// throw new Error(NO_COUCH)
// }
if (typeof password === 'object') {
// handle `db.crypto({ password, ...options })`
options = password
password = password.password
delete options.password
}
// setup ignore list
db._ignore = IGNORE.concat(options.ignore || [])
// setup crypto helper
const trySetup = async () => {
// try saving credentials to a local doc
try {
// first we try to get saved creds from the local doc
const localDb = await PouchdbCrypto.localIdb()
let exportString = await localDb.get('crypto','encryption_data')
if(!exportString){
// no existing encryption key found
// do first-time setup
db._crypt = new Crypt(password)
let exportString = await db._crypt.export()
await localDb.put('crypto', exportString, 'encryption_data')
} else {
db._crypt = await Crypt.import(password, exportString)
}
} catch (err) {
throw err
}
}
await trySetup()
// instrument document transforms
db.transform({
incoming: async (doc) => {
// if no crypt, ex: after .removeCrypto(), just return the doc
if (!db._crypt) {
console.warn("=======>WARNING DOCUMENT NOT ENCRYPTED")
return doc
}
console.log("=======>saving... raw doc", doc)
if (doc._attachments && !db._ignore.includes('_attachments')) {
throw new Error('Attachments cannot be encrypted. Use {ignore: "_attachments"} option')
}
let encrypted: any = {}
for (let key of db._ignore) {
// attach ignored fields to encrypted doc
if (key in doc) encrypted[key] = doc[key]
}
encrypted.payload = await db._crypt.encrypt(JSON.stringify(doc))
console.log("=======>saving encrpted doc", encrypted)
return encrypted
},
outgoing: async (doc) => {
// if no crypt, ex: after .removeCrypto(), just return the doc
if (!db._crypt) { return doc }
console.log("=======>retrieved doc", doc.payload)
let decryptedString = await db._crypt.decrypt(doc.payload)
console.log("=======>retrieved decrypted string", decryptedString)
let decrypted = JSON.parse(decryptedString)
for (let key of db._ignore) {
// patch decrypted doc with ignored fields
if (key in doc) decrypted[key] = doc[key]
}
console.log("=======>retrieved decrypted", decrypted)
return decrypted
}
})
return db
}
public static removeCrypto(db) {
delete db._crypt
}
}

View File

@ -1,3 +1,18 @@
// This is a Typescript module that recreates the functionality defined in https://github.com/pouchdb/upsert/blob/master/index.js
// This file only exists because the PouchDB upsert plugin must work in both the browser and web-worker environment (where `window` is
// undefined and causes errors).
//
// We've attempted to use the Typescript Module Plugin/Augmentation pattern to modify the global `pouchdb` object, however that
// failed for a variety of reasons, so instead we're using a PouchdbUpsert class with static methods to re-implement the upsert logic
//
// See:
// - https://github.com/pouchdb/upsert/blob/master/index.js
// - https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-plugin-d-ts.html
// - https://www.typescriptlang.org/docs/handbook/declaration-merging.html
// - https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-plugin-d-ts.html
// - https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html
// - https://stackoverflow.com/questions/35074713/extending-typescript-global-object-in-node-js
// - https://github.com/Microsoft/TypeScript/issues/15818
export class PouchdbUpsert {
public static upsert(db, docId, diffFun, cb?) {
var promise = PouchdbUpsert.upsertInner(db, docId, diffFun);

View File

@ -7,12 +7,17 @@ import {Base64} from '../utils/base64';
// PouchDB & plugins
import * as PouchDB from 'pouchdb/dist/pouchdb';
import * as PouchCrypto from 'crypto-pouch';
// import * as PouchCrypto from 'crypto-pouch';
// PouchDB.plugin(PouchCrypto);
import * as PouchTransform from 'transform-pouch';
PouchDB.plugin(PouchTransform);
import {PouchdbUpsert} from './plugins/upsert';
import {UpsertSummary} from '../models/fasten/upsert-summary';
PouchDB.plugin(PouchCrypto);
import {PouchdbCrypto, PouchdbCryptoOptions} from './plugins/crypto';
// !!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!
// most pouchdb plugins seem to fail when used in a webworker.
@ -52,6 +57,7 @@ export class PouchdbRepository implements IDatabaseRepository {
// replicationHandler: any
remotePouchEndpoint: string // "http://localhost:5984"
encryptionKey: string
encryptionInitComplete: boolean = false
pouchDb: PouchDB.Database
/**
@ -217,18 +223,21 @@ export class PouchdbRepository implements IDatabaseRepository {
if(!this.pouchDb) {
throw(new Error( "Database is not available - please configure an instance." ));
}
// if(this.encryptionKey){
// return this.pouchDb.crypto(this.encryptionKey, {ignore:[
// 'doc_type',
// 'source_id',
// 'source_resource_type',
// 'source_resource_id',
// ]}).then(() => {
// return this.pouchDb
// })
// } else {
if(this.encryptionKey && !this.encryptionInitComplete){
return PouchdbCrypto.crypto(this.pouchDb, this.encryptionKey, {ignore:[
'doc_type',
'source_id',
'source_resource_type',
'source_resource_id',
]})
.then((encryptedPouchDb) => {
this.pouchDb = encryptedPouchDb
this.encryptionInitComplete = true
return this.pouchDb
})
} else {
return this.pouchDb;
// }
}
}

View File

@ -4579,6 +4579,11 @@ icss-utils@^5.0.0, icss-utils@^5.1.0:
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
idb@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.0.tgz#2cc886be57738419e57f9aab58f647e5e2160270"
integrity sha512-Wsk07aAxDsntgYJY4h0knZJuTxM73eQ4reRAO+Z1liOh8eMCJ/MoDS8fCui1vGT9mnjtl1sOu3I2i/W1swPYZg==
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"