mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00
1121 lines
42 KiB
JavaScript
1121 lines
42 KiB
JavaScript
/* global Gun,describe,expect,it,beforeEach */
|
|
/*eslint max-len: ["error", 95, { "ignoreComments": true }]*/
|
|
/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}]*/
|
|
/*eslint object-curly-spacing: ["error", "never"]*/
|
|
/*eslint node/no-deprecated-api: [error, {ignoreModuleItems: ["new buffer.Buffer()"]}] */
|
|
|
|
var root;
|
|
(function(env){
|
|
root = env.window ? env.window : global;
|
|
env.window && root.localStorage && root.localStorage.clear();
|
|
try{ require('fs').unlinkSync('data.json') }catch(e){}
|
|
//root.Gun = root.Gun || require('../gun');
|
|
if(root.Gun){
|
|
root.Gun = root.Gun;
|
|
} else {
|
|
var expect = global.expect = require("./expect");
|
|
root.Gun = require('../gun');
|
|
Gun.serve = require('../lib/serve');
|
|
//require('./s3');
|
|
//require('./uws');
|
|
//require('./wsp/server');
|
|
require('../lib/file');
|
|
require('../sea');
|
|
}
|
|
}(this));
|
|
|
|
if(typeof Buffer === 'undefined'){
|
|
var Buffer = require('buffer').Buffer;
|
|
}
|
|
|
|
function checkIndexedDB(key, prop, resolve_){
|
|
var result;
|
|
Gun.SEA._callonstore_(function(store) {
|
|
var getData = store.get(key);
|
|
getData.onsuccess = function(){
|
|
result = getData.result && getData.result[prop];
|
|
};
|
|
}, function(){
|
|
resolve_(result);
|
|
});
|
|
}
|
|
|
|
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';
|
|
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('keyid', function(done){
|
|
Gun.SEA.pair().then(function(key){
|
|
var check = function(keyid){
|
|
expect(keyid).to.not.be(undefined);
|
|
expect(keyid).to.not.be('');
|
|
expect(keyid.length).to.eql(16);
|
|
done();
|
|
};
|
|
// keyid - creates 8 byte KeyID from public key
|
|
if(type === 'callback'){
|
|
Gun.SEA.keyid(key.pub, check);
|
|
} else {
|
|
Gun.SEA.keyid(key.pub).then(check);
|
|
}
|
|
}).catch(function(e){done(e)});
|
|
});
|
|
|
|
it('enc', 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);
|
|
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.enc(clearText, key.priv, check);
|
|
} else {
|
|
Gun.SEA.enc(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, check);
|
|
} else {
|
|
Gun.SEA.sign(key.pub, key).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).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('dec', function(done){
|
|
Gun.SEA.pair().then(function(key){
|
|
var check = function(decText){
|
|
expect(decText).to.not.be(undefined);
|
|
expect(decText).to.not.be('');
|
|
expect(decText).to.be.eql(clearText);
|
|
done();
|
|
};
|
|
Gun.SEA.enc(clearText, key.priv).then(function(jsonSecret){
|
|
// de - decrypts JSON data using user's private or derived ECDH key
|
|
if(type === 'callback'){
|
|
Gun.SEA.dec(jsonSecret, key.priv, check);
|
|
} else {
|
|
Gun.SEA.dec(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, check);
|
|
} else {
|
|
Gun.SEA.derive(keys.rx.pub, keys.tx).then(check);
|
|
}
|
|
}).catch(function(e){done(e)});
|
|
});
|
|
|
|
it('write', function(done){
|
|
Gun.SEA.pair().then(function(key){
|
|
Gun.SEA.sign(key.pub, key).then(function(signature){
|
|
var check = function(result){
|
|
var parts;
|
|
try{
|
|
expect(result).to.not.be(undefined);
|
|
expect(result).to.not.be('');
|
|
expect(result.slice(0, 4)).to.eql('SEA[');
|
|
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);
|
|
}catch(e){ return done(e) }
|
|
Gun.SEA.verify(key.pub, key.pub, parts[1]).then(function(flag){
|
|
expect(flag).to.be.true;
|
|
done();
|
|
});
|
|
};
|
|
// write - wraps data to 'SEA["data","signature"]'
|
|
if(type === 'callback'){
|
|
Gun.SEA.write(key.pub, key, check);
|
|
} else {
|
|
Gun.SEA.write(key.pub, key).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).then(function(signature){
|
|
Gun.SEA.write(key.pub, key).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
|
|
sessionStorage.removeItem('remember');
|
|
sessionStorage.removeItem('alias');
|
|
Gun.SEA._callonstore_(function(store) {
|
|
var act = store.clear(); // Wipes whole IndexedDB
|
|
act.onsuccess = function(){};
|
|
});
|
|
}
|
|
};
|
|
|
|
['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, notStored){
|
|
return function(){
|
|
var checkValue = function(data, val){
|
|
if(notStored){
|
|
expect(typeof data !== 'undefined' && data !== null && data !== '')
|
|
.to.not.eql(true);
|
|
} 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');
|
|
checkValue(alias);
|
|
checkValue(root.sessionStorage.getItem('remember'));
|
|
if(alias){
|
|
checkIndexedDB(alias, 'auth', function(auth){
|
|
checkValue(auth);
|
|
done();
|
|
});
|
|
} else {
|
|
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();
|
|
};
|
|
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', function(done){
|
|
user.auth(alias+type, pass+' new').then(checkStorage(done)).catch(done);
|
|
});
|
|
|
|
it('with PIN auth session stored', function(done){
|
|
if(type === 'callback'){
|
|
user.auth(alias+type, pass+' new', checkStorage(done), {pin: 'PIN'});
|
|
} else {
|
|
user.auth(alias+type, pass+' new', {pin: 'PIN'})
|
|
.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(){
|
|
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' ]);
|
|
expect(gun.back(-1)._.user).to.not.be.ok();
|
|
}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 (from IndexedDB)', 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('');
|
|
|
|
var ret;
|
|
if(wantAck && ack){
|
|
['err', 'pub', 'sea', 'alias', 'put'].forEach(function(key){
|
|
if(typeof ack[key] !== 'undefined'){
|
|
(ret = ret || {})[key] = ack[key];
|
|
}
|
|
});
|
|
}
|
|
// NOTE: done can be Promise returning function
|
|
return !hasPin || !wantAck || !ack ? done(ret)
|
|
: new Promise(function(resolve){
|
|
checkIndexedDB(ack.alias, 'auth', function(auth){
|
|
expect(auth).to.not.be(undefined);
|
|
expect(auth).to.not.be('');
|
|
resolve(done(wantAck && Object.assign(ret || {}, {auth: auth})));
|
|
});
|
|
});
|
|
};
|
|
};
|
|
// This re-constructs 'remember-me' data modified by manipulate func
|
|
var manipulateStorage = function(manipulate, pin){
|
|
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
|
|
pin = pin && new Buffer(pin, 'utf8').toString('base64');
|
|
return !pin ? Promise.resolve(sessionStorage.getItem('remember'))
|
|
: new Promise(function(resolve){
|
|
checkIndexedDB(usr._.alias, 'auth', resolve);
|
|
}).then(function(remember){
|
|
return Gun.SEA.read(remember, usr._.pub).then(function(props){
|
|
return !pin ? props
|
|
: Gun.SEA.dec(props, pin);
|
|
});
|
|
}).then(function(props){
|
|
try{ props && (props = JSON.parse(props)) }catch(e){} //eslint-disable-line no-empty
|
|
return props;
|
|
}).then(manipulate).then(function(props){
|
|
expect(props).to.not.be(undefined);
|
|
expect(props).to.not.be('');
|
|
var keys = {pub: usr._.pub, priv: usr._.sea.priv};
|
|
return Gun.SEA.write(JSON.stringify(props), keys)
|
|
.then(function(remember){
|
|
return !pin ? sessionStorage.setItem('remember', remember)
|
|
: Gun.SEA.enc(remember, pin).then(function(encauth){
|
|
return new Promise(function(resolve){
|
|
setIndexedDB(usr._.alias, encauth, resolve);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
it('with PIN auth session stores', 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);
|
|
} else {
|
|
user.recall().then(doAction).catch(done);
|
|
}
|
|
});
|
|
|
|
it('without PIN auth session stores', function(done){
|
|
var doAction = function(){
|
|
user.auth(alias+type, pass+' new').then(doCheck(done));
|
|
};
|
|
user.leave().then(function(){
|
|
if(type === 'callback'){
|
|
user.recall(doAction);
|
|
} else {
|
|
user.recall().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('with validity but no PIN stores 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);
|
|
} else {
|
|
user.recall(12 * 60).then(doAction)
|
|
.catch(done);
|
|
}
|
|
});
|
|
|
|
it('validity and auth with PIN but storage empty', 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');
|
|
|
|
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{
|
|
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 }
|
|
// Restore but leave IndexedDB empty
|
|
root.sessionStorage.setItem('user', sUser);
|
|
root.sessionStorage.setItem('remember', sRemember);
|
|
|
|
user.recall(12 * 60).then(
|
|
doCheck(function(ack){
|
|
expect(ack).to.have.key('err');
|
|
expect(ack.err.toLowerCase().indexOf('no authentication')).to.not.be(-1);
|
|
checkIndexedDB(alias+type, 'auth', function(auth){
|
|
expect((typeof auth !== 'undefined' && auth !== null && auth !== ''))
|
|
.to.not.eql(true);
|
|
done();
|
|
});
|
|
}, false, true))
|
|
.catch(done);
|
|
}).catch(done);
|
|
}).catch(done);
|
|
});
|
|
|
|
it('valid session bootstrap', function(done){
|
|
var sUser;
|
|
var sRemember;
|
|
var iAuth;
|
|
user.auth(alias+type, pass+' new', {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');
|
|
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 }
|
|
|
|
return new Promise(function(resolve){
|
|
checkIndexedDB(sUser, 'auth', function(auth){ resolve(iAuth = auth) });
|
|
});
|
|
}).then(function(){
|
|
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 }
|
|
|
|
return new Promise(function(resolve){
|
|
checkIndexedDB(sUser, 'auth', function(auth){
|
|
expect(auth).to.not.be(iAuth);
|
|
resolve();
|
|
});
|
|
});
|
|
}).then(function(){
|
|
root.sessionStorage.setItem('user', sUser);
|
|
root.sessionStorage.setItem('remember', sRemember);
|
|
|
|
return new Promise(function(resolve){
|
|
setIndexedDB(sUser, iAuth, resolve);
|
|
});
|
|
}).then(function(){
|
|
user.recall(12 * 60).then(doCheck(done))
|
|
.catch(done);
|
|
}).catch(done);
|
|
}).catch(done);
|
|
});
|
|
|
|
it('valid session bootstrap using alias & PIN', function(done){
|
|
user.recall(12 * 60).then(function(){
|
|
return user.auth(alias+type, pass+' new', {pin: 'PIN'});
|
|
}).then(doCheck(function(ack){
|
|
// Let's save remember props
|
|
var sUser = root.sessionStorage.getItem('user');
|
|
var sRemember = root.sessionStorage.getItem('remember');
|
|
var iAuth = ack.auth;
|
|
return new Promise(function(resolve){
|
|
checkIndexedDB(sUser, 'auth', function(auth){
|
|
iAuth = auth;
|
|
resolve(user.leave()); // Then logout user
|
|
});
|
|
}).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 }
|
|
return new Promise(function(resolve){
|
|
checkIndexedDB(sUser, 'auth', function(auth){
|
|
try{ expect(auth).to.not.be(iAuth) }catch(e){ done(e) }
|
|
// Then restore IndexedDB but skip sessionStorage remember
|
|
setIndexedDB(sUser, iAuth, function(){
|
|
root.sessionStorage.setItem('user', sUser);
|
|
resolve(ack);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}, true, true)).then(function(){
|
|
// Then try to recall authentication
|
|
return user.recall(12 * 60).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 session fails to bootstrap with alias & wrong PIN',
|
|
function(done){
|
|
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');
|
|
var sRemember = root.sessionStorage.getItem('remember');
|
|
var iAuth = ack.auth;
|
|
return new Promise(function(resolve){
|
|
checkIndexedDB(sUser, 'auth', function(auth){
|
|
iAuth = auth;
|
|
resolve(user.leave()); // Then logout user
|
|
});
|
|
}).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 }
|
|
return new Promise(function(resolve){
|
|
checkIndexedDB(sUser, 'auth', function(auth){
|
|
try{ expect(auth).to.not.be(iAuth) }catch(e){ done(e) }
|
|
// Then restore IndexedDB auth data, skip sessionStorage
|
|
setIndexedDB(sUser, iAuth, function(){
|
|
root.sessionStorage.setItem('user', sUser);
|
|
resolve(ack);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}, true, 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){
|
|
var pin = 'PIN';
|
|
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
|
|
return manipulateStorage(function(props){
|
|
var ret = Object.assign({}, props, {iat: props.iat - 65 - props.exp});
|
|
return ret;
|
|
}, pin);
|
|
})).then(function(){
|
|
// Simulate browser reload
|
|
throwOutUser();
|
|
user.recall(60).then(function(ack){
|
|
expect(ack).to.not.be(undefined);
|
|
expect(ack).to.not.be('');
|
|
expect(ack).to.not.have.keys([ 'pub', 'sea' ]);
|
|
expect(ack).to.have.key('err');
|
|
expect(ack.err).to.not.be(undefined);
|
|
expect(ack.err).to.not.be('');
|
|
expect(ack.err.toLowerCase()
|
|
.indexOf('no authentication session')).not.to.be(-1);
|
|
done();
|
|
}).catch(done);
|
|
}).catch(done);
|
|
});
|
|
|
|
it('changed password', function(done){
|
|
var pin = 'PIN';
|
|
var sUser;
|
|
var sRemember;
|
|
var iAuth;
|
|
user.recall(60).then(function(){
|
|
return user.auth(alias+type, pass+' new', {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');
|
|
|
|
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 }
|
|
|
|
return new Promise(function(resolve){
|
|
checkIndexedDB(sUser, 'auth', function(auth){ resolve(iAuth = auth) });
|
|
});
|
|
}).then(function(){
|
|
return 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 pre-update remember...
|
|
root.sessionStorage.setItem('user', sUser);
|
|
root.sessionStorage.setItem('remember', sRemember);
|
|
// ... and IndexedDB auth
|
|
return new Promise(function(resolve){
|
|
setIndexedDB(sUser, iAuth, resolve);
|
|
});
|
|
}).then(function(){
|
|
user.recall(60).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 pin = 'PIN';
|
|
var exp;
|
|
var hookFunc = function(props){
|
|
exp = props.exp * 2; // Doubles session expiration time
|
|
var ret = Object.assign({}, props, {exp: exp});
|
|
return (type === 'callback' && ret) || new Promise(function(resolve){
|
|
resolve(ret); // Both callback & Promise methods here
|
|
});
|
|
};
|
|
user.recall(60, {hook: hookFunc}).then(function(){
|
|
return user.auth(alias+type, pass, {pin: pin});
|
|
}).then(function(){
|
|
return manipulateStorage(function(props){
|
|
expect(props).to.not.be(undefined);
|
|
expect(props).to.have.key('exp');
|
|
expect(props.exp).to.be(exp);
|
|
return props;
|
|
}, pin);
|
|
}).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;
|
|
});
|
|
});
|