SEA upgraded thanks to @mhelander 's @BrockAtkinson 's & @robertheessels 's hard work!!!

Sea
This commit is contained in:
Mark Nadal 2017-09-10 13:48:46 -07:00 committed by GitHub
commit 6e4f44979f
13 changed files with 56497 additions and 8631 deletions

2
gun.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -52,14 +52,18 @@
"ws": "~>2.2.3"
},
"devDependencies": {
"uws": "~>0.14.1",
"express": ">=4.15.2",
"subtle": "^0.1.8",
"text-encoding": "^0.6.4",
"buffer": "^5.0.7",
"eccrypto": "^1.0.3",
"hapi": "^16.1.1",
"inert": "^4.2.0",
"ip": "^1.1.5",
"mocha": ">=3.2.0",
"panic-manager": "^1.2.0",
"panic-server": "^1.1.0",
"uglify-js": ">=2.8.22"
"uglify-js": ">=2.8.22",
"uws": "~>0.14.1"
}
}

1202
sea.js

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,11 @@ var root, noop = function(){}, u;
if(typeof window !== 'undefined'){ root = window }
var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop};
/*
NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design!
If you update anything here, consider updating the other adapters as well.
*/
Gun.on('opt', function(ctx){
this.to.next(ctx);
var opt = ctx.opt;
@ -56,7 +61,7 @@ Gun.on('opt', function(ctx){
var ack = acks;
acks = {};
try{store.setItem(opt.file, JSON.stringify(disk));
}catch(e){ err = e || "localStorage failure" }
}catch(e){ Gun.log(err = e || "localStorage failure") }
if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers.
Gun.obj.map(ack, function(yes, id){
ctx.on('in', {

View File

@ -18,6 +18,7 @@ function output(at){
if(!at.gun){
at.gun = gun;
}
this.to.next(at);
if(get = at.get){
if(tmp = get[_soul]){
tmp = (root.get(tmp)._);
@ -98,7 +99,7 @@ function output(at){
proxy.at.on('in', proxy.at);
});
}
if(cat.ack){
if(cat.ack >= 0){
if(!obj_has(cat, 'put')){ // u !== cat.put instead?
//if(u !== cat.put){
return;

View File

@ -2,7 +2,7 @@
var Type = require('./type');
function Dup(opt){
var dup = {s:{}};
opt = opt || {max: 1000, age: 1000 * 60 * 2};
opt = opt || {max: 1000, age: 1000 * 9};//1000 * 60 * 2};
dup.check = function(id){
return dup.s[id]? dup.track(id) : false;
}

View File

@ -127,6 +127,22 @@ Gun.chain.off = function(){
}
return gun;
}
Gun.chain.off = function(){
var gun = this, at = gun._, tmp;
var back = at.back || {}, cat = back._;
if(!cat){ return }
if(tmp = cat.next){
if(tmp[at.get]){
obj_del(tmp, at.get);
} else {
}
}
if(tmp = at.soul){
obj_del(cat.root._.graph, tmp);
}
return gun;
}
var obj = Gun.obj, obj_has = obj.has, obj_del = obj.del, obj_to = obj.to;
var rel = Gun.val.rel;
var empty = {}, noop = function(){}, u;

View File

@ -17,6 +17,9 @@ module.exports = function onto(tag, arg, as){
this.to.back = this.back;
this.next = onto._.next;
this.back.to = this.to;
if(this.the.last === this.the){
delete this.on.tag[this.the.tag];
}
}),
to: onto._,
next: arg,

View File

@ -44,7 +44,7 @@ Gun.chain.put = function(data, cb, as){
as.ref.get('_').get(any, {as: as});
if(!as.out){
// TODO: Perf idea! Make a global lock, that blocks everything while it is on, but if it is on the lock it does the expensive lookup to see if it is a dependent write or not and if not then it proceeds full speed. Meh? For write heavy async apps that would be terrible.
as.res = as.res || noop; // Gun.on.stun(as.ref); // TODO: BUG! Deal with locking?
as.res = as.res || stun; // Gun.on.stun(as.ref); // TODO: BUG! Deal with locking?
as.gun._.stun = as.ref._.stun;
}
return gun;
@ -63,16 +63,32 @@ function ify(as){
as.batch();
}
function stun(cb){
if(cb){ cb() }
return;
var as = this;
if(!as.ref){ return }
if(cb){
as.after = as.ref._.tag;
as.now = as.ref._.tag = {};
cb();
return;
}
if(as.after){
as.ref._.tag = as.after;
}
}
function batch(){ var as = this;
if(!as.graph || obj_map(as.stun, no)){ return }
(as.res||iife)(function(){
var cat = (as.gun.back(-1)._), ask = cat.ask(function(ack){
this.off(); // One response is good enough for us currently. Later we may want to adjust this.
if(!as.ack){ return }
as.ack(ack, this);
}, as.opt);
(as.ref._).on('out', {
cap: 3,
gun: as.ref, put: as.out = as.env.graph, opt: as.opt,
'#': as.gun.back(-1)._.ask(function(ack){ this.off(); // One response is good enough for us currently. Later we may want to adjust this.
if(!as.ack){ return }
as.ack(ack, this);
}, as.opt)
gun: as.ref, put: as.out = as.env.graph, opt: as.opt, '#': ask
});
}, as);
if(as.res){ as.res() }

View File

@ -148,14 +148,21 @@ Gun._ = { // some reserved key words, these are not the only ones.
Gun.on.ask = function(cb, as){
if(!this.on){ return }
var id = text_rand(9);
if(cb){ this.on(id, cb, as) }
if(cb){
var to = this.on(id, cb, as), lack = (this.gun._.opt.lack || 9000);
to.err = setTimeout(function(){
to.next({err: "Error: No ACK received yet."});
to.off();
}, lack < 1000 ? 1000 : lack);
}
return id;
}
Gun.on.ack = function(at, reply){
if(!at || !reply || !this.on){ return }
var id = at['#'] || at;
if(!this.tag || !this.tag[id]){ return }
var id = at['#'] || at, tmp = (this.tag||empty)[id];
if(!tmp){ return }
this.on(id, reply);
clearTimeout(tmp.err);
return true;
}
}());

File diff suppressed because it is too large Load Diff

966
test/sea.js Normal file
View File

@ -0,0 +1,966 @@
var root;
(function(env){
root = env.window ? env.window : global;
}(this));
Gun.SEA && describe('SEA', function(){
console.log('TODO: SEA! THIS IS AN EARLY ALPHA!!!');
var alias = 'dude';
var pass = 'my secret password';
var userKeys = ['pub', 'priv'];
var clearText = 'My precious secret!';
var encKeys = ['ct', 'iv', 's'];
['callback', 'Promise'].forEach(function(type){
describe(type+':', function(){
it('proof', function(done){
var check = function(proof){
expect(proof).to.not.be(undefined);
expect(proof).to.not.be('');
done();
}
// proof - generates PBKDF2 hash from user's alias and password
// which is then used to decrypt user's auth record
if(type === 'callback'){
Gun.SEA.proof(alias, pass, check);
} else {
Gun.SEA.proof(alias, pass).then(check).catch(done);
}
});
it('pair', function(done){
var check = function(key){
expect(key).to.not.be(undefined);
expect(key).to.not.be('');
expect(key).to.have.keys(userKeys);
userKeys.map(function(fld){
expect(key[fld]).to.not.be(undefined);
expect(key[fld]).to.not.be('');
});
done();
};
// pair - generates ECDH key pair (for new user when created)
if(type === 'callback'){
Gun.SEA.pair(check);
} else {
Gun.SEA.pair().then(check).catch(done);;
}
});
it('en', function(done){
Gun.SEA.pair().then(function(key){
var check = function(jsonSecret){
expect(jsonSecret).to.not.be(undefined);
expect(jsonSecret).to.not.be('');
expect(jsonSecret).to.not.eql(clearText);
expect(jsonSecret).to.not.eql(JSON.stringify(clearText));
var objSecret = JSON.parse(jsonSecret);
expect(objSecret).to.have.keys(encKeys);
encKeys.map(function(key){
expect(objSecret[key]).to.not.be(undefined);
expect(objSecret[key]).to.not.be('');
});
done();
};
// en - encrypts JSON data using user's private or derived ECDH key
if(type === 'callback'){
Gun.SEA.en(JSON.stringify(clearText), key.priv, check);
} else {
Gun.SEA.en(JSON.stringify(clearText), key.priv).then(check);
}
}).catch(function(e){done(e)});
});
it('sign', function(done){
Gun.SEA.pair().then(function(key){
var check = function(signature){
expect(signature).to.not.be(undefined);
expect(signature).to.not.be('');
expect(signature).to.not.eql(key.pub);
done();
};
// sign - calculates signature for data using user's private ECDH key
if(type === 'callback'){
Gun.SEA.sign(key.pub, key.priv, check);
} else {
Gun.SEA.sign(key.pub, key.priv).then(check);
}
}).catch(function(e){done(e)});
});
it('verify', function(done){
Gun.SEA.pair().then(function(key){
var check = function(ok){
expect(ok).to.not.be(undefined);
expect(ok).to.not.be('');
expect(ok).to.be(true);
done();
};
// sign - calculates signature for data using user's private ECDH key
Gun.SEA.sign(key.pub, key.priv).then(function(signature){
if(type === 'callback'){
Gun.SEA.verify(key.pub, key.pub, signature, check);
} else {
Gun.SEA.verify(key.pub, key.pub, signature).then(check);
}
});
}).catch(function(e){done(e)});
});
it('de', function(done){
Gun.SEA.pair().then(function(key){
var check = function(jsonText){
expect(jsonText).to.not.be(undefined);
expect(jsonText).to.not.be('');
expect(jsonText).to.not.eql(clearText);
var decryptedSecret = JSON.parse(jsonText);
expect(decryptedSecret).to.not.be(undefined);
expect(decryptedSecret).to.not.be('');
expect(decryptedSecret).to.be.eql(clearText);
done();
};
Gun.SEA.en(JSON.stringify(clearText), key.priv).then(function(jsonSecret){
// de - decrypts JSON data using user's private or derived ECDH key
if(type === 'callback'){
Gun.SEA.de(jsonSecret, key.priv, check);
} else {
Gun.SEA.de(jsonSecret, key.priv).then(check);
}
});
}).catch(function(e){done(e)});
});
it('derive', function(done){
Gun.SEA.pair().then(function(txKey){
return Gun.SEA.pair().then(function(rxKey){
return { tx: txKey, rx: rxKey };
});
}).then(function(keys){
var check = function(shared){
expect(shared).to.not.be(undefined);
expect(shared).to.not.be('');
[keys.rx.pub, keys.rx.priv, keys.tx.pub, keys.tx.priv]
.map(function(val){
expect(shared).to.not.eql(val);
});
done();
};
// derive - provides shared secret for both receiver and sender
// which can be used to encrypt or sign data
if(type === 'callback'){
Gun.SEA.derive(keys.rx.pub, keys.tx.priv, check);
} else {
Gun.SEA.derive(keys.rx.pub, keys.tx.priv).then(check);
}
}).catch(function(e){done(e)});
});
it('write', function(done){
Gun.SEA.pair().then(function(key){
Gun.SEA.sign(key.pub, key.priv).then(function(signature){
var check = function(result){
expect(result).to.not.be(undefined);
expect(result).to.not.be('');
expect(result.slice(0, 4)).to.eql('SEA[');
var parts = JSON.parse(result.slice(3));
expect(parts).to.not.be(undefined);
expect(parts[0]).to.be.eql(key.pub);
expect(parts[1]).to.be.eql(signature);
done();
};
// write - wraps data to 'SEA["data","signature"]'
if(type === 'callback'){
Gun.SEA.write(key.pub, key.priv, check);
} else {
Gun.SEA.write(key.pub, key.priv).then(check);
}
});
}).catch(function(e){done(e)});
});
it('read', function(done){
Gun.SEA.pair().then(function(key){
var check = function(result){
expect(result).to.not.be(undefined);
expect(result).to.not.be('');
expect(result).to.be.equal(key.pub);
done();
};
Gun.SEA.sign(key.pub, key.priv).then(function(signature){
Gun.SEA.write(key.pub, key.priv).then(function(signed){
// read - unwraps data from 'SEA["data","signature"]'
if(type === 'callback'){
Gun.SEA.read(signed, key.pub, check);
} else {
Gun.SEA.read(signed, key.pub).then(check);
}
});
});
}).catch(function(e){done(e)});
});
});
});
});
Gun().user && describe('Gun', function(){
describe('User', function(){
console.log('TODO: User! THIS IS AN EARLY ALPHA!!!');
var alias = 'dude';
var pass = 'my secret password';
var gun = Gun();
var user = gun.user();
Gun.log.off = true; // Supress all console logging
var throwOutUser = function(wipeStorageData){
// Get rid of authenticated Gun user
var user = gun.back(-1)._.user;
// TODO: is this correct way to 'logout' user from Gun.User ?
[ 'alias', 'sea', 'pub' ].forEach(function(key){
delete user._[key];
});
user._.is = user.is = {};
if(wipeStorageData){
// ... and persisted session
localStorage.removeItem('remember')
sessionStorage.removeItem('remember');
sessionStorage.removeItem('alias');
}
};
['callback', 'Promise'].forEach(function(type){
describe(type+':', function(){
beforeEach(function(done){
// Simulate browser reload
throwOutUser(true);
done();
});
describe('create', function(){
it('new', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.have.keys([ 'ok', 'pub' ]);
}catch(e){ done(e); return };
done();
};
// Gun.user.create - creates new user
if(type === 'callback'){
user.create(alias+type, pass, check);
} else {
user.create(alias+type, pass).then(check).catch(done);
}
});
it('conflict', function(done){
Gun.log.off = true; // Supress all console logging
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.have.key('err');
expect(ack.err).not.to.be(undefined);
expect(ack.err).not.to.be('');
expect(ack.err.toLowerCase().indexOf('already created')).not.to.be(-1);
}catch(e){ done(e); return };
done();
};
// Gun.user.create - fails to create existing user
if(type === 'callback'){
user.create(alias+type, pass, check);
} else {
user.create(alias+type, pass).then(function(ack){
done('Failed to decline creating existing user!');
}).catch(check);
}
});
});
describe('auth', function(){
var checkStorage = function(done, hasPin){
return function(){
expect(root.sessionStorage.getItem('user')).to.not.be(undefined);
expect(root.sessionStorage.getItem('user')).to.not.be('');
expect(root.sessionStorage.getItem('remember')).to.not.be(undefined);
expect(root.sessionStorage.getItem('remember')).to.not.be('');
if(hasPin){
expect(root.localStorage.getItem('remember')).to.not.be(undefined);
expect(root.localStorage.getItem('remember')).to.not.be('');
}
done();
};
};
it('login', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.key('err');
}catch(e){ done(e); return };
done();
};
// Gun.user.auth - authenticates existing user
if(type === 'callback'){
user.auth(alias+type, pass, check);
} else {
user.auth(alias+type, pass).then(check).catch(done);
}
});
it('wrong password', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.have.key('err');
expect(ack.err).to.not.be(undefined);
expect(ack.err).to.not.be('');
expect(ack.err.toLowerCase().indexOf('failed to decrypt secret'))
.not.to.be(-1);
}catch(e){ done(e); return };
done();
};
if(type === 'callback'){
user.auth(alias+type, pass+'not', check);
} else {
user.auth(alias+type, pass+'not').then(function(ack){
done('Unexpected login success!');
}).catch(check);
}
});
it('unknown alias', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
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 user')).not.to.be(-1);
}catch(e){ done(e); return };
done();
};
if(type === 'callback'){
user.auth(alias+type+'not', pass, check);
} else {
user.auth(alias+type+'not', pass).then(function(ack){
done('Unexpected login success!');
}).catch(check);
}
});
it('new password', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.key('err');
}catch(e){ done(e); return };
done();
};
// Gun.user.auth - with newpass props sets new password
if(type === 'callback'){
user.auth(alias+type, pass, check, {newpass: pass+' new'});
} else {
user.auth(alias+type, pass, {newpass: pass+' new'}).then(check)
.catch(done);
}
});
it('failed new password', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.have.key('err');
expect(ack.err).to.not.be(undefined);
expect(ack.err).to.not.be('');
expect(ack.err.toLowerCase().indexOf('failed to decrypt secret'))
.not.to.be(-1);
}catch(e){ done(e); return };
done();
};
var props = {alias: alias+type, pass: pass+'not', newpass: pass+' new'};
if(type === 'callback'){
user.auth(alias+type, pass+'not', check, {newpass: pass+' new'});
} else {
user.auth(alias+type, pass+'not', {newpass: pass+' new'})
.then(function(ack){
done('Unexpected password change success!');
}).catch(check);
}
});
it('without PIN auth session stored to sessionStorage', function(done){
user.auth(alias+type, pass+' new').then(checkStorage(done)).catch(done);
});
it('with PIN auth session stored to sessionStorage', function(done){
if(type === 'callback'){
user.auth(alias+type, pass+' new', checkStorage(done, true), {pin: 'PIN'});
} else {
user.auth(alias+type, pass+' new', {pin: 'PIN'})
.then(checkStorage(done, true)).catch(done);
}
});
});
describe('leave', function(){
it('valid session', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.key('err');
expect(ack).to.have.key('ok');
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
}catch(e){ done(e); return };
done();
};
var usr = alias+type+'leave';
user.create(usr, pass).then(function(ack){
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.have.keys([ 'ok', 'pub' ]);
user.auth(usr, pass).then(function(usr){
try{
expect(usr).to.not.be(undefined);
expect(usr).to.not.be('');
expect(usr).to.not.have.key('err');
expect(usr).to.have.key('put');
}catch(e){ done(e); return };
// Gun.user.leave - performs logout for authenticated user
if(type === 'callback'){
user.leave(check);
} else {
user.leave().then(check).catch(done);
}
}).catch(done);
}).catch(done);
});
it('no session', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.key('err');
expect(ack).to.have.key('ok');
}catch(e){ done(e); return };
done();
};
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
if(type === 'callback'){
user.leave(check);
} else {
user.leave().then(check).catch(done);
}
});
});
describe('delete', function(){
var usr = alias+type+'del';
var createUser = function(a, p){
return user.create(a, p).then(function(ack){
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.have.keys([ 'ok', 'pub' ]);
return ack;
});
};
var check = function(done){
return function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.key('err');
expect(ack).to.have.key('ok');
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
}catch(e){ done(e); return };
done();
};
};
it('existing authenticated user', function(done){
createUser(usr, pass).then(function(){
user.auth(usr, pass).then(function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.key('err');
expect(ack).to.have.key('put');
}catch(e){ done(e); return };
// Gun.user.delete - deletes existing user account
if(type === 'callback'){
user.delete(usr, pass, check(done));
} else {
user.delete(usr, pass).then(check(done)).catch(done);
}
}).catch(done);
}).catch(done);
});
it('unauthenticated existing user', function(done){
createUser(usr, pass).catch(function(){})
.then(function(){
if(type === 'callback'){
user.delete(usr, pass, check(done));
} else {
user.delete(usr, pass).then(check(done)).catch(done);
}
});
});
it('non-existing user', function(done){
var notFound = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.key('put');
expect(ack).to.have.key('err');
expect(ack.err.toLowerCase().indexOf('no user')).not.to.be(-1);
}catch(e){ done(e); return };
done();
};
if(type === 'callback'){
user.delete('someone', 'password guess', notFound);
} else {
user.delete('someone', 'password guess').then(function(){
done('Unexpectedly deleted guessed user!');
}).catch(notFound);
}
});
});
describe('recall', function(){
var doCheck = function(done, hasPin, wantAck){
expect(typeof done).to.be('function');
return function(ack){
var user = root.sessionStorage.getItem('user');
var sRemember = root.sessionStorage.getItem('remember');
expect(user).to.not.be(undefined);
expect(user).to.not.be('');
expect(sRemember).to.not.be(undefined);
expect(sRemember).to.not.be('');
if(hasPin){
var lRemember = root.localStorage.getItem('remember');
expect(lRemember).to.not.be(undefined);
expect(lRemember).to.not.be('');
}
// NOTE: done can be Promise returning function
var ret;
if (wantAck) {
[ 'err', 'pub', 'sea', 'alias', 'put' ].forEach(function(key){
if(typeof ack[key] !== 'undefined'){
(ret = ret || {})[key] = ack[key];
}
});
}
return done(ret);
};
};
// This re-constructs 'remember-me' data modified by manipulate func
var manipulateStorage = function(manipulate, hasPin){
expect(typeof manipulate).to.be('function');
// We'll use Gun internal User data
var usr = gun.back(-1)._.user;
expect(usr).to.not.be(undefined);
expect(usr).to.have.key('_');
expect(usr._).to.have.keys([ 'pub', 'sea' ]);
// ... to validate 'remember' data
var remember = hasPin ? localStorage.getItem('remember')
: sessionStorage.getItem('remember');
return Gun.SEA.read(remember, usr._.pub).then(function(props){
try{ props && (props = JSON.parse(props)) }catch(e){}
return props;
}).then(manipulate).then(function(props){
expect(props).to.not.be(undefined);
expect(props).to.not.be('');
return Gun.SEA.write(JSON.stringify(props), usr._.sea)
.then(function(remember){
return hasPin ? sessionStorage.setItem('remember', remember)
: sessionStorage.setItem('remember', remember);
});
});
};
it('with PIN auth session stored to localStorage', function(done){
var doAction = function(){
user.auth(alias+type, pass+' new', { pin: 'PIN' })
.then(doCheck(done, true)).catch(done);
};
if(type === 'callback'){
user.recall(doAction, { session: false });
} else {
user.recall({ session: false }).then(doAction).catch(done)
}
});
it('without PIN auth session stored to sessionStorage', function(done){
var doAction = function(){
user.auth(alias+type, pass+' new').then(doCheck(done));
};
user.leave().then(function(){
if(type === 'callback'){
user.recall(doAction, { session: false });
} else {
user.recall({ session: false }).then(doAction).catch(done)
}
}).catch(done);
});
it('no validity no session storing', function(done){
var doAction = function(){
user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done);
};
if(type === 'callback'){
user.recall(0, doAction);
} else {
user.recall(0).then(doAction).catch(done);
}
});
it('validity but no PIN stored to sessionStorage', function(done){
var doAction = function(){
user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done);
};
if(type === 'callback'){
user.recall(12 * 60, doAction, {session: false});
} else {
user.recall(12 * 60, {session: false}).then(doAction)
.catch(done);
}
});
it('valid sessionStorage session', function(done){
user.auth(alias+type, pass+' new').then(function(usr){
var sUser;
var sRemember;
try{
expect(usr).to.not.be(undefined);
expect(usr).to.not.be('');
expect(usr).to.not.have.key('err');
expect(usr).to.have.key('put');
expect(root.sessionStorage.getItem('user')).to.be(alias+type);
expect(root.sessionStorage.getItem('remember')).to.not.be(undefined);
expect(root.sessionStorage.getItem('remember')).to.not.be('');
sUser = root.sessionStorage.getItem('user');
sRemember = root.sessionStorage.getItem('remember');
}catch(e){ done(e); return };
user.leave().then(function(ack){
try{
expect(ack).to.have.key('ok');
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
expect(root.sessionStorage.getItem('user')).to.not.be(sUser);
expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember);
}catch(e){ done(e); return };
root.sessionStorage.setItem('user', sUser);
root.sessionStorage.setItem('remember', sRemember);
user.recall(12 * 60, {session: false}).then(doCheck(done))
.catch(done);
}).catch(done);
}).catch(done);
});
it('valid localStorage session bootstrap', function(done){
user.auth(alias+type, pass+' new', { pin: 'PIN' }).then(function(usr){
var sUser;
var sRemember;
var lRemember;
try{
expect(usr).to.not.be(undefined);
expect(usr).to.not.be('');
expect(usr).to.not.have.key('err');
expect(usr).to.have.key('put');
expect(root.sessionStorage.getItem('user')).to.be(alias+type);
expect(root.sessionStorage.getItem('remember')).to.not.be(undefined);
expect(root.sessionStorage.getItem('remember')).to.not.be('');
expect(root.localStorage.getItem('remember')).to.not.be(undefined);
expect(root.localStorage.getItem('remember')).to.not.be('');
sUser = root.sessionStorage.getItem('user');
sRemember = root.sessionStorage.getItem('remember');
lRemember = root.localStorage.getItem('remember');
}catch(e){ done(e); return };
user.leave().then(function(ack){
try{
expect(ack).to.have.key('ok');
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
expect(root.sessionStorage.getItem('user')).to.not.be(sUser);
expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember);
expect(root.localStorage.getItem('remember')).to.not.be(lRemember);
}catch(e){ done(e); return };
root.sessionStorage.setItem('user', sUser);
root.sessionStorage.setItem('remember', sRemember);
root.localStorage.setItem('remember', lRemember);
user.recall(12 * 60, {session: false}).then(doCheck(done))
.catch(done);
}).catch(done);
}).catch(done);
});
it('valid localStorage session bootstraps using PIN', function(done){
user.recall(12 * 60, {session: false}).then(function(){
return user.auth(alias+type, pass+' new', { pin: 'PIN' });
}).then(doCheck(function(){
// Let's save remember props
var sUser = root.sessionStorage.getItem('user');
var sRemember = root.sessionStorage.getItem('remember');
var lRemember = root.localStorage.getItem('remember');
// Then logout user
return user.leave().then(function(ack){
try{
expect(ack).to.have.key('ok');
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
expect(root.sessionStorage.getItem('user')).to.not.be(sUser);
expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember);
expect(root.localStorage.getItem('remember')).to.not.be(lRemember);
}catch(e){ done(e); return };
// Then restore localStorage remember data, skip sessionStorage
root.localStorage.setItem('remember', lRemember);
});
}, true)).then(function(){
// Then try to recall authentication
return user.recall(12 * 60, {session: false}).then(function(props){
try{
expect(props).to.not.be(undefined);
expect(props).to.not.be('');
expect(props).to.have.key('err');
// Which fails to missing PIN
expect(props.err.toLowerCase()
.indexOf('missing pin')).not.to.be(-1);
}catch(e){ done(e); return };
// Ok, time to try auth with alias & PIN
return user.auth(alias+type, undefined, { pin: 'PIN' });
});
}).then(doCheck(function(usr){
try{
expect(usr).to.not.be(undefined);
expect(usr).to.not.be('');
expect(usr).to.not.have.key('err');
expect(usr).to.have.key('put');
}catch(e){ done(e); return };
// We've recalled authenticated session using alias & PIN!
done();
}, true, true)).catch(done);
});
it('valid localStorage session fails to bootstrap using wrong PIN',
function(done){
user.recall(12 * 60, {session: false}).then(function(){
return user.auth(alias+type, pass+' new', { pin: 'PIN' });
}).then(doCheck(function(){
var sUser = root.sessionStorage.getItem('user');
var sRemember = root.sessionStorage.getItem('remember');
var lRemember = root.localStorage.getItem('remember');
return user.leave().then(function(ack){
try{
expect(ack).to.have.key('ok');
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
expect(root.sessionStorage.getItem('user')).to.not.be(sUser);
expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember);
expect(root.localStorage.getItem('remember')).to.not.be(lRemember);
}catch(e){ done(e); return };
root.localStorage.setItem('remember', lRemember);
});
}, true)).then(function(){
// Ok, time to try auth with alias & PIN
return user.auth(alias+type, undefined, { pin: 'PiN' });
}).then(function(){
done('Unexpected login success!');
}).catch(function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.have.key('err');
expect(ack.err.toLowerCase()
.indexOf('no session data for alias & pin')).not.to.be(-1);
}catch(e){ done(e); return };
// We've recalled authenticated session using alias & PIN!
done();
});
});
it('expired session fails to bootstrap', function(done){
user.recall(60, {session: true}).then(function(){
return user.auth(alias+type, pass+' new');
}).then(doCheck(function(){
// Storage data OK, let's back up time of auth to exp + 65 seconds
return manipulateStorage(function(props){
var ret = Object.assign({}, props, { iat: props.iat - 65 - props.exp });
return ret;
}, false);
})).then(function(){
// Simulate browser reload
throwOutUser();
user.recall(60, {session: true}).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);
});
it('changed password', function(done){
user.recall(60, {session: false}).then(function(){
return user.auth(alias+type, pass+' new', { pin: 'PIN' });
}).then(function(usr){
var sUser;
var sRemember;
var lRemember;
try{
expect(usr).to.not.be(undefined);
expect(usr).to.not.be('');
expect(usr).to.not.have.key('err');
expect(usr).to.have.key('put');
expect(root.sessionStorage.getItem('user')).to.be(alias+type);
expect(root.sessionStorage.getItem('remember')).to.not.be(undefined);
expect(root.sessionStorage.getItem('remember')).to.not.be('');
expect(root.localStorage.getItem('remember')).to.not.be(undefined);
expect(root.localStorage.getItem('remember')).to.not.be('');
sUser = root.sessionStorage.getItem('user');
sRemember = root.sessionStorage.getItem('remember');
lRemember = root.localStorage.getItem('remember');
}catch(e){ done(e); return };
// Time to do new login with new password set
user.leave().then(function(ack){
try{
expect(ack).to.have.key('ok');
}catch(e){ done(e); return };
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(){
// Simulate browser reload
// Call back previous remember data
root.sessionStorage.setItem('user', sUser);
root.sessionStorage.setItem('remember', sRemember);
root.localStorage.setItem('remember', lRemember);
user.recall(60, {session: false}).then(function(props){
expect(props).to.not.be(undefined);
expect(props).to.not.be('');
expect(props).to.have.key('err');
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);
done();
}).catch(done);
}).catch(done);
}).catch(done);
});
it('recall hook session manipulation', function(done){
var exp;
var hookFunc = function(props){
exp = props.exp * 2;
var ret = Object.assign({}, props, { exp: exp });
return (type === 'callback' && ret) || new Promise(function(resolve){
resolve(ret);
});
};
user.recall(60, { session: true, hook: hookFunc }).then(function(){
return user.auth(alias+type, pass);
}).then(function(){
// Storage data OK, let's back up time of auth 65 minutes
return manipulateStorage(function(props){
expect(props).to.not.be(undefined);
expect(props).to.have.key('exp');
expect(props.exp).to.be(exp);
return props;
}, false);
}).then(done).catch(done);
});
});
describe('alive', function(){
it('valid session', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.key('err');
expect(ack).to.have.keys([ 'sea', 'pub' ]);
}catch(e){ done(e); return };
done();
};
var aliveUser = alias+type+'alive';
user.create(aliveUser, pass).then(function(ack){
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.have.keys([ 'ok', 'pub' ]);
user.auth(aliveUser, pass, { pin: 'PIN' }).then(function(usr){
try{
expect(usr).to.not.be(undefined);
expect(usr).to.not.be('');
expect(usr).to.not.have.key('err');
expect(usr).to.have.key('put');
}catch(e){ done(e); return };
// Gun.user.alive - keeps/checks User authentiation state
if(type === 'callback'){
user.alive(check);
} else {
user.alive().then(check).catch(done);
}
}).catch(done);
}).catch(done);
});
it('expired session', function(done){
var check = function(ack){
try{
expect(ack).to.not.be(undefined);
expect(ack).to.not.be('');
expect(ack).to.not.have.keys([ 'sea', 'pub' ]);
expect(ack).to.have.key('err');
expect(ack.err.toLowerCase().indexOf('no session')).not.to.be(-1);
}catch(e){ done(e); return };
done();
};
user.leave().catch(function(){}).then(function(){
user.alive().then(function(){
done('Unexpected alive session!');
}).catch(check);
}).catch(done);
});
});
});
});
process.env.SEA_CHANNEL && describe('User channel', function(){
it.skip('create');
it.skip('add member');
});
Gun.log.off = false;
});
});