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 un-encrypted
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.
The PouchDB plugin pouchdb-hoodie-api
is also supported!
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! And all keys that start with an underscore (_) will not get encrypted!
Read more in the API Docs.
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)
const encrypted = await hoodie.store.find(obj._id)
})
Table of Contents
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
Add it to your Hoodie-Client
There are 3 ways to use this plugin in your app:
- Use it with the Hoodie Plugin API
- Use it with a bundler (Webpack or Browserify)
- Use it with PouchDB and pouchdb-hoodie-api
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
.
Back to top
Usage with a Bundler
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:
import Hoodie from '@hoodie/client'
import PouchDB from 'pouchdb'
import CryptoStore from 'hoodie-plugin-store-crypto'
const hoodie = new Hoodie({
url: window.location.origin,
PouchDB: PouchDB
})
const cryptoStore = new CryptoStore(hoodie.store, { })
cryptoStore.setup('test')
.then(function () {
console.log('done')
})
hoodie.account.on('signout', () => {
cryptoStore.lock()
})
Back to top
Usage with PouchDB and pouchdb-hoodie-api
To use this plugin with PouchDB you must install the plugin
pouchdb-hoodie-api.
npm install --save-dev pouchdb-hoodie-api
Setup is like the usage with Bundler, but you must pass the
the db.hoodieApi()
from pouchdb-hoodie-api. And optionally a remote PouchDB database (or its URL)
for checking and fetching the existence of the encryption setup.
import PouchDB from 'pouchdb-browser'
import hoodieApiPlugin from 'pouchdb-hoodie-api'
import CryptoStore from 'hoodie-plugin-store-crypto'
PouchDB.plugin(hoodieApiPlugin)
const db = new PouchDB('local_db')
const remoteDb = new PouchDB('https://example.com/my_db', {
auth: {
username: 'user',
password: 'geheim'
}
})
const cryptoStore = new CryptoStore(db.hoodieApi(), {
remote: remoteDb
})
cryptoStore.setup('test')
.then(() => {
console.log('done')
})
To use it in combination with PouchDB's APIs like db.query()
and db.find()
use
cryptoStore.encrypt(jsonValue, aad) and
cryptoStore.decrypt(encrypted, aad):
async function encryptedQuery (db, cryptoStore, viewName, options) {
const result = await db.query(viewName, {
...options,
include_docs: true
})
const decrypt = result.rows.map(row => {
return cryptoStore.decrypt(row.doc, row.id)
})
return Promise.all(decrypt)
}
Fields required for a view/search must get stored un-encrypted to be usable for PouchDB.
Tip: use random UUIDs
(v4)
for a key, and encrypt the human-readable version.
Back to top
Get started
To use the cryptoStore you need to set a password for encryption. This can be your users password to
your app, a second password, or a derived password.
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
(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)
}
Back to top
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)
}
Back to top
Sign out
If you use hoodie's plugin system, then 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.
Use-cases for the cryptoStore.lock()
method are:
- a lock after a timeout functionality
- lock the store in a save way when closing an tab.
- lock on sign out, if you didn't use hoodie's plugin system.
window.addEventListener('beforeunload', function (event) {
hoodie.cryptoStore.lock()
})
hoodie.account.on('signout', () => {
cryptoStore.lock()
})
Back to top
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)
}
Back to top
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)
}
Back to top
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!
Back to top
Contributing
Contributions in all forms are welcome♡
Contributing might answer your questions about Contributing.
To create a welcoming project to all, this project uses a Code of Conduct. Please read it.
Setup development
hoodie-plugin-store-crypto is a node.js package. You need node version 6 or higher and npm version 5 or higher. Check your installed version with node -v
and npm -v
.
git clone https://github.com/Terreii/hoodie-plugin-store-crypto.git
cd hoodie-plugin-store-crypto
npm install
npm scripts
Scripts for development.
Command | What it does |
---|
npm start | Starts a Hoodie-server with this plugin attached. |
npm test | Run all tests. |
npm run textlint | Lint the documentation. |
npm run fix:docs | Fix some lint errors in the documentation. |
npm run fix:style | Fix some code-style errors. |
npm run update-coc | Update the Code of Conduct.md. |
npm run update-contrib | Update the Contributing.md. |
Back to top