dexie-encrypted
Advanced tools
Comparing version 1.2.2 to 2.0.0-beta.0
@@ -1,372 +0,49 @@ | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var Dexie = _interopDefault(require('dexie')); | ||
var nacl = _interopDefault(require('tweetnacl')); | ||
var Typeson = _interopDefault(require('typeson')); | ||
var builtinTypes = _interopDefault(require('typeson-registry/presets/builtin')); | ||
// Import some usable helper functions | ||
const override = Dexie.override; | ||
const Promise = Dexie.Promise; | ||
const tableEncryptionOptions = { | ||
DATA: 'NON_INDEXED_FIELDS', | ||
NON_INDEXED_FIELDS: 'NON_INDEXED_FIELDS', | ||
// DATA_AND_INDICES: 'DATA_AND_INDICES', // not implemented. | ||
WHITELIST: 'WHITELIST', | ||
BLACKLIST: 'BLACKLIST', | ||
}; | ||
const cryptoOptions = tableEncryptionOptions; | ||
/* options example: | ||
{ | ||
table1: cryptoOptions.NON_INDEXED_FIELDS, | ||
table2: { | ||
type: cryptoOptions.WHITELIST, | ||
fields: ['harmlessData1', 'harmlessId'] | ||
}, | ||
table3: { | ||
type: cryptoOptions.BLACKLIST, | ||
fields: ['sensitiveField1', 'sensitiveField2'] | ||
} | ||
} | ||
*/ | ||
const tson = new Typeson().register([builtinTypes]); | ||
function overrideParseStoresSpec(origFunc) { | ||
return function(stores, dbSchema) { | ||
stores._encryptionSettings = '++id'; | ||
origFunc.call(this, stores, dbSchema); | ||
}; | ||
} | ||
function compareArrays(a, b) { | ||
if (a.length !== b.length) { | ||
return false; | ||
} | ||
for (let i = 0; i < a.length; i++) { | ||
if (a[i] !== b[i]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
const encoder = new TextEncoder(); | ||
const decoder = new TextDecoder(); | ||
function encryptObject(key, object, nonce) { | ||
nonce = nonce || nacl.randomBytes(nacl.secretbox.nonceLength); | ||
const stringToEncrypt = tson.stringify(object); | ||
const encoded = encoder.encode(stringToEncrypt); | ||
const encrypted = nacl.secretbox(encoded, nonce, key); | ||
const data = new Uint8Array(nonce.length + encrypted.length); | ||
data.set(nonce); | ||
data.set(encrypted, nonce.length); | ||
return data; | ||
} | ||
// this prevents changing the shape of the object so | ||
// the underlying engine can optimize the hidden class | ||
function hideValue(input) { | ||
switch (typeof input) { | ||
case 'number': | ||
return 0; | ||
case 'string': | ||
return ''; | ||
case 'boolean': | ||
return false; | ||
case 'undefined': | ||
return undefined; | ||
case 'symbol': | ||
return undefined; | ||
} | ||
return {}; | ||
} | ||
function encrypt(db, keyOrPromise, cryptoSettings, onKeyChange, nonceOverride) { | ||
let keyPromise; | ||
if (keyOrPromise.then) { | ||
keyPromise = keyOrPromise; | ||
} else if (keyOrPromise instanceof Uint8Array && keyOrPromise.length === 32) { | ||
keyPromise = Dexie.Promise.resolve(keyOrPromise); | ||
} else { | ||
throw new Error('Dexie-encrypted requires a UInt8Array of length 32 for a encryption key.'); | ||
} | ||
db.Version.prototype._parseStoresSpec = override( | ||
db.Version.prototype._parseStoresSpec, | ||
overrideParseStoresSpec | ||
); | ||
if (db.verno > 0) { | ||
// Make sure new tables are added if calling encrypt after defining versions. | ||
try { | ||
db.version(db.verno).stores({}); | ||
} catch (error) { | ||
throw new Error( | ||
'Dexie-encrypt: The call to encrypt() cannot be done on an open database' | ||
); | ||
} | ||
} | ||
function encryptWithRule(table, entity, rule) { | ||
if (rule === undefined) { | ||
return entity; | ||
} | ||
const toEncrypt = {}; | ||
if (rule.type === cryptoOptions.BLACKLIST) { | ||
for (let i = 0; i < rule.fields.length; i++) { | ||
toEncrypt[rule.fields[i]] = entity[rule.fields[i]]; | ||
entity[rule.fields[i]] = hideValue(entity[rule.fields[i]]); | ||
} | ||
} else { | ||
const indices = table.schema.indexes.map(index => index.name); | ||
const whitelist = rule.type === cryptoOptions.WHITELIST ? rule.fields : []; | ||
for (const key in entity) { | ||
if ( | ||
key !== table.schema.primKey.name && | ||
entity.hasOwnProperty(key) && | ||
indices.includes(key) === false && | ||
whitelist.includes(key) === false | ||
) { | ||
toEncrypt[key] = entity[key]; | ||
entity[key] = hideValue(entity[key]); | ||
} | ||
} | ||
} | ||
entity.__encryptedData = encryptObject(key, toEncrypt, nonceOverride); | ||
return entity; | ||
} | ||
function decryptWithRule(entity, rule) { | ||
if (rule === undefined) { | ||
return entity; | ||
} | ||
if (entity && entity.__encryptedData) { | ||
const nonce = entity.__encryptedData.slice(0, nacl.secretbox.nonceLength); | ||
const message = entity.__encryptedData.slice( | ||
nacl.secretbox.nonceLength, | ||
entity.__encryptedData.length | ||
); | ||
const rawDecrypted = nacl.secretbox.open(message, nonce, key); | ||
const stringified = decoder.decode(rawDecrypted); | ||
const decrypted = tson.parse(stringified); | ||
const toReturn = {}; | ||
for (const k in entity) { | ||
if (decrypted.hasOwnProperty(k)) { | ||
toReturn[k] = decrypted[k]; | ||
} else if (entity.hasOwnProperty(k) && k !== '__encryptedData') { | ||
toReturn[k] = entity[k]; | ||
} | ||
} | ||
return toReturn; | ||
} | ||
return entity; | ||
} | ||
let key; | ||
db.on('ready', function() { | ||
let encryptionSettings; | ||
try { | ||
encryptionSettings = db.table('_encryptionSettings'); | ||
} catch (error) { | ||
throw new Error( | ||
"Dexie-encrypted can't find its encryption table. You may need to bump your database version." | ||
); | ||
} | ||
return keyPromise | ||
.then(receivedKey => { | ||
if (receivedKey instanceof Uint8Array && receivedKey.length === 32) { | ||
key = receivedKey; | ||
} else { | ||
throw new Error( | ||
'Dexie-encrypted requires a UInt8Array of length 32 for a encryption key.' | ||
); | ||
} | ||
}) | ||
.then(() => | ||
encryptionSettings | ||
.toCollection() | ||
.last() | ||
.then(oldSettings => { | ||
const changeDetectionObj = oldSettings | ||
? oldSettings['__key_change_detection'] | ||
: null; | ||
let onKeyChangeResult; | ||
let keyChangePromise = Promise.resolve(); | ||
if (changeDetectionObj) { | ||
const nonce = changeDetectionObj.slice(0, nacl.secretbox.nonceLength); | ||
const message = changeDetectionObj.slice( | ||
nacl.secretbox.nonceLength, | ||
changeDetectionObj.length | ||
); | ||
const rawDecrypted = nacl.secretbox.open(message, nonce, key); | ||
if (!rawDecrypted) { | ||
// The key has changed. Let's call the handler | ||
onKeyChangeResult = onKeyChange(db); | ||
keyChangePromise = onKeyChangeResult.then | ||
? onKeyChangeResult | ||
: new Promise(resolve => { | ||
resolve(onKeyChangeResult); | ||
}); | ||
} | ||
} | ||
return keyChangePromise.then(() => { | ||
return Promise.all( | ||
db.tables.map(function(table) { | ||
const oldSetting = oldSettings | ||
? oldSettings[table.name] | ||
: undefined; | ||
const newSetting = cryptoSettings[table.name]; | ||
function setupHooks() { | ||
if (newSetting === undefined) { | ||
return; | ||
} | ||
table.hook('creating', function(primKey, obj) { | ||
const preservedValue = { ...obj }; | ||
encryptWithRule(table, obj, newSetting); | ||
this.onsuccess = () => { | ||
delete obj.__encryptedData; | ||
Object.assign(obj, preservedValue); | ||
}; | ||
this.onerror = () => { | ||
delete obj.__encryptedData; | ||
Object.assign(obj, preservedValue); | ||
}; | ||
}); | ||
table.hook('updating', function(modifications) { | ||
const encrypted = encryptWithRule( | ||
table, | ||
{...this.value}, | ||
newSetting | ||
); | ||
return encrypted; | ||
}); | ||
table.hook('reading', function(obj) { | ||
return decryptWithRule(obj, newSetting); | ||
}); | ||
} | ||
if (oldSetting === newSetting) { | ||
// no upgrade needed. | ||
setupHooks(); | ||
return; | ||
} | ||
if (oldSetting === undefined || newSetting === undefined) ; else if ( | ||
typeof oldSetting !== 'string' && | ||
typeof newSetting !== 'string' | ||
) { | ||
// both non-strings. Figure out if they're the same. | ||
if (newSetting.type === oldSetting.type) { | ||
if ( | ||
compareArrays(newSetting.fields, oldSetting.fields) | ||
) { | ||
// no upgrade needed. | ||
setupHooks(); | ||
return; | ||
} | ||
} | ||
} | ||
return table | ||
.toCollection() | ||
.modify(function(entity, ref) { | ||
const decrypted = decryptWithRule(entity, oldSetting); | ||
ref.value = encryptWithRule( | ||
table, | ||
decrypted, | ||
newSetting | ||
); | ||
return true; | ||
}) | ||
.then(setupHooks); | ||
}) | ||
); | ||
}); | ||
}) | ||
.then(function() { | ||
return encryptionSettings.clear(); | ||
}) | ||
.then(function() { | ||
return encryptionSettings.put({ | ||
__key_change_detection: encryptObject( | ||
key, | ||
[1, 2, 3, 4, 5], | ||
new Uint8Array(24) | ||
), | ||
...cryptoSettings, | ||
}); | ||
}) | ||
.catch(error => { | ||
if (error.name === 'NotFoundError') { | ||
throw new Error( | ||
"Dexie-encrypted can't find its encryption table. You may need to bump your database version." | ||
); | ||
} else { | ||
return Promise.reject(error); | ||
} | ||
}) | ||
); | ||
}); | ||
} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.encryptDatabase = exports.clearEncryptedTables = exports.clearAllTables = exports.UNENCRYPTED_LIST = exports.ENCRYPT_LIST = exports.NON_INDEXED_FIELDS = void 0; | ||
const tslib_1 = require("tslib"); | ||
const encryptDatabase_1 = require("./encryptDatabase"); | ||
const encryptionMethods_1 = require("./encryptionMethods"); | ||
const types_1 = require("./types"); | ||
var types_2 = require("./types"); | ||
Object.defineProperty(exports, "cryptoOptions", { enumerable: true, get: function () { return types_2.cryptoOptions; } }); | ||
exports.NON_INDEXED_FIELDS = types_1.cryptoOptions.NON_INDEXED_FIELDS; | ||
exports.ENCRYPT_LIST = types_1.cryptoOptions.ENCRYPT_LIST; | ||
exports.UNENCRYPTED_LIST = types_1.cryptoOptions.UNENCRYPTED_LIST; | ||
function clearAllTables(db) { | ||
return Promise.all( | ||
db.tables.map(function(table) { | ||
return table.clear(); | ||
}) | ||
); | ||
return Promise.all(db.tables.map(function (table) { | ||
return table.clear(); | ||
})); | ||
} | ||
async function clearEncryptedTables(db) { | ||
let encryptionSettings; | ||
try { | ||
encryptionSettings = await db | ||
exports.clearAllTables = clearAllTables; | ||
function clearEncryptedTables(db) { | ||
return tslib_1.__awaiter(this, void 0, void 0, function* () { | ||
let encryptionSettings = (yield db | ||
.table('_encryptionSettings') | ||
.toCollection() | ||
.last(); | ||
} catch (error) { | ||
throw new Error( | ||
"Dexie-encrypted can't find its encryption table. You may need to bump your database version." | ||
); | ||
} | ||
const promises = Object.keys(encryptionSettings).map(async function(key) { | ||
const encryptionSettingValue = encryptionSettings[key]; | ||
if (tableEncryptionOptions[encryptionSettingValue]) { | ||
await db.table(key).clear(); | ||
} | ||
.last() | ||
.catch(() => { | ||
throw new Error("Dexie-encrypted can't find its encryption table. You may need to bump your database version."); | ||
})); | ||
const promises = Object.keys(encryptionSettings.settings).map(function (key) { | ||
return tslib_1.__awaiter(this, void 0, void 0, function* () { | ||
yield db.table(key).clear(); | ||
}); | ||
}); | ||
return Promise.all(promises); | ||
}); | ||
return Promise.all(promises); | ||
} | ||
Object.assign(encrypt, cryptoOptions, { | ||
clearAllTables: clearAllTables, | ||
clearEncryptedTables: clearEncryptedTables, | ||
}); | ||
exports.clearAllTables = clearAllTables; | ||
exports.clearEncryptedTables = clearEncryptedTables; | ||
exports.cryptoOptions = cryptoOptions; | ||
exports.default = encrypt; | ||
exports.tableEncryptionOptions = tableEncryptionOptions; | ||
function encryptDatabase(db, encryptionKey, tableSettings, onKeyChange, nonceOverrideForTesting) { | ||
encryptDatabase_1.encryptDatabaseWithCustomEncryption({ | ||
db, | ||
encryptionKey, | ||
tableSettings, | ||
encrypt: encryptionMethods_1.encryptWithNacl, | ||
decrypt: encryptionMethods_1.decryptWithNacl, | ||
onKeyChange, | ||
nonceOverrideForTesting, | ||
}); | ||
} | ||
exports.encryptDatabase = encryptDatabase; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "dexie-encrypted", | ||
"version": "1.2.2", | ||
"description": "Encryption middleware for Dexie", | ||
"main": "dist/index.js", | ||
"license": "MIT", | ||
"files": [ | ||
"dist/index.js" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/mark43/dexie-encrypted.git" | ||
}, | ||
"dependencies": { | ||
"tweetnacl": "^1.0.1", | ||
"typeson": "^5.13.0", | ||
"typeson-registry": "^1.0.0-alpha.28" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.5.5", | ||
"@babel/preset-env": "^7.5.5", | ||
"babel-eslint": "^10.0.3", | ||
"babel-jest": "^24.9.0", | ||
"dexie": "^2.0.4", | ||
"dexie-export-import": "^1.0.0-beta.13", | ||
"eslint": "^6.3.0", | ||
"fake-indexeddb": "^2.1.1", | ||
"jest": "^24.9.0", | ||
"prettier": "^1.18.2", | ||
"rollup": "^1.20.3", | ||
"text-encoding": "^0.7.0" | ||
}, | ||
"peerDependencies": { | ||
"dexie": "^2.0.4 | ^3.0.0" | ||
}, | ||
"scripts": { | ||
"test": "jest", | ||
"build": "rollup index.js --format commonjs --file dist/index.js", | ||
"format": "prettier index.js README.md --write" | ||
} | ||
"name": "dexie-encrypted", | ||
"version": "2.0.0-beta.0", | ||
"description": "Encryption middleware for Dexie", | ||
"main": "dist", | ||
"license": "MIT", | ||
"files": [ | ||
"dist/*" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/mark43/dexie-encrypted.git" | ||
}, | ||
"dependencies": { | ||
"@stablelib/utf8": "1.0.0", | ||
"tweetnacl": "1.0.3", | ||
"typeson": "5.18.2", | ||
"typeson-registry": "1.0.0-alpha.38" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "26.0.10", | ||
"dexie": "^3.0.0", | ||
"dexie-export-import": "^1.0.0-beta.13", | ||
"eslint": "^6.3.0", | ||
"fake-indexeddb": "^2.1.1", | ||
"jest": "26.4.0", | ||
"prettier": "^1.18.2", | ||
"text-encoding": "^0.7.0", | ||
"ts-jest": "26.2.0", | ||
"tsc": "1.20150623.0", | ||
"typescript": "3.9.7" | ||
}, | ||
"peerDependencies": { | ||
"dexie": "^3.0.0" | ||
}, | ||
"scripts": { | ||
"test": "jest", | ||
"build": "tsc", | ||
"format": "prettier index.js README.md --write" | ||
} | ||
} |
@@ -59,7 +59,7 @@ # Dexie-encrypted | ||
Dexie-encrypted can be configured to encrypt all the data of a table, to whitelist fields that are non-sensitive, or to blacklist sensitive fields. | ||
Dexie-encrypted can be configured to encrypt all the data of a table, to select fields that are senesitvie or non-sensitive. | ||
- `encrypt.NON_INDEXED_FIELDS` - all data other than indices will be encrypted. | ||
- `encrypt.WHITELIST` - all data other than indices and whitelisted fields will be encrypted. | ||
- `encrypt.BLACKLIST` - listed fields will be encrypted. | ||
- `encrypt.UNENCRYPTED_LIST` - all data other than indices and whitelisted fields will be encrypted. | ||
- `encrypt.ENCRYPT_LIST` - listed fields will be encrypted. | ||
@@ -70,7 +70,7 @@ ```javascript | ||
friends: { | ||
type: encrypt.WHITELIST, | ||
type: encrypt.UNENCRYPTED_LIST, | ||
fields: ['street', 'picture'], // these two fields and indices will be plain text | ||
}, | ||
enemies: { | ||
type: encrypt.BLACKLIST, | ||
type: encrypt.ENCRYPT_LIST, | ||
fields: ['picture', 'isMortalEnemy'], // note: these cannot be indices | ||
@@ -77,0 +77,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
42347
11
24
460
5
2
1
+ Added@stablelib/utf8@1.0.0
+ Added@stablelib/utf8@1.0.0(transitive)
+ Addedbase64-arraybuffer-es6@0.6.0(transitive)
+ Addeddexie@3.2.7(transitive)
+ Addedtypeson-registry@1.0.0-alpha.38(transitive)
- Removedbase64-arraybuffer-es6@0.7.0(transitive)
- Removedtypeson@6.1.0(transitive)
- Removedtypeson-registry@1.0.0-alpha.39(transitive)
Updatedtweetnacl@1.0.3
Updatedtypeson@5.18.2