|  | @@ -1,15 +1,15 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    const SEA = require('./sea')
 | 
	
		
			
				|  |  | -    const Gun = SEA.Gun;
 | 
	
		
			
				|  |  | +    var SEA = require('./sea')
 | 
	
		
			
				|  |  | +    var Gun = SEA.Gun;
 | 
	
		
			
				|  |  |      // 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)
 | 
	
		
			
				|  |  |      Gun.on('opt', function(at){
 | 
	
		
			
				|  |  |        if(!at.sea){ // only add SEA once per instance, on the "at" context.
 | 
	
		
			
				|  |  |          at.sea = {own: {}};
 | 
	
		
			
				|  |  | -        at.on('in', security, at); // now listen to all input data, acting as a firewall.
 | 
	
		
			
				|  |  | -        at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
 | 
	
		
			
				|  |  | -        at.on('node', each, at);
 | 
	
		
			
				|  |  | +        //at.on('in', security, at); // now listen to all input data, acting as a firewall.
 | 
	
		
			
				|  |  | +        //at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
 | 
	
		
			
				|  |  | +        at.on('put', check, at);
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        this.to.next(at); // make sure to call the "next" middleware adapter.
 | 
	
		
			
				|  |  |      });
 | 
	
	
		
			
				|  | @@ -58,6 +58,94 @@
 | 
	
		
			
				|  |  |        security.call(this, msg);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    var u;
 | 
	
		
			
				|  |  | +    function check(msg){ // REVISE / IMPROVE, NO NEED TO PASS MSG/EVE EACH SUB?
 | 
	
		
			
				|  |  | +      var eve = this, at = eve.as, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp;
 | 
	
		
			
				|  |  | +      if(!soul || !key){ return }
 | 
	
		
			
				|  |  | +      if((msg._||'').faith && (at.opt||'').faith && 'function' == typeof msg._){
 | 
	
		
			
				|  |  | +        SEA.verify(SEA.opt.pack(put), false, function(data){ // this is synchronous if false
 | 
	
		
			
				|  |  | +          put['='] = SEA.opt.unpack(data);
 | 
	
		
			
				|  |  | +          eve.to.next(msg);
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        return 
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      var no = function(why){ at.on('in', {'@': id, err: why}) };
 | 
	
		
			
				|  |  | +      //var no = function(why){ msg.ack(why) };
 | 
	
		
			
				|  |  | +      (msg._||'').DBG && ((msg._||'').DBG.c = +new Date);
 | 
	
		
			
				|  |  | +      if(0 <= soul.indexOf('<?')){ // special case for "do not sync data X old"
 | 
	
		
			
				|  |  | +        // 'a~pub.key/b<?9'
 | 
	
		
			
				|  |  | +        tmp = parseFloat(soul.split('<?')[1]||'');
 | 
	
		
			
				|  |  | +        if(tmp && (state < (Gun.state() - (tmp * 1000)))){ // sec to ms
 | 
	
		
			
				|  |  | +          (tmp = msg._) && (tmp = tmp.lot) && (tmp.more--); // THIS IS BAD CODE! It assumes GUN internals do something that will probably change in future, but hacking in now.
 | 
	
		
			
				|  |  | +          return; // omit!
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if('~@' === soul){  // special case for shared system data, the list of aliases.
 | 
	
		
			
				|  |  | +        check.alias(eve, msg, val, key, soul, at, no); return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
 | 
	
		
			
				|  |  | +        check.pubs(eve, msg, val, key, soul, at, no); return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      //if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
 | 
	
		
			
				|  |  | +      if(tmp = SEA.opt.pub(soul)){ // special case, account data for a public key.
 | 
	
		
			
				|  |  | +        check.pub(eve, msg, val, key, soul, at, no, at.user||'', tmp); return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if(0 <= soul.indexOf('#')){ // special case for content addressing immutable hashed data.
 | 
	
		
			
				|  |  | +        check.hash(eve, msg, val, key, soul, at, no); return;
 | 
	
		
			
				|  |  | +      } 
 | 
	
		
			
				|  |  | +      check.any(eve, msg, val, key, soul, at, no, at.user||''); return;
 | 
	
		
			
				|  |  | +      eve.to.next(msg); // not handled
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    check.hash = function(eve, msg, val, key, soul, at, no){
 | 
	
		
			
				|  |  | +      SEA.work(val, null, function(data){
 | 
	
		
			
				|  |  | +        if(data && data === key.split('#').slice(-1)[0]){ return eve.to.next(msg) }
 | 
	
		
			
				|  |  | +        no("Data hash not same as hash!");
 | 
	
		
			
				|  |  | +      }, {name: 'SHA-256'});
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    check.alias = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@, ~@alice: {#~@alice}}
 | 
	
		
			
				|  |  | +      if(!val){ return no("Data must exist!") } // data MUST exist
 | 
	
		
			
				|  |  | +      if('~@'+key === link_is(val)){ return eve.to.next(msg) } // in fact, it must be EXACTLY equal to itself
 | 
	
		
			
				|  |  | +      no("Alias not same!"); // if it isn't, reject.
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +    check.pubs = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
 | 
	
		
			
				|  |  | +      if(!val){ return no("Alias must exist!") } // data MUST exist
 | 
	
		
			
				|  |  | +      if(key === link_is(val)){ return eve.to.next(msg) } // and the ID must be EXACTLY equal to its property
 | 
	
		
			
				|  |  | +      no("Alias not same!"); // that way nobody can tamper with the list of public keys.
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +    check.pub = function(eve, msg, val, key, soul, at, no, user, pub){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
 | 
	
		
			
				|  |  | +      if('pub' === key && '~'+pub === soul){
 | 
	
		
			
				|  |  | +        if(val === pub){ return eve.to.next(msg) } // the account MUST match `pub` property that equals the ID of the public key.
 | 
	
		
			
				|  |  | +        return no("Account not same!");
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if((tmp = user.is) && pub === tmp.pub){
 | 
	
		
			
				|  |  | +        SEA.sign(SEA.opt.pack(msg.put), (user._).sea, function(data){
 | 
	
		
			
				|  |  | +          if(u === data){ return no(SEA.err || 'Signature fail.') }
 | 
	
		
			
				|  |  | +          if(tmp = link_is(val)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
 | 
	
		
			
				|  |  | +          msg.put[':'] = JSON.stringify({':': tmp = SEA.opt.unpack(data.m), '~': data.s});
 | 
	
		
			
				|  |  | +          msg.put['='] = tmp;
 | 
	
		
			
				|  |  | +          eve.to.next(msg);
 | 
	
		
			
				|  |  | +        }, {raw: 1});
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      SEA.verify(SEA.opt.pack(msg.put), pub, function(data){ var tmp;
 | 
	
		
			
				|  |  | +        data = SEA.opt.unpack(data);
 | 
	
		
			
				|  |  | +        if(u === data){ return no("Unverified data.") } // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account.
 | 
	
		
			
				|  |  | +        if((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
 | 
	
		
			
				|  |  | +        msg.put['='] = data;
 | 
	
		
			
				|  |  | +        eve.to.next(msg);
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +    check.any = function(eve, msg, val, key, soul, at, no, user){ var tmp, pub;
 | 
	
		
			
				|  |  | +      if(at.opt.secure){ return no("Soul missing public key at '" + key + "'.") }
 | 
	
		
			
				|  |  | +      // TODO: Ask community if should auto-sign non user-graph data.
 | 
	
		
			
				|  |  | +      at.on('secure', function(msg){ this.off();
 | 
	
		
			
				|  |  | +        if(!at.opt.secure){ return eve.to.next(msg) }
 | 
	
		
			
				|  |  | +        no("Data cannot be changed.");
 | 
	
		
			
				|  |  | +      }).on.on('secure', msg);
 | 
	
		
			
				|  |  | +      return;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    var link_is = Gun.val.link.is, state_ify = Gun.state.ify;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // okay! The security function handles all the heavy lifting.
 | 
	
		
			
				|  |  |      // It needs to deal read and write of input and output of system data, account/public key data, and regular data.
 | 
	
		
			
				|  |  |      // This is broken down into some pretty clear edge cases, let's go over them:
 | 
	
	
		
			
				|  | @@ -83,6 +171,11 @@
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        if(msg.put){
 | 
	
		
			
				|  |  | +        /*
 | 
	
		
			
				|  |  | +          NOTICE: THIS IS OLD AND GETTING DEPRECATED.
 | 
	
		
			
				|  |  | +          ANY SECURITY CHANGES SHOULD HAPPEN ABOVE FIRST
 | 
	
		
			
				|  |  | +          THEN PORTED TO HERE.
 | 
	
		
			
				|  |  | +        */
 | 
	
		
			
				|  |  |          // potentially parallel async operations!!!
 | 
	
		
			
				|  |  |          var check = {}, each = {}, u;
 | 
	
		
			
				|  |  |          each.node = function(node, soul){
 | 
	
	
		
			
				|  | @@ -207,12 +300,14 @@
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    var pubcut = /[^\w_-]/; // anything not alphanumeric or _ -
 | 
	
		
			
				|  |  |      SEA.opt.pub = function(s){
 | 
	
		
			
				|  |  |        if(!s){ return }
 | 
	
		
			
				|  |  |        s = s.split('~');
 | 
	
		
			
				|  |  |        if(!s || !(s = s[1])){ return }
 | 
	
		
			
				|  |  | -      s = s.split('.');
 | 
	
		
			
				|  |  | -      if(!s || 2 > s.length){ return }
 | 
	
		
			
				|  |  | +      s = s.split(pubcut).slice(0,2);
 | 
	
		
			
				|  |  | +      if(!s || 2 != s.length){ return }
 | 
	
		
			
				|  |  | +      if('@' === (s[0]||'')[0]){ return }
 | 
	
		
			
				|  |  |        s = s.slice(0,2).join('.');
 | 
	
		
			
				|  |  |        return s;
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -221,16 +316,18 @@
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      SEA.opt.pack = function(d,k, n,s){ // pack for verifying
 | 
	
		
			
				|  |  |        if(SEA.opt.check(d)){ return d }
 | 
	
		
			
				|  |  | -      var meta = (Gun.obj.ify(d)||noop), sig = meta['~'];
 | 
	
		
			
				|  |  | -      return sig? {m: {'#':s,'.':k,':':meta[':'],'>':Gun.state.is(n, k)}, s: sig} : d;
 | 
	
		
			
				|  |  | +      var meta = (Gun.obj.ify((d && d[':'])||d)||''), sig = meta['~'];
 | 
	
		
			
				|  |  | +      return sig? {m: {'#':s||d['#'],'.':k||d['.'],':':meta[':'],'>':d['>']||Gun.state.is(n, k)}, s: sig} : d;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    var O = SEA.opt;
 | 
	
		
			
				|  |  |      SEA.opt.unpack = function(d, k, n){ var tmp;
 | 
	
		
			
				|  |  |        if(u === d){ return }
 | 
	
		
			
				|  |  |        if(d && (u !== (tmp = d[':']))){ return tmp }
 | 
	
		
			
				|  |  | +      k = k || O.fall_key; if(!n && O.fall_val){ n = {}; n[k] = O.fall_val }
 | 
	
		
			
				|  |  |        if(!k || !n){ return }
 | 
	
		
			
				|  |  |        if(d === n[k]){ return d }
 | 
	
		
			
				|  |  |        if(!SEA.opt.check(n[k])){ return d }
 | 
	
		
			
				|  |  | -      var soul = Gun.node.soul(n), s = Gun.state.is(n, k);
 | 
	
		
			
				|  |  | +      var soul = Gun.node.soul(n) || O.fall_soul, s = Gun.state.is(n, k) || O.fall_state;
 | 
	
		
			
				|  |  |        if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
 | 
	
		
			
				|  |  |          return d[2];
 | 
	
		
			
				|  |  |        }
 | 
	
	
		
			
				|  | @@ -242,6 +339,7 @@
 | 
	
		
			
				|  |  |      var noop = function(){}, u;
 | 
	
		
			
				|  |  |      var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
 | 
	
		
			
				|  |  |      var rel_is = Gun.val.rel.is;
 | 
	
		
			
				|  |  | +    var obj_ify = Gun.obj.ify;
 | 
	
		
			
				|  |  |      // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    
 |