From 975d7786489aeb0160459c57143e4485c153ef24 Mon Sep 17 00:00:00 2001 From: tommy-carlos williams Date: Thu, 16 Mar 2017 15:14:36 +1100 Subject: [PATCH] OK. Working toward having an actual useable lib... --- .babelrc | 3 + .eslintrc.js | 37 +++++++++ .gitignore | 1 + .npmignore | 1 + package.json | 35 +++++--- src/index.js | 75 +++++++++++++++++ test/browser-test-1.js | 113 ++++++++++++++++++++++++++ {src => test}/data.js | 0 test/pouch-local-only-test.js | 81 ++++++++++++++++++ {src => test}/quick-node-time-test.js | 0 10 files changed, 335 insertions(+), 11 deletions(-) create mode 100644 .babelrc create mode 100644 .eslintrc.js create mode 100644 .npmignore create mode 100644 src/index.js create mode 100644 test/browser-test-1.js rename {src => test}/data.js (100%) create mode 100644 test/pouch-local-only-test.js rename {src => test}/quick-node-time-test.js (100%) diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..002b4aa --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["env"] +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..9c302ab --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,37 @@ +module.exports = { + root: true, + parser: 'babel-eslint', + parserOptions: { + sourceType: 'module' + }, + extends: 'airbnb', + env: { + browser: true, + node: true, + mocha: true, + jasmine: true, + }, + // required to lint *.vue files + plugins: [ + 'html' + ], + // check if imports actually resolve + 'settings': { + 'import/resolver': { + 'webpack': { + 'config': `${__dirname}/build/webpack.base.conf.js` + } + } + }, + // add your custom rules here + 'rules': { + // don't require .vue extension when importing + 'import/extensions': ['error', 'always', { + 'js': 'never', + 'vue': 'never' + }], + 'no-console': 0, + // allow debugger during development + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 + } +} diff --git a/.gitignore b/.gitignore index 3c3629e..491fc35 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +lib diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..85de9cf --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +src diff --git a/package.json b/package.json index 20fc9c2..091518e 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,43 @@ { - "name": "tweetnacl-pouchdb-thing", - "version": "0.1.0", - "description": "Crypton is dead, I want something else to replace it — Edit", - "main": "src/index.js", + "name": "echidna.js", + "version": "0.0.1", + "description": "Crypton is dead, long live Echidna.js", + "main": "lib/index.js", "dependencies": { + "babel-polyfill": "^6.23.0", "fast-sha256": "^1.0.0", - "install": "^0.8.2", - "npm": "^4.0.3", "pouchdb-browser": "^6.0.7", "pouchdb-node": "^6.0.7", "transform-pouch": "^1.1.3", "tweetnacl": "^0.14.3", "tweetnacl-util": "^0.13.3" }, - "devDependencies": {}, + "devDependencies": { + "babel-cli": "^6.24.0", + "babel-preset-env": "^1.2.2", + "babel-preset-es2015": "^6.24.0", + "eslint": "^3.17.1", + "eslint-config-airbnb": "^14.1.0", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-jsx-a11y": "^4.0.0", + "eslint-plugin-react": "^6.10.0" + }, "scripts": { + "build": "babel src --presets babel-preset-es2015 --out-dir lib", + "prepublish": "npm run build", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", - "url": "git+https://github.com/devgeeks/tweetnacl-pouchdb-thing.git" + "url": "git+https://github.com/devgeeks/echidna.js.git" }, - "author": "", "license": "MIT", "bugs": { - "url": "https://github.com/devgeeks/tweetnacl-pouchdb-thing/issues" + "url": "https://github.com/devgeeks/echidna.js/issues" + }, + "homepage": "https://github.com/devgeeks/echidna.js#readme", + "directories": { + "test": "test" }, - "homepage": "https://github.com/devgeeks/tweetnacl-pouchdb-thing#readme" + "author": "Tommy-Carlos Williams " } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..9c18492 --- /dev/null +++ b/src/index.js @@ -0,0 +1,75 @@ +import nacl from 'tweetnacl'; +import naclUtil from 'tweetnacl-util'; +import sha256 from 'fast-sha256'; +import PouchDB from 'pouchdb-browser'; +import TransformPouch from 'transform-pouch'; + +import 'babel-polyfill'; + +PouchDB.plugin(TransformPouch); +nacl.util = naclUtil; +window.naclUtil = naclUtil; +window.sha256 = sha256; + +function encrypt(text, nonce, key) { + const encText = nacl.secretbox(nacl.util.decodeUTF8(text), nonce, key); + return nacl.util.encodeBase64(encText); +} + +function decrypt(text, nonce, key) { + const decText = nacl.secretbox.open(nacl.util.decodeBase64(text), nonce, key); + if (decText === null) { + return undefined; + } + return nacl.util.encodeUTF8(decText); +} + +export function generateNonce() { + return nacl.randomBytes(24); +} + +export function keyFromPassphrase(passphrase, salt, rounds = 100000) { + const key = sha256.pbkdf2( + nacl.util.decodeUTF8(passphrase), + nacl.util.decodeUTF8(salt), + rounds, 32); + return key; +} + +export default class Echidnajs { + constructor(username, passphrase, salt, rounds = 100000) { + const key = keyFromPassphrase(passphrase, salt, rounds); + this.pouch = new PouchDB(`echidnadb-${username}`); + this.pouch.transform({ + incoming(doc) { + // @TODO We NEED to dcheck the passphrase is correct before adding or modifying any items + const keys = Object.keys(doc); + const newDoc = {}; + const nonce = generateNonce(); + // do something to the document before storage + keys.forEach((field) => { + if (field !== '_id' && field !== '_rev' && field !== 'nonce') { + newDoc[field] = encrypt(doc[field], nonce, key); + } + }); + newDoc.nonce = nacl.util.encodeBase64(nonce); + return Object.assign(doc, newDoc); + }, + outgoing(doc) { + const keys = Object.keys(doc); + const newDoc = {}; + // do something to the document after retrieval + let error; + keys.forEach((field) => { + if (field !== '_id' && field !== '_rev' && field !== 'nonce') { + newDoc[field] = decrypt(doc[field], nacl.util.decodeBase64(doc.nonce), key); + if (newDoc[field] === undefined) { + error = new Error('Failed to decrypt'); + } + } + }); + return error || Object.assign(doc, newDoc); + }, + }); + } +} diff --git a/test/browser-test-1.js b/test/browser-test-1.js new file mode 100644 index 0000000..5f02a0c --- /dev/null +++ b/test/browser-test-1.js @@ -0,0 +1,113 @@ +var nacl = require('tweetnacl'); +nacl.util = require('tweetnacl-util'); +var sha256 = require('fast-sha256'); +var book = require('./data').book; + +//console.time('start'); +console.log('Starting timers...'); +const startTime = new Date().getTime(); +var nonce = nacl.randomBytes(24); +var key = sha256.pbkdf2( + 'my totally secure passphrase', + 'this is a salty bugger', + 100000, + 32 +); + +var completeKeyGen = (new Date().getTime() - startTime) / 1000; +console.log('completeGen', completeKeyGen); + +var pouch; //, pouchAlt; +//document.addEventListener('load', function() { + //console.log('device ready'); + var PouchDB = require('pouchdb-browser'); + //PouchDB.plugin(require('pouchdb-adapter-cordova-sqlite')); + PouchDB.plugin(require('transform-pouch')); + + pouch = new PouchDB('testlocal1'); + //pouchAlt = new PouchDB('testlocal1'); + + var remoteCouch = false; + + console.log('newPouches', pouch); + + pouch.changes({ + since: 'now', + live: true + }).on('change', e => { + //console.time('show'); + var startShow = new Date().getTime(); + showTodos(pouch); + var completeShow1 = (new Date().getTime() - startShow) / 1000; + console.log('show pouch (decrypt)', completeShow1); + //console.timeEnd('show'); + //console.timeEnd('start'); + var completeShow2 = (new Date().getTime() - completeKeyGen - startTime) / 1000; + console.log('all of it...', completeShow2); + //showTodos(pouchAlt); + }); + + pouch.transform({ + incoming: function (doc) { + // do something to the document before storage + Object.keys(doc).forEach(function (field) { + if (field !== '_id' && field !== '_rev') { + doc[field] = encrypt(doc[field]); + } + }); + return doc; + }, + outgoing: function (doc) { + // do something to the document after retrieval + Object.keys(doc).forEach(function (field) { + if (field !== '_id' && field !== '_rev') { + doc[field] = decrypt(doc[field]); + } + }); + return doc; + } + }); + + const todo = book; // 'test the browser at encryption'; + + addDoc(pouch, todo); + + setTimeout(() => { + pouch.destroy().then(function () { + console.log('database destroyed'); + }).catch(function (err) { + console.log('error occurred'); + }) + }, 30000); +//}); + +function encrypt(text) { + const encText = nacl.secretbox(nacl.util.decodeUTF8(text), nonce, key); + return nacl.util.encodeBase64(encText); +} + +function decrypt(text) { + const decText = nacl.secretbox.open(nacl.util.decodeBase64(text), nonce, key); + return nacl.util.encodeUTF8(decText); +} + +function addDoc(db, text) { + var todo = { + _id: 'testTodo', + title: text, + completed: false + }; + db.put(todo, function callback(err, result) { + if (err) { + console.log('error: ', err); + return; + } + console.log('Successfully posted a todo!'); + }); +} + +function showTodos(db) { + db.allDocs({include_docs: true, descending: true}, function(err, doc) { + console.log(doc.rows); + }); +} diff --git a/src/data.js b/test/data.js similarity index 100% rename from src/data.js rename to test/data.js diff --git a/test/pouch-local-only-test.js b/test/pouch-local-only-test.js new file mode 100644 index 0000000..e7be061 --- /dev/null +++ b/test/pouch-local-only-test.js @@ -0,0 +1,81 @@ +var nacl = require('tweetnacl'); +nacl.util = require('tweetnacl-util'); +var sha256 = require('fast-sha256'); +var book = require('./data').book; + +var nonce = nacl.randomBytes(24); +var key = sha256.pbkdf2( + 'my totally secure passphrase', + 'this is a salty bugger', + 100000, + 32 +); + +var PouchDB = require('pouchdb-node'); +PouchDB.plugin(require('transform-pouch')); + +var pouch = new PouchDB('testlocal1'); +var pouchAlt = new PouchDB('testlocal1'); +var remoteCouch = false; + +function encrypt(text) { + const encText = nacl.secretbox(nacl.util.decodeUTF8(text), nonce, key); + return nacl.util.encodeBase64(encText); +} + +function decrypt(text) { + const decText = nacl.secretbox.open(nacl.util.decodeBase64(text), nonce, key); + return nacl.util.encodeUTF8(decText); +} + +encrypt('this sucks'); + +pouch.transform({ + incoming: function (doc) { + // do something to the document before storage + Object.keys(doc).forEach(function (field) { + if (field !== '_id' && field !== '_rev') { + doc[field] = encrypt(doc[field]); + } + }); + return doc; + }, + outgoing: function (doc) { + // do something to the document after retrieval + Object.keys(doc).forEach(function (field) { + if (field !== '_id' && field !== '_rev') { + doc[field] = decrypt(doc[field]); + } + }); + return doc; + } +}); + +function addDoc(db, text) { + var todo = { + _id: 'test', + title: text, + completed: false + }; + db.put(todo, function callback(err, result) { + if (!err) { + console.log('Successfully posted a todo!'); + } + }); +} + +function showTodos(db) { + db.allDocs({include_docs: true, descending: true}, function(err, doc) { + console.log(doc.rows); + }); +} + +pouch.changes({ + since: 'now', + live: true +}).on('change', () => { + showTodos(pouch); + showTodos(pouchAlt); +}); + +addDoc(pouch, book); diff --git a/src/quick-node-time-test.js b/test/quick-node-time-test.js similarity index 100% rename from src/quick-node-time-test.js rename to test/quick-node-time-test.js