hoodie-plugin-store-crypto
End-to-end crypto plugin for the Hoodie client store.
This Hoodie plugin adds methods, to add, read, update and delete encrypted
documents in your users store, while still being able to add, read, update and delete 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 get encrypted. Except for _id
, _rev
, _deleted
, _attachments
, _conflicts
and the hoodie
object!
Example
hoodie.store.add({foo: 'bar'})
.then(function (obj) { console.log(obj) })
hoodie.cryptoStore.setup('secret')
.then(async () => {
await hoodie.cryptoStore.unlock('secret')
const obj = await hoodie.cryptoStore.add({ foo: 'bar' })
console.log(obj)
})
Update notes
Please read the update notes for migrating from v1 to v2!.
Acknowledgments
This project heavily uses code and inspiration by
@calvinmetcalf's crypto-pouch
and Hoodie's hoodie-store-client.
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 import 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({
url: window.location.origin,
PouchDB: PouchDB
})
cryptoStore(hoodie)
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 5 use-cases you must put in place:
Setup
The first use of the cryptoStore. Setup can get done in your sign up function, but also if
you newly added this plugin.
Use cryptoStore.setup(password, [salt])
to set the
encryption password. cryptoStore.setup(password, [salt])
will not unlock your cryptoStore instance
(just like hoodie.account.signUp)!
A salt is a second part of a password. cryptoStore.setup(password, [salt])
will save the generated salt in hoodiePluginCryptoStore/salt
, and use it. More about what the salt is.
Example:
async function signUp (username, password, cryptoPassword) {
const accountProperties = await hoodie.account.signUp({
username: username,
password: password
})
const resetKeys = await hoodie.cryptoStore.setup(cryptoPassword)
displayResetKeys(resetKeys)
return signIn(username, password, cryptoPassword)
}
Sign in
Every time your user signs in you also need to unlock the cryptoStore.
Use cryptoStore.unlock(password)
for unlocking.
unlock
will try to pull hoodiePluginCryptoStore/salt
from the server, to have the latest version of it.
Example:
async function signIn (username, password, cryptoPassword) {
const accountProperties = await hoodie.account.signIn({
username: username,
password: password
})
await hoodie.cryptoStore.unlock(cryptoPassword)
}
Sign out
cryptoStore
will automatically listen to account.on('signout')
events. And locks itself if it emits an event. You don't need to add any setup for it.
Usecases for the cryptoStore.lock()
method are:
- a lock after a timeout functionality
- lock the store in a save way when closing an tab.
window.addEventListener('beforeunload', function (event) {
hoodie.cryptoStore.lock()
})
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!
Example:
async function unlock (cryptoPassword) {
await hoodie.cryptoStore.unlock(cryptoPassword)
}
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 got encrypted with the old password!
Please sync before the password change! To update all documents.
Example:
async function changePassword (oldPassword, newPassword) {
await hoodie.connectionStatus.check()
if (hoodie.connectionStatus.ok) {
await hoodie.store.sync()
}
const result = await hoodie.cryptoStore.changePassword(oldPassword, newPassword)
console.log(result.notUpdated)
displayResetKeys(result.resetKeys)
}
Reset the password
This works like changing the password. With the difference of:
The user must enter a reset-key not the old password, and calling resetPassword()
!
setup()
, changePassword()
and resetPassword()
result 10 reset-keys. You should display them to your user.
Or/and generate a text-file for your user to download.
function generateResetKeysFile (resetKeys) {
const text = resetKeys.join('\n')
const file = new Blob([text], { type: 'text/plain' })
const url = URL.createObjectURL(file)
const a = document.getElementById('yourDownloadLink')
a.href = url
a.download = '[Your app name] reset-keys.txt'
}
Then, when the user did forget their encryption password, call cryptoStore.resetPassword(aResetKey, newPassword)
.
Every resetKey has a doc. Their _id
starts with hoodiePluginCryptoStore/pwReset_
, followed with the number 0 to 9. Please don't change them!
v2 Update Notes
setPassword
setPassword
got split into setup
and unlock
.
Fail if not unlocked
All reading and writing methods fail now if this plugin wasn't unlocked!
Checking the Password
v1 didn't check if the entered password was correct! This version does now!
It uses an encrypted random string in the hoodiePluginCryptoStore/salt
doc. Saved in the check
-field. With the same encryption as the other docs. It will get added/updated with setup
and changePassword
.
{
"_id": "hoodiePluginCryptoStore/salt",
"salt": "bf11fa9bafca73586e103d60898989d4",
"check": {
"nonce": "6e9cf8a4a6eee26f19ff8c70",
"tag": "0d2cfd645fe49b8a29ce22dbbac26b1e",
"data": "5481cf42b7e3f1d15477ed8f1d938bd9fd6103903be6dd4e146f69d9f124e34f33b7f ... this is 256 chars long ..."
}
}
It will still unlock, if no password check is present on the salt-doc! But it will add a check as soon as the first encrypted doc got read without an error!
This is to ensure backwards compatibility.
The password check autofix can get deactivated
To deactivate the password check autofix add the option noPasswordCheckAutoFix
.
{
"name": "your-hoodie-app",
...
"hoodie": {
"plugins": [
"hoodie-plugin-store-crypto"
],
"app": {
"hoodie-plugin-store-crypto": {
"noPasswordCheckAutoFix": true
}
}
}
}
var Hoodie = require('@hoodie/client')
var PouchDB = require('pouchdb')
var cryptoStore = require('hoodie-plugin-store-crypto')
var hoodie = new Hoodie({
url: '',
PouchDB: PouchDB
})
cryptoStore(hoodie, { noPasswordCheckAutoFix: true })
Then no password check will get added, until the next password change.
v2.2 Update Notes
This version adds password-resetKeys. Display them to your user. If the user forgets their password, they can
reset their password, using one of the 10 reset keys.
They get generated by:
setup
changePassword
resetPassword
If the user was already setup, then no reset key will get generated, until the next password change!
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 gets 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 goes through JSON.stringify
and gets encrypted.
This includes all fields of old documents. Those fields will then get deleted!
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')
async function deriveKey (password) {
const doc = await hoodie.store.find('hoodiePluginCryptoStore/salt')
const digest = 'sha256'
const iterations = 100000
const salt = doc.salt != null && typeof doc.salt === 'string' && doc.salt.length === 32
? doc.salt
: randomBytes(16).toString('hex')
const key = await pbkdf2(password, Buffer.from(salt, 'hex'), iterations, 256 / 8, digest)
return {
key: key,
salt: salt
}
}
Encrypt a document
var encrypt = require('native-crypto/encrypt')
var randomBytes = require('randombytes')
var ignore = [
'_id',
'_rev',
'_deleted',
'_attachments',
'_conflicts',
'hoodie'
]
async 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)
const response = await encrypt(key, nonce, data, Buffer.from(outDoc._id))
outDoc.tag = response.slice(-16).toString('hex')
outDoc.data = response.slice(0, -16).toString('hex')
return outDoc
}
Decrypt a document
var decrypt = require('native-crypto/decrypt')
var ignore = [
'_id',
'_rev',
'_deleted',
'_attachments',
'_conflicts',
'hoodie'
]
async 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)
const outData = await decrypt(key, nonce, encryptedData, aad)
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, options)
Setup the cryptoStore and adds it to hoodie.
Returns undefined
Required if you setup your hoodie-client yourself! Else Hoodie does it for you!
Example
var Hoodie = require('@hoodie/client')
var PouchDB = require('pouchdb')
var cryptoStore = require('hoodie-plugin-store-crypto')
var hoodie = new Hoodie({
url: window.location.origin,
PouchDB: PouchDB
})
cryptoStore(hoodie)
hoodie.cryptoStore.setup('test')
.then(function () {
console.log('done')
})
cryptoStore.setup(password)
cryptoStore.setup(password)
Setup an users encryption.
Argument | Type | Description | Required |
---|
password | String | A password for encrypting the objects | Yes |
Results with an Array of 10 resetKeys
(Strings). crytoStore.resetPassword()
requires them, in case the
user did forget their encryption-password.
Sets up the encryption, generates a salt and saves it in hoodiePluginCryptoStore/salt
.
A salt is a string that will get used with the password together for the encryption. More about what the salt is.
This will not unlock the cryptoStore!
Rejects if there is already a local or remote hoodiePluginCryptoStore/salt
or _design/cryptoStore/salt
doc!
Rejects with:
Name | Status | Description | Why |
---|
badarg | 500 | password must be a string! | The password wasn't a string. |
badarg | 500 | password is to short! | The password must be longer than 2 chars. (You should require an even longer password!) |
unauthorized | 401 | Name or password is incorrect. | Did already setup. |
Example
async function signUp (username, password, cryptoPassword) {
const accountProperties = await hoodie.account.signUp({
username: username,
password: password
})
if (cryptoPassword == null) {
cryptoPassword = password
}
const resetKeys = await hoodie.cryptoStore.setup(cryptoPassword)
displayResetKeys(resetKeys)
return signIn(username, password, cryptoPassword)
}
cryptoStore.setup(password, salt)
cryptoStore.setup(password, salt)
Setup an user for encryption. But provide your own salt. This is not recommended!
Argument | Type | Description | Required |
---|
password | String | A password for encrypting the objects | Yes |
salt | String | 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 |
Results with an Array of 10 resetKeys
(Strings). crytoStore.resetPassword()
requires them, in case the
user did forget their encryption-password.
Sets up the encryption and saves the salt in hoodiePluginCryptoStore/salt
.
A salt is a string that will get used with the password together for the encryption.
More about what the salt is.
This will not unlock the cryptoStore!
Rejects if there is already a local or remote hoodiePluginCryptoStore/salt
or _design/cryptoStore/salt
doc!
Rejects with:
Name | Status | Description | Why |
---|
badarg | 500 | password must be a string! | The password wasn't a string. |
badarg | 500 | password is to short! | The password must be longer than 2 chars. (You should require an even longer password!) |
badarg | 500 | salt must be a 32 char string! | The passed salt wasn't a string or not 32 chars long! |
unauthorized | 401 | Name or password is incorrect. | Did already setup. |
Example
async function signUp (username, password, cryptoPassword, salt) {
const accountProperties = await hoodie.account.signUp({
username: username,
password: password
})
if (cryptoPassword == null) {
cryptoPassword = password
}
const resetKeys = await hoodie.cryptoStore.setup(cryptoPassword, salt)
displayResetKeys(resetKeys)
return signIn(username, password, cryptoPassword)
}
cryptoStore.unlock(password)
cryptoStore.unlock(password)
Unlock the cryptoStore. It will be ready for usage after it. The user must be setup
first!
Argument | Type | Description | Required |
---|
password | String | The password used for encrypting the objects | Yes |
Uses the salt in hoodiePluginCryptoStore/salt
or _design/cryptoStore/salt
and unlocks the cryptoStore.
It will pull hoodiePluginCryptoStore/salt
and _design/cryptoStore/salt
from the remote and
reject if they don't exists or got deleted or the password mismatch.
Rejects with:
Name | Status | Description | Why |
---|
badarg | 500 | password must be a string! | The password wasn't a string. |
badarg | 500 | password is to short! | The password must be longer than 2 chars. |
badarg | 500 | salt in "hoodiePluginCryptoStore/salt" must be a 32 char string! | The salt was changed and is not a 32 char string! |
invalid_request | 400 | store is already unlocked! | Store is unlocked. |
unauthorized | 401 | Name or password is incorrect. | The password wasn't correct. (user input) |
not_found | 404 | missing | The salt-doc couldn't be found! Was it deleted or the user wasn't setup? |
Example
async function signIn (username, password, cryptoPassword) {
const accountProperties = await hoodie.account.signIn({
username: username,
password: password
})
await hoodie.cryptoStore.unlock(cryptoPassword)
}
cryptoStore.changePassword(oldPassword, newPassword)
cryptoStore.changePassword(oldPassword, newPassword)
Changes the encryption password and salt. Then it will update all encrypted documents.
All encrypted documents, that couldn't get decrypted, will not get updated! The Array, at the notUpdated
field, will include all their _id
s.
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
, an array (notUpdated
) with the ids of not updated docs and
an Array of 10 new resetKeys
.
It will update all with oldPassword
encrypted documents. And encrypt them with with the help of
the newPassword
. It also updates the salt
in hoodiePluginCryptoStore/salt
.
Rejects with:
Name | Status | Description | Why |
---|
badarg | 500 | New password must be a string! | The new password wasn't a string. |
badarg | 500 | password is to short! | The new password must be longer than 2 chars. |
unauthorized | 401 | Name or password is incorrect. | The entered old password is wrong. |
Example
hoodie.cryptoStore.changePassword('my-old-password', 'secret').then(function (report) {
console.log('all documents are updated!')
console.log(report.salt)
console.log(report.notUpdated)
displayResetKeys(report.resetKeys)
}).catch(function (error) {
console.error(error)
})
cryptoStore.resetPassword(resetKey, newPassword)
cryptoStore.resetPassword(resetKey, newPassword)
This is for when the user did forget their password.
Changes the encryption password and salt. Then it will update all encrypted documents.
All encrypted documents, that couldn't get decrypted, will not get updated! The Array, at the notUpdated
field, will include all their _id
s.
Argument | Type | Description | Required |
---|
resetKey | String | One of the resetKeys generated by setup() , changePassword() and resetPassword() | Yes |
newPassword | String | New password, with which the docs will be encrypted | Yes |
Resolves with an object with the new salt
, an array (notUpdated
) with the ids of not updated docs and
an Array of 10 new resetKeys
.
It will update all with the main password encrypted documents. And encrypt them with with the help of
the newPassword
. It also updates the salt
in hoodiePluginCryptoStore/salt
.
Rejects with:
Name | Status | Description | Why |
---|
badarg | 500 | New password must be a string! | The new password wasn't a string. |
badarg | 500 | password is to short! | The new password must be longer than 2 chars. |
unauthorized | 401 | Reset-key is incorrect. | The entered resetKey is wrong. |
Example
async function userDidForgetPassword (resetKey) {
try {
const report = await hoodie.cryptoStore.resetPassword('my-old-password', 'secret')
console.log('all documents are updated!')
console.log(report.salt)
console.log(report.notUpdated)
displayResetKeys(report.resetKeys)
} catch (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)
Encrypt and add a document to the users store.
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 get encrypted.
{
"_id": "12345678-1234-1234-1234-123456789ABC",
"foo": "bar",
"hoodie": {
"createdAt": "2018-05-26T18:38:32.920Z",
"updatedAt": "2018-05-26T18:38:32.920Z"
}
}
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | properties isn't an object. |
Conflict | 409 | Object with id "id" already exists | An object with this _id already exists. |
Example
hoodie.cryptoStore.add({foo: 'bar'}).then(function (doc) {
console.log(doc.foo)
}).catch(function (error) {
console.error(error)
})
cryptoStore.add(arrayOfProperties)
cryptoStore.add([properties])
Encrypt and add one or more documents to the users store.
Argument | Type | Description | Required |
---|
arrayOfProperties | Array | Array of properties , see cryptoStore.add(properties) | Yes |
It adds _id
(unless provided) and a hoodie
property with createdAt
and updatedAt
properties. And encrypts them.
Resolves with an array of the added documents, unencrypted.
[
{
"_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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | properties isn't an object. |
Conflict | 409 | Object with id "id" already exists | An object with this _id already exists. |
Example
hoodie.cryptoStore.add([{foo: 'bar'}, {bar: 'baz'}]).then(function (docs) {
console.log(docs.length)
}).catch(function (error) {
console.error(error)
})
cryptoStore.find(id)
cryptoStore.find(id)
Find a document in the users store. And decrypt encrypted documents.
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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
Not found | 404 | Object with id "id" is missing | There is no object with this _id . |
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)
Find a document in the users store. And decrypt encrypted documents.
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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
Not found | 404 | Object with id "id" is missing | There is no object with this _id . |
Example
hoodie.cryptoStore.find(doc).then(function (doc) {
console.log(doc)
}).catch(function (error) {
console.error(error)
})
cryptoStore.find(idsOrDocs)
cryptoStore.find([doc])
Find one or more documents in the users store. And decrypt encrypted documents.
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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
Not found | 404 | Object with id "id" is missing | There is no object with this _id . |
Example
hoodie.cryptoStore.find([
doc,
"12345678-1234-1234-1234-123456789ABC"
]).then(function (docs) {
console.log(docs.length)
}).catch(function (error) {
console.error(error)
})
cryptoStore.findOrAdd(id, doc)
cryptoStore.findOrAdd(id, doc)
Find a document in the users store. And decrypt encrypted documents. If no document is present: doc
will get added (and encrypted).
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 gets added, it will also encrypt it and add a hoodie
property with createdAt
and updatedAt
properties.
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
missing_id | 412 | _id is required for puts | id is not a string or an object with an _id . |
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)
Find a document in the users store. And decrypt encrypted documents. If no document is present: doc
will get added (and encrypted).
Argument | Type | Description | Required |
---|
doc | Object | Document with _id property | Yes |
Resolves with properties
unencrypted. Works on encrypted and unencrypted documents. If doc gets added, it will also encrypt it and add a hoodie
property with createdAt
and updatedAt
properties.
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
missing_id | 412 | _id is required for puts | id is not a string or an object with an _id . |
Example
hoodie.cryptoStore.findOrAdd(doc).then(function (doc) {
console.log(doc)
}).catch(function (error) {
console.error(error)
})
cryptoStore.findOrAdd(idsOrDocs)
cryptoStore.findOrAdd(idsOrDocs)
Find one or more documents in the users store. And decrypt encrypted documents. If a document is not present: a new one will get added (and encrypted).
Argument | Type | Description | Required |
---|
idsOrDocs | Array | Array of documents with _id property or ids | Yes |
Resolves with an array of properties
unencrypted. Works on encrypted and unencrypted documents. If a doc gets added, it will also encrypt it and add a hoodie
property with createdAt
and updatedAt
properties.
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
missing_id | 412 | _id is required for puts | id is not a string or an object with an _id . |
Example
hoodie.cryptoStore.findOrAdd([
doc,
'12345678-1234-1234-1234-123456789ABC'
]).then(function (docs) {
console.log(docs.length)
}).catch(function (error) {
console.error(error)
})
cryptoStore.findAll()
cryptoStore.findAll(filterFunction)
Find all documents. And decrypt encrypted documents. The filterFunction
filters out documents, the same way as Array.prototype.filter
does.
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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
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)
Find a document with id
and update all changedProperties. Then encrypt it.
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. Unencrypted documents will get encrypted!
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | changedProperties isn't an object. |
not_found | 404 | missing | There is no object with this _id . |
- | - | Must provide change | changedProperties isn't an object or function. |
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)
Find a document with id
and update it with an updateFunction. Then encrypt it.
The document will be how the updateFunction changes it. This can add, update and delete field on the document.
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. Unencrypted documents will get encrypted!
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | updateFunction isn't an object or function. |
not_found | 404 | missing | There is no object with this _id . |
- | - | Must provide change | updateFunction isn't an object or function. |
Example
function updater (doc) {
doc.foo = 'bar'
}
hoodie.cryptoStore.update('12345678-1234-1234-1234-123456789ABC', updater).then(function (doc) {
console.log(doc.foo)
}).catch(function (error) {
console.error(error)
})
cryptoStore.update(doc)
cryptoStore.update(doc)
Find a document with _id
of that object. And assigns all properties of this object to the doc. And then encrypts it.
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. Unencrypted documents will get encrypted!
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | doc isn't an object with an _id field. |
not_found | 404 | missing | There is no object with this _id . |
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)
Find one or more documents. It uses the _id
of every object to find the document. Then all properties of that object will get assigned to the doc. And then encrypts it.
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. Unencrypted documents will get encrypted!
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | This element in the array isn't an object with an _id field. |
not_found | 404 | missing | There is no object with this _id . |
Example
hoodie.cryptoStore.update([
{
_id: '12345678-1234-1234-1234-123456789ABC',
foo: 'bar'
},
otherDoc
]).then(function (docs) {
console.log(docs.length)
}).catch(function (error) {
console.error(error)
})
cryptoStore.updateOrAdd(id, doc)
cryptoStore.updateOrAdd(id, doc)
Try to find and update a doc with id
. If none exist add one with id
as its `_id' and doc as its properties. And encrypt the document.
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. Unencrypted documents will get encrypted! If the doc gets added, it will encrypt it and add a hoodie
property with createdAt
and updatedAt
properties added.
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | doc isn't an object. |
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)
Try to find and update a doc with _id
. If none exist add this doc as it. And encrypt the document.
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. Unencrypted documents will get encrypted! If the doc gets added, it will encrypt it and add a hoodie
property with createdAt
and updatedAt
properties added.
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | doc isn't an object with an _id field. |
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)
Try to find and update one or more documents. It uses the _id
of every object to find the document. If a document doesn't exist, that object will get added as it. And encrypt the document.
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. Unencrypted documents will get encrypted! If the doc gets added, it will encrypt it and add a hoodie
property with createdAt
and updatedAt
properties added.
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | This element in the array isn't an object with an _id field. |
Example
hoodie.cryptoStore.updateOrAdd([
{
_id: '12345678-1234-1234-1234-123456789ABC',
foo: 'bar'
},
otherDoc
]).then(function (docs) {
console.log(docs.length)
}).catch(function (error) {
console.error(error)
})
cryptoStore.updateAll(changedProperties)
cryptoStore.updateAll(changedProperties)
Find all documents and update them. Assign changedProperties
to every document. And then encrypt all documents.
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. Unencrypted documents will get encrypted!
This updates and encrypts all documents with its idPrefix!
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
- | - | Must provide object or function | changedProperties isn't an object or a function. |
Example
hoodie.cryptoStore.updateAll({foo: 'bar'}).then(function (docs) {
console.log(docs)
}).catch(function (error) {
console.error(error)
})
hoodie.cryptoStore.withIdPrefix('foo/').updateAll({foo: 'bar'}).then(function (docs) {
console.log(docs)
}).catch(function (error) {
console.error(error)
})
cryptoStore.updateAll(updateFunction)
cryptoStore.updateAll(updateFunction)
Find all documents and update them. The updateFunction
will be get called on every document. And then encrypt all documents.
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. Unencrypted documents will get encrypted!
This updates and encrypts all documents with its idPrefix!
Rejects with:
Name | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
- | - | Must provide object or function | changedProperties isn't an object or a function. |
Example
hoodie.cryptoStore.updateAll(function (doc) {
doc.foo = 'bar'
}).then(function (docs) {
console.log(docs)
}).catch(function (error) {
console.error(error)
})
hoodie.cryptoStore.withIdPrefix('foo/').updateAll(function (doc) {
doc.foo = 'bar'
}).then(function (docs) {
console.log(docs)
}).catch(function (error) {
console.error(error)
})
cryptoStore.remove(id)
cryptoStore.remove(id)
Find a document with id
and removes it.
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. Unencrypted documents will get 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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
not_found | 404 | missing | There is no object with this _id . |
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)
Find a document using the _id
of that doc. It will update, remove, and encrypt the document.
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
. Unencrypted documents will get 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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | doc isn't an object with an _id field. |
not_found | 404 | missing | There is no object with this _id . |
Example
hoodie.cryptoStore.remove({
_id: '12345678-1234-1234-1234-123456789ABC',
foo: 'bar'
}).then(function (doc) {
console.log(doc.foo)
}).catch(function (error) {
console.error(error)
})
cryptoStore.remove(idsOrDocs)
cryptoStore.remove(idsOrDocs)
Find one or more documents using the _id
of that doc. It will update, remove, and encrypt all those documents.
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
. Unencrypted documents will get 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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
bad_request | 400 | Document must be a JSON object | That element of the array isn't an object with an _id field or a string. |
not_found | 404 | missing | There is no object with this _id . |
Example
hoodie.cryptoStore.remove([
doc,
'12345678-1234-1234-1234-123456789ABC'
]).then(function (docs) {
console.log(docs.length)
}).catch(function (error) {
console.error(error)
})
cryptoStore.removeAll()
cryptoStore.removeAll(filterFunction)
Remove all documents. If a filterFunction
gets passed, it will behave like Array.prototype.filter. The resulting documents will get then removed and encrypted.
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. Unencrypted documents will get 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 | Status | Description | Why |
---|
unauthorized | 401 | Name or password is incorrect. | This plugin wasn't unlocked yet. |
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.isEncrypted(object)
cryptoStore.isEncrypted(object)
Checks if the object matches the structure of an encrypted document.
Argument | Type | Description | Required |
---|
object | Object | Document or object to be checked if it has the structure of an encrypted document. | Yes |
Returns a Boolean. Returns true
if the passed object matches and encrypted document, and false
if it is not.
Rejects with:
Name | Status | Description | Why |
---|
bad_request | 400 | Document must be a JSON object | object isn't an object. |
Example
async function test () {
const obj = await hoodie.cryptoStore.add({
_id: 'test',
value: 3
})
hoodie.cryptoStore.isEncrypted(obj)
const doc = await hoodie.store.find('test')
return hoodie.cryptoStore.isEncrypted(doc)
}
cryptoStore.isEncrypted(Promise)
cryptoStore.isEncrypted(Promise.resolve(object))
Resolves the Promise. Then checks if the resulting object matches the structure of an encrypted document.
Argument | Type | Description | Required |
---|
Promise | Promise | Promise that will resolve into an Object. That object will then be check if it has the structure of an encrypted document. | Yes |
Resolves a Boolean. Resolves true
if the passed object matches and encrypted document, and false
if it is not.
Rejects with:
Name | Status | Description | Why |
---|
bad_request | 400 | Document must be a JSON object | object isn't an object. |
Error | - | - | Rejects with that error the passed Promise rejects to. |
Example
function isEncrypted (id) {
return hoodie.cryptoStore.isEncrypted(
hoodie.store.find(id)
)
}
cryptoStore.on()
cryptoStore.on(eventName, handler)
Add an event-handler. It behaves like hoodie-store-client's on. But will not emit events for unencrypted documents or documents it couldn't decrypted. It will also decrypt the document.
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
. handler
will get called with an updated doc. If the event is change
, than the first argument is a eventName
.
Rejects with:
Example
function changeHandler (eventName, doc) {
console.log(eventName, doc)
}
hoodie.cryptoStore.on('change', changeHandler)
.on('add', function (doc) {
console.log('a doc with ' + doc._id + 'was added')
})
cryptoStore.one()
cryptoStore.one(eventName, handler)
Add an one-time event-handler. It behaves like hoodie-store-client's one. But will not emit events for unencrypted documents or documents it couldn't decrypted. It will also decrypt the document.
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
. handler
will get called with an updated doc. If the event is change
, than the first argument is a eventName
. After that event get emitted, that handler will get removed.
Rejects with:
Example
function changeHandler (eventName, doc) {
console.log(eventName, doc)
}
hoodie.cryptoStore.one('change', changeHandler)
.one('add', function (doc) {
console.log('a doc with ' + doc._id + 'was added')
})
cryptoStore.off()
cryptoStore.off(eventName, handler)
Remove an event-handler. It behaves like hoodie-store-client's off.
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:
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 subset of cryptoStore
API with _id
property implicitly prefixed by passed string.
Rejects with:
Example
var userData = hoodie.cryptoStore.withIdPrefix('user/')
userData.on('change', function (eventName, doc) {
console.log(eventName, doc)
})
userData.add({
_id: 'test-user',
name: 'Tester'
})
.then(function (doc) {
console.log(doc._id)
return userData.find('test-user')
})
.then(function (doc) {
doc.isTester = true
userData.update(doc)
})
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 subset of cryptoStore
API. This API will have the encryption key
from password
and salt
. If no salt
or a now correct one, got passed, a new salt will get created.
This also works if the main instance isn't unlocked!
{
"salt": "1234567890",
"store": {
"add": function () {},
"withPassword": function () {},
...
}
}
Rejects with:
Example
var result = hoodie.cryptoStore.withPassword('secretPassword')
.then(function (result) {
var store = result.store
var salt = result.salt
store.on('change', function (eventName, doc) {
console.log(eventName, doc)
})
store.add({foo: 'bar'})
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. |