hoodie-plugin-store-crypto
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -55,2 +55,3 @@ 'use strict' | ||
api.changePassword = require('./change-password').bind(null, store, state) | ||
api.resetPassword = require('./reset-password').bind(null, store, state) | ||
api.isEncrypted = require('./is-encrypted') | ||
@@ -57,0 +58,0 @@ |
@@ -7,9 +7,13 @@ 'use strict' | ||
var createKey = require('./create-key') | ||
var createPasswordCheck = require('./utils/create-password-check') | ||
var isEncryptedObject = require('./utils/is-encrypted-object') | ||
var decryptOne = require('./helpers/decrypt-one') | ||
var encryptMany = require('./helpers/encrypt-many') | ||
var changePasswordAndUpdateDocs = require('./helpers/change-password-and-update-docs') | ||
module.exports = changePassword | ||
/** | ||
* Changes the encryption and updates old docs. | ||
* @param {object} store Instance of the Hoodie-Client-Store. | ||
* @param {object} state Internal state of hoodie-plugin-crypto-store. | ||
* @param {string} oldPassword Old password. | ||
* @param {string} newPassword New password. | ||
*/ | ||
function changePassword (store, state, oldPassword, newPassword) { | ||
@@ -42,93 +46,4 @@ if (newPassword == null || typeof newPassword !== 'string' || newPassword.length === 0) { | ||
.then(function (oldKey) { | ||
// create new key and salt | ||
return createKey(newPassword, null) | ||
.then(function (res) { | ||
// set new state | ||
state.key = res.key | ||
state.salt = res.salt | ||
return { | ||
oldKey: oldKey, | ||
newKey: res.key, | ||
newSalt: res.salt | ||
} | ||
}) | ||
return changePasswordAndUpdateDocs(store, state, oldKey, newPassword) | ||
}) | ||
.then(function (data) { | ||
var notDecrypted = [] // will store the ids of docs that couldn't be decrypted/updated | ||
// update old encrypted docs | ||
return store.db.allDocs({ include_docs: true }) | ||
.then(function (rows) { | ||
var docs = rows.rows // update all encrypted docs! _design/* too! | ||
.map(function (row) { | ||
return row.doc | ||
}) | ||
return docs | ||
}) | ||
.catch(function (err) { // TODO: remove if Store-client with db as getter is published | ||
if (err.message === 'database is destroyed') { | ||
return store.findAll() | ||
} | ||
throw err | ||
}) | ||
.then(function (docs) { | ||
var decrypted = docs | ||
.filter(isEncryptedObject) | ||
.map(function (doc) { | ||
return decryptOne(data.oldKey, doc) | ||
.catch(function (error) { | ||
if (error.message === 'Unsupported state or unable to authenticate data') { | ||
return doc._id | ||
} | ||
throw error | ||
}) | ||
}) | ||
return Promise.all(decrypted) | ||
}) | ||
.then(function (decrypted) { | ||
return decrypted.reduce(function (all, doc) { | ||
if (typeof doc === 'string') { | ||
all.notDecrypted.push(doc) | ||
} else { | ||
all.decrypted.push(doc) | ||
} | ||
return all | ||
}, { | ||
decrypted: [], | ||
notDecrypted: [] | ||
}) | ||
}) | ||
.then(function (result) { | ||
notDecrypted = result.notDecrypted | ||
return encryptMany(data.newKey, null, result.decrypted) | ||
}) | ||
.then(function (toUpdate) { | ||
return createPasswordCheck(data.newKey) | ||
.then(function (check) { | ||
toUpdate.push({ | ||
_id: 'hoodiePluginCryptoStore/salt', | ||
salt: data.newSalt, | ||
check: check | ||
}) | ||
return store.update(toUpdate) | ||
}) | ||
}) | ||
.then(function (updated) { | ||
return { | ||
salt: data.newSalt, | ||
notUpdated: notDecrypted | ||
} | ||
}) | ||
}) | ||
} |
@@ -18,3 +18,3 @@ 'use strict' | ||
_id: '', | ||
_rev: '', | ||
_rev: null, | ||
hoodie: null, | ||
@@ -21,0 +21,0 @@ tag: '', |
@@ -8,2 +8,3 @@ 'use strict' | ||
var createPasswordCheck = require('./utils/create-password-check') | ||
var createResetKeys = require('./helpers/create-reset-keys') | ||
@@ -72,3 +73,3 @@ module.exports = setup | ||
.then(function (result) { | ||
return createPasswordCheck(result.key) | ||
var saltDoc = createPasswordCheck(result.key) | ||
@@ -83,5 +84,12 @@ .then(function (check) { | ||
}) | ||
var resetKeys = createResetKeys(store, result.key) | ||
return Promise.all([saltDoc, resetKeys]) | ||
}) | ||
.then(function (obj) { | ||
.then(function (objs) { | ||
var obj = objs[0] | ||
var resetKeys = objs[1] | ||
// if there was an deleted salt doc: undelete it | ||
@@ -92,5 +100,9 @@ if (obj._deleted) { | ||
.then(function () {}) // no result | ||
.then(function () { | ||
return resetKeys | ||
}) | ||
} | ||
return resetKeys | ||
}) | ||
} |
{ | ||
"name": "hoodie-plugin-store-crypto", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "End-to-end crypto plugin for the Hoodie client store.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
143
README.md
@@ -25,10 +25,10 @@ # hoodie-plugin-store-crypto | ||
hoodie.store.add({foo: 'bar'}) | ||
.then(function (obj) {console.log(obj)}) | ||
.then(function (obj) { console.log(obj) }) | ||
hoodie.cryptoStore.setup('secret') | ||
.then(async () => { | ||
const salt = await hoodie.cryptoStore.unlock('secret') | ||
await hoodie.cryptoStore.unlock('secret') | ||
const obj = await hoodie.cryptoStore.add({foo: 'bar'}) // adds the object encrypted | ||
console.log(obj) // returns it unencrypted! | ||
const obj = await hoodie.cryptoStore.add({ foo: 'bar' }) // add the object encrypted | ||
console.log(obj) // returns it unencrypted! | ||
}) | ||
@@ -116,3 +116,3 @@ ``` | ||
There are 4 use-cases you must put in place: | ||
There are 5 use-cases you must put in place: | ||
@@ -123,2 +123,3 @@ - [Sign up / setup / start of using encryption](#setup) | ||
- [changing the password for encryption](#changing-the-password) | ||
- [reset the password](#reset-the-password) | ||
@@ -144,4 +145,6 @@ #### Setup | ||
await hoodie.cryptoStore.setup(cryptoPassword) | ||
const resetKeys = await hoodie.cryptoStore.setup(cryptoPassword) | ||
displayResetKeys(resetKeys) | ||
return signIn(username, password, cryptoPassword) // Call your signIn function | ||
@@ -157,4 +160,3 @@ } | ||
`unlock` will try to pull `hoodiePluginCryptoStore/salt` from the server, | ||
to have the latest version of it. | ||
`unlock` will try to pull `hoodiePluginCryptoStore/salt` from the server, to have the latest version of it. | ||
@@ -186,4 +188,5 @@ Example: | ||
// do your cleanup | ||
hoodie.cryptoStore.lock() // lock the cryptoStore in an cryptographic saver way. | ||
// It overwrites the key data 10 times. | ||
// lock the cryptoStore in an cryptographic saver way. | ||
// It overwrites the key data 10 times. | ||
hoodie.cryptoStore.lock() | ||
}) | ||
@@ -225,5 +228,35 @@ ``` | ||
console.log(result.notUpdated) // array of ids of all docs that weren't updated | ||
displayResetKeys(result.resetKeys) | ||
} | ||
``` | ||
#### Reset the password | ||
This works like [changing the password](#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. | ||
```javascript | ||
// Generate a text file with the reset keys in it. | ||
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' // This will be the standard file name. | ||
// then call later URL.revokeObjectURL(url) with the url of the file. | ||
// To remove it from memory. | ||
} | ||
``` | ||
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 | ||
@@ -299,2 +332,14 @@ | ||
## 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 | ||
@@ -417,2 +462,3 @@ | ||
- [cryptoStore.changePassword(oldPassword, newPassword)](#cryptostorechangepasswordoldpassword-newpassword) | ||
- [cryptoStore.resetPassword(resetKey, newPassword)](#cryptostoreresetpasswordresetkey-newpassword) | ||
- [cryptoStore.lock()](#cryptostorelock) | ||
@@ -498,2 +544,5 @@ - [cryptoStore.add(properties)](#cryptostoreaddproperties) | ||
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`. | ||
@@ -525,4 +574,8 @@ A salt is a string that will get used with the password together for the encryption. [More about what the salt is](http://www.passwordbreeder.com/page/salt). | ||
} | ||
await hoodie.cryptoStore.setup(cryptoPassword) | ||
const resetKeys = await hoodie.cryptoStore.setup(cryptoPassword) | ||
// This can be: displaying the 10 keys to the user | ||
// or/and generate a text-file for the user to download. | ||
displayResetKeys(resetKeys) | ||
return signIn(username, password, cryptoPassword) // Call your signIn function | ||
@@ -545,2 +598,5 @@ } | ||
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`. | ||
@@ -574,4 +630,8 @@ A salt is a string that will get used with the password together for the encryption. | ||
} | ||
await hoodie.cryptoStore.setup(cryptoPassword, salt) | ||
const resetKeys = await hoodie.cryptoStore.setup(cryptoPassword, salt) | ||
// This can be: displaying the 10 keys to the user | ||
// or/and generate a text-file for the user to download. | ||
displayResetKeys(resetKeys) | ||
return signIn(username, password, cryptoPassword) // Call your signIn function | ||
@@ -637,3 +697,5 @@ } | ||
Resolves with an object with the new `salt` and an array (`notUpdated`) with the ids of not updated docs. | ||
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 | ||
@@ -647,3 +709,3 @@ the `newPassword`. It also updates the `salt` in `hoodiePluginCryptoStore/salt`. | ||
badarg | 500 | New password must be a string! | The new password wasn't a string. | ||
badarg | 500 | password is to short! | The password must be longer than 2 chars. | ||
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. | ||
@@ -657,2 +719,6 @@ | ||
console.log(report.notUpdated) // array with all ids of encrypted docs that have not been updated | ||
// This can be: displaying the 10 keys to the user | ||
// or/and generate a text-file for the user to download. | ||
displayResetKeys(report.resetKeys) | ||
}).catch(function (error) { | ||
@@ -663,2 +729,51 @@ console.error(error) | ||
### cryptoStore.resetPassword(resetKey, newPassword) | ||
```javascript | ||
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 | ||
```javascript | ||
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) // the new salt | ||
console.log(report.notUpdated) // array with all ids of encrypted docs that have not been updated | ||
// This can be: displaying the 10 keys to the user | ||
// or/and generate a text-file for the user to download. | ||
displayResetKeys(report.resetKeys) | ||
} catch (error) { | ||
console.error(error) | ||
} | ||
} | ||
``` | ||
### cryptoStore.lock() | ||
@@ -665,0 +780,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
132712
41
1662
1940