mirror of
https://github.com/amark/gun.git
synced 2025-06-08 07:06:44 +00:00
Almost completed Gun.user test cases - remember is still TODO:
This commit is contained in:
parent
50b69db8d5
commit
09db7fd9d2
236
sea.js
236
sea.js
@ -44,6 +44,7 @@
|
|||||||
var user = root._.user || (root._.user = root.chain()); // create a user context.
|
var user = root._.user || (root._.user = root.chain()); // create a user context.
|
||||||
user.create = User.create; // attach a factory method to it.
|
user.create = User.create; // attach a factory method to it.
|
||||||
user.auth = User.auth; // and a login method.
|
user.auth = User.auth; // and a login method.
|
||||||
|
user.remember = User.remember; // and a credentials persisting method.
|
||||||
return user; // return the user!
|
return user; // return the user!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,130 +75,147 @@
|
|||||||
// Well first we have to actually create a user. That is what this function does.
|
// Well first we have to actually create a user. That is what this function does.
|
||||||
User.create = function(alias, pass, cb){
|
User.create = function(alias, pass, cb){
|
||||||
var root = this.back(-1);
|
var root = this.back(-1);
|
||||||
cb = cb || function(){};
|
var doCreate = function(resolve, reject){
|
||||||
// Because more than 1 user might have the same username, we treat the alias as a list of those users.
|
// Because more than 1 user might have the same username, we treat the alias as a list of those users.
|
||||||
root.get('alias/'+alias).get(function(at, ev){
|
root.get('alias/'+alias).get(function(at, ev){
|
||||||
ev.off();
|
ev.off();
|
||||||
if(at.put){
|
if(at.put){
|
||||||
// If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
|
// If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
|
||||||
return cb({err: Gun.log("User already created!")});
|
var err = 'User already created!';
|
||||||
}
|
Gun.log(err);
|
||||||
var user = {alias: alias, salt: Gun.text.random(64)};
|
return reject({err: err});
|
||||||
// pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it.
|
}
|
||||||
SEA.proof(pass, user.salt).then(function(proof){
|
var user = {alias: alias, salt: Gun.text.random(64)};
|
||||||
// this will take some short amount of time to produce a proof, which slows brute force attacks.
|
// pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it.
|
||||||
SEA.pair().then(function(pair){
|
SEA.proof(pass, user.salt).then(function(proof){
|
||||||
// now we have generated a brand new ECDSA key pair for the user account.
|
// this will take some short amount of time to produce a proof, which slows brute force attacks.
|
||||||
user.pub = pair.pub;
|
SEA.pair().then(function(pair){
|
||||||
// the user's public key doesn't need to be signed. But everything else needs to be signed with it!
|
// now we have generated a brand new ECDSA key pair for the user account.
|
||||||
SEA.write(alias, pair.priv).then(function(sAlias){
|
user.pub = pair.pub;
|
||||||
user.alias = sAlias;
|
// the user's public key doesn't need to be signed. But everything else needs to be signed with it!
|
||||||
return SEA.write(user.salt, pair.priv);
|
SEA.write(alias, pair.priv).then(function(sAlias){
|
||||||
}).then(function(sSalt){
|
user.alias = sAlias;
|
||||||
user.salt = sSalt;
|
return SEA.write(user.salt, pair.priv);
|
||||||
// to keep the private key safe, we AES encrypt it with the proof of work!
|
}).then(function(sSalt){
|
||||||
return SEA.en(pair.priv, proof);
|
user.salt = sSalt;
|
||||||
}).then(function(encVal){
|
// to keep the private key safe, we AES encrypt it with the proof of work!
|
||||||
return SEA.write(encVal, pair.priv);
|
return SEA.en(pair.priv, proof);
|
||||||
}).then(function(sAuth){
|
}).then(function(encVal){
|
||||||
user.auth = sAuth;
|
return SEA.write(encVal, pair.priv);
|
||||||
var tmp = 'pub/'+pair.pub;
|
}).then(function(sAuth){
|
||||||
//console.log("create", user, pair.pub);
|
user.auth = sAuth;
|
||||||
// awesome, now we can actually save the user with their public key as their ID.
|
var tmp = 'pub/'+pair.pub;
|
||||||
root.get(tmp).put(user);
|
//console.log("create", user, pair.pub);
|
||||||
// next up, we want to associate the alias with the public key. So we add it to the alias list.
|
// awesome, now we can actually save the user with their public key as their ID.
|
||||||
var ref = root.get('alias/'+alias).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp)));
|
root.get(tmp).put(user);
|
||||||
// callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
|
// next up, we want to associate the alias with the public key. So we add it to the alias list.
|
||||||
cb({ok: 0, pub: pair.pub});
|
var ref = root.get('alias/'+alias).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp)));
|
||||||
|
// callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
|
||||||
|
resolve({ok: 0, pub: pair.pub});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
if (cb){doCreate(cb, cb)} else {return new Promise(doCreate)}
|
||||||
};
|
};
|
||||||
// now that we have created a user, we want to authenticate them!
|
// now that we have created a user, we want to authenticate them!
|
||||||
User.auth = function(props, cb){
|
User.auth = function(props, cb){
|
||||||
var alias = props.alias, pass = props.pass, newpass = props.newpass;
|
var alias = props.alias, pass = props.pass, newpass = props.newpass;
|
||||||
var root = this.back(-1);
|
var root = this.back(-1);
|
||||||
cb = cb || function(){};
|
var doAuth = function(resolve, reject){
|
||||||
// load all public keys associated with the username alias we want to log in with.
|
// load all public keys associated with the username alias we want to log in with.
|
||||||
root.get('alias/'+alias).get(function(at, ev){
|
root.get('alias/'+alias).get(function(at, ev){
|
||||||
ev.off();
|
ev.off();
|
||||||
if(!at.put){
|
if(!at.put){
|
||||||
// if no user, don't do anything.
|
// if no user, don't do anything.
|
||||||
return cb({err: Gun.log("No user!")});
|
var err = 'No user!';
|
||||||
}
|
Gun.log(err);
|
||||||
// then attempt to log into each one until we find ours!
|
return reject({err: err});
|
||||||
// (if two users have the same username AND the same password... that would be bad)
|
}
|
||||||
Gun.obj.map(at.put, function(val, key){
|
// then attempt to log into each one until we find ours!
|
||||||
// grab the account associated with this public key.
|
// (if two users have the same username AND the same password... that would be bad)
|
||||||
root.get(key).get(function(at, ev){
|
Gun.obj.map(at.put, function(val, key){
|
||||||
key = key.slice(4);
|
// grab the account associated with this public key.
|
||||||
ev.off();
|
root.get(key).get(function(at, ev){
|
||||||
if(!at.put){ return cb({err: "Public key does not exist!"}) }
|
key = key.slice(4);
|
||||||
// attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
|
ev.off();
|
||||||
SEA.read(at.put.salt, key).then(function(salt){
|
if(!at.put){ return reject({err: 'Public key does not exist!'}) }
|
||||||
return SEA.proof(pass, salt);
|
// attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
|
||||||
}).then(function(proof){
|
SEA.read(at.put.salt, key).then(function(salt){
|
||||||
// the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
|
return SEA.proof(pass, salt);
|
||||||
return SEA.read(at.put.auth, key).then(function(auth){
|
}).then(function(proof){
|
||||||
return SEA.de(auth, proof);
|
// 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, key).then(function(auth){
|
||||||
}).then(function(priv){
|
return SEA.de(auth, proof);
|
||||||
// now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
|
});
|
||||||
if(priv){ // if we were successful, then that means...
|
}).then(function(priv){
|
||||||
// we're logged in!
|
// now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
|
||||||
function doLogin(){
|
if(priv){ // if we were successful, then that means...
|
||||||
var user = root._.user;
|
// we're logged in!
|
||||||
// add our credentials in-memory only to our root gun instance
|
function doLogin(){
|
||||||
user._ = at.gun._;
|
var user = root._.user;
|
||||||
// so that way we can use the credentials to encrypt/decrypt data
|
// add our credentials in-memory only to our root gun instance
|
||||||
user._.is = user.is = {};
|
user._ = at.gun._;
|
||||||
// that is input/output through gun (see below)
|
// so that way we can use the credentials to encrypt/decrypt data
|
||||||
user._.sea = priv;
|
user._.is = user.is = {};
|
||||||
user._.pub = key;
|
// that is input/output through gun (see below)
|
||||||
//console.log("authorized", user._);
|
user._.sea = priv;
|
||||||
// callbacks success with the user data credentials.
|
user._.pub = key;
|
||||||
cb(user._);
|
//console.log("authorized", user._);
|
||||||
// emit an auth event, useful for page redirects and stuff.
|
// callbacks success with the user data credentials.
|
||||||
Gun.on('auth', user._);
|
resolve(user._);
|
||||||
}
|
// emit an auth event, useful for page redirects and stuff.
|
||||||
if(newpass) {
|
Gun.on('auth', user._);
|
||||||
// password update so encrypt private key using new pwd + salt
|
}
|
||||||
var newsalt = Gun.text.random(64);
|
if(newpass) {
|
||||||
SEA.proof(newpass, newsalt).then(function(proof){
|
// password update so encrypt private key using new pwd + salt
|
||||||
SEA.en(priv, proof).then(function(encVal){
|
var newsalt = Gun.text.random(64);
|
||||||
return SEA.write(encVal, priv).then(function(sAuth){
|
SEA.proof(newpass, newsalt).then(function(proof){
|
||||||
return { pub: key, auth: sAuth };
|
SEA.en(priv, proof).then(function(encVal){
|
||||||
|
return SEA.write(encVal, priv).then(function(sAuth){
|
||||||
|
return { pub: key, auth: sAuth };
|
||||||
|
});
|
||||||
|
}).then(function(user){
|
||||||
|
return SEA.write(alias, priv).then(function(sAlias){
|
||||||
|
user.alias = sAlias; return user;
|
||||||
|
});
|
||||||
|
}).then(function(user){
|
||||||
|
return SEA.write(newsalt, priv).then(function(sSalt){
|
||||||
|
user.salt = sSalt; return user;
|
||||||
|
});
|
||||||
|
}).then(function(user){
|
||||||
|
var tmp = 'pub/'+key;
|
||||||
|
// awesome, now we can update the user using public key ID.
|
||||||
|
root.get(tmp).put(user);
|
||||||
|
// then we're done
|
||||||
|
doLogin();
|
||||||
});
|
});
|
||||||
}).then(function(user){
|
|
||||||
return SEA.write(alias, priv).then(function(sAlias){
|
|
||||||
user.alias = sAlias; return user;
|
|
||||||
});
|
|
||||||
}).then(function(user){
|
|
||||||
return SEA.write(newsalt, priv).then(function(sSalt){
|
|
||||||
user.salt = sSalt; return user;
|
|
||||||
});
|
|
||||||
}).then(function(user){
|
|
||||||
var tmp = 'pub/'+key;
|
|
||||||
// awesome, now we can update the user using public key ID.
|
|
||||||
root.get(tmp).put(user);
|
|
||||||
// then we're done
|
|
||||||
doLogin();
|
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
doLogin();
|
||||||
doLogin();
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
// Or else we failed to log in...
|
||||||
}
|
Gun.log('Failed to sign in!');
|
||||||
// Or else we failed to log in...
|
reject({err: 'Attempt failed'});
|
||||||
console.log("Failed to sign in!");
|
});
|
||||||
cb({err: "Attempt failed"});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
if (cb){doAuth(cb, cb)} else {return new Promise(doAuth)}
|
||||||
};
|
};
|
||||||
|
// now that we have created a user, we want to authenticate them!
|
||||||
|
User.remember = function(props, cb){
|
||||||
|
var doRemember = function(resolve, reject){
|
||||||
|
Gun.log('User.remember is TODO: still');
|
||||||
|
reject({ err: 'Not implemented.' });
|
||||||
|
}
|
||||||
|
if (cb){doRemember(cb, cb)} else {return new Promise(doRemember)}
|
||||||
|
};
|
||||||
|
|
||||||
// After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
|
// After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
|
||||||
|
|
||||||
// We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
|
// We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
|
||||||
|
@ -8035,20 +8035,87 @@ describe('Gun', function(){
|
|||||||
|
|
||||||
Gun().user && describe('User', function(){
|
Gun().user && describe('User', function(){
|
||||||
console.log('TODO: User! THIS IS AN EARLY ALPHA!!!');
|
console.log('TODO: User! THIS IS AN EARLY ALPHA!!!');
|
||||||
var user = Gun().user;
|
var alias = 'dude';
|
||||||
|
var pass = 'my secret password';
|
||||||
|
var user = Gun().user();
|
||||||
|
|
||||||
['callback', 'Promise'].forEach(function(type){
|
['callback', 'Promise'].forEach(function(type){
|
||||||
describe(type, function(){
|
describe(type, function(){
|
||||||
it.skip('create', function(done){
|
describe('create', function(){
|
||||||
done();
|
it('new', function(done){
|
||||||
|
var check = function(ack){
|
||||||
|
expect(ack).to.not.be(undefined);
|
||||||
|
expect(ack).to.not.be('');
|
||||||
|
expect(ack).to.have.keys(['ok','pub']);
|
||||||
|
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){
|
||||||
|
var gunLog = Gun.log; // Temporarily removing logging
|
||||||
|
Gun.log = function(){};
|
||||||
|
var check = function(ack){
|
||||||
|
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('');
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
Gun.log = gunLog;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('auth', function(done){
|
describe('auth', function(){
|
||||||
done();
|
it('login', function(done){
|
||||||
|
var check = function(ack){
|
||||||
|
expect(ack).to.not.be(undefined);
|
||||||
|
expect(ack).to.not.be('');
|
||||||
|
expect(ack).to.not.have.key('err');
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
var props = {alias: alias+'-'+type, pass: pass};
|
||||||
|
user.create(props.alias, props.pass).catch(function(){})
|
||||||
|
.then(function(){
|
||||||
|
// Gun.user.create - creates new user
|
||||||
|
if(type === 'callback'){
|
||||||
|
user.auth(props, check);
|
||||||
|
} else {
|
||||||
|
user.auth(props).then(check).catch(done);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('failed login', function(done){
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('new password', function(done){
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('failed new password', function(done){
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('remember', function(done){
|
describe('remember', function(){
|
||||||
done();
|
it.skip('TBD', function(done){
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user