Removed 'session' to fulfill three step remember-me spec

This commit is contained in:
mhelander 2017-09-18 13:00:06 +03:00
parent d32eaf833e
commit 9157b0de6b
2 changed files with 89 additions and 75 deletions

37
sea.js
View File

@ -52,14 +52,12 @@
var _initial_authsettings = { var _initial_authsettings = {
validity: 12 * 60 * 60, // internally in seconds : 12 hours validity: 12 * 60 * 60, // internally in seconds : 12 hours
session: true,
hook: function(props){ return props } // { iat, exp, alias, remember } hook: function(props){ return props } // { iat, exp, alias, remember }
// or return new Promise(function(resolve, reject){(resolve(props))}) // or return new Promise(function(resolve, reject){(resolve(props))})
}; };
// These are used to persist user's authentication "session" // These are used to persist user's authentication "session"
var authsettings = { var authsettings = {
validity: _initial_authsettings.validity, validity: _initial_authsettings.validity,
session: _initial_authsettings.session,
hook: _initial_authsettings.hook hook: _initial_authsettings.hook
}; };
@ -178,12 +176,12 @@
return function(props){ return function(props){
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
if(!Gun.obj.has(props, 'alias')){ return resolve() } if(!Gun.obj.has(props, 'alias')){ return resolve() }
if(proof && Gun.obj.has(props, 'iat')){ if(authsettings.validity && proof && Gun.obj.has(props, 'iat')){
props.proof = proof; props.proof = proof;
delete props.remember; // Not stored if present delete props.remember; // Not stored if present
var remember = (pin && {alias: props.alias, pin: pin}) || props; var remember = {alias: props.alias, pin: pin};
var persist = !authsettings.session && pin && props; var persist = props;
return SEA.write(JSON.stringify(remember), priv).then(function(signed){ return SEA.write(JSON.stringify(remember), priv).then(function(signed){
sessionStorage.setItem('user', props.alias); sessionStorage.setItem('user', props.alias);
@ -209,6 +207,7 @@
}).then(function(){ resolve(props) }) }).then(function(){ resolve(props) })
.catch(function(e){ reject({err: 'Session persisting failed!'}) }); .catch(function(e){ reject({err: 'Session persisting failed!'}) });
} }
// TODO: remove IndexedDB when using random PIN
return new Promise(function(resolve){ return new Promise(function(resolve){
SEA._callonstore_(function(store) { SEA._callonstore_(function(store) {
var act = store.clear(); // Wipes whole IndexedDB var act = store.clear(); // Wipes whole IndexedDB
@ -226,14 +225,13 @@
// This internal func persists User authentication if so configured // This internal func persists User authentication if so configured
function authpersist(user,proof,opts){ function authpersist(user,proof,opts){
// opts = { pin: 'string' } // opts = { pin: 'string' }
// authsettings.session = true // disables PIN method // no opts.pin then uses random PIN
// TODO: how this works: // How this works:
// called when app bootstraps, with wanted options // called when app bootstraps, with wanted options
// IF authsettings.validity === 0 THEN no remember-me, ever // IF authsettings.validity === 0 THEN no remember-me, ever
// IF authsettings.session === true THEN no window.indexedDB in use; nor PIN // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
// ELSE if no PIN then window.sessionStorage var pin = (Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10);
var pin = Gun.obj.has(opts, 'pin') && opts.pin pin = new Buffer(pin, 'utf8').toString('base64');
&& new Buffer(opts.pin, 'utf8').toString('base64');
if(proof && user && user.alias && authsettings.validity){ if(proof && user && user.alias && authsettings.validity){
var args = {alias: user.alias}; var args = {alias: user.alias};
@ -253,7 +251,7 @@
// This internal func recalls persisted User authentication if so configured // This internal func recalls persisted User authentication if so configured
function authrecall(root,authprops){ function authrecall(root,authprops){
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
// TODO: sessionStorage to only hold signed { alias, pin } !!! // window.sessionStorage only holds signed { alias, pin } !!!
var remember = authprops || sessionStorage.getItem('remember'); var remember = authprops || sessionStorage.getItem('remember');
var alias = Gun.obj.has(authprops, 'alias') && authprops.alias var alias = Gun.obj.has(authprops, 'alias') && authprops.alias
|| sessionStorage.getItem('user'); || sessionStorage.getItem('user');
@ -375,7 +373,7 @@
}; };
}, function(){ // And return proof if for matching alias }, function(){ // And return proof if for matching alias
reject({ reject({
err: (gotRemember && 'Missing PIN and alias!') err: (gotRemember && authsettings.validity && 'Missing PIN and alias!')
|| 'No authentication session found!'}); || 'No authentication session found!'});
}); });
}); });
@ -498,8 +496,6 @@
cb = typeof cb === 'function' && cb; cb = typeof cb === 'function' && cb;
var doIt = function(resolve, reject){ var doIt = function(resolve, reject){
// TODO: !pass && opt.pin => try to recall
// return reject({err: 'Auth attempt failed! Reason: No session data for alias & PIN'});
if(!pass && Gun.obj.has(opts, 'pin')){ if(!pass && Gun.obj.has(opts, 'pin')){
return authrecall(root, {alias: alias, pin: opts.pin}).then(function(props){ return authrecall(root, {alias: alias, pin: opts.pin}).then(function(props){
resolve(props); resolve(props);
@ -624,17 +620,14 @@
var doIt = function(resolve, reject){ var doIt = function(resolve, reject){
// opts = { hook: function({ iat, exp, alias, proof }), // opts = { hook: function({ iat, exp, alias, proof }),
// session: false } // true disables PIN requirement/support // session: false } // true uses random PIN, no PIN UX error generated
// iat == Date.now() when issued, exp == seconds to expire from iat // iat == Date.now() when issued, exp == seconds to expire from iat
// TODO: how this works: // How this works:
// called when app bootstraps, with wanted options // called when app bootstraps, with wanted options
// IF validity === 0 THEN no remember-me, ever // IF authsettings.validity === 0 THEN no remember-me, ever
// IF opt.session === true THEN no window.indexedDB in use; nor PIN // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
authsettings.validity = typeof validity !== 'undefined' ? validity authsettings.validity = typeof validity !== 'undefined' ? validity
: _initial_authsettings.validity; : _initial_authsettings.validity;
if(Gun.obj.has(opts, 'session')){
authsettings.session = opts.session;
}
authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function') authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
? opts.hook : _initial_authsettings.hook; ? opts.hook : _initial_authsettings.hook;
// All is good. Should we do something more with actual recalled data? // All is good. Should we do something more with actual recalled data?

View File

@ -25,6 +25,14 @@ function checkIndexedDB(key, prop, resolve_){
}); });
} }
function setIndexedDB(key, prop, resolve_){
Gun.SEA._callonstore_(function(store){
store.put({id: key, auth: prop});
}, function(){
resolve_();
});
}
Gun.SEA && describe('SEA', function(){ Gun.SEA && describe('SEA', function(){
console.log('TODO: SEA! THIS IS AN EARLY ALPHA!!!'); console.log('TODO: SEA! THIS IS AN EARLY ALPHA!!!');
var alias = 'dude'; var alias = 'dude';
@ -300,22 +308,30 @@ Gun().user && describe('Gun', function(){
}); });
describe('auth', function(){ describe('auth', function(){
var checkStorage = function(done, hasPin){ var checkStorage = function(done, notStored){
return function(){ return function(){
var checkValue = function(data, val){
if(notStored){
if(typeof data !== 'undefined' && data !== null && data !== ''){
expect(data).to.not.be(undefined);
}
} else {
expect(data).to.not.be(undefined);
expect(data).to.not.be('');
if(val){ expect(data).to.eql(val) }
}
};
var alias = root.sessionStorage.getItem('user'); var alias = root.sessionStorage.getItem('user');
expect(alias).to.not.be(undefined); checkValue(alias);
expect(alias).to.not.be(''); checkValue(root.sessionStorage.getItem('remember'));
expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); if(alias){
expect(root.sessionStorage.getItem('remember')).to.not.be(''); checkIndexedDB(alias, 'auth', function(auth){
checkValue(auth);
if(!hasPin){ done();
return done(); });
} } else {
checkIndexedDB(alias, 'auth', function(auth){
expect(auth).to.not.be(undefined);
expect(auth).to.not.be('');
done(); done();
}); }
}; };
}; };
@ -420,18 +436,32 @@ Gun().user && describe('Gun', function(){
} }
}); });
it('without PIN auth session stored to sessionStorage', function(done){ it('without PIN auth session stored', function(done){
user.auth(alias+type, pass+' new').then(checkStorage(done)).catch(done); user.auth(alias+type, pass+' new').then(checkStorage(done)).catch(done);
}); });
it('with PIN auth session stored to sessionStorage', function(done){ it('with PIN auth session stored', function(done){
if(type === 'callback'){ if(type === 'callback'){
user.auth(alias+type, pass+' new', checkStorage(done/*, true*/), {pin: 'PIN'}); user.auth(alias+type, pass+' new', checkStorage(done), {pin: 'PIN'});
} else { } else {
user.auth(alias+type, pass+' new', {pin: 'PIN'}) user.auth(alias+type, pass+' new', {pin: 'PIN'})
.then(checkStorage(done/*, true*/)).catch(done); .then(checkStorage(done)).catch(done);
} }
}); });
it('without PIN and zero validity no auth session storing', function(done){
user.recall(0).then(function(){
user.auth(alias+type, pass+' new')
.then(checkStorage(done, true)).catch(done);
});
});
it('with PIN and zero validity no auth session storing', function(done){
user.recall(0).then(function(){
user.auth(alias+type, pass+' new', {pin: 'PIN'})
.then(checkStorage(done, true)).catch(done);
});
});
}); });
describe('leave', function(){ describe('leave', function(){
@ -621,9 +651,7 @@ Gun().user && describe('Gun', function(){
return !pin ? sessionStorage.setItem('remember', remember) return !pin ? sessionStorage.setItem('remember', remember)
: Gun.SEA.enc(remember, pin).then(function(encauth){ : Gun.SEA.enc(remember, pin).then(function(encauth){
return new Promise(function(resolve){ return new Promise(function(resolve){
Gun.SEA._callonstore_(function(store){ setIndexedDB(usr._.alias, encauth, resolve);
store.put({id: usr._.alias, auth: encauth});
}, resolve);
}); });
}); });
}); });
@ -636,21 +664,21 @@ Gun().user && describe('Gun', function(){
.then(doCheck(done, true)).catch(done); .then(doCheck(done, true)).catch(done);
}; };
if(type === 'callback'){ if(type === 'callback'){
user.recall(doAction, {session: false}); user.recall(doAction);
} else { } else {
user.recall({session: false}).then(doAction).catch(done); user.recall().then(doAction).catch(done);
} }
}); });
it('without PIN auth session stored to sessionStorage', function(done){ it('without PIN auth session stored to IndexedDB', function(done){
var doAction = function(){ var doAction = function(){
user.auth(alias+type, pass+' new').then(doCheck(done)); user.auth(alias+type, pass+' new').then(doCheck(done));
}; };
user.leave().then(function(){ user.leave().then(function(){
if(type === 'callback'){ if(type === 'callback'){
user.recall(doAction, {session: false}); user.recall(doAction);
} else { } else {
user.recall({session: false}).then(doAction).catch(done); user.recall().then(doAction).catch(done);
} }
}).catch(done); }).catch(done);
}); });
@ -666,14 +694,14 @@ Gun().user && describe('Gun', function(){
} }
}); });
it('validity but no PIN stored to sessionStorage', function(done){ it('validity but no PIN stored to IndexedDB using random PIN', function(done){
var doAction = function(){ var doAction = function(){
user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done);
}; };
if(type === 'callback'){ if(type === 'callback'){
user.recall(12 * 60, doAction, {session: false}); user.recall(12 * 60, doAction);
} else { } else {
user.recall(12 * 60, {session: false}).then(doAction) user.recall(12 * 60).then(doAction)
.catch(done); .catch(done);
} }
}); });
@ -687,12 +715,13 @@ Gun().user && describe('Gun', function(){
expect(usr).to.not.be(''); expect(usr).to.not.be('');
expect(usr).to.not.have.key('err'); expect(usr).to.not.have.key('err');
expect(usr).to.have.key('put'); 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'); sUser = root.sessionStorage.getItem('user');
expect(sUser).to.be(alias+type);
sRemember = root.sessionStorage.getItem('remember'); sRemember = root.sessionStorage.getItem('remember');
expect(sRemember).to.not.be(undefined);
expect(sRemember).to.not.be('');
}catch(e){ done(e); return } }catch(e){ done(e); return }
user.leave().then(function(ack){ user.leave().then(function(ack){
try{ try{
@ -705,7 +734,7 @@ Gun().user && describe('Gun', function(){
root.sessionStorage.setItem('user', sUser); root.sessionStorage.setItem('user', sUser);
root.sessionStorage.setItem('remember', sRemember); root.sessionStorage.setItem('remember', sRemember);
user.recall(12 * 60, {session: false}).then(doCheck(done)) user.recall(12 * 60).then(doCheck(done))
.catch(done); .catch(done);
}).catch(done); }).catch(done);
}).catch(done); }).catch(done);
@ -752,19 +781,17 @@ Gun().user && describe('Gun', function(){
root.sessionStorage.setItem('remember', sRemember); root.sessionStorage.setItem('remember', sRemember);
return new Promise(function(resolve){ return new Promise(function(resolve){
Gun.SEA._callonstore_(function(store){ setIndexedDB(sUser, iAuth, resolve);
store.put({id: sUser, auth: iAuth});
}, resolve);
}); });
}).then(function(){ }).then(function(){
user.recall(12 * 60, {session: false}).then(doCheck(done)) user.recall(12 * 60).then(doCheck(done))
.catch(done); .catch(done);
}).catch(done); }).catch(done);
}).catch(done); }).catch(done);
}); });
it('valid IndexedDB session bootstraps using PIN', function(done){ it('valid IndexedDB session bootstraps using PIN', function(done){
user.recall(12 * 60, {session: false}).then(function(){ user.recall(12 * 60).then(function(){
return user.auth(alias+type, pass+' new', {pin: 'PIN'}); return user.auth(alias+type, pass+' new', {pin: 'PIN'});
}).then(doCheck(function(ack){ }).then(doCheck(function(ack){
// Let's save remember props // Let's save remember props
@ -787,9 +814,7 @@ Gun().user && describe('Gun', function(){
checkIndexedDB(sUser, 'auth', function(auth){ checkIndexedDB(sUser, 'auth', function(auth){
try{ expect(auth).to.not.be(iAuth) }catch(e){ done(e) } try{ expect(auth).to.not.be(iAuth) }catch(e){ done(e) }
// Then restore IndexedDB auth data, skip sessionStorage // Then restore IndexedDB auth data, skip sessionStorage
Gun.SEA._callonstore_(function(store){ setIndexedDB(sUser, iAuth, function(){
store.put({id: sUser, auth: iAuth});
}, function(){
root.sessionStorage.setItem('user', sUser); root.sessionStorage.setItem('user', sUser);
resolve(ack); resolve(ack);
}); });
@ -798,7 +823,7 @@ Gun().user && describe('Gun', function(){
}); });
}, true, true)).then(function(){ }, true, true)).then(function(){
// Then try to recall authentication // Then try to recall authentication
return user.recall(12 * 60, {session: false}).then(function(props){ return user.recall(12 * 60).then(function(props){
try{ try{
expect(props).to.not.be(undefined); expect(props).to.not.be(undefined);
expect(props).to.not.be(''); expect(props).to.not.be('');
@ -824,7 +849,7 @@ Gun().user && describe('Gun', function(){
it('valid IndexedDB session fails to bootstrap using wrong PIN', it('valid IndexedDB session fails to bootstrap using wrong PIN',
function(done){ function(done){
user.recall(12 * 60, {session: false}).then(function(){ user.recall(12 * 60).then(function(){
return user.auth(alias+type, pass+' new', {pin: 'PIN'}); return user.auth(alias+type, pass+' new', {pin: 'PIN'});
}).then(doCheck(function(ack){ }).then(doCheck(function(ack){
var sUser = root.sessionStorage.getItem('user'); var sUser = root.sessionStorage.getItem('user');
@ -846,9 +871,7 @@ Gun().user && describe('Gun', function(){
checkIndexedDB(sUser, 'auth', function(auth){ checkIndexedDB(sUser, 'auth', function(auth){
try{ expect(auth).to.not.be(iAuth) }catch(e){ done(e) } try{ expect(auth).to.not.be(iAuth) }catch(e){ done(e) }
// Then restore IndexedDB auth data, skip sessionStorage // Then restore IndexedDB auth data, skip sessionStorage
Gun.SEA._callonstore_(function(store){ setIndexedDB(sUser, iAuth, function(){
store.put({id: sUser, auth: iAuth});
}, function(){
root.sessionStorage.setItem('user', sUser); root.sessionStorage.setItem('user', sUser);
resolve(ack); resolve(ack);
}); });
@ -875,7 +898,7 @@ Gun().user && describe('Gun', function(){
it('expired session fails to bootstrap', function(done){ it('expired session fails to bootstrap', function(done){
var pin = 'PIN'; var pin = 'PIN';
user.recall(60, {session: false}).then(function(){ user.recall(60).then(function(){
return user.auth(alias+type, pass+' new', {pin: pin}); return user.auth(alias+type, pass+' new', {pin: pin});
}).then(doCheck(function(){ }).then(doCheck(function(){
// Storage data OK, let's back up time of auth to exp + 65 seconds // Storage data OK, let's back up time of auth to exp + 65 seconds
@ -886,7 +909,7 @@ Gun().user && describe('Gun', function(){
})).then(function(){ })).then(function(){
// Simulate browser reload // Simulate browser reload
throwOutUser(); throwOutUser();
user.recall(60, {session: false}).then(function(ack){ user.recall(60).then(function(ack){
expect(ack).to.not.be(undefined); expect(ack).to.not.be(undefined);
expect(ack).to.not.be(''); expect(ack).to.not.be('');
expect(ack).to.not.have.keys([ 'pub', 'sea' ]); expect(ack).to.not.have.keys([ 'pub', 'sea' ]);
@ -905,7 +928,7 @@ Gun().user && describe('Gun', function(){
var sUser; var sUser;
var sRemember; var sRemember;
var iAuth; var iAuth;
user.recall(60, {session: false}).then(function(){ user.recall(60).then(function(){
return user.auth(alias+type, pass+' new', {pin: pin}); return user.auth(alias+type, pass+' new', {pin: pin});
}).then(function(usr){ }).then(function(usr){
try{ try{
@ -945,12 +968,10 @@ Gun().user && describe('Gun', function(){
root.sessionStorage.setItem('remember', sRemember); root.sessionStorage.setItem('remember', sRemember);
return new Promise(function(resolve){ return new Promise(function(resolve){
Gun.SEA._callonstore_(function(store){ setIndexedDB(sUser, iAuth, resolve);
store.put({id: sUser, auth: iAuth});
}, resolve);
}); });
}).then(function(){ }).then(function(){
user.recall(60, {session: false}).then(function(props){ user.recall(60).then(function(props){
expect(props).to.not.be(undefined); expect(props).to.not.be(undefined);
expect(props).to.not.be(''); expect(props).to.not.be('');
expect(props).to.have.key('err'); expect(props).to.have.key('err');
@ -974,7 +995,7 @@ Gun().user && describe('Gun', function(){
resolve(ret); resolve(ret);
}); });
}; };
user.recall(60, {session: false, hook: hookFunc}).then(function(){ user.recall(60, {hook: hookFunc}).then(function(){
return user.auth(alias+type, pass, {pin: pin}); return user.auth(alias+type, pass, {pin: pin});
}).then(function(){ }).then(function(){
// Storage data OK, let's back up time of auth 65 minutes // Storage data OK, let's back up time of auth 65 minutes