mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00
Refactored use of IndexedDB - still some tests are failing
This commit is contained in:
parent
faf4c1e658
commit
19a662bf62
110
sea.js
110
sea.js
@ -148,6 +148,55 @@
|
||||
|
||||
const Buffer = SafeBuffer
|
||||
|
||||
// This is safe class to operate with IndexedDB data - all methods are Promise
|
||||
function EasyIndexedDB(objectStoreName, dbName = 'GunDB', dbVersion = 1) {
|
||||
// Private internals, including constructor props
|
||||
const runTransaction = (fn_) => new Promise((resolve, reject) => {
|
||||
const open = indexedDB.open(dbName, dbVersion) // Open (or create) the DB
|
||||
open.onerror = (e) => {
|
||||
reject(new Error('IndexedDB error:', e))
|
||||
}
|
||||
open.onupgradeneeded = () => {
|
||||
const db = open.result // Create the schema; props === current version
|
||||
db.createObjectStore(objectStoreName, { keyPath: 'id' })
|
||||
}
|
||||
let result
|
||||
open.onsuccess = () => { // Start a new transaction
|
||||
const db = open.result
|
||||
const tx = db.transaction(objectStoreName, 'readwrite')
|
||||
const store = tx.objectStore(objectStoreName)
|
||||
tx.oncomplete = () => {
|
||||
db.close() // Close the db when the transaction is done
|
||||
resolve(result) // Resolves result returned by action function fn_
|
||||
}
|
||||
result = fn_(store)
|
||||
}
|
||||
})
|
||||
|
||||
Object.assign(this, {
|
||||
async wipe() { // Wipe IndexedDB completedy!
|
||||
return runTransaction((store) => {
|
||||
const act = store.clear()
|
||||
act.onsuccess = () => {}
|
||||
})
|
||||
},
|
||||
async put(id, props) {
|
||||
const data = Object.assign({}, props, { id })
|
||||
return runTransaction((store) => { store.put(data) })
|
||||
},
|
||||
async get(id, prop) {
|
||||
return runTransaction((store) => new Promise((resolve) => {
|
||||
const getData = store.get(id)
|
||||
getData.onsuccess = () => {
|
||||
const { result = {} } = getData
|
||||
resolve(result[prop])
|
||||
}
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
const seaIndexedDb = new EasyIndexedDB('SEA', 'GunDB', 1)
|
||||
|
||||
// Encryption parameters
|
||||
const pbkdf2 = { hash: 'SHA-256', iter: 50000, ks: 64 }
|
||||
|
||||
@ -190,7 +239,6 @@
|
||||
// Practical examples about usage found from ./test/common.js
|
||||
|
||||
// These are internal wrappers for ES6 async/await use:
|
||||
const seaCallOnStorage = async (...props) => SEA._callonstore_(...props)
|
||||
const seaRead = async (...props) => SEA.read(...props)
|
||||
const seaWrite = async (...props) => SEA.write(...props)
|
||||
const seaEnc = async (...props) => SEA.enc(...props)
|
||||
@ -208,14 +256,6 @@
|
||||
} catch (e) {} //eslint-disable-line no-empty
|
||||
return props
|
||||
}
|
||||
const seaIndexedDbGetter = async (key, prop) =>
|
||||
await seaCallOnStorage((store) => new Promise((resolve) => {
|
||||
const getData = store.get(key)
|
||||
getData.onsuccess = () => {
|
||||
const { result = {} } = getData
|
||||
resolve(result[prop])
|
||||
}
|
||||
}))
|
||||
|
||||
// This is internal func queries public key(s) for alias.
|
||||
const querygunaliases = (alias,root) => new Promise((resolve, reject) => {
|
||||
@ -348,14 +388,8 @@
|
||||
|
||||
if (encrypted) {
|
||||
const auth = await seaWrite(encrypted, key)
|
||||
|
||||
await seaCallOnStorage((store) => {
|
||||
const act = store.clear() // Wipe IndexedDB completedy!
|
||||
act.onsuccess = () => {}
|
||||
}) // Then set encrypted auth props
|
||||
await seaCallOnStorage((store) => {
|
||||
store.put({ id, auth })
|
||||
})
|
||||
await seaIndexedDb.wipe()
|
||||
await seaIndexedDb.put(id, { auth })
|
||||
}
|
||||
|
||||
return props
|
||||
@ -365,10 +399,7 @@
|
||||
}
|
||||
|
||||
// Wiping IndexedDB completely when using random PIN
|
||||
await seaCallOnStorage((store) => {
|
||||
const act = store.clear()
|
||||
act.onsuccess = () => {}
|
||||
})
|
||||
await seaIndexedDb.wipe()
|
||||
// And remove sessionStorage data
|
||||
sessionStorage.removeItem('user')
|
||||
sessionStorage.removeItem('remember')
|
||||
@ -431,8 +462,6 @@
|
||||
}
|
||||
const readAndDecrypt = async (data, pub, key) =>
|
||||
parseProps(await seaDec(await seaRead(data, pub), key))
|
||||
// Returns auth value prop from IndexedDB (encryption key)
|
||||
const getIndexedDbAuth = async () => await seaIndexedDbGetter(alias, 'auth')
|
||||
|
||||
// Already authenticated?
|
||||
if (root._.user
|
||||
@ -447,7 +476,7 @@
|
||||
// Yes, got persisted 'remember'?
|
||||
if (!remember) {
|
||||
throw { // And return proof if for matching alias
|
||||
err: (await getIndexedDbAuth() && authsettings.validity
|
||||
err: (await seaIndexedDb.get(alias, 'auth') && authsettings.validity
|
||||
&& 'Missing PIN and alias!') || 'No authentication session found!'
|
||||
}
|
||||
}
|
||||
@ -471,7 +500,7 @@
|
||||
// No PIN, let's try short-term proof if for matching alias
|
||||
? await checkRememberData(props)
|
||||
// Got PIN so get IndexedDB secret if signature is ok
|
||||
: await checkRememberData(await readAndDecrypt(await getIndexedDbAuth(), pub, pin))
|
||||
: await checkRememberData(await readAndDecrypt(await seaIndexedDb.get(alias, 'auth'), pub, pin))
|
||||
}
|
||||
// got pub, try auth with pin & alias :: or unwrap Storage data...
|
||||
const args = pin ? { pin, alias } : await readStorageData()
|
||||
@ -560,15 +589,13 @@
|
||||
const { pub: id } = p
|
||||
const importAndStoreKey = async () => {
|
||||
const key = await importKey(p)
|
||||
await seaCallOnStorage((store) => {
|
||||
store.put({ id, key })
|
||||
})
|
||||
await seaIndexedDb.put(id, { key })
|
||||
return key
|
||||
}
|
||||
if (Gun.obj.has(p, 'set')) {
|
||||
return importAndStoreKey() // proof update so overwrite
|
||||
}
|
||||
const aesKey = await seaIndexedDbGetter(id, 'key')
|
||||
const aesKey = await seaIndexedDb.get(id, 'key')
|
||||
return aesKey ? aesKey : importAndStoreKey()
|
||||
}
|
||||
|
||||
@ -706,7 +733,7 @@
|
||||
// Delete user data
|
||||
root.get(`pub/${pub}`).put(null)
|
||||
// Wipe user data from memory
|
||||
const { user = { _: {} } } = root._
|
||||
const { user = { _: {} } } = root._;
|
||||
// TODO: is this correct way to 'logout' user from Gun.User ?
|
||||
[ 'alias', 'sea', 'pub' ].map((key) => delete user._[key])
|
||||
user._.is = user.is = {}
|
||||
@ -1044,6 +1071,8 @@
|
||||
}
|
||||
|
||||
const SEA = {
|
||||
// This is easy way to use IndexedDB, all methods are Promises
|
||||
EasyIndexedDB,
|
||||
// This is Buffer used in SEA and usable from Gun/SEA application also.
|
||||
// For documentation see https://nodejs.org/api/buffer.html
|
||||
Buffer: SafeBuffer,
|
||||
@ -1304,27 +1333,6 @@
|
||||
if (cb && typeof cb === 'function') { doIt(cb, () => cb()) } else {
|
||||
return new Promise(doIt)
|
||||
}
|
||||
},
|
||||
// Internal helper for IndexedDB use - exposed for testing purposes only
|
||||
_callonstore_(fn_) {
|
||||
return new Promise((resolve) => {
|
||||
const open = indexedDB.open('GunDB', 1) // Open (or create) the database; 1 === 'version'
|
||||
open.onupgradeneeded = () => { // Create the schema; props === current version
|
||||
const db = open.result
|
||||
db.createObjectStore('SEA', { keyPath: 'id' })
|
||||
}
|
||||
let result
|
||||
open.onsuccess = () => { // Start a new transaction
|
||||
const db = open.result
|
||||
const tx = db.transaction('SEA', 'readwrite')
|
||||
const store = tx.objectStore('SEA')
|
||||
tx.oncomplete = () => { // Close the db when the transaction is done
|
||||
db.close()
|
||||
resolve(result)
|
||||
}
|
||||
result = fn_(store)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
109
test/sea.js
109
test/sea.js
@ -24,23 +24,14 @@ var root;
|
||||
}
|
||||
}(this));
|
||||
|
||||
if(typeof Buffer === 'undefined'){
|
||||
var Buffer = require('buffer').Buffer;
|
||||
}
|
||||
const Buffer = Gun.SEA.Buffer
|
||||
|
||||
const seaIndexedDb = new Gun.SEA.EasyIndexedDB('SEA', 'GunDB', 1)
|
||||
|
||||
const checkIndexedDB = (key, prop, resolve_) => {
|
||||
const doIt = (resolve, reject) => {
|
||||
try {
|
||||
Gun.SEA._callonstore_((store) => new Promise((reslv) => {
|
||||
const getData = store.get(key)
|
||||
getData.onsuccess = () => {
|
||||
reslv(getData.result && getData.result[prop])
|
||||
}
|
||||
})).then(resolve)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
}
|
||||
const doIt = (resolve, reject) => seaIndexedDb.get(key, prop)
|
||||
.then(resolve).catch(reject)
|
||||
|
||||
if (resolve_) {
|
||||
doIt(resolve_, (e) => { throw e })
|
||||
} else {
|
||||
@ -48,12 +39,15 @@ const checkIndexedDB = (key, prop, resolve_) => {
|
||||
}
|
||||
}
|
||||
|
||||
function setIndexedDB(key, prop, resolve_){
|
||||
Gun.SEA._callonstore_(function(store){
|
||||
store.put({id: key, auth: prop});
|
||||
}, function(){
|
||||
resolve_();
|
||||
});
|
||||
const setIndexedDB = (key, auth, resolve_) => {
|
||||
const doIt = (resolve, reject) => seaIndexedDb.put(key, { auth })
|
||||
.then(resolve).catch(reject)
|
||||
|
||||
if (resolve_) {
|
||||
doIt(resolve_, (e) => { throw e })
|
||||
} else {
|
||||
return new Promise(doIt)
|
||||
}
|
||||
}
|
||||
|
||||
Gun.SEA && describe('SEA', function(){
|
||||
@ -282,7 +276,7 @@ Gun().user && describe('Gun', function(){
|
||||
var user = gun.user();
|
||||
Gun.log.off = true; // Supress all console logging
|
||||
|
||||
var throwOutUser = function(wipeStorageData){
|
||||
const throwOutUser = (wipeStorageData, done) => {
|
||||
// Get rid of authenticated Gun user
|
||||
var user = gun.back(-1)._.user;
|
||||
// TODO: is this correct way to 'logout' user from Gun.User ?
|
||||
@ -295,20 +289,20 @@ Gun().user && describe('Gun', function(){
|
||||
// ... and persisted session
|
||||
sessionStorage.removeItem('remember');
|
||||
sessionStorage.removeItem('alias');
|
||||
Gun.SEA._callonstore_(function(store) {
|
||||
var act = store.clear(); // Wipes whole IndexedDB
|
||||
act.onsuccess = function(){};
|
||||
});
|
||||
if (typeof done === 'function') {
|
||||
seaIndexedDb.wipe().then(done)
|
||||
return
|
||||
} else {
|
||||
return seaIndexedDb.wipe()
|
||||
}
|
||||
}
|
||||
};
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
['callback', 'Promise'].forEach(function(type){
|
||||
describe(type+':', function(){
|
||||
beforeEach(function(done){
|
||||
// Simulate browser reload
|
||||
throwOutUser(true);
|
||||
done();
|
||||
});
|
||||
// Simulate browser reload
|
||||
beforeEach((done) => { throwOutUser(true, done) })
|
||||
|
||||
describe('create', function(){
|
||||
|
||||
@ -677,7 +671,7 @@ Gun().user && describe('Gun', function(){
|
||||
expect(usr).to.have.key('_');
|
||||
expect(usr._).to.have.keys(['pub', 'sea']);
|
||||
// ... to validate 'remember' data
|
||||
pin = pin && new Buffer(pin, 'utf8').toString('base64');
|
||||
pin = pin && Buffer.from(pin, 'utf8').toString('base64');
|
||||
return !pin ? Promise.resolve(sessionStorage.getItem('remember'))
|
||||
: new Promise(function(resolve){
|
||||
checkIndexedDB(usr._.alias, 'auth', resolve);
|
||||
@ -784,7 +778,7 @@ Gun().user && describe('Gun', function(){
|
||||
user.recall(12 * 60).then(
|
||||
doCheck(function(ack){
|
||||
expect(ack).to.have.key('err');
|
||||
expect(ack.err.toLowerCase().indexOf('no authentication')).to.not.be(-1);
|
||||
expect(ack.err.toLowerCase().indexOf('no session')).to.not.be(-1);
|
||||
checkIndexedDB(alias+type, 'auth', function(auth){
|
||||
expect((typeof auth !== 'undefined' && auth !== null && auth !== ''))
|
||||
.to.not.eql(true);
|
||||
@ -847,12 +841,13 @@ Gun().user && describe('Gun', function(){
|
||||
});
|
||||
|
||||
it('valid session bootstrap using alias & PIN', function(done){
|
||||
let sRemember
|
||||
user.recall(12 * 60).then(function(){
|
||||
return user.auth(alias+type, pass+' new', {pin: 'PIN'});
|
||||
}).then(doCheck(function(ack){
|
||||
// Let's save remember props
|
||||
var sUser = root.sessionStorage.getItem('user');
|
||||
var sRemember = root.sessionStorage.getItem('remember');
|
||||
sRemember = root.sessionStorage.getItem('remember')
|
||||
var iAuth = ack.auth;
|
||||
return new Promise(function(resolve){
|
||||
checkIndexedDB(sUser, 'auth', function(auth){
|
||||
@ -888,6 +883,7 @@ Gun().user && describe('Gun', function(){
|
||||
expect(props.err.toLowerCase()
|
||||
.indexOf('missing pin')).not.to.be(-1);
|
||||
}catch(e){ done(e); return }
|
||||
root.sessionStorage.setItem('remember', sRemember)
|
||||
// Ok, time to try auth with alias & PIN
|
||||
return user.auth(alias+type, undefined, {pin: 'PIN'});
|
||||
});
|
||||
@ -962,21 +958,18 @@ Gun().user && describe('Gun', function(){
|
||||
var ret = Object.assign({}, props, {iat: props.iat - 65 - props.exp});
|
||||
return ret;
|
||||
}, pin);
|
||||
})).then(function(){
|
||||
// Simulate browser reload
|
||||
throwOutUser();
|
||||
user.recall(60).then(function(ack){
|
||||
expect(ack).to.not.be(undefined);
|
||||
expect(ack).to.not.be('');
|
||||
expect(ack).to.not.have.keys([ 'pub', 'sea' ]);
|
||||
expect(ack).to.have.key('err');
|
||||
expect(ack.err).to.not.be(undefined);
|
||||
expect(ack.err).to.not.be('');
|
||||
expect(ack.err.toLowerCase()
|
||||
.indexOf('no authentication session')).not.to.be(-1);
|
||||
done();
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
})).then(() => throwOutUser(false)) // Simulate browser reload
|
||||
.then(() => user.recall(60).then((ack) => {
|
||||
expect(ack).to.not.be(undefined)
|
||||
expect(ack).to.not.be('')
|
||||
expect(ack).to.not.have.keys([ 'pub', 'sea' ])
|
||||
expect(ack).to.have.key('err')
|
||||
expect(ack.err).to.not.be(undefined)
|
||||
expect(ack.err).to.not.be('')
|
||||
expect(ack.err.toLowerCase()
|
||||
.indexOf('no session')).not.to.be(-1)
|
||||
done()
|
||||
})).catch(done)
|
||||
});
|
||||
|
||||
it('changed password', function(done){
|
||||
@ -1010,14 +1003,12 @@ Gun().user && describe('Gun', function(){
|
||||
|
||||
return user.auth(alias+type, pass+' new', {newpass: pass, pin: pin})
|
||||
.then(function(usr){ expect(usr).to.not.have.key('err') });
|
||||
}).then(function(){
|
||||
return user.leave().then(function(ack){
|
||||
try{
|
||||
expect(ack).to.have.key('ok');
|
||||
}catch(e){ done(e); return }
|
||||
throwOutUser();
|
||||
});
|
||||
}).then(function(){
|
||||
}).then(() => user.leave().then((ack) => {
|
||||
try {
|
||||
expect(ack).to.have.key('ok')
|
||||
} catch (e) { done(e); return }
|
||||
return throwOutUser(false)
|
||||
})).then(function(){
|
||||
// Simulate browser reload
|
||||
// Call back pre-update remember...
|
||||
root.sessionStorage.setItem('user', sUser);
|
||||
@ -1034,7 +1025,7 @@ Gun().user && describe('Gun', function(){
|
||||
expect(props.err).to.not.be(undefined);
|
||||
expect(props.err).to.not.be('');
|
||||
expect(props.err.toLowerCase()
|
||||
.indexOf('no authentication session')).not.to.be(-1);
|
||||
.indexOf('no session')).not.to.be(-1);
|
||||
done();
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
|
Loading…
x
Reference in New Issue
Block a user