mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00
All test cases completed & some bugfixes & 'remember-me' recovery with PIN now supported
This commit is contained in:
parent
0d920931a5
commit
b0b87a14a0
278
sea.js
278
sea.js
@ -14,7 +14,7 @@
|
||||
|
||||
var crypto, TextEncoder, TextDecoder, localStorage, sessionStorage;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
if(typeof window !== 'undefined'){
|
||||
crypto = window.crypto;
|
||||
TextEncoder = window.TextEncoder;
|
||||
TextDecoder = window.TextDecoder;
|
||||
@ -46,12 +46,17 @@
|
||||
enc: 'aes-256-cbc'
|
||||
};
|
||||
|
||||
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: 60 * 60 * 12, // 12 hours
|
||||
session: true,
|
||||
hook: function(props) { return props } // { iat, exp, alias, remember }
|
||||
// or return new Promise(function(resolve, reject){(resolve(props))})
|
||||
validity: _initial_authsettings.validity,
|
||||
session: _initial_authsettings.session,
|
||||
hook: _initial_authsettings.hook
|
||||
};
|
||||
|
||||
// let's extend the gun chain with a `user` function.
|
||||
@ -84,7 +89,7 @@
|
||||
// if no user, don't do anything.
|
||||
var err = 'No user!';
|
||||
Gun.log(err);
|
||||
return reject({err: err});
|
||||
return reject(err);
|
||||
}
|
||||
// then figuring out all possible candidates having matching username
|
||||
var aliases = [];
|
||||
@ -99,7 +104,7 @@
|
||||
});
|
||||
});
|
||||
return aliases.length && resolve(aliases)
|
||||
|| reject({err: 'Public key does not exist!'})
|
||||
|| reject('Public key does not exist!')
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -171,7 +176,7 @@
|
||||
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(proof && Gun.obj.has(props, 'iat')){
|
||||
props.proof = proof;
|
||||
delete props.remember; // Not stored if present
|
||||
|
||||
@ -181,18 +186,16 @@
|
||||
return SEA.write(JSON.stringify(remember), priv).then(function(signed){
|
||||
sessionStorage.setItem('user', props.alias);
|
||||
sessionStorage.setItem('remember', signed);
|
||||
if (!protected) {
|
||||
if(!protected){
|
||||
localStorage.removeItem('remember');
|
||||
}
|
||||
return !protected || SEA.en(protected, pin).then(function(encrypted){
|
||||
return encrypted && SEA.write(encrypted, priv)
|
||||
.then(function(encsig){
|
||||
return encrypted && SEA.write(encrypted, priv).then(function(encsig){
|
||||
localStorage.setItem('remember', encsig);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).then(function(){
|
||||
resolve(props);
|
||||
}).catch(function(e){ reject({err: 'Session persisting failed!'}) });
|
||||
}).then(function(){ resolve(props); })
|
||||
.catch(function(e){ reject({err: 'Session persisting failed!'}) });
|
||||
} else {
|
||||
localStorage.removeItem('remember');
|
||||
sessionStorage.removeItem('user');
|
||||
@ -214,39 +217,66 @@
|
||||
// ELSE if no PIN then window.sessionStorage
|
||||
var pin = Gun.obj.has(opts, 'pin') && opts.pin
|
||||
&& new Buffer(opts.pin, 'utf8').toString('base64');
|
||||
var args = { alias: user.alias };
|
||||
|
||||
if(proof && authsettings.validity){
|
||||
if(proof && user && user.alias && authsettings.validity){
|
||||
var args = { alias: user.alias };
|
||||
args.iat = Math.ceil(Date.now() / 1000); // seconds
|
||||
args.exp = authsettings.validity * 60; // seconds
|
||||
if (Gun.obj.has(opts, 'pin')){
|
||||
args.exp = authsettings.validity; // seconds
|
||||
if(Gun.obj.has(opts, 'pin')){
|
||||
args.remember = true; // for hook - not stored
|
||||
}
|
||||
var props = authsettings.hook(args);
|
||||
if(props instanceof Promise){
|
||||
return props.then(updatestorage(proof, user.sea, pin));
|
||||
} else {
|
||||
return updatestorage(proof, user.sea, pin)(props);
|
||||
}
|
||||
} else {
|
||||
return updatestorage()(args);
|
||||
return updatestorage(proof, user.sea, pin)(props);
|
||||
}
|
||||
return updatestorage()({alias: 'delete'});
|
||||
}
|
||||
|
||||
// This internal func recalls persisted User authentication if so configured
|
||||
function authrecall(root){
|
||||
function authrecall(root,authprops){
|
||||
return new Promise(function(resolve, reject){
|
||||
var remember = sessionStorage.getItem('remember');
|
||||
var alias = sessionStorage.getItem('user');
|
||||
var err = 'Not authenticated';
|
||||
var pin;
|
||||
var remember = authprops || sessionStorage.getItem('remember');
|
||||
var alias = Gun.obj.has(authprops, 'alias') && authprops.alias
|
||||
|| sessionStorage.getItem('user');
|
||||
var pin = Gun.obj.has(authprops, 'pin')
|
||||
&& new Buffer(authprops.pin, 'utf8').toString('base64');
|
||||
|
||||
var checkRememberData = function(decr){
|
||||
if(Gun.obj.has(decr, 'proof')
|
||||
&& Gun.obj.has(decr, 'alias') && decr.alias === alias){
|
||||
var proof = decr.proof;
|
||||
var iat = decr.iat; // No way hook to update this
|
||||
delete decr.proof; // We're not gonna give proof to hook!
|
||||
var checkNotExpired = function(args){
|
||||
if(Math.floor(Date.now() / 1000) < (iat + args.exp)){
|
||||
args.iat = iat;
|
||||
args.proof = proof;
|
||||
return args;
|
||||
} else {
|
||||
Gun.log('Authentication expired!') }
|
||||
};
|
||||
var hooked = authsettings.hook(decr);
|
||||
return ((hooked instanceof Promise)
|
||||
&& hooked.then(checkNotExpired)) || checkNotExpired(hooked);
|
||||
}
|
||||
};
|
||||
var readAndDecrypt = function(data, pub, key){
|
||||
return SEA.read(data, pub).then(function(encrypted){
|
||||
return SEA.de(encrypted, key);
|
||||
}).then(function(decrypted){
|
||||
try{ return decrypted.slice ? JSON.parse(decrypted) : decrypted }catch(e){}
|
||||
return decrypted;
|
||||
});
|
||||
};
|
||||
|
||||
// Already authenticated?
|
||||
if(Gun.obj.has(root._.user._, 'pub')){
|
||||
return resolve(root._.user._.pub);
|
||||
if(Gun.obj.has(root._.user._, 'pub') && Gun.obj.has(root._.user._, 'sea')){
|
||||
return resolve(root._.user._);
|
||||
}
|
||||
// No, got alias?
|
||||
if (alias && remember){
|
||||
if(alias && remember){
|
||||
return querygunaliases(alias, root).then(function(aliases){
|
||||
return new Promise(function(resolve, reject){
|
||||
// then attempt to log into each one until we find ours!
|
||||
@ -257,60 +287,31 @@
|
||||
if(!at.put){
|
||||
return !remaining && reject({err: 'Public key does not exist!'})
|
||||
}
|
||||
// got pub, time to unwrap Storage data...
|
||||
return SEA.read(remember, pub, true).then(function(props){
|
||||
props = !props.slice ? props : JSON.parse(props);
|
||||
var checkProps = function(decr){
|
||||
return new Promise(function(resolve){
|
||||
if(Gun.obj.has(decr, 'proof')
|
||||
&& Gun.obj.has(decr, 'alias') && decr.alias === alias){
|
||||
var proof = decr.proof;
|
||||
var iat = decr.iat; // No way hook to update this
|
||||
delete decr.proof; // We're not gonna give proof to hook!
|
||||
var doIt = function(args){
|
||||
if(Math.floor(Date.now() / 1000) < (iat + args.exp)){
|
||||
args.iat = iat;
|
||||
args.proof = proof;
|
||||
return args;
|
||||
} else { Gun.log('Authentication expired!') }
|
||||
};
|
||||
var hooked = authsettings.hook(decr);
|
||||
return resolve(((hooked instanceof Promise)
|
||||
&& hooked.then(doIt))
|
||||
|| doIt(decr));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
// Got PIN ?
|
||||
if(Gun.obj.has(props, 'pin')){
|
||||
pin = props.pin;
|
||||
// Yes! We can get localStorage secret if signature is ok
|
||||
return SEA.read(localStorage.getItem('remember'), pub)
|
||||
.then(function(encrypted){
|
||||
// And decrypt it
|
||||
return SEA.de(encrypted, pin);
|
||||
}).then(function(decr){
|
||||
decr = !decr.slice ? decr : JSON.parse(decr);
|
||||
// And return proof if for matching alias
|
||||
return checkProps(decr);
|
||||
});
|
||||
// got pub, time to try auth with alias & PIN...
|
||||
return ((pin && Promise.resolve({pin: pin, alias: alias}))
|
||||
// or just unwrap Storage data...
|
||||
|| SEA.read(remember, pub, true)).then(function(props){
|
||||
try{ props = props.slice ? JSON.parse(props) : props }catch(e){}
|
||||
if(Gun.obj.has(props, 'pin') && Gun.obj.has(props, 'alias')
|
||||
&& props.alias === alias){
|
||||
pin = props.pin; // Got PIN so get localStorage secret if signature is ok
|
||||
return readAndDecrypt(localStorage.getItem('remember'), pub, pin)
|
||||
.then(checkRememberData); // And return proof if for matching alias
|
||||
}
|
||||
// No PIN, let's try short-term proof if for matching alias
|
||||
return checkProps(props);
|
||||
return checkRememberData(props);
|
||||
}).then(function(args){
|
||||
var proof = args && args.proof;
|
||||
if (!proof){
|
||||
return updatestorage()(args).then(function(){
|
||||
reject({err: 'No secret found!'});
|
||||
if(!proof){
|
||||
return (!args && reject({err: 'No valid authentication session found!'}))
|
||||
|| updatestorage()(args).then(function(){
|
||||
reject({err: 'Expired session!'});
|
||||
}).catch(function(){
|
||||
reject({err: 'No secret found!'});
|
||||
reject({err: 'Expired session!'});
|
||||
});
|
||||
}
|
||||
// the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
|
||||
return SEA.read(at.put.auth, pub).then(function(auth){
|
||||
return SEA.de(auth, proof)
|
||||
.catch(function(e){ reject({err: 'Failed to decrypt secret!'}) });
|
||||
return readAndDecrypt(at.put.auth, pub, proof).catch(function(e){
|
||||
return !remaining && reject({err: 'Failed to decrypt private key!'});
|
||||
}).then(function(priv){
|
||||
// now we have AES decrypted the private key,
|
||||
// if we were successful, then that means we're logged in!
|
||||
@ -333,7 +334,9 @@
|
||||
reject({err: 'No authentication session found!'});
|
||||
});
|
||||
}
|
||||
reject({err: 'No authentication session found!'});
|
||||
reject({
|
||||
err: (localStorage.getItem('remember') && 'Missing PIN and alias!')
|
||||
|| 'No authentication session found!'});
|
||||
});
|
||||
}
|
||||
|
||||
@ -341,10 +344,18 @@
|
||||
function authleave(root, alias){
|
||||
return function(resolve, reject){
|
||||
// remove persisted authentication
|
||||
authpersist((alias && { alias: alias }) || root._.user._).then(function(){
|
||||
root._.user = root.chain();
|
||||
user = root._.user;
|
||||
alias = alias || (user._ && user._.alias);
|
||||
var doIt = function(){
|
||||
// TODO: is this correct way to 'logout' user from Gun.User ?
|
||||
[ 'alias', 'sea', 'pub' ].forEach(function(key){
|
||||
delete user._[key];
|
||||
});
|
||||
user._.is = user.is = {};
|
||||
// Let's use default
|
||||
resolve({ok: 0});
|
||||
});
|
||||
};
|
||||
authpersist(alias && { alias: alias }).then(doIt).catch(doIt);
|
||||
};
|
||||
}
|
||||
|
||||
@ -352,8 +363,7 @@
|
||||
function nodehash(m){
|
||||
try{
|
||||
m = m.slice ? m : JSON.stringify(m);
|
||||
var ret = nodeCrypto.createHash(nHash).update(m, 'utf8').digest();
|
||||
return ret;
|
||||
return nodeCrypto.createHash(nHash).update(m, 'utf8').digest();
|
||||
}catch(e){ return m }
|
||||
}
|
||||
|
||||
@ -413,6 +423,16 @@
|
||||
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);
|
||||
}).catch(function(e){
|
||||
reject({err: 'Auth attempt failed! Reason: No session data for alias & PIN'});
|
||||
});
|
||||
}
|
||||
|
||||
authenticate(alias, pass, root).then(function(key){
|
||||
// we're logged in!
|
||||
var pin = Gun.obj.has(opts, 'pin') && { pin: opts.pin };
|
||||
@ -439,7 +459,6 @@
|
||||
// awesome, now we can update the user using public key ID.
|
||||
// root.get(tmp).put(null);
|
||||
root.get(tmp).put(user);
|
||||
|
||||
// then we're done
|
||||
finalizelogin(alias, key, root, pin).then(resolve).catch(function(e){
|
||||
Gun.log('Failed to finalize login with new password!');
|
||||
@ -447,17 +466,17 @@
|
||||
});
|
||||
}).catch(function(e){
|
||||
Gun.log('Failed encrypt private key using new password!');
|
||||
reject({err: 'Password set attempt failed! Reason: '+(e && e.err) || e || ''});
|
||||
reject({err: 'Password set attempt failed! Reason: ' + (e && e.err) || e || ''});
|
||||
});
|
||||
} else {
|
||||
finalizelogin(alias, key, root, pin).then(resolve).catch(function(e){
|
||||
Gun.log('Failed to finalize login!');
|
||||
reject({err: 'Finalizing login failed! Reason: '+(e && e.err) || e || ''});
|
||||
reject({err: 'Finalizing login failed! Reason: ' + (e && e.err) || e || ''});
|
||||
});
|
||||
}
|
||||
}).catch(function(e){
|
||||
Gun.log('Failed to sign in!');
|
||||
reject({err: 'Auth attempt failed! Reason: '+(e && e.err) || e || ''});
|
||||
reject({err: 'Auth attempt failed! Reason: ' + (e && e.err) || e || ''});
|
||||
});
|
||||
};
|
||||
if(cb){doIt(cb, cb)} else { return new Promise(doIt) }
|
||||
@ -473,37 +492,46 @@
|
||||
authenticate(alias, pass, root).then(function(key){
|
||||
new Promise(authleave(root, alias)).catch(function(){})
|
||||
.then(function(){
|
||||
// Delete user data
|
||||
root.get('pub/'+key.pub).put(null);
|
||||
root._.user = root.chain();
|
||||
// Wipe user data from memory
|
||||
user = root._.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 = {};
|
||||
resolve({ok: 0});
|
||||
}).catch(function(e){
|
||||
Gun.log('User.delete failed! Error:', e);
|
||||
reject({err: 'Delete attempt failed! Reason:'+(e && e.err) || e || ''});
|
||||
reject({err: 'Delete attempt failed! Reason: ' + (e && e.err) || e || ''});
|
||||
});
|
||||
}).catch(function(e){
|
||||
Gun.log('User.delete authentication failed! Error:', e);
|
||||
reject({err: 'Delete attempt failed! Reason:'+(e && e.err) || e || ''});
|
||||
reject({err: 'Delete attempt failed! Reason: ' + (e && e.err) || e || ''});
|
||||
});
|
||||
};
|
||||
if(cb){doIt(cb, cb)} else { return new Promise(doIt) }
|
||||
};
|
||||
// If authentication is to be remembered over reloads or browser closing,
|
||||
// set validity time in seconds.
|
||||
User.recall = function(validity,cb,opts){
|
||||
// set validity time in minutes.
|
||||
User.recall = function(v,cb,o){
|
||||
var root = this.back(-1);
|
||||
if(!opts){
|
||||
if(typeof cb !== 'function' && !Gun.val.is(cb)){
|
||||
opts = cb;
|
||||
cb = undefined;
|
||||
}
|
||||
var validity, callback, opts;
|
||||
if(!o && typeof cb !== 'function' && !Gun.val.is(cb)){
|
||||
opts = cb;
|
||||
} else {
|
||||
callback = cb;
|
||||
}
|
||||
if(!cb){
|
||||
if(typeof validity === 'function'){
|
||||
cb = validity;
|
||||
validity = undefined;
|
||||
} else if(!Gun.val.is(validity)){
|
||||
opts = validity;
|
||||
validity = undefined;
|
||||
if(!callback){
|
||||
if(typeof v === 'function'){
|
||||
callback = v;
|
||||
validity = _initial_authsettings.validity;
|
||||
} else if(!Gun.val.is(v)){
|
||||
opts = v;
|
||||
validity = _initial_authsettings.validity;
|
||||
} else {
|
||||
validity = v * 60; // minutes to seconds
|
||||
}
|
||||
}
|
||||
var doIt = function(resolve, reject){
|
||||
@ -514,25 +542,21 @@
|
||||
// called when app bootstraps, with wanted options
|
||||
// IF validity === 0 THEN no remember-me, ever
|
||||
// IF opt.session === true THEN no window.localStorage in use; nor PIN
|
||||
if(Gun.val.is(validity)){
|
||||
authsettings.validity = validity;
|
||||
}
|
||||
authsettings.validity = typeof validity !== 'undefined' ? validity
|
||||
: _initial_authsettings.validity;
|
||||
if(Gun.obj.has(opts, 'session')){
|
||||
authsettings.session = opts.session;
|
||||
}
|
||||
if(Gun.obj.has(opts, 'hook')){
|
||||
authsettings.hook = opt.hook;
|
||||
}
|
||||
authrecall(root).then(function(props){
|
||||
// All is good. Should we do something more with actual recalled data?
|
||||
resolve(root._.user._)
|
||||
}).catch(function(e){
|
||||
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?
|
||||
authrecall(root).then(resolve).catch(function(e){
|
||||
var err = 'No session!';
|
||||
Gun.log(err);
|
||||
resolve({ err: err });
|
||||
resolve({ err: (e && e.err) || err });
|
||||
});
|
||||
};
|
||||
if(cb){doIt(cb, cb)} else { return new Promise(doIt) }
|
||||
if(callback){doIt(callback, callback)} else { return new Promise(doIt) }
|
||||
};
|
||||
User.alive = function(cb){
|
||||
var root = this.back(-1);
|
||||
@ -643,7 +667,7 @@
|
||||
if(val === tmp){ return } // the account MUST have a `pub` property that equals the ID of the public key.
|
||||
return no = true; // if not, reject the update.
|
||||
}
|
||||
if(at.user){ // if we are logged in
|
||||
if(at.user && at.user._){ // if we are logged in
|
||||
if(tmp === at.user._.pub){ // as this user
|
||||
SEA.write(val, at.user._.sea).then(function(data){
|
||||
val = node[key] = data; // then sign our updates as we output them.
|
||||
@ -789,8 +813,7 @@
|
||||
} else { // NodeJS doesn't support crypto.subtle.importKey properly
|
||||
try{
|
||||
var cipher = nodeCrypto.createCipheriv(aes.enc, key, iv);
|
||||
r.ct = cipher.update(m, 'utf8', 'base64');
|
||||
r.ct += cipher.final('base64');
|
||||
r.ct = cipher.update(m, 'utf8', 'base64') + cipher.final('base64');
|
||||
}catch(e){ Gun.log(e); return reject(e) }
|
||||
resolve(JSON.stringify(r));
|
||||
}
|
||||
@ -799,23 +822,24 @@
|
||||
};
|
||||
SEA.de = function(m,p,cb){
|
||||
var doIt = function(resolve, reject){
|
||||
var d = !m.slice ? m : JSON.parse(m);
|
||||
var key = makeKey(p, new Buffer(d.s, 'hex'));
|
||||
var iv = new Buffer(d.iv, 'hex');
|
||||
try{ m = m.slice ? JSON.parse(m) : m }catch(e){}
|
||||
var key = makeKey(p, new Buffer(m.s, 'hex'));
|
||||
var iv = new Buffer(m.iv, 'hex');
|
||||
if(typeof window !== 'undefined'){ // Browser doesn't run createDecipheriv
|
||||
crypto.subtle.importKey('raw', key, 'AES-CBC', false, ['decrypt'])
|
||||
.then(function(aesKey){
|
||||
crypto.subtle.decrypt({
|
||||
name: 'AES-CBC', iv: iv
|
||||
}, aesKey, new Buffer(d.ct, 'base64')).then(function(ct){
|
||||
}, aesKey, new Buffer(m.ct, 'base64')).then(function(ct){
|
||||
var ctUtf8 = new TextDecoder('utf8').decode(ct);
|
||||
return !ctUtf8.slice ? ctUtf8 : JSON.parse(ctUtf8);
|
||||
try{ return ctUtf8.slice ? JSON.parse(ctUtf8) : ctUtf8;
|
||||
}catch(e){ return ctUtf8 }
|
||||
}).then(resolve).catch(function(e){Gun.log(e); reject(e)});
|
||||
}).catch(function(e){Gun.log(e); reject(e)});
|
||||
} else { // NodeJS doesn't support crypto.subtle.importKey properly
|
||||
try{
|
||||
var decipher = nodeCrypto.createDecipheriv(aes.enc, key, iv);
|
||||
r = decipher.update(d.ct, 'base64', 'utf8') + decipher.final('utf8');
|
||||
r = decipher.update(m.ct, 'base64', 'utf8') + decipher.final('utf8');
|
||||
}catch(e){ Gun.log(e); return reject(e) }
|
||||
resolve(r);
|
||||
}
|
||||
@ -829,7 +853,7 @@
|
||||
if(mm.slice){
|
||||
// Needs to remove previous signature envelope
|
||||
while('SEA[' === m.slice(0,4)){
|
||||
try{m = JSON.parse(m.slice(3))[0];
|
||||
try{ m = JSON.parse(m.slice(3))[0];
|
||||
}catch(e){ m = mm; break }
|
||||
}
|
||||
}
|
||||
@ -845,7 +869,7 @@
|
||||
if(!m){ return resolve() }
|
||||
if(!m.slice || 'SEA[' !== m.slice(0,4)){ return resolve(m) }
|
||||
m = m.slice(3);
|
||||
try{m = !m.slice ? m : JSON.parse(m);
|
||||
try{ m = m.slice ? JSON.parse(m) : m;
|
||||
}catch(e){ return reject(e) }
|
||||
m = m || '';
|
||||
SEA.verify(m[0], p, m[1]).then(function(ok){
|
||||
|
@ -186,7 +186,7 @@ describe('Performance', function(){ return; // performance tests
|
||||
|
||||
describe('Gun', function(){
|
||||
var t = {};
|
||||
describe('Utility', function(){
|
||||
!Gun.SEA && describe('Utility', function(){
|
||||
var u;
|
||||
/* // causes logger to no longer log.
|
||||
it('verbose console.log debugging', function(done) {
|
||||
|
257
test/sea.js
257
test/sea.js
@ -212,18 +212,39 @@ Gun().user && describe('Gun', function(){
|
||||
var user = gun.user();
|
||||
Gun.log.off = true; // Supress all console logging
|
||||
|
||||
// Simulate browser reload
|
||||
gun.back(-1)._.user = gun.back(-1).chain();
|
||||
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']);
|
||||
expect(ack).to.have.keys([ 'ok', 'pub' ]);
|
||||
}catch(e){ done(e); return };
|
||||
done();
|
||||
};
|
||||
@ -234,6 +255,7 @@ Gun().user && describe('Gun', function(){
|
||||
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){
|
||||
@ -243,6 +265,7 @@ Gun().user && describe('Gun', function(){
|
||||
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();
|
||||
};
|
||||
@ -297,6 +320,8 @@ Gun().user && describe('Gun', function(){
|
||||
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();
|
||||
};
|
||||
@ -317,6 +342,7 @@ Gun().user && describe('Gun', function(){
|
||||
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();
|
||||
};
|
||||
@ -355,6 +381,8 @@ Gun().user && describe('Gun', function(){
|
||||
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();
|
||||
};
|
||||
@ -391,7 +419,7 @@ Gun().user && describe('Gun', function(){
|
||||
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']);
|
||||
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
|
||||
}catch(e){ done(e); return };
|
||||
done();
|
||||
};
|
||||
@ -399,7 +427,7 @@ Gun().user && describe('Gun', function(){
|
||||
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']);
|
||||
expect(ack).to.have.keys([ 'ok', 'pub' ]);
|
||||
user.auth(usr, pass).then(function(usr){
|
||||
try{
|
||||
expect(usr).to.not.be(undefined);
|
||||
@ -427,7 +455,7 @@ Gun().user && describe('Gun', function(){
|
||||
}catch(e){ done(e); return };
|
||||
done();
|
||||
};
|
||||
expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']);
|
||||
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
|
||||
if(type === 'callback'){
|
||||
user.leave(check);
|
||||
} else {
|
||||
@ -443,7 +471,7 @@ Gun().user && describe('Gun', function(){
|
||||
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']);
|
||||
expect(ack).to.have.keys([ 'ok', 'pub' ]);
|
||||
return ack;
|
||||
});
|
||||
};
|
||||
@ -454,7 +482,7 @@ Gun().user && describe('Gun', function(){
|
||||
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']);
|
||||
expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]);
|
||||
}catch(e){ done(e); return };
|
||||
done();
|
||||
};
|
||||
@ -497,6 +525,7 @@ Gun().user && describe('Gun', function(){
|
||||
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();
|
||||
};
|
||||
@ -511,29 +540,51 @@ Gun().user && describe('Gun', function(){
|
||||
});
|
||||
|
||||
describe('recall', function(){
|
||||
var doCheck = 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('');
|
||||
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){
|
||||
expect(root.localStorage.getItem('remember')).to.not.be(undefined);
|
||||
expect(root.localStorage.getItem('remember')).to.not.be('');
|
||||
var lRemember = root.localStorage.getItem('remember');
|
||||
expect(lRemember).to.not.be(undefined);
|
||||
expect(lRemember).to.not.be('');
|
||||
}
|
||||
return done();
|
||||
// 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){
|
||||
props = manipulate(JSON.parse(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){
|
||||
// remember = JSON.stringify(remember);
|
||||
return hasPin ? sessionStorage.setItem('remember', remember)
|
||||
: sessionStorage.setItem('remember', remember);
|
||||
});
|
||||
@ -581,9 +632,9 @@ Gun().user && describe('Gun', function(){
|
||||
user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done);
|
||||
};
|
||||
if(type === 'callback'){
|
||||
user.recall(12 * 60 * 60, doAction, {session: false});
|
||||
user.recall(12 * 60, doAction, {session: false});
|
||||
} else {
|
||||
user.recall(12 * 60 * 60, {session: false}).then(doAction)
|
||||
user.recall(12 * 60, {session: false}).then(doAction)
|
||||
.catch(done);
|
||||
}
|
||||
});
|
||||
@ -607,7 +658,7 @@ Gun().user && describe('Gun', function(){
|
||||
user.leave().then(function(ack){
|
||||
try{
|
||||
expect(ack).to.have.key('ok');
|
||||
expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']);
|
||||
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 };
|
||||
@ -615,7 +666,7 @@ Gun().user && describe('Gun', function(){
|
||||
root.sessionStorage.setItem('user', sUser);
|
||||
root.sessionStorage.setItem('remember', sRemember);
|
||||
|
||||
user.recall(12 * 60 * 60, {session: false}).then(doCheck(done))
|
||||
user.recall(12 * 60, {session: false}).then(doCheck(done))
|
||||
.catch(done);
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
@ -645,7 +696,7 @@ Gun().user && describe('Gun', function(){
|
||||
user.leave().then(function(ack){
|
||||
try{
|
||||
expect(ack).to.have.key('ok');
|
||||
expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']);
|
||||
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);
|
||||
@ -655,34 +706,115 @@ Gun().user && describe('Gun', function(){
|
||||
root.sessionStorage.setItem('remember', sRemember);
|
||||
root.localStorage.setItem('remember', lRemember);
|
||||
|
||||
user.recall(12 * 60 * 60, {session: false}).then(doCheck(done))
|
||||
user.recall(12 * 60, {session: false}).then(doCheck(done))
|
||||
.catch(done);
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it.skip('invalid sessionStorage session');
|
||||
it.skip('valid localStorage data but not in sessionStorage');
|
||||
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('expired session', function(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 65 minutes
|
||||
// Storage data OK, let's back up time of auth to exp + 65 seconds
|
||||
return manipulateStorage(function(props){
|
||||
props.iat -= 65 * 60;
|
||||
return props;
|
||||
var ret = Object.assign({}, props, { iat: props.iat - 65 - props.exp });
|
||||
return ret;
|
||||
}, false);
|
||||
})).then(function(){
|
||||
// Simulate browser reload
|
||||
gun.back(-1)._.user = gun.back(-1).chain();
|
||||
// TODO: re-make sessionStorage.remember to 65 seconds past
|
||||
user.recall(60, {session: true}).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('');
|
||||
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);
|
||||
@ -724,7 +856,7 @@ Gun().user && describe('Gun', function(){
|
||||
try{
|
||||
expect(ack).to.have.key('ok');
|
||||
}catch(e){ done(e); return };
|
||||
gun.back(-1)._.user = gun.back(-1).chain();
|
||||
throwOutUser();
|
||||
});
|
||||
}).then(function(){
|
||||
// Simulate browser reload
|
||||
@ -739,13 +871,35 @@ Gun().user && describe('Gun', function(){
|
||||
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.skip('no session');
|
||||
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(){
|
||||
@ -755,16 +909,16 @@ Gun().user && describe('Gun', function(){
|
||||
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']);
|
||||
expect(ack).to.have.keys([ 'sea', 'pub' ]);
|
||||
}catch(e){ done(e); return };
|
||||
done();
|
||||
};
|
||||
var usr = alias+type+'alive';
|
||||
user.create(usr, pass).then(function(ack){
|
||||
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(usr, pass, { pin: 'PIN' }).then(function(usr){
|
||||
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('');
|
||||
@ -786,8 +940,9 @@ Gun().user && describe('Gun', function(){
|
||||
try{
|
||||
expect(ack).to.not.be(undefined);
|
||||
expect(ack).to.not.be('');
|
||||
expect(ack).to.not.have.keys(['sea', 'pub']);
|
||||
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();
|
||||
};
|
||||
@ -797,11 +952,15 @@ Gun().user && describe('Gun', function(){
|
||||
}).catch(check);
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it.skip('recall hook session manipulation');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
process.env.SEA_CHANNEL && describe('User channel', function(){
|
||||
it.skip('create');
|
||||
it.skip('add member');
|
||||
});
|
||||
|
||||
Gun.log.off = false;
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user