create.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. // TODO: This needs to be split into all separate functions.
  2. // Not just everything thrown into 'create'.
  3. var SEA = require('./sea');
  4. var User = require('./user');
  5. var authsettings = require('./settings');
  6. var Gun = SEA.Gun;
  7. var noop = function(){};
  8. // Well first we have to actually create a user. That is what this function does.
  9. User.prototype.create = function(alias, pass, cb, opt){
  10. var gun = this, cat = (gun._), root = gun.back(-1);
  11. cb = cb || noop;
  12. if(cat.ing){
  13. cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
  14. return gun;
  15. }
  16. cat.ing = true;
  17. opt = opt || {};
  18. var act = {}, u;
  19. act.a = function(pubs){
  20. act.pubs = pubs;
  21. if(pubs && !opt.already){
  22. // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
  23. var ack = {err: Gun.log('User already created!')};
  24. cat.ing = false;
  25. cb(ack);
  26. gun.leave();
  27. return;
  28. }
  29. act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it.
  30. SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks.
  31. }
  32. act.b = function(proof){
  33. act.proof = proof;
  34. SEA.pair(act.c); // now we have generated a brand new ECDSA key pair for the user account.
  35. }
  36. act.c = function(pair){ var tmp;
  37. act.pair = pair || {};
  38. if(tmp = cat.root.user){
  39. tmp._.sea = pair;
  40. tmp.is = {pub: pair.pub, epub: pair.epub, alias: alias};
  41. }
  42. // the user's public key doesn't need to be signed. But everything else needs to be signed with it! // we have now automated it! clean up these extra steps now!
  43. act.data = {pub: pair.pub};
  44. SEA.sign(alias, pair, act.d);
  45. }
  46. act.d = function(sig){
  47. act.data.alias = alias || sig;
  48. SEA.sign(act.pair.epub, act.pair, act.e);
  49. }
  50. act.e = function(epub){
  51. act.data.epub = act.pair.epub || epub;
  52. SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f); // to keep the private key safe, we AES encrypt it with the proof of work!
  53. }
  54. act.f = function(auth){
  55. act.data.auth = JSON.stringify({ek: auth, s: act.salt});
  56. SEA.sign({ek: auth, s: act.salt}, act.pair, act.g);
  57. }
  58. act.g = function(auth){ var tmp;
  59. act.data.auth = act.data.auth || auth;
  60. root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID.
  61. root.get('~@'+alias).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))); // next up, we want to associate the alias with the public key. So we add it to the alias list.
  62. setTimeout(function(){ // we should be able to delete this now, right?
  63. cat.ing = false;
  64. cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
  65. if(noop === cb){ gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
  66. },10);
  67. }
  68. root.get('~@'+alias).once(act.a);
  69. return gun;
  70. }
  71. // now that we have created a user, we want to authenticate them!
  72. User.prototype.auth = function(alias, pass, cb, opt){
  73. var gun = this, cat = (gun._), root = gun.back(-1);
  74. cb = cb || function(){};
  75. if(cat.ing){
  76. cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
  77. return gun;
  78. }
  79. cat.ing = true;
  80. opt = opt || {};
  81. var pair = (alias && (alias.pub || alias.epub))? alias : (pass && (pass.pub || pass.epub))? pass : null;
  82. var act = {}, u;
  83. act.a = function(data){
  84. if(!data){ return act.b() }
  85. if(!data.pub){
  86. var tmp = [];
  87. Gun.node.is(data, function(v){ tmp.push(v) })
  88. return act.b(tmp);
  89. }
  90. if(act.name){ return act.f(data) }
  91. act.c((act.data = data).auth);
  92. }
  93. act.b = function(list){
  94. var get = (act.list = (act.list||[]).concat(list||[])).shift();
  95. if(u === get){
  96. if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') }
  97. return act.err('Wrong user or password.')
  98. }
  99. root.get(get).once(act.a);
  100. }
  101. act.c = function(auth){
  102. if(u === auth){ return act.b() }
  103. if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // new format
  104. SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
  105. }
  106. act.d = function(proof){
  107. SEA.decrypt(act.auth.ek, proof, act.e, act.enc);
  108. }
  109. act.e = function(half){
  110. if(u === half){
  111. if(!act.enc){ // try old format
  112. act.enc = {encode: 'utf8'};
  113. return act.c(act.auth);
  114. } act.enc = null; // end backwards
  115. return act.b();
  116. }
  117. act.half = half;
  118. act.f(act.data);
  119. }
  120. act.f = function(data){
  121. if(!data || !data.pub){ return act.b() }
  122. var tmp = act.half || {};
  123. act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv});
  124. }
  125. act.g = function(pair){
  126. act.pair = pair;
  127. var user = (root._).user, at = (user._);
  128. var tmp = at.tag;
  129. var upt = at.opt;
  130. at = user._ = root.get('~'+pair.pub)._;
  131. at.opt = upt;
  132. // add our credentials in-memory only to our root user instance
  133. user.is = {pub: pair.pub, epub: pair.epub, alias: alias};
  134. at.sea = act.pair;
  135. cat.ing = false;
  136. if(pass && !Gun.text.is(act.data.auth)){ opt.shuffle = opt.change = pass; } // migrate UTF8 + Shuffle! Test against NAB alias test_sea_shuffle + passw0rd
  137. opt.change? act.z() : cb(at);
  138. if(SEA.window && ((gun.back('user')._).opt||opt).remember){
  139. // TODO: this needs to be modular.
  140. try{var sS = {};
  141. sS = window.sessionStorage;
  142. sS.recall = true;
  143. sS.alias = alias;
  144. sS.tmp = pass;
  145. }catch(e){}
  146. }
  147. try{
  148. (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do.
  149. //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
  150. }catch(e){
  151. Gun.log("Your 'auth' callback crashed with:", e);
  152. }
  153. }
  154. act.z = function(){
  155. // password update so encrypt private key using new pwd + salt
  156. act.salt = Gun.text.random(64); // pseudo-random
  157. SEA.work(opt.change, act.salt, act.y);
  158. }
  159. act.y = function(proof){
  160. SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x);
  161. }
  162. act.x = function(auth){
  163. act.w(JSON.stringify({ek: auth, s: act.salt}));
  164. //SEA.sign({ek: auth, s: act.salt}, act.pair, act.w);
  165. }
  166. act.w = function(auth){
  167. if(opt.shuffle){ // delete in future!
  168. var tmp = Gun.obj.to(act.data);
  169. Gun.obj.del(tmp, '_');
  170. tmp.auth = auth;
  171. console.log('migrate core account from UTF8 & shuffle', tmp);
  172. root.get('~'+act.pair.pub).put(tmp);
  173. } // end delete
  174. root.get('~'+act.pair.pub).get('auth').put(auth, cb);
  175. }
  176. act.err = function(e){
  177. var ack = {err: Gun.log(e || 'User cannot be found!')};
  178. cat.ing = false;
  179. cb(ack);
  180. }
  181. act.plugin = function(name){
  182. if(!(act.name = name)){ return act.err() }
  183. var tmp = [name];
  184. if('~' !== name[0]){
  185. tmp[1] = '~'+name;
  186. tmp[2] = '~@'+name;
  187. }
  188. act.b(tmp);
  189. }
  190. if(pair){
  191. act.g(pair);
  192. } else
  193. if(alias){
  194. root.get('~@'+alias).once(act.a);
  195. } else
  196. if(!alias && !pass){
  197. SEA.name(act.plugin);
  198. }
  199. return gun;
  200. }
  201. User.prototype.pair = function(){
  202. console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!");
  203. var user = this;
  204. if(!user.is){ return false }
  205. return user._.sea;
  206. }
  207. User.prototype.leave = function(opt, cb){
  208. var gun = this, user = (gun.back(-1)._).user;
  209. if(user){
  210. delete user.is;
  211. delete user._.is;
  212. delete user._.sea;
  213. }
  214. if(SEA.window){
  215. try{var sS = {};
  216. sS = window.sessionStorage;
  217. delete sS.alias;
  218. delete sS.tmp;
  219. delete sS.recall;
  220. }catch(e){};
  221. }
  222. return gun;
  223. }
  224. // If authenticated user wants to delete his/her account, let's support it!
  225. User.prototype.delete = async function(alias, pass, cb){
  226. var gun = this, root = gun.back(-1), user = gun.back('user');
  227. try {
  228. user.auth(alias, pass, function(ack){
  229. var pub = (user.is||{}).pub;
  230. // Delete user data
  231. user.map().once(function(){ this.put(null) });
  232. // Wipe user data from memory
  233. user.leave();
  234. (cb || noop)({ok: 0});
  235. });
  236. } catch (e) {
  237. Gun.log('User.delete failed! Error:', e);
  238. }
  239. return gun;
  240. }
  241. User.prototype.recall = function(opt, cb){
  242. var gun = this, root = gun.back(-1), tmp;
  243. opt = opt || {};
  244. if(opt && opt.sessionStorage){
  245. if(SEA.window){
  246. try{var sS = {};
  247. sS = window.sessionStorage;
  248. if(sS){
  249. (root._).opt.remember = true;
  250. ((gun.back('user')._).opt||opt).remember = true;
  251. if(sS.recall || (sS.alias && sS.tmp)){
  252. root.user().auth(sS.alias, sS.tmp, cb);
  253. }
  254. }
  255. }catch(e){}
  256. }
  257. return gun;
  258. }
  259. /*
  260. TODO: copy mhelander's expiry code back in.
  261. Although, we should check with community,
  262. should expiry be core or a plugin?
  263. */
  264. return gun;
  265. }
  266. User.prototype.alive = async function(){
  267. const gunRoot = this.back(-1)
  268. try {
  269. // All is good. Should we do something more with actual recalled data?
  270. await authRecall(gunRoot)
  271. return gunRoot._.user._
  272. } catch (e) {
  273. const err = 'No session!'
  274. Gun.log(err)
  275. throw { err }
  276. }
  277. }
  278. User.prototype.trust = async function(user){
  279. // TODO: BUG!!! SEA `node` read listener needs to be async, which means core needs to be async too.
  280. //gun.get('alice').get('age').trust(bob);
  281. if (Gun.is(user)) {
  282. user.get('pub').get((ctx, ev) => {
  283. console.log(ctx, ev)
  284. })
  285. }
  286. }
  287. User.prototype.grant = function(to, cb){
  288. console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
  289. var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
  290. gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
  291. (async function(){
  292. var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
  293. sec = await SEA.decrypt(sec, pair);
  294. if(!sec){
  295. sec = SEA.random(16).toString();
  296. enc = await SEA.encrypt(sec, pair);
  297. user.get('trust').get(pair.pub).get(path).put(enc);
  298. }
  299. var pub = to.get('pub').then();
  300. var epub = to.get('epub').then();
  301. pub = await pub; epub = await epub;
  302. var dh = await SEA.secret(epub, pair);
  303. enc = await SEA.encrypt(sec, dh);
  304. user.get('trust').get(pub).get(path).put(enc, cb);
  305. }());
  306. return gun;
  307. }
  308. User.prototype.secret = function(data, cb){
  309. console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
  310. var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
  311. gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
  312. (async function(){
  313. var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
  314. sec = await SEA.decrypt(sec, pair);
  315. if(!sec){
  316. sec = SEA.random(16).toString();
  317. enc = await SEA.encrypt(sec, pair);
  318. user.get('trust').get(pair.pub).get(path).put(enc);
  319. }
  320. enc = await SEA.encrypt(data, sec);
  321. gun.put(enc, cb);
  322. }());
  323. return gun;
  324. }
  325. module.exports = User