New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

crypto-pouch

Package Overview
Dependencies
Maintainers
2
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

crypto-pouch - npm Package Compare versions

Comparing version 3.1.3 to 4.0.0

.github/workflows/ci.yaml

253

index.js

@@ -1,185 +0,86 @@

'use strict';
var pbkdf2 = require('native-crypto/pbkdf2');
var randomBytes = require('randombytes');
var configId = '_local/crypto';
var chachaHelper = require('./chacha');
var gcmHelper = require('./gcm');
var defaultDigest = 'sha256';
var defaultIterations = 100000;
var previousIterations = 1000;
var defaultAlgo = 'aes-gcm';
var transform = require('transform-pouch').transform;
var uuid = require('uuid');
function noop(){}
function cryptoInit(password, options) {
var db = this;
var key, cb;
var turnedOff = false;
var ignore = ['_id', '_rev', '_deleted']
if (!options) {
options = {};
}
var algo;
if (password && typeof password === 'object') {
options = password;
password = password.password;
delete options.password;
}
if (options.ignore) {
ignore = ignore.concat(options.ignore);
}
if(typeof options.cb === 'function') {
cb = options.cb;
} else {
cb = noop;
}
var pending;
if (options.key && !Buffer.isBuffer(options.key) && options.key instanceof global.Uint8Array) {
options.key = new Buffer(options.key)
}
if (Buffer.isBuffer(options.key) && options.key.length === 32) {
key = options.key;
pending = db.get(configId).catch(function () {
return {};
}).then(function (doc) {
algo = setAlgo(doc);
});
} else {
var digest = options.digest || defaultDigest;
var iterations = options.iteration || defaultIterations;
pending = db.get(configId).then(function (doc){
if (!doc.salt) {
throw {
status: 'invalid',
doc: doc
};
}
return doc;
}).catch(function (err) {
var doc;
if (err.status === 404) {
doc = {
_id: configId,
salt: randomBytes(16).toString('hex'),
digest: digest,
iterations: iterations,
algo: options.algorithm || defaultAlgo
};
} else if (err.status === 'invalid' && err.doc) {
doc = err.doc;
doc.salt = randomBytes(16).toString('hex');
doc.digest = digest;
doc.iterations = iterations;
doc.algo = options.algorithm || defaultAlgo;
}
if (doc) {
return db.put(doc).then(function () {
return doc;
});
}
throw err;
}).then(function (doc) {
algo = setAlgo(doc);
return pbkdf2(password, new Buffer(doc.salt, 'hex'), doc.iterations || options.iteration || previousIterations, 256 / 8, doc.digest || digest);
}).then(function (_key) {
password = null;
if (turnedOff) {
randomize(key);
} else {
key = _key;
}
process.nextTick(function () {
cb(null, key);
});
}).catch(function (e) {
process.nextTick(function (){
cb(e);
});
throw e;
});
}
db.transform({
incoming: function (doc) {
return pending.then(function () {
return encrypt(doc);
});
},
outgoing: function (doc) {
return pending.then(function () {
return decrypt(doc);
});
const Crypt = require('garbados-crypt')
const { transform } = require('transform-pouch')
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.'
module.exports = {
transform,
crypto: async function (password, options = {}) {
if (this.adapter === 'http') {
throw new Error(NO_COUCH)
}
});
db.removeCrypto = function () {
if (key) {
randomize(key);
if (typeof password === 'object') {
// handle `db.crypto({ password, ...options })`
options = password
password = password.password
delete options.password
}
turnedOff = true;
};
function setAlgo(doc) {
if (typeof doc.algo !== 'string') {
return chachaHelper;
} else if (doc.algo.toLowerCase().indexOf('chacha') > -1) {
return chachaHelper;
} else if (doc.algo.toLowerCase().indexOf('gcm') > -1) {
return gcmHelper;
} else {
throw new Error('invalid algo');
// setup ignore list
this._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 { exportString } = await this.get(LOCAL_ID)
this._crypt = await Crypt.import(password, exportString)
} catch (err) {
// istanbul ignore else
if (err.status === 404) {
// but if the doc doesn't exist, we do first-time setup
this._crypt = new Crypt(password)
const exportString = await this._crypt.export()
try {
await this.put({ _id: LOCAL_ID, exportString })
} catch (err2) {
// istanbul ignore else
if (err2.status === 409) {
// if the doc was created while we were setting up,
// try setting up again to retrieve the saved credentials.
await trySetup()
} else {
throw err2
}
}
} else {
throw err
}
}
}
}
function encrypt(doc) {
var nonce = randomBytes(12)
var outDoc = {
nonce: nonce.toString('hex')
};
// for loop performs better than .forEach etc
for (var i = 0, len = ignore.length; i < len; i++) {
outDoc[ignore[i]] = doc[ignore[i]]
delete doc[ignore[i]]
}
if (!outDoc._id) {
outDoc._id = uuid.v4()
}
// Encrypting attachments is complicated
// https://github.com/calvinmetcalf/crypto-pouch/pull/18#issuecomment-186402231
if (doc._attachments) {
throw new Error('Attachments cannot be encrypted. Use {ignore: "_attachments"} option')
}
var data = JSON.stringify(doc);
return algo.encrypt(data, key, nonce, new Buffer(outDoc._id)).then(function (resp) {
outDoc.tag = resp.tag;
outDoc.data = resp.data;
return outDoc;
await trySetup()
// instrument document transforms
this.transform({
incoming: async (doc) => {
// if no crypt, ex: after .removeCrypto(), just return the doc
if (!this._crypt) { return doc }
if (doc._attachments && !this._ignore.includes('_attachments')) {
throw new Error('Attachments cannot be encrypted. Use {ignore: "_attachments"} option')
}
const encrypted = {}
for (const key of this._ignore) {
encrypted[key] = doc[key]
}
encrypted.payload = await this._crypt.encrypt(JSON.stringify(doc))
return encrypted
},
outgoing: async (doc) => {
// if no crypt, ex: after .removeCrypto(), just return the doc
if (!this._crypt) { return doc }
const decryptedString = await this._crypt.decrypt(doc.payload)
const decrypted = JSON.parse(decryptedString)
return decrypted
}
})
},
removeCrypto: function () {
delete this._crypt
}
function decrypt(doc) {
if (turnedOff || !doc.nonce || !doc._id || !doc.tag || !doc.data) {
return doc;
}
return algo.decrypt(new Buffer(doc.data, 'hex'), key, new Buffer(doc.nonce, 'hex'), new Buffer(doc._id), new Buffer(doc.tag, 'hex')).then(function (outData) {
var out = JSON.parse(outData);
for (var i = 0, len = ignore.length; i < len; i++) {
out[ignore[i]] = doc[ignore[i]]
}
return out;
});
}
}
function randomize(buf) {
var len = buf.length;
var data = randomBytes(len);
var i = -1;
while (++i < len) {
buf[i] = data[i];
}
}
exports.transform = transform;
exports.crypto = cryptoInit;
// istanbul ignore next
if (typeof window !== 'undefined' && window.PouchDB) {
window.PouchDB.plugin(module.exports);
window.PouchDB.plugin(module.exports)
}
{
"name": "crypto-pouch",
"version": "3.1.3",
"version": "4.0.0",
"description": "encrypted pouchdb/couchdb database",
"main": "index.js",
"scripts": {
"test": "node test.js | tspec",
"build": "browserify ./ > bundle.js",
"min": "browserify ./ | uglifyjs -mc > bundle.min.js"
"test": "standard && dependency-check --unused --no-dev . && mocha",
"cov": "nyc npm test",
"build": "browserify . > bundle.js",
"min": "browserify . | uglifyjs -mc > bundle.min.js"
},

@@ -27,17 +28,15 @@ "repository": {

"dependencies": {
"chacha": "^2.1.0",
"native-crypto": "^1.5.2",
"pouchdb-promise": "^6.1.0",
"randombytes": "^2.0.3",
"transform-pouch": "^1.1.0",
"uuid": "^3.0.1"
"garbados-crypt": "^3.0.0-beta",
"transform-pouch": "^1.1.5"
},
"devDependencies": {
"browserify": "^13.0.0",
"memdown": "^1.0.0",
"pouchdb": "^6.1.0",
"tap-spec": "^4.1.1",
"tape": "^4.5.1",
"uglify-js": "^2.6.2"
"browserify": "^17.0.0",
"dependency-check": "^4.1.0",
"memdown": "^6.0.0",
"mocha": "^8.3.2",
"nyc": "^15.1.0",
"pouchdb": "^7.2.2",
"standard": "^16.0.3",
"uglify-js": "^3.13.5"
}
}

@@ -1,148 +0,94 @@

crypto pouch [![Build Status](https://travis-ci.org/calvinmetcalf/crypto-pouch.svg)](https://travis-ci.org/calvinmetcalf/crypto-pouch)
===
# Crypto-Pouch
Plugin to encrypt a PouchDB/CouchDB database.
[![CI](https://github.com/calvinmetcalf/crypto-pouch/actions/workflows/ci.yaml/badge.svg)](https://github.com/calvinmetcalf/crypto-pouch/actions/workflows/ci.yaml)
[![NPM Version](https://img.shields.io/npm/v/crypto-pouch.svg?style=flat-square)](https://www.npmjs.com/package/crypto-pouch)
[![JS Standard Style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
Plugin to encrypt a PouchDB database.
```js
var db = new PouchDB('my_db');
const PouchDB = require('pouchdb')
PouchDB.plugin(require('crypto-pouch'))
db.crypto(password);
// all done, docs should be transparently encrypted/decrypted
const db = new PouchDB('my_db')
db.removeCrypto();
// will no longer encrypt decrypt your data
// init; after this, docs will be transparently en/decrypted
db.crypto(password).then(() => {
// db will now transparently encrypt writes and decrypt reads
await db.put({ ... })
// you can disable transparent en/decryption,
// though encrypted docs remain encrypted
db.removeCrypto()
})
```
It encrypts with the AES-GCM using [native crypto](https://github.com/calvinmetcalf/native-crypto) which prefers the native version in node or the web crypto version in the browser, falling back to the version from [crypto browserify](https://github.com/crypto-browserify/crypto-browserify) if no native version exists. [Chacha20-Poly1305](https://github.com/calvinmetcalf/chacha20poly1305) is also available and previous versions defaulted to this algorithm. You might consider using this if your app will primarily be used in browsers that don't support the web crypto api (e.g. safari).
Crypto-Pouch encrypts documents using [TweetNaCl.js](https://github.com/dchest/tweetnacl-js), an [audited](https://cure53.de/tweetnacl.pdf) encryption library. It uses the *xsalsa20-poly1305* algorithm.
**Note**: Attachments cannot be encrypted at this point. Use `{ignore: '_attachments'}` to leave attachments unencrypted. Also note that `db.putAttachment` / `db.getAttachment` are not supported. Use `db.put` and `db.get({binary: true, attachment: true})` instead. ([#18](https://github.com/calvinmetcalf/crypto-pouch/issues/13)).
This only encrypts the contents of documents, NOT THE ID (or rev). So if you have a document with the id `plan_to_screw_over_db_admin`, while this plugin will happily encrypt that document, that may not be as helpful as you'd want it to be.
This only encrypts the contents of documents, **not the \_id or \_rev, nor view keys and values**. This means that `_id` values always remain unencrypted, and any keys or values emitted by views are stored unencrypted as well. If you need total encryption at rest, consider using the PouchDB plugin [ComDB](https://github.com/garbados/comdb) instead.
Usage
-------
## Usage
This plugin is hosted on npm. To use in Node.js:
This plugin is hosted on [npm](http://npmjs.com/). To install it in your project:
```bash
npm install crypto-pouch
$ npm install crypto-pouch
```
If you want to use it in the browser, download [the browserified version from wzrd.in](http://wzrd.in/standalone/crypto-pouch) and then include it after `pouchdb`:
## Usage
```html
<script src="pouchdb.js"></script>
<script src="pouchdb.crypto-pouch.js"></script>
```
### async db.crypto(password [, options])
API
--------
Set up encryption on the database.
- `password`: A string password, used to encrypt documents. Make sure it's good!
- `options.ignore`: Array of strings of properties that will not be encrypted.
### db.crypto(password [, options])
You may also pass an options object as the first parameter, like so:
Set up encryption on the database.
```javascript
db.crypto({ password, ignore: [...] }).then(() => {
// database will now encrypt writes and decrypt reads
})
```
If the second argument is an object:
- `options.ignore`
String or Array of Strings of properties that will not be encrypted.
- `options.digest`
Any of `sha1`, `sha256`, `sha512` (default).
- `options.algorithm`
Valid options are `chacha20` and `aes-gcm` (default).
- `iterations`
How many iterations of pbkdf2 to perform, defaults to 100000 (1000 in older versions).
- `key`
If passed a 32 byte buffer then this will be used as the key instead of it being generated from the password. **Warning** this buffer will be randomized when encryption is removed so pass in a copy of the buffer if that will be a problem.
- `password`
You can pass the options object as the first param if you really want and pass in the password in as an option.
- `cb`
A function you can pass in to get the derived key back called with 2 parameters, an error if there is one and the key if no error. **Warning** this buffer will be randomized when encryption is removed copy it or convert it to a string if that will be a problem.
### db.removeCrypto()
Disables encryption on the database and randomizes the key buffer.
Disables encryption on the database and forgets your password.
Details
===
## Details
If you replicate to another database, it will decrypt before sending it to
the external one. So make sure that one also has a password set as well if you want
it encrypted too.
If you replicate to another database, Crypto-Pouch will decrypt documents before
sending them to the target database. Documents received through replication will
be encrypted before being saved to disk.
If you change the name of a document, it will throw an error when you try
If you change the ID of a document, Crypto-Pouch will throw an error when you try
to decrypt it. If you manually move a document from one database to another,
it will not decrypt correctly. If you need to decrypt it a file manually
you will find a local doc named `_local/crypto` in the database. This doc has
fields named `salt` which is a hex-encoded buffer, `digest` which is a string, `iterations` which is an integer to use and `algo` which is the encryption algorithm. Run pbkdf2 your password with the
salt, digest and iterations values from that document as the parameters generate
a 32 byte (256 bit) key; that is the key for decoding documents. If digest, iterations, or algo are not on the local document due to it being created with an older version of the library, use 'sha256', 1000, and 'chacha20' respectively.
it will not decrypt correctly.
Each document has 3 relevant fields: `data`, `nonce`, and `tag`.
`nonce` is the initialization vector to give to the encryption algorithm in addition to the key
you generated. Pass the document `_id` as additional authenticated data and the tag
as the auth tag and then decrypt the data. If it throws an error, then you either
screwed up or somebody modified the data.
Encrypted documents have only one custom property, `payload`, which contains the
encrypted contents of the unencrypted document. So, `{ hello: 'world' }` becomes
`{ payload: '...' }`. This `payload` value is produced by [garbados-crypt](https://github.com/garbados/crypt#garbados-crypt); see that library for more details.
Examples
===
## Development
Derive key from password and salt
---
First, get the source:
```js
db.get('_local/crypto').then(function (doc) {
return new Promise(function (resolve, reject) {
crypto.pbkdf2(password, doc.salt, doc.iterations, 256/8, doc.digest, function (err, key) {
if (err) {
return reject(err);
}
resolve(key);
});
});
}).then(function (key) {
// you have the key
});
```bash
$ git clone git@github.com:calvinmetcalf/crypto-pouch.git
$ cd crypto-pouch
$ npm i
```
Decrypt a document encrypted with chacha
---
Use the test suite:
```js
var chacha = require('chacha');
db.get(id).then(function (doc) {
var decipher = chacha.createDecipher(key, new Buffer(doc.nonce, 'hex'));
decipher.setAAD(new Buffer(doc._id));
decipher.setAuthTag(new Buffer(doc.tag, 'hex'));
var out = decipher.update(new Buffer(doc.data, 'hex')).toString();
decipher.final();
// parse it AFTER calling final
// you don't want to parse it if it has been manipulated
out = JSON.parse(out);
out._id = doc._id;
out._rev = doc._rev;
return out;
});
```bash
$ npm test
```
Decrypt a document encrypted with aes-gcm
---
*When contributing patches, be a good neighbor and include tests!*
```js
var decrypt = require('native-crypto/decrypt');
## License
db.get(id).then(function (doc) {
var encryptedData = Buffer.concat([
new Buffer(doc.data, 'hex'),
new Buffer(doc.tag, 'hex')
]);
return decrypt(key, new Buffer(doc.nonce, 'hex'), encryptedData, new Buffer(doc._id)).then(function (resp) {
var out = JSON.parse(resp.toString());
out._id = doc._id;
out._rev = doc._rev;
return out;
});
});
```
See [LICENSE](./LICENSE).

@@ -1,275 +0,167 @@

var test = require('tape');
var PouchDB = require('pouchdb');
var memdown = require('memdown');
var Promise = require('pouchdb-promise');
PouchDB.plugin(require('./'));
test('basic', function (t) {
t.plan(4);
var dbName = 'one';
var db = new PouchDB(dbName, {db: memdown});
db.crypto('password');
db.put({foo: 'bar', _id: 'baz'}).then(function () {
return db.get('baz');
}).then(function (resp) {
t.equals(resp.foo, 'bar', 'decrypts data');
db.removeCrypto();
return db.get('baz');
}).then(function (doc) {
t.ok(doc.nonce, 'has nonce');
t.ok(doc.tag, 'has tag');
t.ok(doc.data, 'has data');
}).catch(function (e) {
t.error(e);
});
});
test('reopen', function (t) {
t.plan(1);
var dbName = 'one';
var db = new PouchDB(dbName, {db: memdown});
db.crypto('password');
db.get('baz').then(function (resp) {
t.equals(resp.foo, 'bar', 'decrypts data');
});
});
test('changes', function (t) {
t.plan(7);
var dbName = 'five';
var db = new PouchDB(dbName, {db: memdown});
db.changes({ live: true, include_docs: true}).on('change', function () {
t.ok(true, 'changes called');
/* global describe it beforeEach afterEach emit */
const assert = require('assert').strict
const memdown = require('memdown')
const PouchDB = require('pouchdb')
PouchDB.plugin(require('.'))
const PASSWORD = 'hello world'
const BAD_PASS = 'goodbye sol'
const NAME = 'crypto-pouch-testing'
const DOCS = [
{ _id: 'a', hello: 'world' },
{ _id: 'b', hello: 'sol' },
{ _id: 'c', hello: 'galaxy' }
]
const ATTACHMENTS = {
'att.txt': {
content_type: 'text/plain',
data: 'TGVnZW5kYXJ5IGhlYXJ0cywgdGVhciB1cyBhbGwgYXBhcnQKTWFrZSBvdXIgZW1vdGlvbnMgYmxlZWQsIGNyeWluZyBvdXQgaW4gbmVlZA=='
}
}
describe('crypto-pouch', function () {
beforeEach(async function () {
this.db = new PouchDB(NAME, { db: memdown })
await this.db.crypto(PASSWORD)
})
db.crypto('password');
db.put({foo: 'bar', _id: 'baz'}).then(function () {
return db.get('baz');
}).then(function (resp) {
t.equals(resp.foo, 'bar', 'decrypts data');
return db.post({baz: 'bat'});
}).then(function (d){
return new Promise(function (yes) {
setTimeout(function () {
yes(d);
}, 200);
});
}).then(function(d) {
return db.put({
_id: d.id,
_rev: d.rev,
once: 'more',
with: 'feeling'
});
}).then(function () {
return db.allDocs({include_docs: true});
}).then(function () {
db.removeCrypto();
return db.get('baz');
}).then(function (doc) {
t.ok(doc.nonce, 'has nonce');
t.ok(doc.tag, 'has tag');
t.ok(doc.data, 'has data');
}).catch(function (e) {
t.error(e);
});
});
test('ignore: _attachments', function (t) {
t.plan(1);
var dbName = 'six';
var db = new PouchDB(dbName, {db: memdown});
db.crypto('password', {ignore: '_attachments'})
db.put({
_id: 'id-12345678',
_attachments: {
'att.txt': {
content_type: 'text/plain',
data: 'TGVnZW5kYXJ5IGhlYXJ0cywgdGVhciB1cyBhbGwgYXBhcnQKTWFrZS' +
'BvdXIgZW1vdGlvbnMgYmxlZWQsIGNyeWluZyBvdXQgaW4gbmVlZA=='
}
afterEach(async function () {
await this.db.destroy()
})
it('should encrypt documents', async function () {
const doc = DOCS[0]
await this.db.put(doc)
const decrypted = await this.db.get(doc._id)
assert.equal(decrypted.hello, doc.hello)
// now let's ensure that doc is encrypted at rest
this.db.removeCrypto()
const encrypted = await this.db.get(doc._id)
assert.notEqual(encrypted.hello, doc.hello)
})
it('should not encrypt documents after crypto is removed', async function () {
const [doc1, doc2] = DOCS.slice(0, 2)
await this.db.put(doc1)
this.db.removeCrypto()
await this.db.put(doc2)
const encrypted = await this.db.get(doc1._id)
assert.notEqual(encrypted.hello, doc1.hello)
const decrypted = await this.db.get(doc2._id)
assert.equal(decrypted.hello, doc2.hello)
})
it('should fail when using a bad password', async function () {
await this.db.put({ _id: 'a', hello: 'world' })
this.db.removeCrypto()
await this.db.crypto(BAD_PASS)
try {
await this.db.get('a')
throw new Error('read succeeded but should have failed')
} catch (error) {
assert.equal(error.message, 'Could not decrypt!')
}
}).then(function () {
return db.get('id-12345678', {
attachments: true,
binary: true
});
}).then(function (doc) {
t.ok(Buffer.isBuffer(doc._attachments['att.txt'].data), 'returns _attachments as Buffers');
})
.catch(t.error);
})
test('throws error when document has attachments', function (t) {
t.plan(1);
var dbName = 'eight';
var db = new PouchDB(dbName, {db: memdown});
db.crypto('password')
db.put({
_id: 'id-12345678',
_attachments: {
'att.txt': {
content_type: 'text/plain',
data: 'TGVnZW5kYXJ5IGhlYXJ0cywgdGVhciB1cyBhbGwgYXBhcnQKTWFrZS' +
'BvdXIgZW1vdGlvbnMgYmxlZWQsIGNyeWluZyBvdXQgaW4gbmVlZA=='
it('should preserve primary index sorting', async function () {
for (const doc of DOCS) { await this.db.put(doc) }
const result = await this.db.allDocs()
for (let i = 0; i < result.rows.length - 1; i++) {
const row = result.rows[i]
const next = result.rows[i + 1]
assert(row.key < next.key)
}
})
it('should preserve secondary index sorting', async function () {
for (const doc of DOCS) { await this.db.put(doc) }
await this.db.put({
_id: '_design/test',
views: {
test: {
map: function (doc) { emit(doc.hello) }.toString()
}
}
})
const result = await this.db.query('test')
const EXPECTED = DOCS.map(({ hello }) => { return hello })
for (let i = 0; i < result.rows.length - 1; i++) {
const row = result.rows[i]
const next = result.rows[i + 1]
assert(row.key < next.key)
// ensure that keys are not encrypted
assert(EXPECTED.includes(row.key))
assert(EXPECTED.includes(next.key))
}
}).then(function () {
t.error('does not throw error');
}).catch(function (e) {
t.ok(/Attachments cannot be encrypted/.test(e.message), 'throws error');
})
})
test('options.digest with sha512 default', function (t) {
t.plan(2);
var db1 = new PouchDB('ten', {db: memdown});
var db2 = new PouchDB('eleven', {db: memdown});
// simulate previously doc created with {digest: sha512}
var docSha256 = {
nonce: '619cf4a32914bc9b5ca26ddf',
data: 'bdc160a9ff46151af37ccd6e20',
tag: '1d082c358bc4cda3e8249bb0bb19eb3e',
_id: 'baz'
};
var cryptoDoc = {
_id: '_local/crypto',
salt: 'f5c011aea21f25b9e975dbacbe38d235'
};
db1.bulkDocs([docSha256, cryptoDoc]).then(function () {
db1.crypto('password');
return db1.get('baz');
}).then(function (doc) {
t.equals(doc.foo, 'bar', 'returns doc for same write / read digest');
});
it('should error on attachments', async function () {
const doc = { ...DOCS[0], _attachments: ATTACHMENTS }
try {
await this.db.put(doc)
throw new Error('write should not have succeeded')
} catch (error) {
assert.equal(error.message, 'Attachments cannot be encrypted. Use {ignore: "_attachments"} option')
}
})
db2.bulkDocs([docSha256, cryptoDoc]).then(function () {
db2.crypto('password', {digest: 'sha512'});
return db2.get('baz');
}).then(function () {
t.error('does not throw error');
}).catch(function (err) {
t.ok(err, 'throws error for different write / read digest');
});
});
test('put with _deleted: true', function (t) {
t.plan(1);
var dbName = 'twelve';
var db = new PouchDB(dbName, {db: memdown});
db.crypto('password')
var doc = {_id: 'baz', foo: 'bar'}
db.put(doc).then(function (result) {
doc._rev = result.rev
doc._deleted = true
return db.put(doc)
}).then(function () {
return db.get('baz')
}).then(function () {
t.error('should not find doc after delete');
}).catch(function (err) {
t.equal(err.status, 404, 'cannot find doc after delete')
it('should ignore attachments when so instructed', async function () {
this.db.removeCrypto()
await this.db.crypto(PASSWORD, { ignore: '_attachments' })
const doc = { ...DOCS[0], _attachments: ATTACHMENTS }
await this.db.put(doc)
})
})
test('pass key in explicitly', function (t) {
t.plan(1);
var db = new PouchDB('thirteen', {db: memdown});
var ourDoc = {
nonce: '000000000000000000000000',
data: 'e42581d13a730258fadbe55e0e',
tag: 'b52f7f0f3e2926d7ee43f867d2c597e2',
_id: 'baz'
};
db.bulkDocs([ourDoc]).then(function () {
db.crypto({key: new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex')});
return db.get('baz');
}).then(function (doc) {
t.equals(doc.foo, 'bar', 'returns doc for same write / read digest');
}).catch(function (e) {
t.error(e);
});
});
test('pass key in explicitly as arr buff', function (t) {
t.plan(1);
var db = new PouchDB('tweentyteen', {db: memdown});
it('should handle _deleted:true ok', async function () {
const doc = DOCS[0]
const { rev } = await this.db.put(doc)
const deleted = { _id: doc._id, _rev: rev, _deleted: true }
await this.db.put(deleted)
try {
await this.db.get(doc._id)
throw new Error('read should not have succeeded')
} catch (error) {
assert.equal(error.reason, 'deleted')
}
})
var ourDoc = {
nonce: '000000000000000000000000',
data: 'e42581d13a730258fadbe55e0e',
tag: 'b52f7f0f3e2926d7ee43f867d2c597e2',
_id: 'baz'
};
db.bulkDocs([ourDoc]).then(function () {
db.crypto({key: new Uint8Array(new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'))});
return db.get('baz');
}).then(function (doc) {
t.equals(doc.foo, 'bar', 'returns doc for same write / read digest');
}).catch(function (e) {
t.error(e);
});
});
test('wrong password throws error', function (t) {
t.plan(1);
var db = new PouchDB('thirteen', {db: memdown});
it('should accept crypto params as an object', async function () {
this.db.removeCrypto()
await this.db.crypto({ password: PASSWORD })
const doc = DOCS[0]
await this.db.put(doc)
const { hello } = await this.db.get(doc._id)
assert.equal(hello, 'world')
})
var ourDoc = {
nonce: '000000000000000000000000',
data: 'e42581d13a730258fadbe55e0e',
tag: 'b52f7f0f3e2926d7ee43f867d2c597e2',
_id: 'baz'
};
db.bulkDocs([ourDoc]).then(function () {
db.crypto('broken');
return db.get('baz');
}).then(function (doc) {
t.notEqual(doc.foo, 'bar', 'returns doc for same write / read digest');
}).catch(function (e) {
t.ok(e);
});
});
test('plain options object', function (t) {
t.plan(4);
var dbName = 'fourteen';
var db = new PouchDB(dbName, {db: memdown});
db.crypto({password: 'password'});
db.put({_id: 'baz', foo: 'bar'}).then(function () {
return db.get('baz');
}).then(function (resp) {
t.equals(resp.foo, 'bar', 'decrypts data');
db.removeCrypto();
return db.get('baz');
}).then(function (doc) {
t.ok(doc.nonce, 'has nonce');
t.ok(doc.tag, 'has tag');
t.ok(doc.data, 'has data');
}).catch(function (e) {
t.error(e);
});
});
test('get key explicitly', function (t) {
t.plan(3);
var db = new PouchDB('fifteen', {db: memdown});
var key;
var docs = [
{ _id: '_local/crypto',
salt: '0dac47a196e46680a359c9c18da0bc83',
digest: 'sha256',
iterations: 100000}
];
db.bulkDocs(docs).then(function () {
db.crypto('password', {
cb: function (err, resp) {
t.error(err);
key = resp.toString('base64');
t.equals(key, 'jr9j3Krslfck3UkxjiCNYI4hoKQWesoquw11yypC528=');
}
});
return db.put({
_id: 'baz',
foo: 'bar'
});
}).then(function () {
db.removeCrypto();
db.crypto({key: new Buffer(key, 'base64')});
return db.get('baz');
it('should fail to init with http adapter', async function () {
const db = new PouchDB('http://localhost:5984')
assert.rejects(
async () => { await db.crypto(PASSWORD) },
new Error('crypto-pouch does not work with pouchdb\'s http adapter. Use a local adapter instead.')
)
})
.then(function (doc) {
t.equals(doc.foo, 'bar', 'decrypts data');
}).catch(function (e) {
t.error(e);
});
});
describe('concurrency', function () {
beforeEach(async function () {
this.db1 = new PouchDB(NAME)
this.db2 = new PouchDB(NAME)
})
afterEach(async function () {
await this.db1.destroy() // also destroys db2 THANKS
})
it('should handle concurrent crypt instances ok', async function () {
this.timeout(10 * 1000)
await Promise.all([
this.db1.crypto(PASSWORD),
this.db2.crypto(PASSWORD)
])
await this.db1.put(DOCS[0])
const doc = await this.db2.get(DOCS[0]._id)
assert.equal(DOCS[0].hello, doc.hello)
})
})
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc