JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 96
  • Score
    100M100P100Q90442F
  • License Apache-2.0

End-to-end crypto plugin for the Hoodie client store.

Package Exports

  • hoodie-plugin-store-crypto

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (hoodie-plugin-store-crypto) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

hoodie-plugin-store-crypto

End-to-end crypto plugin for the Hoodie client store.

js-standard-style Build Status dependencies Status devDependencies Status Greenkeeper badge

This Hoodie plugin adds methods, to add, read and update encrypted documents in your users store, while still being able to add, read and update unencrypted documents.

It does this by adding an object to your Hoodie-client, with similar methods to the client's store. Those methods encrypt and decrypt objects, while using the corresponding methods from Hoodie to save them.

There is no server side to this plugin!

Everything of a doc will be encrypted, except _id, _rev, _deleted, _attachments, _conflicts and the hoodie object!

Example

hoodie.store.add({foo: 'bar'})
  .then(function (obj) {console.log(obj)})

hoodie.cryptoStore.setPassword('secret')        // unlock
  .then(function (salt) {
    hoodie.cryptoStore.add({foo: 'bar'})        // adds the object encrypted
      .then(function (obj) {console.log(obj)})  // returns it unencrypted!
  })

Acknowledgments

This project heavily uses code and is inspired by @calvinmetcalf's crypto-pouch and Hoodie's hoodie-store-client.

A huge thank you to those projects and their maintainers.

Usage

There are 2 ways to use this plugin in your app:

  • Use it with the Hoodie Plugin API
  • Use it with a bundler (Webpack or Browserify)

Usage with the Hoodie Plugin API

This will add the cryptoStore to your /hoodie/client.js if you use the hoodie package.

First, install the plugin as dependency of your Hoodie app:

npm install --save hoodie-plugin-store-crypto

Then add it to the hoodie.plugins array in your app’s package.json file.

{
  "name": "your-hoodie-app",
  ...
  "hoodie": {
    "plugins": [
      "hoodie-plugin-store-crypto"
    ]
  }
}

You can now start your app with npm start. There should now be an cryptoStore property on your client hoodie instance. You can access it with hoodie.cryptoStore.

Usage with Browserify or Webpack

If you are using a client bundler (e.g. Browserify or Webpack), then you can import it manually.

First, install the plugin as dev-dependency of your Hoodie app:

npm install --save-dev hoodie-plugin-store-crypto

Then require it and set it up:

var Hoodie = require('@hoodie/client')
var PouchDB = require('pouchdb')
var cryptoStore = require('hoodie-plugin-store-crypto')

var hoodie = new Hoodie({ // create an instance of the hoodie-client
  url: '',
  PouchDB: PouchDB
})

cryptoStore(hoodie) // sets up hoodie.cryptoStore

You only need to do it this way, if you directly require/import the @hoodie/client! If you get the client with <script src="/hoodie/client.js"></script>, then the first way is recommended.

Get started

To use the cryptoStore you need to set a password for encryption. This can be your users password to your app, or a special password, which they will enter or you generate.

There are 4 use-cases you must implement:

Sign up

The first use of the cryptoStore. This is usually in your sign up function, but can also be done if you newly added this plugin.

cryptoStore.setPassword(password) is used to set the encryption password. It will resolve with a salt. A salt is a second part of a password. cryptoStore.setPassword(password) will save the generated salt in _design/cryptoStore/salt, and use it.

Example:

function signUp (username, password, cryptoPassword) {
  return hoodie.account.signUp({username: username, password: password})

    .then(function (accountProperties) {
      return hoodie.cryptoStore.setPassword(cryptoPassword)

        .then(function (salt) {
          // now do what you do after you did sign up a user.
        })
    })
}

Sign in

Every time your user signs in you also need to unlock the cryptoStore.

cryptoStore.setPassword(password) is also used for unlocking. After sign in you must wait for the store to sync and then unlock the cryptoStore.

You must wait for the sync to finish, because the _design/cryptoStore/salt and hoodiePluginCryptoStore/salt objects must be updated to the latest version to unlock the cryptoStore! If the salt doc is missing, a new one will be created! Resulting in a new encryption key!

Example:

function signIn (username, password, cryptoPassword) {
  return hoodie.account.signIn({username: username, password: password})

    .then(function (accountProperties) {
      return hoodie.store.sync([
        '_design/cryptoStore/salt',
        'hoodiePluginCryptoStore/salt'
      ]) // wait for syncing to finish!
        .then(function () {
          return accountProperties
        })
    })

    .then(function (accountProperties) {
      return hoodie.cryptoStore.setPassword(cryptoPassword)

        .then(function (salt) {
          // now do what you do after sign in.
        })
    })
}

Sign out

The cryptoStore listen automatically to hoodie.account.on('signout') events and locks itself. You don't need to add any setup for it.

The cryptoStore.lock() method is there, so that you can add a lock after a timeout functionality or lock the store in a save way when closing an tab.

window.addEventListener('beforeunload', function (event) {
  // do your cleanup
  hoodie.cryptoStore.lock() // lock the cryptoStore in an cryptographic save way.
})

Open your app while signed in

This plugin doesn't save your users password! That results in you having to unlock the cryptoStore on every instance/tap of your web-app!

Here you also must wait for syncing to finish! But only if your user is online.

Example:

function unlock (cryptoPassword) {
  return hoodie.connectionStatus.check() // check if your app is online

    .then(function () {
      if (hoodie.connectionStatus.ok) { // if your app is online: sync your users store
        return hoodie.store.sync()
      }
    })

    .then(function () {
      return hoodie.cryptoStore.setPassword(cryptoPassword) // then unlock
    })
}

Changing the password

You can change the password and salt used for encryption with cryptoStore.changePassword(oldPassword, newPassword). This method also updates all documents, that are encrypted with the old password!

It is recommended to sync before the password change! To update all documents.

Example:

function changePassword (oldPassword, newPassword) {
  return hoodie.connectionStatus.check() // check if your app is online

    .then(function () {
      if (hoodie.connectionStatus.ok) { // if your app is online: sync your users store
        return hoodie.store.sync()
      }
    })

    .then(function () {
      return hoodie.cryptoStore.changePassword(oldPassword, newPassword)
    })

    .then(function (result) {
      console.log(result.notUpdated) // array of ids of all docs that weren't updated
    })
}

About the cryptography

This plugin uses the sha256 and pbkdf2 algorithm for generating a key from your password. The key is a 32 char Hash. And for encryption and decryption of your docs the AES-GCM algorithm is used.

What is encrypted

Hoodie, CouchDB and PouchDB need _id, _rev, _deleted, _attachments and _conflicts to function. They and the content of the hoodie object, are not encrypted! Everything else is run through JSON.stringify and encrypted.

Please be aware, that the _id of a doc is not encrypted! Don't store important or personal information in the _id!

Derive key from password and salt

var pbkdf2 = require('native-crypto/pbkdf2')
var randomBytes = require('randombytes')

function deriveKey (password) {
  return hoodie.store.find('_design/cryptoStore/salt')

    .then(function (doc) {
      var digest = 'sha256'
      var iterations = 100000
      var salt = doc.salt != null && typeof doc.salt === 'string' && doc.salt.length === 32
        ? doc.salt
        : randomBytes(16).toString('hex')

      return pbkdf2(password, Buffer.from(salt, 'hex'), iterations, 256 / 8, digest)
        .then(function (key) {
          return {
            key: key,
            salt: salt
          }
        })
    })
}

Encrypt a document

var nativeCrypto = require('native-crypto')
var randomBytes = require('randombytes')

var ignore = [
  '_id',
  '_rev',
  '_deleted',
  '_attachments',
  '_conflicts',
  'hoodie'
]

function encryptDoc (key, doc) {
  var nonce = randomBytes(12)
  var outDoc = {
    nonce: nonce.toString('hex')
  }

  ignore.forEach(function (key) {
    outDoc[key] = doc[key]
    delete doc[key]
  })

  var data = JSON.stringify(doc)
  return nativeCrypto.encrypt(key, nonce, data, Buffer.from(outDoc._id))
    .then(function (response) {
      outDoc.tag = response.slice(-16).toString('hex')
      outDoc.data = response.slice(0, -16).toString('hex')
      return outDoc
    })
}

Decrypt a document

var nativeCrypto = require('native-crypto')

var ignore = [
  '_id',
  '_rev',
  '_deleted',
  '_attachments',
  '_conflicts',
  'hoodie'
]

function decryptDoc (key, doc) {
  var data = Buffer.from(doc.data, 'hex')
  var tag = Buffer.from(doc.tag, 'hex')
  var encryptedData = Buffer.concat([data, tag])

  var nonce = Buffer.from(doc.nonce, 'hex')
  var aad = Buffer.from(doc._id)

  return nativeCrypto.decrypt(key, nonce, encryptedData, aad)

    .then(function (outData) {
      var out = JSON.parse(outData)

      ignore.forEach(function (key) {
        var ignoreValue = doc[key]

        if (ignoreValue !== undefined) {
          out[key] = ignoreValue
        }
      })

      return out
    })
}

API

cryptoStore (setup function)

cryptoStore(hoodie)
Argument Type Description Required
hoodie Object Hoodie client instance Yes

Returns undefined

Only required if you setup your hoodie-client youself!

Example

var Hoodie = require('@hoodie/client')
var PouchDB = require('pouchdb')
var cryptoStore = require('hoodie-plugin-store-crypto')

var hoodie = new Hoodie({ // create an instance of the hoodie-client
  url: '',
  PouchDB: PouchDB
})

cryptoStore(hoodie) // sets up hoodie.cryptoStore

hoodie.cryptoStore.setPassword('test')
  .then(function (salt) {
    console.log('done')
  })

cryptoStore.setPassword(password)

cryptoStore.setPassword(password)
Argument Type Description Required
password String A password for encrypting the objects Yes

Resolves with a salt. A salt is a string that will be used with the password together for the encryption. It is saved with the id _design/cryptoStore/salt. It also tests hoodiePluginCryptoStore/salt for the salt.

It doesn't reject!

Example

function signUp (accountProperties, encryptionPW) {
  hoodie.account.signUp(accountProperties)
    .then(function () {
      if (encryptionPW == null) {                  // Use a separate password for encryption or the same
        encryptionPW = accountProperties.password
      }
      return hoodie.cryptoStore.setPassword(encryptionPW)
        .then(function (salt) { // Salt is saved for you under `_design/cryptoStore/salt`
          console.log('Encryption is enabled!')
        })
    })
}

cryptoStore.setPassword(password, salt)

cryptoStore.setPassword(password, salt)
Argument Type Description Required
password String A password for encrypting the objects Yes
salt String A string generated by setPassword(password), to add another protection lair, as a second password. If this is missing, a salt will be generated. Which will result in a different encryption! Yes

Resolves with a salt. A salt is a string that will be used with the password together for the encryption. It is saved with the id _design/cryptoStore/salt.

It doesn't reject!

Example

function signIn (accountProperties, encryptionPW) {
  hoodie.account.signIn(accountProperties)
    .then(function () {
      return hoodie.store.find('cryptoSalt')
    })
    .then(function (saltObj) {
      if (encryptionPW == null) {                  // Use a separate password for encryption or the same
        encryptionPW = accountProperties.password
      }
      return hoodie.cryptoStore.setPassword(encryptionPW, saltObj.salt)
    })
    .then(function (salt) {
      console.log('you did sign in!')
    })
}

cryptoStore.changePassword(oldPassword, newPassword)

cryptoStore.changePassword(oldPassword, newPassword)
Argument Type Description Required
oldPassword String The old password, that was used up until now Yes
newPassword String New password, with which the docs will be encrypted Yes

Resolves with an object with the new salt and an array (notUpdated) with the ids of not updated docs. It will update all with oldPassword encrypted documents. And encrypt them with with the help of the newPassword. It also updates the salt in _design/cryptoStore/salt.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.changePassword('my-old-password', 'secret').then(function (report) {
  console.log('all documents are updated!')
  console.log(report.salt) // the new salt
  console.log(report.notUpdated) // array with all ids of encrypted docs that hasn't been updated
}).catch(function (error) {
  console.error(error)
})

cryptoStore.lock()

cryptoStore.lock()

This locks the store and every method fails until a new password is set. It also overwrites the internal key's memory in a in an cryptographic save way (10 times).

Resolves with a Boolean. true if the store is now locked, false if the store was already locked.

The cryptoStore listen automatically to hoodie.account.on('signout') events and locks itself.

cryptoStore.add(properties)

cryptoStore.add(properties)
Argument Type Description Required
properties Object properties of document Yes
properties._id String If set, the document will be stored at given id No

Resolves with properties unencrypted and adds id (unless provided). And adds a hoodie property with createdAt and updatedAt properties. It will be encrypted.

{
  "_id": "12345678-1234-1234-1234-123456789ABC",
  "foo": "bar",
  "hoodie": {
    "createdAt": "2018-05-26T18:38:32.920Z",
    "updatedAt": "2018-05-26T18:38:32.920Z"
  }
}

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.add({foo: 'bar'}).then(function (doc) {
  console.log(doc.foo) // bar
}).catch(function (error) {
  console.error(error)
})

cryptoStore.add(arrayOfProperties)

cryptoStore.add([properties])
Argument Type Description Required
arrayOfProperties Array Array of properties, see cryptoStore.add(properties) Yes

Resolves with an array of properties unencrypted in the arrayOfProperties and adds _id (unless provided). And adds a hoodie property with createdAt and updatedAt properties. It will be encrypted.

[
  {
    "_id": "12345678-1234-1234-1234-123456789ABC",
    "foo": "bar",
    "hoodie": {
      "createdAt": "2018-05-26T18:38:32.920Z",
      "updatedAt": "2018-05-26T18:38:32.920Z"
    }
  }
]

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.add([{foo: 'bar'}, {bar: 'baz'}]).then(function (docs) {
  console.log(docs.length) // 2
}).catch(function (error) {
  console.error(error)
})

cryptoStore.find(id)

cryptoStore.find(id)
Argument Type Description Required
id String Unique id of the document Yes

Resolves with properties unencrypted. Works on encrypted and unencrypted documents.

{
  "_id": "12345678-1234-1234-1234-123456789ABC",
  "foo": "bar",
  "hoodie": {
    "createdAt": "2018-05-26T18:38:32.920Z",
    "updatedAt": "2018-05-26T18:38:32.920Z"
  }
}

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.find('12345678-1234-1234-1234-123456789ABC').then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.find(doc)

cryptoStore.find(doc)
Argument Type Description Required
doc Object Document with _id property Yes

Resolves with properties unencrypted. Works on encrypted and unencrypted documents.

{
  "_id": "12345678-1234-1234-1234-123456789ABC",
  "foo": "bar",
  "hoodie": {
    "createdAt": "2018-05-26T18:38:32.920Z",
    "updatedAt": "2018-05-26T18:38:32.920Z"
  }
}

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.find(doc).then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.find(idsOrDocs)

cryptoStore.find([doc])
Argument Type Description Required
idsOrDocs Array Array of id (String) or doc (Object) items Yes

Resolves with array of properties unencrypted. Works on encrypted and unencrypted documents.

[
  {
    "_id": "12345678-1234-1234-1234-123456789ABC",
    "foo": "bar",
    "hoodie": {
      "createdAt": "2018-05-26T18:38:32.920Z",
      "updatedAt": "2018-05-26T18:38:32.920Z"
    }
  }
]

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.find([
  doc,
  "12345678-1234-1234-1234-123456789ABC"
]).then(function (docs) {
  console.log(docs.length) // 2
}).catch(function (error) {
  console.error(error)
})

cryptoStore.findOrAdd(id, doc)

cryptoStore.findOrAdd(id, doc)
Argument Type Description Required
id String Unique id of the document Yes
doc Object Document that will be saved if no document with the id exists Yes

Resolves with properties unencrypted. Works on encrypted and unencrypted documents. If doc is added, it will be encrypted and a hoodie property with createdAt and updatedAt properties added.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.findOrAdd('12345678-1234-1234-1234-123456789ABC', doc).then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.findOrAdd(doc)

cryptoStore.findOrAdd(doc)
Argument Type Description Required
doc Object Document with _id property Yes

Resolves with properties unencrypted. Works on encrypted and unencrypted documents. If doc is added, it will be encrypted and a hoodie property with createdAt and updatedAt properties added.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.findOrAdd(doc).then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.findOrAdd(idsOrDocs)

cryptoStore.findOrAdd(idsOrDocs)
Argument Type Description Required
idsOrDocs Array Array of documents with _id property or ids Yes

Resolves with array of properties unencrypted. Works on encrypted and unencrypted documents. If a doc is added, it will be encrypted and a hoodie property with createdAt and updatedAt properties added.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.findOrAdd([
  doc,
  '12345678-1234-1234-1234-123456789ABC'
]).then(function (docs) {
  console.log(docs.length) // 2
}).catch(function (error) {
  console.error(error)
})

cryptoStore.findAll()

cryptoStore.findAll(filterFunction)
Argument Type Description Required
filterFunction Function Function that will be called for every doc with doc, index and arrayOfAllDocs. And returns true if doc should be returned, false if not. No

Resolves with array of properties unencrypted. Works on encrypted and unencrypted documents.

[
  {
    "_id": "12345678-1234-1234-1234-123456789ABC",
    "foo": "bar",
    "hoodie": {
      "createdAt": "2018-05-26T18:38:32.920Z",
      "updatedAt": "2018-05-26T18:38:32.920Z"
    }
  }
]

Rejects with:

Name Description
Error ...

Example

function filter (doc, index, allDocs) {
  return index % 2 === 0
}

hoodie.cryptoStore.findAll(filter).then(function (docs) {
  console.log(docs.length)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.update(id, changedProperties)

cryptoStore.update(id, changedProperties)
Argument Type Description Required
id String Unique id of the document Yes
changedProperties Object Properties that should be changed Yes

Resolves with updated properties unencrypted. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.update('12345678-1234-1234-1234-123456789ABC', {foo: 'bar'}).then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.update(id, updateFunction)

cryptoStore.update(id, updateFunction)
Argument Type Description Required
id String Unique id of the document Yes
updateFunction Function Function that get the document passed and changes the document. Yes

Resolves with updated properties unencrypted. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted.

Rejects with:

Name Description
Error ...

Example

function updater (doc) {
  doc.foo = 'bar'
}

hoodie.cryptoStore.update('12345678-1234-1234-1234-123456789ABC', updater).then(function (doc) {
  console.log(doc.foo) // bar
}).catch(function (error) {
  console.error(error)
})

cryptoStore.update(doc)

cryptoStore.update(doc)
Argument Type Description Required
doc Object Properties that should be changed with a _id property Yes

Resolves with updated properties unencrypted. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.update({
  _id: '12345678-1234-1234-1234-123456789ABC',
  foo: 'bar'
}).then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.update(arrayOfDocs)

cryptoStore.update(arrayOfDocs)
Argument Type Description Required
arrayOfDocs Array Array properties that should be changed with a _id property Yes

Resolves with an array of updated properties unencrypted. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.update([
  {
    _id: '12345678-1234-1234-1234-123456789ABC',
    foo: 'bar'
  },
  otherDoc
]).then(function (docs) {
  console.log(docs.length) // 2
}).catch(function (error) {
  console.error(error)
})

cryptoStore.updateOrAdd(id, doc)

cryptoStore.updateOrAdd(id, doc)
Argument Type Description Required
id String Unique id of the document Yes
doc Object Properties that should be changed or added if doc doesn't exist Yes

Resolves with updated properties unencrypted. Updates existing documents and adds nonexistent docs. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted. If the doc is added, it will be encrypted and a hoodie property with createdAt and updatedAt properties added.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.updateOrAdd('12345678-1234-1234-1234-123456789ABC', {foo: 'bar'}).then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.updateOrAdd(doc)

cryptoStore.updateOrAdd(doc)
Argument Type Description Required
doc Object Properties that should be changed or added with a _id property Yes

Resolves with updated properties unencrypted. Updates existing documents and adds nonexistent docs. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted. If the doc is added, it will be encrypted and a hoodie property with createdAt and updatedAt properties added.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.updateOrAdd({
  _id: '12345678-1234-1234-1234-123456789ABC',
  foo: 'bar'
}).then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.updateOrAdd(arrayOfDocs)

cryptoStore.updateOrAdd(arrayOfDocs)
Argument Type Description Required
arrayOfDocs Array Array properties that should be changed or added with a _id property Yes

Resolves with an array of updated properties unencrypted. Updates existing documents and adds nonexistent docs. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted. If the doc is added, it will be encrypted and a hoodie property with createdAt and updatedAt properties added.

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.updateOrAdd([
  {
    _id: '12345678-1234-1234-1234-123456789ABC',
    foo: 'bar'
  },
  otherDoc
]).then(function (docs) {
  console.log(docs.length) // 2
}).catch(function (error) {
  console.error(error)
})

cryptoStore.updateAll(changedProperties)

cryptoStore.updateAll(changedProperties)
Argument Type Description Required
changedProperties Object Properties that should be changed by all documents Yes

Resolves with updated properties unencrypted. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted.

This updates and encrypts all documents with its idPrefix!

Rejects with:

Name Description
Error ...

Example

// This updates and encrypts all documents in the users store!
hoodie.cryptoStore.updateAll({foo: 'bar'}).then(function (docs) {
  console.log(docs) // all docs!
}).catch(function (error) {
  console.error(error)
})

// This updates and encrypts all documents that have an _id that starts with 'foo/'!
hoodie.cryptoStore.withIdPrefix('foo/').updateAll({foo: 'bar'}).then(function (docs) {
  console.log(docs) // all docs whose _id starts with 'foo/'!
}).catch(function (error) {
  console.error(error)
})

cryptoStore.updateAll(updateFunction)

cryptoStore.updateAll(updateFunction)
Argument Type Description Required
updateFunction Function Function that get the document passed and changes the document. Yes

Resolves with updated properties unencrypted. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted.

This updates and encrypts all documents with its idPrefix!

Rejects with:

Name Description
Error ...

Example

// This updates and encrypts all documents in the users store!
hoodie.cryptoStore.updateAll(function (doc) {
  doc.foo = 'bar'
}).then(function (docs) {
  console.log(docs) // all docs!
}).catch(function (error) {
  console.error(error)
})

// This updates and encrypts all documents that have an _id that starts with 'foo/'!
hoodie.cryptoStore.withIdPrefix('foo/').updateAll(function (doc) {
  doc.foo = 'bar'
}).then(function (docs) {
  console.log(docs) // all docs whose _id starts with 'foo/'!
}).catch(function (error) {
  console.error(error)
})

cryptoStore.remove(id)

cryptoStore.remove(id)
Argument Type Description Required
id String Unique id of the document Yes

Resolves with properties unencrypted. Works on encrypted and unencrypted documents. It set the document to deleted. If the document was unencrypted it will be encrypted. It adds deletedAt to the hoodie property.

{
  "_id": "12345678-1234-1234-1234-123456789ABC",
  "_deleted": true,
  "foo": "bar",
  "hoodie": {
    "createdAt": "2018-05-26T18:38:32.920Z",
    "updatedAt": "2018-05-30T00:05:46.976Z",
    "deletedAt": "2018-05-30T00:05:46.976Z"
  }
}

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.remove('12345678-1234-1234-1234-123456789ABC').then(function (doc) {
  console.log(doc)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.remove(doc)

cryptoStore.remove(doc)
Argument Type Description Required
doc Object Properties that should be changed with a _id property Yes

Resolves with properties unencrypted. Works on encrypted and unencrypted documents. It set the document to deleted and updates properties. If the document was unencrypted it will be encrypted. It adds deletedAt to the hoodie property.

{
  "_id": "12345678-1234-1234-1234-123456789ABC",
  "_deleted": true,
  "foo": "bar",
  "hoodie": {
    "createdAt": "2018-05-26T18:38:32.920Z",
    "updatedAt": "2018-05-30T00:05:46.976Z",
    "deletedAt": "2018-05-30T00:05:46.976Z"
  }
}

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.remove({
  _id: '12345678-1234-1234-1234-123456789ABC',
  foo: 'bar'
}).then(function (doc) {
  console.log(doc.foo) // bar
}).catch(function (error) {
  console.error(error)
})

cryptoStore.remove(idsOrDocs)

cryptoStore.remove(idsOrDocs)
Argument Type Description Required
idsOrDocs Array Properties that should be changed with a _id property or ids Yes

Resolves with properties unencrypted. Works on encrypted and unencrypted documents. It set the document to deleted and updates properties. If the document was unencrypted it will be encrypted. It adds deletedAt to the hoodie property.

[
  {
    "_id": "12345678-1234-1234-1234-123456789ABC",
    "_deleted": true,
    "foo": "bar",
    "hoodie": {
      "createdAt": "2018-05-26T18:38:32.920Z",
      "updatedAt": "2018-05-30T00:05:46.976Z",
      "deletedAt": "2018-05-30T00:05:46.976Z"
    }
  }
]

Rejects with:

Name Description
Error ...

Example

hoodie.cryptoStore.remove([
  doc,
  '12345678-1234-1234-1234-123456789ABC'
]).then(function (docs) {
  console.log(docs.length) // 2
}).catch(function (error) {
  console.error(error)
})

cryptoStore.removeAll()

cryptoStore.removeAll(updateFunction)
Argument Type Description Required
filterFunction Function Function that will be called for every doc with doc, index and arrayOfAllDocs. And returns true if doc should be returned, false if not. No

Resolves with updated properties unencrypted. Works on encrypted and unencrypted documents. If the document was unencrypted it will be encrypted.

[
  {
    "_id": "12345678-1234-1234-1234-123456789ABC",
    "_deleted": true,
    "foo": "bar",
    "hoodie": {
      "createdAt": "2018-05-26T18:38:32.920Z",
      "updatedAt": "2018-05-30T00:05:46.976Z",
      "deletedAt": "2018-05-30T00:05:46.976Z"
    }
  }
]

Rejects with:

Name Description
Error ...

Example

function filter (doc, index, allDocs) {
  return index % 2 === 0
}

hoodie.cryptoStore.removeAll(filter).then(function (docs) {
  console.log(docs.length)
}).catch(function (error) {
  console.error(error)
})

cryptoStore.on()

cryptoStore.on(eventName, handler)
Argument Type Description Required
eventName String Event type. One of add, update, remove or change. Yes
handler Function Event Handler, that will be called every time that event emits. Yes

Returns the cryptoStore. hander will be called with an updated doc. If the event is change, than the first argument is a eventName.

Rejects with:

Name Description
Error ...

Example

function changeHandler (eventName, doc) {
  console.log(eventName, doc)
}

hoodie.cryptoStore.on('change', changeHandler)
  .on('add', function (doc) { // .on returns the cryptoStore
    console.log('a doc with ' + doc._id + 'was added')
  })

cryptoStore.one()

cryptoStore.one(eventName, handler)
Argument Type Description Required
eventName String Event type. One of add, update, remove or change. Yes
handler Function Event Handler, that will be called one time that event emits. Yes

Returns the cryptoStore. hander will be called with an updated doc. If the event is change, than the first argument is a eventName. After that event is emitted, that handler will be removed.

Rejects with:

Name Description
Error ...

Example

function changeHandler (eventName, doc) {
  console.log(eventName, doc)
}

hoodie.cryptoStore.one('change', changeHandler)
  .one('add', function (doc) { // .on returns the cryptoStore
    console.log('a doc with ' + doc._id + 'was added')
  })

cryptoStore.off()

cryptoStore.off(eventName, handler)
Argument Type Description Required
eventName String Event type. One of add, update, remove or change. Yes
handler Function Event Handler, that will be removed Yes

Returns the cryptoStore.

Rejects with:

Name Description
Error ...

Example

var changeHandler = function (eventName, doc) {
  console.log(eventName, doc)
}

hoodie.cryptoStore.on('change', changeHandler)

hoodie.cryptoStore.off('change', changeHandler)

cryptoStore.withIdPrefix

cryptoStore.withIdPrefix(prefix)
Argument Type Description Required
prefix String Section that will be added before every id. Yes

Returns the prefixed copy of the cryptoStore.

Rejects with:

Name Description
Error ...

Example

var userData = hoodie.cryptoStore.withIdPrefix('user/')

// Only emits changes for docs with a 'user/'-prefix.
userData.on('change', function (eventName, doc) {
  console.log(eventName, doc)
})

userData.add({
  _id: 'test-user', // will be saved as 'user/test-user'
  name: 'Tester'
})
  .then(function (doc) {
    console.log(doc._id) // 'user/test-user'
    return userData.find('test-user')
  })

  .then(function (doc) {
    doc.isTester = true
    userData.update(doc) // 'user/test-user' and 'test-user' work!
  })

cryptoStore.withPassword

cryptoStore.withPassword(password, salt)
Argument Type Description Required
password String A password for encrypting the objects Yes
salt String A second password part, to add another protection lair. If this is missing a salt will be generated. Which will result in a different encryption! No

Resolves with an object containing the used salt and a prefixed copy of the cryptoStore.

{
  "salt": "1234567890",
  "store": {
    "add": function () {},
    "withPassword": function () {},
    ...
  }
}

Rejects with:

Name Description
Error ...

Example

var result = hoodie.cryptoStore.withPassword('secretPassword')

  .then(function (result) {
    var store = result.store
    var salt = result.salt

    // Only emits changes for docs that are encrypted with this password and salt.
    store.on('change', function (eventName, doc) {
      console.log(eventName, doc)
    })

    store.add({foo: 'bar'})

    // you must save the salt! Only with the same salt it is the same encryption!
    hoodie.cryptoStore.findOrAdd({
      _id: 'secondPasswordSalt',
      salt: salt
    })
  })

Events

Event Description Arguments
add Is emitted every time a doc is added/created. doc the added document.
update Is emitted every time a doc is updated/changed. doc the changed document
remove Is emitted every time a doc is removed. doc the removed document.
change Is emitted every time a doc is added, updated or removed. event what did happen? (add, update or remove), doc the changed document.