diff --git a/sea.js b/sea.js index 8dbe9cb3..cafe6c48 100644 --- a/sea.js +++ b/sea.js @@ -52,14 +52,12 @@ var _initial_authsettings = { validity: 12 * 60 * 60, // internally in seconds : 12 hours - session: true, hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise(function(resolve, reject){(resolve(props))}) }; // These are used to persist user's authentication "session" var authsettings = { validity: _initial_authsettings.validity, - session: _initial_authsettings.session, hook: _initial_authsettings.hook }; @@ -178,12 +176,12 @@ return function(props){ return new Promise(function(resolve, reject){ 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; delete props.remember; // Not stored if present - var remember = (pin && {alias: props.alias, pin: pin}) || props; - var persist = !authsettings.session && pin && props; + var remember = {alias: props.alias, pin: pin}; + var persist = props; return SEA.write(JSON.stringify(remember), priv).then(function(signed){ sessionStorage.setItem('user', props.alias); @@ -209,6 +207,7 @@ }).then(function(){ resolve(props) }) .catch(function(e){ reject({err: 'Session persisting failed!'}) }); } + // TODO: remove IndexedDB when using random PIN return new Promise(function(resolve){ SEA._callonstore_(function(store) { var act = store.clear(); // Wipes whole IndexedDB @@ -226,14 +225,13 @@ // This internal func persists User authentication if so configured function authpersist(user,proof,opts){ // opts = { pin: 'string' } - // authsettings.session = true // disables PIN method - // TODO: how this works: + // no opts.pin then uses random PIN + // How this works: // called when app bootstraps, with wanted options // IF authsettings.validity === 0 THEN no remember-me, ever - // IF authsettings.session === true THEN no window.indexedDB in use; nor PIN - // ELSE if no PIN then window.sessionStorage - var pin = Gun.obj.has(opts, 'pin') && opts.pin - && new Buffer(opts.pin, 'utf8').toString('base64'); + // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB + var pin = (Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10); + pin = new Buffer(pin, 'utf8').toString('base64'); if(proof && user && user.alias && authsettings.validity){ var args = {alias: user.alias}; @@ -253,7 +251,7 @@ // This internal func recalls persisted User authentication if so configured function authrecall(root,authprops){ 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 alias = Gun.obj.has(authprops, 'alias') && authprops.alias || sessionStorage.getItem('user'); @@ -375,7 +373,7 @@ }; }, function(){ // And return proof if for matching alias reject({ - err: (gotRemember && 'Missing PIN and alias!') + err: (gotRemember && authsettings.validity && 'Missing PIN and alias!') || 'No authentication session found!'}); }); }); @@ -498,8 +496,6 @@ cb = typeof cb === 'function' && cb; 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')){ return authrecall(root, {alias: alias, pin: opts.pin}).then(function(props){ resolve(props); @@ -624,17 +620,14 @@ var doIt = function(resolve, reject){ // 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 - // TODO: how this works: + // How this works: // called when app bootstraps, with wanted options - // IF validity === 0 THEN no remember-me, ever - // IF opt.session === true THEN no window.indexedDB in use; nor PIN + // IF authsettings.validity === 0 THEN no remember-me, ever + // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB authsettings.validity = typeof validity !== 'undefined' ? 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') ? opts.hook : _initial_authsettings.hook; // All is good. Should we do something more with actual recalled data? diff --git a/test/sea.js b/test/sea.js index 29eab3b1..7a007f3a 100644 --- a/test/sea.js +++ b/test/sea.js @@ -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(){ console.log('TODO: SEA! THIS IS AN EARLY ALPHA!!!'); var alias = 'dude'; @@ -300,22 +308,30 @@ Gun().user && describe('Gun', function(){ }); describe('auth', function(){ - var checkStorage = function(done, hasPin){ + var checkStorage = function(done, notStored){ 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'); - expect(alias).to.not.be(undefined); - expect(alias).to.not.be(''); - expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); - expect(root.sessionStorage.getItem('remember')).to.not.be(''); - - if(!hasPin){ - return done(); - } - checkIndexedDB(alias, 'auth', function(auth){ - expect(auth).to.not.be(undefined); - expect(auth).to.not.be(''); + checkValue(alias); + checkValue(root.sessionStorage.getItem('remember')); + if(alias){ + checkIndexedDB(alias, 'auth', function(auth){ + checkValue(auth); + done(); + }); + } else { 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); }); - it('with PIN auth session stored to sessionStorage', function(done){ + it('with PIN auth session stored', function(done){ 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 { 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(){ @@ -621,9 +651,7 @@ Gun().user && describe('Gun', function(){ return !pin ? sessionStorage.setItem('remember', remember) : Gun.SEA.enc(remember, pin).then(function(encauth){ return new Promise(function(resolve){ - Gun.SEA._callonstore_(function(store){ - store.put({id: usr._.alias, auth: encauth}); - }, resolve); + setIndexedDB(usr._.alias, encauth, resolve); }); }); }); @@ -636,21 +664,21 @@ Gun().user && describe('Gun', function(){ .then(doCheck(done, true)).catch(done); }; if(type === 'callback'){ - user.recall(doAction, {session: false}); + user.recall(doAction); } 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(){ user.auth(alias+type, pass+' new').then(doCheck(done)); }; user.leave().then(function(){ if(type === 'callback'){ - user.recall(doAction, {session: false}); + user.recall(doAction); } else { - user.recall({session: false}).then(doAction).catch(done); + user.recall().then(doAction).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(){ user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); }; if(type === 'callback'){ - user.recall(12 * 60, doAction, {session: false}); + user.recall(12 * 60, doAction); } else { - user.recall(12 * 60, {session: false}).then(doAction) + user.recall(12 * 60).then(doAction) .catch(done); } }); @@ -687,12 +715,13 @@ Gun().user && describe('Gun', function(){ 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'); + expect(sUser).to.be(alias+type); + sRemember = root.sessionStorage.getItem('remember'); + expect(sRemember).to.not.be(undefined); + expect(sRemember).to.not.be(''); }catch(e){ done(e); return } user.leave().then(function(ack){ try{ @@ -705,7 +734,7 @@ Gun().user && describe('Gun', function(){ root.sessionStorage.setItem('user', sUser); 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); @@ -752,19 +781,17 @@ Gun().user && describe('Gun', function(){ root.sessionStorage.setItem('remember', sRemember); return new Promise(function(resolve){ - Gun.SEA._callonstore_(function(store){ - store.put({id: sUser, auth: iAuth}); - }, resolve); + setIndexedDB(sUser, iAuth, resolve); }); }).then(function(){ - user.recall(12 * 60, {session: false}).then(doCheck(done)) + user.recall(12 * 60).then(doCheck(done)) .catch(done); }).catch(done); }).catch(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'}); }).then(doCheck(function(ack){ // Let's save remember props @@ -787,9 +814,7 @@ Gun().user && describe('Gun', function(){ checkIndexedDB(sUser, 'auth', function(auth){ try{ expect(auth).to.not.be(iAuth) }catch(e){ done(e) } // Then restore IndexedDB auth data, skip sessionStorage - Gun.SEA._callonstore_(function(store){ - store.put({id: sUser, auth: iAuth}); - }, function(){ + setIndexedDB(sUser, iAuth, function(){ root.sessionStorage.setItem('user', sUser); resolve(ack); }); @@ -798,7 +823,7 @@ Gun().user && describe('Gun', function(){ }); }, true, true)).then(function(){ // Then try to recall authentication - return user.recall(12 * 60, {session: false}).then(function(props){ + return user.recall(12 * 60).then(function(props){ try{ expect(props).to.not.be(undefined); expect(props).to.not.be(''); @@ -824,7 +849,7 @@ Gun().user && describe('Gun', function(){ it('valid IndexedDB session fails to bootstrap using wrong 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'}); }).then(doCheck(function(ack){ var sUser = root.sessionStorage.getItem('user'); @@ -846,9 +871,7 @@ Gun().user && describe('Gun', function(){ checkIndexedDB(sUser, 'auth', function(auth){ try{ expect(auth).to.not.be(iAuth) }catch(e){ done(e) } // Then restore IndexedDB auth data, skip sessionStorage - Gun.SEA._callonstore_(function(store){ - store.put({id: sUser, auth: iAuth}); - }, function(){ + setIndexedDB(sUser, iAuth, function(){ root.sessionStorage.setItem('user', sUser); resolve(ack); }); @@ -875,7 +898,7 @@ Gun().user && describe('Gun', function(){ it('expired session fails to bootstrap', function(done){ var pin = 'PIN'; - user.recall(60, {session: false}).then(function(){ + user.recall(60).then(function(){ return user.auth(alias+type, pass+' new', {pin: pin}); }).then(doCheck(function(){ // 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(){ // Simulate browser reload 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(''); expect(ack).to.not.have.keys([ 'pub', 'sea' ]); @@ -905,7 +928,7 @@ Gun().user && describe('Gun', function(){ var sUser; var sRemember; var iAuth; - user.recall(60, {session: false}).then(function(){ + user.recall(60).then(function(){ return user.auth(alias+type, pass+' new', {pin: pin}); }).then(function(usr){ try{ @@ -945,12 +968,10 @@ Gun().user && describe('Gun', function(){ root.sessionStorage.setItem('remember', sRemember); return new Promise(function(resolve){ - Gun.SEA._callonstore_(function(store){ - store.put({id: sUser, auth: iAuth}); - }, resolve); + setIndexedDB(sUser, iAuth, resolve); }); }).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(''); expect(props).to.have.key('err'); @@ -974,7 +995,7 @@ Gun().user && describe('Gun', function(){ 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}); }).then(function(){ // Storage data OK, let's back up time of auth 65 minutes