MetaMask start, AXE start, SEA refactor, etc.

This commit is contained in:
Mark Nadal 2018-11-17 13:17:16 -08:00
parent 7f4b77a454
commit 16e64e1de5
31 changed files with 998 additions and 1366 deletions

80
axe.js Normal file
View File

@ -0,0 +1,80 @@
;(function(){
/* UNBUILD */
var root;
if(typeof window !== "undefined"){ root = window }
if(typeof global !== "undefined"){ root = global }
root = root || {};
var console = root.console || {log: function(){}};
function USE(arg, req){
return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
arg(mod = {exports: {}});
USE[R(path)] = mod.exports;
}
function R(p){
return p.split('/').slice(-1).toString().replace('.js','');
}
}
if(typeof module !== "undefined"){ var common = module }
/* UNBUILD */
;USE(function(module){
if(typeof window !== "undefined"){ module.window = window }
var tmp = module.window || module;
var AXE = tmp.AXE || function(){};
if(AXE.window = module.window){ try{
AXE.window.AXE = AXE;
tmp = document.createEvent('CustomEvent');
tmp.initCustomEvent('extension', false, false, {type: "AXE"});
(window.dispatchEvent || window.fireEvent)(tmp);
window.postMessage({type: "AXE"}, '*');
} catch(e){} }
try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){}
module.exports = AXE;
})(USE, './root');
;USE(function(module){
var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
(Gun.AXE = AXE).GUN = AXE.Gun = Gun;
Gun.on('opt', function(at){
if(!at.axe){
at.axe = {};
var p = at.opt.peers, tmp;
// 1. If any remembered peers or from last cache or extension
// 2. Fallback to use hard coded peers from dApp
// 3. Or any offered peers.
if(Gun.obj.empty(p)){
Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){
p[url] = {url: url, axe: {}};
});
}
// Our current hypothesis is that it is most optimal
// to take peers in a common network, and align
// them in a line, where you only have left and right
// peers, so messages propagate left and right in
// a linear manner with reduced overlap, and
// with one common superpeer (with ready failovers)
// in case the p2p linear latency is high.
// Or there could be plenty of other better options.
console.log("axe", at.opt);
if(at.opt.super){
at.on('in', USE('./lib/super', 1), at);
} else {
//at.on('in', input, at);
}
}
this.to.next(at); // make sure to call the "next" middleware adapter.
});
function input(msg){
var at = this.as, to = this.to;
}
module.exports = AXE;
})(USE, './axe');
}());

View File

@ -5,6 +5,7 @@
<input id="pass" type="password" placeholder="passphrase"> <input id="pass" type="password" placeholder="passphrase">
<input id="in" type="submit" value="sign in"> <input id="in" type="submit" value="sign in">
<input id="up" type="button" value="sign up"> <input id="up" type="button" value="sign up">
<input id="mask" type="button" value="MetaMask Login">
</form> </form>
<ul></ul> <ul></ul>
@ -23,13 +24,14 @@ var gun = Gun(); //Gun(['http://localhost:8765/gun', 'https://guntest.herokuapp.
var user = gun.user().recall({sessionStorage: true}); var user = gun.user().recall({sessionStorage: true});
$('#up').on('click', function(e){ $('#up').on('click', function(e){
user.create($('#alias').val(), $('#pass').val()); user.create($('#alias').val(), $('#pass').val(), login);
}); });
function login(e){
$('#sign').on('submit', function(e){
e.preventDefault();
user.auth($('#alias').val(), $('#pass').val()); user.auth($('#alias').val(), $('#pass').val());
}); return false; // e.preventDefault();
};
$('#sign').on('submit', login);
$('#mask').on('click', login);
gun.on('auth', function(){ gun.on('auth', function(){
$('#sign').hide(); $('#sign').hide();
@ -38,7 +40,7 @@ gun.on('auth', function(){
$('#said').on('submit', function(e){ $('#said').on('submit', function(e){
e.preventDefault(); e.preventDefault();
if(!user.is){ return } //if(!user.is){ return }
user.get('said').set($('#say').val()); user.get('said').set($('#say').val());
$('#say').val(""); $('#say').val("");
}); });

203
examples/move/index.html Normal file
View File

@ -0,0 +1,203 @@
<!DOCTYPE html>
<html>
<head>
<!--
HELP WANTED!
Build decentralized Open Source Uber/Lyft!
Need people with experience on:
- Leaflet API, zoom and tile coordinates for subscribing to surrounding area.
- Creating cute little driving icons that animate with heading/direction.
-->
<title>Move by Neon ERA</title>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport" />
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.0.3/dist/leaflet.css"/>
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.0.3/dist/leaflet.js"></script>
</head>
<body>
<div id="map"></div>
<a id="share" class="hide"><div class="stick button">Share</div></a>
<div id="link" class="hide">
<p>Copy and Paste this URL to your friends to share your location:</p>
<center>
<p id="follow">Location sharing not available!</p>
</center>
<p><b>Note</b>: Location may not sync when your device's screen is off or the tab is out of focus. You'd need to install this as an app for that to work.</p>
<center>
<a id="close"><div class="button">Close</div></a>
</center>
</div>
<textarea style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 5em;" id="debug"></textarea>
<style>
html, body, #map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font-family: Arial;
font-size: 16pt;
z-index: 1;
}
.stick {
position: absolute;
bottom: 1em;
right: 1em;
}
.button {
display: inline-block;
padding: 1em;
opacity: .5;
background: blue;
color: white;
z-index: 7;
transition: .25s all;
}
.button:hover {
opacity: 1;
}
.hide {
display: none;
}
#link {
position: absolute;
padding: 1em;
top: 2em;
left: 2em;
right: 2em;
bottom: 2em;
background: white;
overflow: scroll;
z-index: 9;
}
#follow {
background: #EEE;
padding: .5em;
word-wrap: break-word;
}
</style>
<script src="/jquery.js"></script>
<script src="/gun.js"></script>
<script>
function Where(opt, cb){
// a small wrapper around Leaflet for map tracking.
var where = {};
where.opt = opt || {};
where.opt.zoom = where.opt.zoom || {};
where.opt.err = where.opt.err || function(){};
where.map = L.map('map', { zoom: where.opt.zoom.level });
where.opt.tile = where.opt.tile || L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
maxZoom: where.opt.zoom.max,
minZoom: where.opt.zoom.min,
detectRetina: true,
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
});
where.map.addLayer(where.opt.tile);
L.GridLayer.DebugCoords = L.GridLayer.extend({
createTile: function (coords) {
var tile = document.createElement('div');
tile.innerHTML = [coords.z, coords.x, coords.y].join(', ');
tile.style.outline = '1px solid black';
return tile;
}
});
L.gridLayer.debugCoords = function(opts) {
return new L.GridLayer.DebugCoords(opts);
};
where.map.addLayer( L.gridLayer.debugCoords() );
where.opt.zoom.ing = where.opt.zoom.ing || function(){
where.opt.zoom.level = where.map.getZoom();
}
where.map.on('zoomstart', where.opt.zoom.ing, where.opt.err);
where.map.on('zoomend', where.opt.zoom.ing, where.opt.err);
where.map.on('zoomlevelschange', where.opt.zoom.ing, where.opt.err);
where.update = function(latlng){
if((+new Date) - where.update.last < 400){
clearTimeout(where.update.to);
where.update.to = setTimeout(function(){
where.update(latlng);
});
return;
}
where.map.setView(latlng, where.opt.zoom.level, {animate: true});
where.marker = where.marker || L.marker().setLatLng(latlng).addTo(where.map);
where.marker.setLatLng(latlng).update();
where.update.last = (+new Date);
}
if(where.opt.track){
where.map.on('locationfound', function(pos){
where.update(pos.latlng);
where.opt.track(pos);
});
where.map.locate({
setView: true,
zoom: where.opt.zoom.level,
watch: where.opt.continuous || true,
timeout: where.opt.timeout || 10000,
maximumAge: where.opt.maximumAge || 0,
enableHighAccuracy: where.opt.enableHighAccuracy || false
});
}
return where;
}
</script>
<script>
;(function(){
// the actual GPS tracking app!
var gun = Gun(location.origin + '/gun');
var gps = {};
gps.opt = {
continuous: true, // get location just once uses `getCurrentPosition()` while continuously uses `watchPosition()`
enableHighAccuracy: true, // HighAccuracy uses more resources, https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions
timeout: 5000, // have this long to get data before erring.
maximumAge: 0, // set to 0 to actually track.
zoom: {max: 18, level: 13, min: 12}
}
function start(){
gps.follow = (window.location.hash || '').slice(1);
if(gps.follow){
gps.where = gps.where || Where(gps.opt);
gps.ref = gun.get('gps/' + gps.follow);
gps.ref.on(function(latlng){
//$('#debug').value = 'track ' + JSON.stringify(latlng);
gps.where.update(latlng);
});
$('#share').addClass("hide");
} else {
document.cookie = 'gps=' + (gps.track = (document.cookie.match(/gps\=(.*?)(\&|$|\;)/i)||[])[1] || Gun.text.random(5)); // trick with cookies!
gps.ref = gun.get('gps/' + gps.track);
gps.opt.track = function(pos){
pos = pos.latlng;
if(gps.follow
|| Gun.time.is() - gps.when < 1000
|| gps.last && gps.last.lat == pos.lat && gps.last.lng == pos.lng){
return; // throttle!
}
gps.when = Gun.time.is();
gps.ref.put(gps.last = pos);
//$('#debug').value = JSON.stringify(gps.last);
}
gps.where = gps.where || Where(gps.opt);
$('#follow').text(("where.gunDB.io/" || (location.origin + location.pathname)) + '#' + gps.track);
$('#share').removeClass("hide");
$('#share').on('click', function(){
$('#link').toggleClass("hide");
});
$('#close').on('click', function(){
$('#link').toggleClass("hide");
});
}
}
start();
window.onhashchange = start;
}());
</script>
</body>
</html>

4
gun.min.js vendored

File diff suppressed because one or more lines are too long

139
lib/normalize.js Normal file
View File

@ -0,0 +1,139 @@
;(function(){
function normalize(opt){
var el = $(this);
opt = opt || $.extend(true, normalize.opt, opt||{});
el.children().each(function(){
var a = {$: $(this), opt: opt};
a.tag = normalize.tag(a.$);
$(a.opt.mutate).each(function(i,fn){
fn && fn(a);
});
})
};
var n = normalize, u;
n.get = function(o, p){
p = p.split('.');
var i = 0, l = p.length, u;
while((o = o[p[i++]]) != null && i < l){};
return i < l ? u : o;
}
n.has = function(o,p){
return Object.prototype.hasOwnProperty.call(o, p);
}
n.tag = function(e){
return (($(e)[0]||{}).nodeName||'').toLowerCase();
}
n.attrs = function(e, cb){
var attr = {};
(e = $(e)) && e.length && $(e[0].attributes||[]).each(function(v,n){
n = n.nodeName||n.name;
v = e.attr(n);
v = cb? cb(v,n,e) : v;
if(v !== u && v !== false){ attr[n] = v }
});
return attr;
}
n.joint = function(e, d){
d = (d? 'next' : 'previous') + 'Sibling'
return $(($(e)[0]||{})[d]);
}
var h = {
attr: function(a$, av, al){
var l = function(i,v){
var t = v;
i = al? v : i;
v = al? av[v.toLowerCase()] : t;
a$.attr(i, v);
}
al? $(al.sort()).each(l) : $.each(av,l);
}
}
n.opt = { // some reasonable defaults, limited to content alone.
tags: {
'a': {attrs:{'src':1}, exclude:{'a':1}},
'b': {exclude:{'b':1}},
//'blockquote':1,
'br': {empty: 1},
'div': 1,
//'code':1,
'i': {exclude:{'i':1}},
'img': {attrs:{'src':1}, empty: 1},
'li':1, 'ol':1,
'p': {exclude:{'p':1,'div':1}},
//'pre':1,
's': {exclude:{'s':1}},
'sub':1, 'sup':1,
'span': {exclude:{'p':1,'ul':1,'ol':1,'li':1,'br':1}},
'u': {exclude:{'u':1,'p':1}},
'ul':1
}
// a, audio, b, br, div, i, img, li, ol, p, s, span, sub, sup, u, ul, video
// button, canvas, embed, form, iframe, input, style, svg, table,
// Text: bold, italics, underline, align, bullet, list,
,convert: {
'em': 'i', 'strong': 'b'
}
,attrs: {
'id':1
,'class':1
,'style':1
}
,mutate: [
function(a){ // attr
a.attrs = [];
a.attr = $.extend(a.opt.attrs, n.get(a.opt,'tags.'+ a.tag +'attrs'));
a.attr = n.attrs(a.$, function(v,i){
a.$.removeAttr(i);
if(a.attr[i.toLowerCase()]){
a.attrs.push(i)
return v;
}
});
// if this tag is gonna get converted, wait to add attr back till after the convert
if(a.attrs && !n.get(a.opt, 'convert.' + a.tag)){
h.attr(a.$, a.attr, a.attrs);
}
}
,function(a, tmp){ // convert
if(!(tmp = n.get(a.opt,'convert.' + a.tag))){ return }
a.attr = a.attr || n.attrs(a.$);
a.$.replaceWith(a.$ = $('<'+ (a.tag = t.toLowerCase()) +'>').append(a.$.contents()));
h.attr(a.$, a.attr, a.attrs);
}
,function(a, tmp){ // lookahead
if((tmp = n.joint(a.$,1)) && (t = t.contents()).length === 1 && a.tag === n.tag(t = t.first())){
a.$.append(t.parent()); // no need to unwrap the child, since the recursion will do it for us
}
}
,function(a){ // recurse
// this needs to precede the exclusion and empty.
normalize(a);
}
,function(a){ // exclude
var t;
if(!n.get(a.opt,'tags.' + a.tag)
|| ((t = n.get(a.opt,'tags.'+ a.tag +'.exclude'))
&& a.$.parents($.map(t,function(i,v){return v})+' ').length)
){
a.$.replaceWith(a.$.contents());
}
}
,function(a){ // prior
var t;
if((t = n.joint(a.$)).length && a.tag === n.tag(t)){
t.append(a.$.contents());
}
}
,function(a){ // empty
// should always go last, since the element will be removed!
if(a.opt.empty || !n.has(a.opt,'empty')){
if(!n.get(a.opt,'tags.'+ a.tag +'.empty')
&& !a.$.contents().length){
a.$.remove();
}
}
}
]
}
$.fn.normalize = normalize;
}());

19
lib/reboot.js Normal file
View File

@ -0,0 +1,19 @@
;(function(){
var exec = require('child_process').execSync;
var dir = __dirname, tmp;
try{exec("crontab -l");
}catch(e){tmp = e}
if(0 > tmp.toString().indexOf('no')){ return }
try{tmp = exec('which node').toString();
}catch(e){console.log(e);return}
try{tmp = exec('echo "@reboot '+tmp+' '+dir+'/../examples/http.js" > '+dir+'/reboot.cron');
}catch(e){console.log(e);return}
try{tmp = exec('crontab '+dir+'/reboot.cron');
}catch(e){console.log(e);return}
console.log(tmp.toString());
}());

View File

@ -3,15 +3,16 @@
Gun.serve = require('./serve'); Gun.serve = require('./serve');
//process.env.GUN_ENV = process.env.GUN_ENV || 'debug'; //process.env.GUN_ENV = process.env.GUN_ENV || 'debug';
Gun.on('opt', function(root){ Gun.on('opt', function(root){
if(u === root.opt.super){
root.opt.super = true;
}
this.to.next(root); this.to.next(root);
if(root.once){ return }
if(u !== root.opt.super){ return }
root.opt.super = true;
}) })
require('../nts'); require('../nts');
require('./store'); require('./store');
require('./rs3'); require('./rs3');
require('./wire'); require('./wire');
//try{require('../axe');}catch(e){}
require('./file'); require('./file');
require('./evict'); require('./evict');
if('debug' === process.env.GUN_ENV){ require('./debug') } if('debug' === process.env.GUN_ENV){ require('./debug') }

View File

@ -1,6 +1,6 @@
{ {
"name": "gun", "name": "gun",
"version": "0.9.99997", "version": "0.9.99998",
"description": "A realtime, decentralized, offline-first, graph data synchronization engine.", "description": "A realtime, decentralized, offline-first, graph data synchronization engine.",
"main": "index.js", "main": "index.js",
"browser": "gun.min.js", "browser": "gun.min.js",

909
sea.js

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +0,0 @@
var SEA = require('./sea');
var Gun = SEA.Gun;
const queryGunAliases = require('./query')
const parseProps = require('./parse')
// This is internal User authentication func.
const authenticate = async (alias, pass, gunRoot) => {
// load all public keys associated with the username alias we want to log in with.
const aliases = (await queryGunAliases(alias, gunRoot))
.filter(a => !!a.pub && !!a.put)
// Got any?
if (!aliases.length) {
throw { err: 'Public key does not exist!' }
}
let err
// then attempt to log into each one until we find ours!
// (if two users have the same username AND the same password... that would be bad)
const users = await Promise.all(aliases.map(async (a, i) => {
// attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
const auth = parseProps(a.put.auth)
// NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here.
// SEA.verify(at.put.auth, pub).then(function(auth){
try {
const proof = await SEA.work(pass, auth.s)
//const props = { pub: pub, proof: proof, at: at }
// the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
/*
MARK TO @mhelander : pub vs epub!???
*/
const salt = auth.salt
const sea = await SEA.decrypt(auth.ek, proof)
if (!sea) {
err = 'Failed to decrypt secret! ' + (i+1) +'/'+aliases.length;
return
}
// now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
// if we were successful, then that meanswe're logged in!
const priv = sea.priv
const epriv = sea.epriv
const epub = a.put.epub
// TODO: 'salt' needed?
err = null
if(SEA.window){
var tmp = SEA.window.sessionStorage;
if(tmp && gunRoot._.opt.remember){
SEA.window.sessionStorage.alias = alias;
SEA.window.sessionStorage.tmp = pass;
}
}
return {priv: priv, pub: a.put.pub, salt: salt, epub: epub, epriv: epriv };
} catch (e) {
err = 'Failed to decrypt secret!'
throw { err }
}
}))
var user = Gun.list.map(users, function(acc){ if(acc){ return acc } })
if (!user) {
throw { err: err || 'Public key does not exist!' }
}
return user
}
module.exports = authenticate;

View File

@ -45,7 +45,7 @@
} }
return buf return buf
} }
const byteLength = input.byteLength const byteLength = input.byteLength // what is going on here? FOR MARTTI
const length = input.byteLength ? input.byteLength : input.length const length = input.byteLength ? input.byteLength : input.length
if (length) { if (length) {
let buf let buf

View File

@ -2,22 +2,75 @@
// TODO: This needs to be split into all separate functions. // TODO: This needs to be split into all separate functions.
// Not just everything thrown into 'create'. // Not just everything thrown into 'create'.
const SEA = require('./sea') var SEA = require('./sea');
const User = require('./user') var User = require('./user');
const authRecall = require('./recall') var authsettings = require('./settings');
const authsettings = require('./settings') var Gun = SEA.Gun;
const authenticate = require('./authenticate')
const finalizeLogin = require('./login') var noop = function(){};
const authLeave = require('./leave')
const _initial_authsettings = require('./settings').recall
const Gun = SEA.Gun;
var u;
// 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.prototype.create = function(username, pass, cb, opt){ User.prototype.create = function(alias, pass, cb, opt){
// TODO: Needs to be cleaned up!!! var gun = this, cat = (gun._), root = gun.back(-1);
const gunRoot = this.back(-1) cb = cb || noop;
var gun = this, cat = (gun._); if(cat.ing){
cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
return gun;
}
cat.ing = true;
opt = opt || {};
var act = {}, u;
act.a = function(pubs){
act.pubs = pubs;
if(pubs && !opt.already){
// If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
var ack = {err: Gun.log('User already created!')};
cat.ing = false;
cb(ack);
gun.leave();
return;
}
act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it.
SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks.
}
act.b = function(proof){
act.proof = proof;
SEA.pair(act.c); // now we have generated a brand new ECDSA key pair for the user account.
}
act.c = function(pair){
act.pair = pair || {};
// the user's public key doesn't need to be signed. But everything else needs to be signed with it!
act.data = {pub: pair.pub};
SEA.sign(alias, pair, act.d);
}
act.d = function(alias){
act.data.alias = alias;
SEA.sign(act.pair.epub, act.pair, act.e);
}
act.e = function(epub){
act.data.epub = epub;
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!
}
act.f = function(auth){
act.data.auth = auth;
SEA.sign({ek: auth, s: act.salt}, act.pair, act.g);
}
act.g = function(auth){ var tmp;
act.data.auth = auth;
root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID.
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.
setTimeout(function(){ // we should be able to delete this now, right?
cat.ing = false;
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)
if(noop === cb){ gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
},10);
}
root.get('~@'+alias).once(act.a);
return gun;
}
// now that we have created a user, we want to authenticate them!
User.prototype.auth = function(alias, pass, cb, opt){
var gun = this, cat = (gun._), root = gun.back(-1);
cb = cb || function(){}; cb = cb || function(){};
if(cat.ing){ if(cat.ing){
cb({err: Gun.log("User is already being created or authenticated!"), wait: true}); cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
@ -25,234 +78,169 @@
} }
cat.ing = true; cat.ing = true;
opt = opt || {}; opt = opt || {};
var resolve = function(){}, reject = resolve; var pair = (alias.pub || alias.epub)? alias : (pass.pub || pass.epub)? pass : null;
// Because more than 1 user might have the same username, we treat the alias as a list of those users. var act = {}, u;
if(cb){ resolve = reject = cb } act.a = function(data){
gunRoot.get('~@'+username).get(async (at, ev) => { if(!data){ return act.b() }
ev.off() if(!data.pub){
if (at.put && !opt.already) { var tmp = [];
// If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed. Gun.node.is(data, function(v){ tmp.push(v) })
const err = 'User already created!' return act.b(tmp);
Gun.log(err)
cat.ing = false;
gun.leave();
return reject({ err: err })
} }
const salt = Gun.text.random(64) if(act.name){ return act.f(data) }
// pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it. act.c((act.data = data).auth);
try {
const proof = await SEA.work(pass, salt)
// this will take some short amount of time to produce a proof, which slows brute force attacks.
const pairs = await SEA.pair()
// now we have generated a brand new ECDSA key pair for the user account.
const pub = pairs.pub
const priv = pairs.priv
const epriv = pairs.epriv
// the user's public key doesn't need to be signed. But everything else needs to be signed with it!
const alias = await SEA.sign(username, pairs)
if(u === alias){ throw SEA.err }
const epub = await SEA.sign(pairs.epub, pairs)
if(u === epub){ throw SEA.err }
// to keep the private key safe, we AES encrypt it with the proof of work!
const auth = await SEA.encrypt({ priv: priv, epriv: epriv }, proof)
.then((auth) => // TODO: So signedsalt isn't needed?
// SEA.sign(salt, pairs).then((signedsalt) =>
SEA.sign({ek: auth, s: salt}, pairs)
// )
).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); cat.ing = false; gun.leave(); reject(e) })
const user = { alias: alias, pub: pub, epub: epub, auth: auth }
const tmp = '~'+pairs.pub;
// awesome, now we can actually save the user with their public key as their ID.
try{
gunRoot.get(tmp).put(user)
}catch(e){console.log(e)}
// next up, we want to associate the alias with the public key. So we add it to the alias list.
gunRoot.get('~@'+username).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp)))
// callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
setTimeout(() => { cat.ing = false; resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it.
} catch (e) {
Gun.log('SEA.create failed!')
cat.ing = false;
gun.leave();
reject(e)
}
})
return gun; // gun chain commands must return gun chains!
}
// now that we have created a user, we want to authenticate them!
User.prototype.auth = function(alias, pass, cb, opt){
// TODO: Needs to be cleaned up!!!!
const opts = opt || (typeof cb !== 'function' && cb)
let pin = opts && opts.pin
let newpass = opts && opts.newpass
const gunRoot = this.back(-1)
cb = typeof cb === 'function' ? cb : () => {}
newpass = newpass || (opts||{}).change;
var gun = this, cat = (gun._);
if(cat.ing){
cb({err: "User is already being created or authenticated!", wait: true});
return gun;
} }
cat.ing = true; act.b = function(list){
var get = (act.list = (act.list||[]).concat(list||[])).shift();
if (!pass && pin) { (async function(){ if(u === get){
try { 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.') }
var r = await authRecall(gunRoot, { alias: alias, pin: pin }) return act.err('Wrong user or password.')
return cat.ing = false, cb(r), gun;
} catch (e) {
var err = { err: 'Auth attempt failed! Reason: No session data for alias & PIN' }
return cat.ing = false, gun.leave(), cb(err), gun;
}}())
return gun;
}
const putErr = (msg) => (e) => {
const { message, err = message || '' } = e
Gun.log(msg)
var error = { err: msg+' Reason: '+err }
return cat.ing = false, gun.leave(), cb(error), gun;
}
(async function(){ try {
const keys = await authenticate(alias, pass, gunRoot)
if (!keys) {
return putErr('Auth attempt failed!')({ message: 'No keys' })
} }
const pub = keys.pub root.get(get).once(act.a);
const priv = keys.priv }
const epub = keys.epub act.c = function(auth){
const epriv = keys.epriv if(u === auth){ return act.b() }
// we're logged in! SEA.work(pass, (act.auth = auth).s, act.d); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
if (newpass) { }
// password update so encrypt private key using new pwd + salt act.d = function(proof){
try { if(u === proof){ return act.b() }
const salt = Gun.text.random(64); SEA.decrypt(act.auth.ek, proof, act.e);
const encSigAuth = await SEA.work(newpass, salt) }
.then((key) => act.e = function(half){
SEA.encrypt({ priv: priv, epriv: epriv }, key) if(u === half){ return act.b() }
.then((auth) => SEA.sign({ek: auth, s: salt}, keys)) act.half = half;
) act.f(act.data);
const signedEpub = await SEA.sign(epub, keys) }
const signedAlias = await SEA.sign(alias, keys) act.f = function(data){
const user = { if(!data || !data.pub){ return act.b() }
pub: pub, var tmp = act.half || {};
alias: signedAlias, act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv});
auth: encSigAuth, }
epub: signedEpub act.g = function(pair){
} act.pair = pair;
// awesome, now we can update the user using public key ID. var user = (root._).user, at = (user._);
gunRoot.get('~'+user.pub).put(user) var tmp = at.tag;
// then we're done var upt = at.opt;
const login = finalizeLogin(alias, keys, gunRoot, { pin }) at = user._ = root.get('~'+pair.pub)._;
login.catch(putErr('Failed to finalize login with new password!')) at.opt = upt;
return cat.ing = false, cb(await login), gun // add our credentials in-memory only to our root user instance
} catch (e) { user.is = {pub: pair.pub, epub: pair.epub, alias: alias};
return putErr('Password set attempt failed!')(e) at.sea = act.pair;
} cat.ing = false;
} else { opt.change? act.z() : cb(at);
const login = finalizeLogin(alias, keys, gunRoot, { pin: pin }) if(SEA.window && ((gun.back('user')._).opt||opt).remember){
login.catch(putErr('Finalizing login failed!')) // TODO: this needs to be modular.
return cat.ing = false, cb(await login), gun; var sS = {}; try{sS = window.sessionStorage}catch(e){}
sS.recall = true;
sS.alias = alias;
sS.tmp = pass;
} }
} catch (e) { try{
return putErr('Auth attempt failed!')(e) (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do.
} }()); //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.
}catch(e){
Gun.log("Your 'auth' callback crashed with:", e);
}
}
act.z = function(){
// password update so encrypt private key using new pwd + salt
act.salt = Gun.text.random(64); // pseudo-random
SEA.work(opt.change, act.salt, act.y);
}
act.y = function(proof){
SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x);
}
act.x = function(auth){
SEA.sign({ek: auth, s: act.salt}, act.pair, act.w);
}
act.w = function(auth){
root.get('~'+act.pair.pub).get('auth').put(auth, cb);
}
act.err = function(e){
var ack = {err: Gun.log(e || 'User cannot be found!')};
cat.ing = false;
cb(ack);
}
act.plugin = function(name){
if(!(act.name = name)){ return act.err() }
var tmp = [name];
if('~' !== name[0]){
tmp[1] = '~'+name;
tmp[2] = '~@'+name;
}
act.b(tmp);
}
if(pair){
act.g(pair);
} else
if(alias){
root.get('~@'+alias).once(act.a);
} else
if(!alias && !pass){
SEA.name(act.plugin);
}
return gun; return gun;
} }
User.prototype.pair = function(){ User.prototype.pair = function(){
console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!");
var user = this; var user = this;
if(!user.is){ return false } if(!user.is){ return false }
return user._.sea; return user._.sea;
} }
User.prototype.leave = async function(){ User.prototype.leave = function(opt, cb){
var gun = this, user = (gun.back(-1)._).user; var gun = this, user = (gun.back(-1)._).user;
if(user){ if(user){
delete user.is; delete user.is;
delete user._.is; delete user._.is;
delete user._.sea; delete user._.sea;
} }
if(typeof window !== 'undefined'){ if(SEA.window){
var tmp = window.sessionStorage; var sS = {}; try{sS = window.sessionStorage}catch(e){};
delete tmp.alias; delete sS.alias;
delete tmp.tmp; delete sS.tmp;
delete sS.recall;
} }
return await authLeave(this.back(-1)) return gun;
} }
// If authenticated user wants to delete his/her account, let's support it! // If authenticated user wants to delete his/her account, let's support it!
User.prototype.delete = async function(alias, pass){ User.prototype.delete = async function(alias, pass, cb){
const gunRoot = this.back(-1) var gun = this, root = gun.back(-1), user = gun.back('user');
try { try {
const __gky40 = await authenticate(alias, pass, gunRoot) user.auth(alias, pass, function(ack){
const pub = __gky40.pub var pub = (user.is||{}).pub;
await authLeave(gunRoot, alias) // Delete user data
// Delete user data user.map().once(function(){ this.put(null) });
gunRoot.get('~'+pub).put(null) // Wipe user data from memory
// Wipe user data from memory user.leave();
const { user = { _: {} } } = gunRoot._; (cb || noop)({ok: 0});
// TODO: is this correct way to 'logout' user from Gun.User ? });
[ 'alias', 'sea', 'pub' ].map((key) => delete user._[key])
user._.is = user.is = {}
gunRoot.user()
return { ok: 0 } // TODO: proper return codes???
} catch (e) { } catch (e) {
Gun.log('User.delete failed! Error:', e) Gun.log('User.delete failed! Error:', e);
throw e // TODO: proper error codes???
} }
return gun;
} }
// If authentication is to be remembered over reloads or browser closing, User.prototype.recall = function(opt, cb){
// set validity time in minutes. var gun = this, root = gun.back(-1), tmp;
User.prototype.recall = function(setvalidity, options){ opt = opt || {};
var gun = this; if(opt && opt.sessionStorage){
const gunRoot = this.back(-1) if(SEA.window){
var sS = {}; try{sS = window.sessionStorage}catch(e){}
let validity if(sS){
let opts (root._).opt.remember = true;
((gun.back('user')._).opt||opt).remember = true;
var o = setvalidity; if(sS.recall || (sS.alias && sS.tmp)){
if(o && o.sessionStorage){ root.user().auth(sS.alias, sS.tmp, cb);
if(typeof window !== 'undefined'){
var tmp = window.sessionStorage;
if(tmp){
gunRoot._.opt.remember = true;
if(tmp.alias && tmp.tmp){
gunRoot.user().auth(tmp.alias, tmp.tmp);
} }
} }
} }
return gun; return gun;
} }
/*
if (!Gun.val.is(setvalidity)) { TODO: copy mhelander's expiry code back in.
opts = setvalidity Although, we should check with community,
validity = _initial_authsettings.validity should expiry be core or a plugin?
} else { */
opts = options return gun;
validity = setvalidity * 60 // minutes to seconds
}
try {
// opts = { hook: function({ iat, exp, alias, proof }) }
// iat == Date.now() when issued, exp == seconds to expire from iat
// How this works:
// called when app bootstraps, with wanted options
// IF authsettings.validity === 0 THEN no remember-me, ever
// IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
authsettings.validity = typeof validity !== 'undefined'
? validity : _initial_authsettings.validity
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?
(async function(){ await authRecall(gunRoot) }());
return gun;
} catch (e) {
const err = 'No session!'
Gun.log(err)
// NOTE! It's fine to resolve recall with reason why not successful
// instead of rejecting...
//return { err: (e && e.err) || err }
return gun;
}
} }
User.prototype.alive = async function(){ User.prototype.alive = async function(){
const gunRoot = this.back(-1) const gunRoot = this.back(-1)
@ -278,7 +266,7 @@
User.prototype.grant = function(to, cb){ User.prototype.grant = function(to, cb){
console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
gun.back(function(at){ if(at.pub){ return } path += (at.get||'') }); gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
(async function(){ (async function(){
var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
sec = await SEA.decrypt(sec, pair); sec = await SEA.decrypt(sec, pair);
@ -299,7 +287,7 @@
User.prototype.secret = function(data, cb){ User.prototype.secret = function(data, cb){
console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
gun.back(function(at){ if(at.pub){ return } path += (at.get||'') }); gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
(async function(){ (async function(){
var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
sec = await SEA.decrypt(sec, pair); sec = await SEA.decrypt(sec, pair);

View File

@ -6,8 +6,12 @@
var parse = require('./parse'); var parse = require('./parse');
SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try { SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
var opt = opt || {}; opt = opt || {};
const key = pair.epriv || pair; var key = (pair||opt).epriv || pair;
if(!key){
pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why});
key = pair.epriv || pair;
}
const json = parse(data) const json = parse(data)
const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt) const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt)
.then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible...

View File

@ -5,8 +5,12 @@
var aeskey = require('./aeskey'); var aeskey = require('./aeskey');
SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try { SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
var opt = opt || {}; opt = opt || {};
const key = pair.epriv || pair; var key = (pair||opt).epriv || pair;
if(!key){
pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why});
key = pair.epriv || pair;
}
const msg = JSON.stringify(data) const msg = JSON.stringify(data)
const rand = {s: shim.random(8), iv: shim.random(16)}; const rand = {s: shim.random(8), iv: shim.random(16)};
const ct = await aeskey(key, rand.s, opt) const ct = await aeskey(key, rand.s, opt)

View File

@ -62,7 +62,7 @@
// if there is a request to read data from us, then... // if there is a request to read data from us, then...
var soul = msg.get['#']; var soul = msg.get['#'];
if(soul){ // for now, only allow direct IDs to be read. if(soul){ // for now, only allow direct IDs to be read.
if(typeof soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors. if(soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
if('alias' === soul){ // Allow reading the list of usernames/aliases in the system? if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
return to.next(msg); // yes. return to.next(msg); // yes.
} else } else
@ -111,9 +111,9 @@
return each.end({err: "Account must match!"}); return each.end({err: "Account must match!"});
} }
check['user'+soul+key] = 1; check['user'+soul+key] = 1;
if(user && (user = user._) && user.sea && pub === user.pub){ if(user && user.is && pub === user.is.pub){
//var id = Gun.text.random(3); //var id = Gun.text.random(3);
SEA.sign(val, user.sea, function(data){ var rel; SEA.sign(val, (user._).sea, function(data){ var rel;
if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) } if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) }
if(rel = Gun.val.link.is(val)){ if(rel = Gun.val.link.is(val)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
@ -146,7 +146,7 @@
return s; return s;
} }
each.any = function(val, key, node, soul, user){ var tmp, pub; each.any = function(val, key, node, soul, user){ var tmp, pub;
if(!user || !(user = user._) || !(user = user.sea)){ if(!user || !user.is){
if(tmp = relpub(soul)){ if(tmp = relpub(soul)){
check['any'+soul+key] = 1; check['any'+soul+key] = 1;
SEA.verify(val, pub = tmp, function(data){ var rel; SEA.verify(val, pub = tmp, function(data){ var rel;
@ -187,20 +187,19 @@
//}); //});
return; return;
} }
var pub = tmp; if((pub = tmp) !== (user.is||noop).pub){
if(pub !== user.pub){
each.any(val, key, node, soul); each.any(val, key, node, soul);
return; return;
} }
/*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
if(user.pub !== p){ return p } if((user.is||{}).pub !== p){ return p }
}); });
if(other){ if(other){
each.any(val, key, node, soul); each.any(val, key, node, soul);
return; return;
}*/ }*/
check['any'+soul+key] = 1; check['any'+soul+key] = 1;
SEA.sign(val, user, function(data){ SEA.sign(val, (user._).sea, function(data){
if(u === data){ return each.end({err: 'My signature fail.'}) } if(u === data){ return each.end({err: 'My signature fail.'}) }
node[key] = data; node[key] = data;
check['any'+soul+key] = 0; check['any'+soul+key] = 0;
@ -210,7 +209,7 @@
each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb? each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb?
if(each.err){ return } if(each.err){ return }
if((each.err = ctx.err) || ctx.no){ if((each.err = ctx.err) || ctx.no){
console.log('NO!', each.err, msg.put); console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI
return; return;
} }
if(!each.end.ed){ return } if(!each.end.ed){ return }
@ -225,5 +224,6 @@
} }
to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols). to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
} }
var noop = {};

View File

@ -1,21 +0,0 @@
const authPersist = require('./persist')
const authsettings = require('./settings')
//const { scope: seaIndexedDb } = require('./indexed')
// This internal func executes logout actions
const authLeave = async (gunRoot, alias = gunRoot._.user._.alias) => {
var user = gunRoot._.user._ || {};
[ 'get', 'soul', 'ack', 'put', 'is', 'alias', 'pub', 'epub', 'sea' ].map((key) => delete user[key])
if(user.$){
delete user.$.is;
}
// Let's use default
gunRoot.user();
// Removes persisted authentication & CryptoKeys
try {
await authPersist({ alias: alias })
} catch (e) {} //eslint-disable-line no-empty
return { ok: 0 }
}
module.exports = authLeave

View File

@ -1,49 +0,0 @@
const authPersist = require('./persist')
// This internal func finalizes User authentication
const finalizeLogin = async (alias, key, gunRoot, opts) => {
const user = gunRoot._.user
// add our credentials in-memory only to our root gun instance
var tmp = user._.tag;
var opt = user._.opt;
user._ = gunRoot.get('~'+key.pub)._;
user._.opt = opt;
var tags = user._.tag;
/*Object.values && Object.values(tmp).forEach(function(tag){
// TODO: This is ugly & buggy code, it needs to be refactored & tested into a event "merge" utility.
var t = tags[tag.tag];
console.log("hm??", tag, t);
if(!t){
tags[tag.tag] = tag;
return;
}
if(tag.last){
tag.last.to = t.to;
t.last = tag.last = t.last || tag.last;
}
t.to = tag.to;
})*/
//user._.tag = tmp || user._.tag;
// so that way we can use the credentials to encrypt/decrypt data
// that is input/output through gun (see below)
const pub = key.pub
const priv = key.priv
const epub = key.epub
const epriv = key.epriv
user._.is = user.is = {alias: alias, pub: pub};
Object.assign(user._, { alias: alias, pub: pub, epub: epub, sea: { pub: pub, priv: priv, epub: epub, epriv: epriv } })
//console.log("authorized", user._);
// persist authentication
//await authPersist(user._, key.proof, opts) // temporarily disabled
// emit an auth event, useful for page redirects and stuff.
try {
gunRoot._.on('auth', user._) // TODO: Deprecate this, emit on user instead! Update docs when you do.
//user._.on('auth', user._) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
} catch (e) {
console.log('Your \'auth\' callback crashed with:', e)
}
// returns success with the user data credentials.
return user._
}
module.exports = finalizeLogin

View File

@ -4,8 +4,18 @@
var S = require('./settings'); var S = require('./settings');
var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer; var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer;
SEA.name = SEA.name || (async (cb, opt) => { try {
if(cb){ try{ cb() }catch(e){console.log(e)} }
return;
} catch(e) {
console.log(e);
SEA.err = e;
if(cb){ cb() }
return;
}});
//SEA.pair = async (data, proof, cb) => { try { //SEA.pair = async (data, proof, cb) => { try {
SEA.pair = SEA.pair || (async (cb) => { try { SEA.pair = SEA.pair || (async (cb, opt) => { try {
const ecdhSubtle = shim.ossl || shim.subtle const ecdhSubtle = shim.ossl || shim.subtle
// First: ECDSA keys for signing/verifying... // First: ECDSA keys for signing/verifying...

View File

@ -1,41 +0,0 @@
const SEA = require('./sea');
const Gun = SEA.Gun;
const Buffer = require('./buffer')
const authsettings = require('./settings')
const updateStorage = require('./update')
// This internal func persists User authentication if so configured
const authPersist = async (user, proof, opts) => {
// opts = { pin: 'string' }
// no opts.pin then uses random PIN
// How this works:
// called when app bootstraps, with wanted options
// IF authsettings.validity === 0 THEN no remember-me, ever
// IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
const pin = Buffer.from(
(Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10),
'utf8'
).toString('base64')
const alias = user.alias
const exp = authsettings.validity // seconds // @mhelander what is `exp`???
if (proof && alias && exp) {
const iat = Math.ceil(Date.now() / 1000) // seconds
const remember = Gun.obj.has(opts, 'pin') || undefined // for hook - not stored
const props = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
const pub = user.pub
const epub = user.epub
const priv = user.sea.priv
const epriv = user.sea.epriv
const key = { pub: pub, priv: priv, epub: epub, epriv: epriv }
if (props instanceof Promise) {
const asyncProps = await props.then()
return await updateStorage(proof, key, pin)(asyncProps)
}
return await updateStorage(proof, key, pin)(props)
}
return await updateStorage()({ alias: 'delete' })
}
module.exports = authPersist

View File

@ -1,43 +0,0 @@
var SEA = require('./sea');
var Gun = SEA.Gun;
// This is internal func queries public key(s) for alias.
const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => {
// load all public keys associated with the username alias we want to log in with.
gunRoot.get('~@'+alias).once((data, key) => {
//rev.off();
if (!data) {
// if no user, don't do anything.
const err = 'No user!'
Gun.log(err)
return reject({ err })
}
// then figuring out all possible candidates having matching username
const aliases = []
let c = 0
// TODO: how about having real chainable map without callback ?
Gun.obj.map(data, (at, pub) => {
if (!pub.slice || '~' !== pub.slice(0, 1)) {
// TODO: ... this would then be .filter((at, pub))
return
}
++c
// grab the account associated with this public key.
gunRoot.get(pub).once(data => {
pub = pub.slice(1)
--c
if (data){
aliases.push({ pub, put: data })
}
if (!c && (c = -1)) {
resolve(aliases)
}
})
})
if (!c) {
reject({ err: 'Public key does not exist!' })
}
})
})
module.exports = queryGunAliases

View File

@ -1,141 +0,0 @@
const Buffer = require('./buffer')
const authsettings = require('./settings')
//const { scope: seaIndexedDb } = require('./indexed')
const queryGunAliases = require('./query')
const parseProps = require('./parse')
const updateStorage = require('./update')
const SEA = require('./sea')
const Gun = SEA.Gun;
const finalizeLogin = require('./login')
// This internal func recalls persisted User authentication if so configured
const authRecall = async (gunRoot, authprops) => {
// window.sessionStorage only holds signed { alias, pin } !!!
const remember = authprops || sessionStorage.getItem('remember')
const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} // @mhelander what is pIn?
const pin = pIn && Buffer.from(pIn, 'utf8').toString('base64')
// Checks for existing proof, matching alias and expiration:
const checkRememberData = async ({ proof, alias: aLias, iat, exp, remember }) => {
if (!!proof && alias === aLias) {
const checkNotExpired = (args) => {
if (Math.floor(Date.now() / 1000) < (iat + args.exp)) {
// No way hook to update 'iat'
return Object.assign(args, { iat: iat, proof: proof })
} else {
Gun.log('Authentication expired!')
}
}
// We're not gonna give proof to hook!
const hooked = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
return ((hooked instanceof Promise)
&& await hooked.then(checkNotExpired)) || checkNotExpired(hooked)
}
}
const readAndDecrypt = async (data, pub, key) =>
parseProps(await SEA.decrypt(await SEA.verify(data, pub), key))
// Already authenticated?
if (gunRoot._.user
&& Gun.obj.has(gunRoot._.user._, 'pub')
&& Gun.obj.has(gunRoot._.user._, 'sea')) {
return gunRoot._.user._ // Yes, we're done here.
}
// No, got persisted 'alias'?
if (!alias) {
throw { err: 'No authentication session found!' }
}
// Yes, got persisted 'remember'?
if (!remember) {
throw { // And return proof if for matching alias
err: (await seaIndexedDb.get(alias, 'auth') && authsettings.validity
&& 'Missing PIN and alias!') || 'No authentication session found!'
}
}
// Yes, let's get (all?) matching aliases
const aliases = (await queryGunAliases(alias, gunRoot))
.filter(({ pub } = {}) => !!pub)
// Got any?
if (!aliases.length) {
throw { err: 'Public key does not exist!' }
}
let err
// Yes, then attempt to log into each one until we find ours!
// (if two users have the same username AND the same password... that would be bad)
const [ { key, at, proof, pin: newPin } = {} ] = await Promise
.all(aliases.filter(({ at: { put } = {} }) => !!put)
.map(async ({ at: at, pub: pub }) => {
const readStorageData = async (args) => {
const props = args || parseProps(await SEA.verify(remember, pub, true))
let pin = props.pin
let aLias = props.alias
const data = (!pin && alias === aLias)
// No PIN, let's try short-term proof if for matching alias
? await checkRememberData(props)
// Got PIN so get IndexedDB secret if signature is ok
: await checkRememberData(await readAndDecrypt(await seaIndexedDb.get(alias, 'auth'), pub, pin))
pin = pin || data.pin
delete data.pin
return { pin: pin, data: data }
}
// got pub, try auth with pin & alias :: or unwrap Storage data...
const __gky20 = await readStorageData(pin && { pin, alias })
const data = __gky20.data
const newPin = __gky20.pin
const proof = data.proof
if (!proof) {
if (!data) {
err = 'No valid authentication session found!'
return
}
try { // Wipes IndexedDB silently
await updateStorage()(data)
} catch (e) {} //eslint-disable-line no-empty
err = 'Expired session!'
return
}
try { // auth parsing or decryption fails or returns empty - silently done
const auth= at.put.auth.auth
const sea = await SEA.decrypt(auth, proof)
if (!sea) {
err = 'Failed to decrypt private key!'
return
}
const priv = sea.priv
const epriv = sea.epriv
const epub = at.put.epub
// Success! we've found our private data!
err = null
return { proof: proof, at: at, pin: newPin, key: { pub: pub, priv: priv, epriv: epriv, epub: epub } }
} catch (e) {
err = 'Failed to decrypt private key!'
return
}
}).filter((props) => !!props))
if (!key) {
throw { err: err || 'Public key does not exist!' }
}
// now we have AES decrypted the private key,
// if we were successful, then that means we're logged in!
try {
await updateStorage(proof, key, newPin || pin)(key)
const user = Object.assign(key, { at: at, proof: proof })
const pIN = newPin || pin
const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') }
return await finalizeLogin(alias, user, gunRoot, pinProp)
} catch (e) { // TODO: right log message ?
Gun.log('Failed to finalize login with new password!')
const { err = '' } = e || {}
throw { err: 'Finalizing new password login failed! Reason: '+err }
}
}
module.exports = authRecall

View File

@ -7,15 +7,9 @@
if(typeof window !== "undefined"){ module.window = window } if(typeof window !== "undefined"){ module.window = window }
var tmp = module.window || module; var tmp = module.window || module;
var SEA = tmp.SEA || function(){}; var SEA = tmp.SEA || {};
if(SEA.window = module.window){ try{ if(SEA.window = module.window){ SEA.window.SEA = SEA }
SEA.window.SEA = SEA;
tmp = document.createEvent('CustomEvent');
tmp.initCustomEvent('extension', false, false, {type: "SEA"});
(window.dispatchEvent || window.fireEvent)(tmp);
window.postMessage({type: "SEA"}, '*');
} catch(e){} }
try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){} try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
module.exports = SEA; module.exports = SEA;

View File

@ -1,5 +1,6 @@
// Old Code... // Old Code...
try{
const __gky10 = require('./shim') const __gky10 = require('./shim')
const crypto = __gky10.crypto const crypto = __gky10.crypto
const subtle = __gky10.subtle const subtle = __gky10.subtle
@ -18,8 +19,8 @@
const keysToEcdsaJwk = __gky11.jwk const keysToEcdsaJwk = __gky11.jwk
const sha1hash = require('./sha1') const sha1hash = require('./sha1')
const sha256hash = require('./sha256') const sha256hash = require('./sha256')
const recallCryptoKey = require('./remember')
const parseProps = require('./parse') const parseProps = require('./parse')
}catch(e){}
// Practical examples about usage found from ./test/common.js // Practical examples about usage found from ./test/common.js
const SEA = require('./root'); const SEA = require('./root');
@ -75,7 +76,7 @@
// Cheers! Tell me what you think. // Cheers! Tell me what you think.
var Gun = (SEA.window||{}).Gun || require('./gun', 1); var Gun = (SEA.window||{}).Gun || require('./gun', 1);
Gun.SEA = SEA; Gun.SEA = SEA;
SEA.Gun = Gun; SEA.GUN = SEA.Gun = Gun;
module.exports = SEA module.exports = SEA

View File

@ -2,8 +2,12 @@
var SEA = require('./root'); var SEA = require('./root');
var shim = require('./shim'); var shim = require('./shim');
var S = require('./settings'); var S = require('./settings');
// Derive shared secret from other's pub and my epub/epriv // Derive shared secret from other's pub and my epub/epriv
SEA.secret = SEA.secret || (async (key, pair, cb) => { try { SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try {
opt = opt || {};
if(!pair || !pair.epriv || !pair.epub){
pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why});
}
const pub = key.epub || key const pub = key.epub || key
const epub = pair.epub const epub = pair.epub
const epriv = pair.epriv const epriv = pair.epriv

View File

@ -1,43 +1,38 @@
const SEA = require('./root')
const Buffer = require('./buffer') const Buffer = require('./buffer')
const api = {Buffer: Buffer} const api = {Buffer: Buffer}
var o = {};
if (typeof window !== 'undefined') { if(SEA.window){
var crypto = window.crypto || window.msCrypto; api.crypto = window.crypto || window.msCrypto;
var subtle = crypto.subtle || crypto.webkitSubtle; api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
const TextEncoder = window.TextEncoder api.TextEncoder = window.TextEncoder;
const TextDecoder = window.TextDecoder api.TextDecoder = window.TextDecoder;
api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))))
}
if(!api.crypto){try{
var crypto = require('crypto', 1);
const { subtle } = require('@trust/webcrypto', 1) // All but ECDH
const { TextEncoder, TextDecoder } = require('text-encoding', 1)
Object.assign(api, { Object.assign(api, {
crypto, crypto,
subtle, subtle,
TextEncoder, TextEncoder,
TextDecoder, TextDecoder,
random: (len) => Buffer.from(crypto.getRandomValues(new Uint8Array(Buffer.alloc(len)))) random: (len) => Buffer.from(crypto.randomBytes(len))
}) });
} else { //try{
try{ const WebCrypto = require('node-webcrypto-ossl', 1)
var crypto = require('crypto', 1); api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
const { subtle } = require('@trust/webcrypto', 1) // All but ECDH //}catch(e){
const { TextEncoder, TextDecoder } = require('text-encoding', 1) //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
Object.assign(api, { //}
crypto, }catch(e){
subtle, console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!");
TextEncoder, console.log("node-webcrypto-ossl is temporarily needed for ECDSA signature verification, and optionally needed for ECDH, please install if needed (currently necessary so add them to your package.json for now).");
TextDecoder, TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;
random: (len) => Buffer.from(crypto.randomBytes(len)) }}
});
//try{
const WebCrypto = require('node-webcrypto-ossl', 1)
api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
//}catch(e){
//console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
//}
}catch(e){
console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!");
console.log("node-webcrypto-ossl is temporarily needed for ECDSA signature verification, and optionally needed for ECDH, please install if needed (currently necessary so add them to your package.json for now).");
TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;
}
}
module.exports = api module.exports = api

View File

@ -4,7 +4,7 @@
var S = require('./settings'); var S = require('./settings');
var sha256hash = require('./sha256'); var sha256hash = require('./sha256');
SEA.sign = SEA.sign || (async (data, pair, cb) => { try { SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try {
if(data && data.slice if(data && data.slice
&& 'SEA{' === data.slice(0,4) && 'SEA{' === data.slice(0,4)
&& '"m":' === data.slice(4,8)){ && '"m":' === data.slice(4,8)){
@ -14,6 +14,10 @@
if(cb){ try{ cb(data) }catch(e){console.log(e)} } if(cb){ try{ cb(data) }catch(e){console.log(e)} }
return data; return data;
} }
opt = opt || {};
if(!(pair||opt).priv){
pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why});
}
const pub = pair.pub const pub = pair.pub
const priv = pair.priv const priv = pair.priv
const jwk = S.jwk(pub, priv) const jwk = S.jwk(pub, priv)

View File

@ -1,48 +0,0 @@
const authsettings = require('./settings')
const SEA = require('./sea');
const Gun = SEA.Gun;
//const { scope: seaIndexedDb } = require('./indexed')
// This updates sessionStorage & IndexedDB to persist authenticated "session"
const updateStorage = (proof, key, pin) => async (props) => {
if (!Gun.obj.has(props, 'alias')) {
return // No 'alias' - we're done.
}
if (authsettings.validity && proof && Gun.obj.has(props, 'iat')) {
props.proof = proof
delete props.remember // Not stored if present
const alias = props.alias
const id = props.alias
const remember = { alias: alias, pin: pin }
try {
const signed = await SEA.sign(JSON.stringify(remember), key)
sessionStorage.setItem('user', alias)
sessionStorage.setItem('remember', signed)
const encrypted = await SEA.encrypt(props, pin)
if (encrypted) {
const auth = await SEA.sign(encrypted, key)
await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out.
await seaIndexedDb.put(id, { auth: auth })
}
return props
} catch (err) {
throw { err: 'Session persisting failed!' }
}
}
// Wiping IndexedDB completely when using random PIN
await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out.
// And remove sessionStorage data
sessionStorage.removeItem('user')
sessionStorage.removeItem('remember')
return props
}
module.exports = updateStorage

View File

@ -19,7 +19,7 @@
(at = (user = at.user = gun.chain(new User))._).opt = {}; (at = (user = at.user = gun.chain(new User))._).opt = {};
at.opt.uuid = function(cb){ at.opt.uuid = function(cb){
var id = uuid(), pub = root.user; var id = uuid(), pub = root.user;
if(!pub || !(pub = (pub._).sea) || !(pub = pub.pub)){ return id } if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
id = id + '~' + pub + '.'; id = id + '~' + pub + '.';
if(cb && cb.call){ cb(null, id) } if(cb && cb.call){ cb(null, id) }
return id; return id;

View File

@ -6,7 +6,7 @@
var parse = require('./parse'); var parse = require('./parse');
var u; var u;
SEA.verify = SEA.verify || (async (data, pair, cb) => { try { SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try {
const json = parse(data) const json = parse(data)
if(false === pair){ // don't verify! if(false === pair){ // don't verify!
const raw = (json !== data)? const raw = (json !== data)?
@ -15,6 +15,9 @@
if(cb){ try{ cb(raw) }catch(e){console.log(e)} } if(cb){ try{ cb(raw) }catch(e){console.log(e)} }
return raw; return raw;
} }
opt = opt || {};
// SEA.I // verify is free! Requires no user permission.
if(json === data){ throw "No signature on data." }
const pub = pair.pub || pair const pub = pair.pub || pair
const jwk = S.jwk(pub) const jwk = S.jwk(pub)
const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify'])
@ -27,7 +30,7 @@
if(cb){ try{ cb(r) }catch(e){console.log(e)} } if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r; return r;
} catch(e) { } catch(e) {
console.log(e); console.log(e); // mismatched owner FOR MARTTI
SEA.err = e; SEA.err = e;
if(cb){ cb() } if(cb){ cb() }
return; return;

View File

@ -1,10 +1,12 @@
if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs? if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs?
var root, noop = function(){}, u; var root, noop = function(){}, store, u;
if(typeof window !== 'undefined'){ root = window } try{store = (Gun.window||noop).localStorage}catch(e){}
var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop}; if(!store){
console.log("Warning: No localStorage exists to persist data to!");
store = {setItem: noop, removeItem: noop, getItem: noop};
}
/* /*
NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design! NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design!
If you update anything here, consider updating the other adapters as well. If you update anything here, consider updating the other adapters as well.

View File

@ -78,19 +78,19 @@ function val(msg, eve, to){
var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp; var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp;
if(tmp = msg.$$){ if(tmp = msg.$$){
link = tmp = (msg.$$._); link = tmp = (msg.$$._);
if(u === tmp.put){ if(u !== link.put){
return; data = link.put;
} }
data = tmp.put;
} }
if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) } if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) }
if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))) if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack))))
|| (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (link||at).ack <= tmp)){ || (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (!to && (link||at).ack <= tmp))){
tmp = (eve.wait = {})[at.id] = setTimeout(function(){ tmp = (eve.wait = {})[at.id] = setTimeout(function(){
val.call({as:opt}, msg, eve, tmp || 1); val.call({as:opt}, msg, eve, tmp || 1);
}, opt.wait || 99); }, opt.wait || 99);
return; return;
} }
if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) }
eve.rid(msg); eve.rid(msg);
opt.ok.call(gun || opt.$, data, msg.get); opt.ok.call(gun || opt.$, data, msg.get);
} }