index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. const SEA = require('./sea')
  2. const Gun = SEA.Gun;
  3. // After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
  4. // We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
  5. Gun.on('opt', function(at){
  6. if(!at.sea){ // only add SEA once per instance, on the "at" context.
  7. at.sea = {own: {}};
  8. at.on('in', security, at); // now listen to all input data, acting as a firewall.
  9. at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
  10. at.on('node', each, at);
  11. }
  12. this.to.next(at); // make sure to call the "next" middleware adapter.
  13. });
  14. // Alright, this next adapter gets run at the per node level in the graph database.
  15. // This will let us verify that every property on a node has a value signed by a public key we trust.
  16. // If the signature does not match, the data is just `undefined` so it doesn't get passed on.
  17. // If it does match, then we transform the in-memory "view" of the data into its plain value (without the signature).
  18. // Now NOTE! Some data is "system" data, not user data. Example: List of public keys, aliases, etc.
  19. // This data is self-enforced (the value can only match its ID), but that is handled in the `security` function.
  20. // From the self-enforced data, we can see all the edges in the graph that belong to a public key.
  21. // Example: ~ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and
  22. // its encrypted private key, but it might also have other signed values on it like `profile = <ID>` edge.
  23. // Using that directed edge's ID, we can then track (in memory) which IDs belong to which keys.
  24. // Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous!
  25. // This means we should ONLY trust our "friends" (our key ring) public keys, not any ones.
  26. // I have not yet added that to SEA yet in this alpha release. That is coming soon, but beware in the meanwhile!
  27. function each(msg){ // TODO: Warning: Need to switch to `gun.on('node')`! Do not use `Gun.on('node'` in your apps!
  28. // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!!
  29. // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT.
  30. var to = this.to, vertex = (msg.$._).put, c = 0, d;
  31. Gun.node.is(msg.put, function(val, key, node){
  32. // only process if SEA formatted?
  33. var tmp = Gun.obj.ify(val) || noop;
  34. if(u !== tmp[':']){
  35. node[key] = SEA.opt.unpack(tmp);
  36. return;
  37. }
  38. if(!SEA.opt.check(val)){ return }
  39. c++; // for each property on the node
  40. SEA.verify(val, false, function(data){ c--; // false just extracts the plain data.
  41. node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value.
  42. if(d && !c && (c = -1)){ to.next(msg) }
  43. });
  44. });
  45. if((d = true) && !c){ to.next(msg) }
  46. }
  47. // signature handles data output, it is a proxy to the security function.
  48. function signature(msg){
  49. if((msg._||noop).user){
  50. return this.to.next(msg);
  51. }
  52. var ctx = this.as;
  53. (msg._||(msg._=function(){})).user = ctx.user;
  54. security.call(this, msg);
  55. }
  56. // okay! The security function handles all the heavy lifting.
  57. // It needs to deal read and write of input and output of system data, account/public key data, and regular data.
  58. // This is broken down into some pretty clear edge cases, let's go over them:
  59. function security(msg){
  60. var at = this.as, sea = at.sea, to = this.to;
  61. if(msg.get){
  62. // if there is a request to read data from us, then...
  63. var soul = msg.get['#'];
  64. if(soul){ // for now, only allow direct IDs to be read.
  65. if(typeof soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
  66. if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
  67. return to.next(msg); // yes.
  68. } else
  69. if('~@' === soul.slice(0,2)){ // Allow reading the list of public keys associated with an alias?
  70. return to.next(msg); // yes.
  71. } else { // Allow reading everything?
  72. return to.next(msg); // yes // TODO: No! Make this a callback/event that people can filter on.
  73. }
  74. }
  75. }
  76. if(msg.put){
  77. // potentially parallel async operations!!!
  78. var check = {}, each = {}, u;
  79. each.node = function(node, soul){
  80. if(Gun.obj.empty(node, '_')){ return check['node'+soul] = 0 } // ignore empty updates, don't reject them.
  81. Gun.obj.map(node, each.way, {soul: soul, node: node});
  82. };
  83. each.way = function(val, key){
  84. var soul = this.soul, node = this.node, tmp;
  85. if('_' === key){ return } // ignore meta data
  86. if('~@' === soul){ // special case for shared system data, the list of aliases.
  87. each.alias(val, key, node, soul); return;
  88. }
  89. if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
  90. each.pubs(val, key, node, soul); return;
  91. }
  92. if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
  93. each.pub(val, key, node, soul, tmp, (msg._||noop).user); return;
  94. }
  95. each.any(val, key, node, soul, (msg._||noop).user); return;
  96. return each.end({err: "No other data allowed!"});
  97. };
  98. each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}}
  99. if(!val){ return each.end({err: "Data must exist!"}) } // data MUST exist
  100. if('~@'+key === Gun.val.link.is(val)){ return check['alias'+key] = 0 } // in fact, it must be EXACTLY equal to itself
  101. each.end({err: "Mismatching alias."}); // if it isn't, reject.
  102. };
  103. each.pubs = function(val, key, node, soul){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
  104. if(!val){ return each.end({err: "Alias must exist!"}) } // data MUST exist
  105. if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property
  106. each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys.
  107. };
  108. each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
  109. if('pub' === key){
  110. if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key.
  111. return each.end({err: "Account must match!"});
  112. }
  113. check['user'+soul+key] = 1;
  114. if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
  115. SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel;
  116. if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) }
  117. if(rel = Gun.val.link.is(val)){
  118. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  119. }
  120. node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
  121. check['user'+soul+key] = 0;
  122. each.end({ok: 1});
  123. }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
  124. return;
  125. }
  126. SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp;
  127. data = SEA.opt.unpack(data, key, node);
  128. if(u === data){ // make sure the signature matches the account it claims to be on.
  129. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
  130. }
  131. if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
  132. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  133. }
  134. check['user'+soul+key] = 0;
  135. each.end({ok: 1});
  136. });
  137. };
  138. each.any = function(val, key, node, soul, user){ var tmp, pub;
  139. if(!(pub = SEA.opt.pub(soul))){
  140. if(at.opt.secure){
  141. each.end({err: "Soul is missing public key at '" + key + "'."});
  142. return;
  143. }
  144. // TODO: Ask community if should auto-sign non user-graph data.
  145. check['any'+soul+key] = 1;
  146. at.on('secure', function(msg){ this.off();
  147. check['any'+soul+key] = 0;
  148. if(at.opt.secure){ msg = null }
  149. each.end(msg || {err: "Data cannot be modified."});
  150. }).on.on('secure', msg);
  151. //each.end({err: "Data cannot be modified."});
  152. return;
  153. }
  154. if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
  155. /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
  156. if((user.is||{}).pub !== p){ return p }
  157. });
  158. if(other){
  159. each.any(val, key, node, soul);
  160. return;
  161. }*/
  162. check['any'+soul+key] = 1;
  163. SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){
  164. if(u === data){ return each.end({err: 'My signature fail.'}) }
  165. node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
  166. check['any'+soul+key] = 0;
  167. each.end({ok: 1});
  168. }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
  169. return;
  170. }
  171. check['any'+soul+key] = 1;
  172. SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel;
  173. data = SEA.opt.unpack(data, key, node);
  174. if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
  175. if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
  176. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  177. }
  178. check['any'+soul+key] = 0;
  179. each.end({ok: 1});
  180. });
  181. }
  182. each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb?
  183. if(each.err){ return }
  184. if((each.err = ctx.err) || ctx.no){
  185. console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI
  186. return;
  187. }
  188. if(!each.end.ed){ return }
  189. if(Gun.obj.map(check, function(no){
  190. if(no){ return true }
  191. })){ return }
  192. (msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out.
  193. to.next(msg);
  194. };
  195. Gun.obj.map(msg.put, each.node);
  196. each.end({end: each.end.ed = true});
  197. return; // need to manually call next after async.
  198. }
  199. to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
  200. }
  201. SEA.opt.pub = function(s){
  202. if(!s){ return }
  203. s = s.split('~');
  204. if(!s || !(s = s[1])){ return }
  205. s = s.split('.');
  206. if(!s || 2 > s.length){ return }
  207. s = s.slice(0,2).join('.');
  208. return s;
  209. }
  210. SEA.opt.prep = function(d,k, n,s){ // prep for signing
  211. return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)};
  212. }
  213. SEA.opt.pack = function(d,k, n,s){ // pack for verifying
  214. if(SEA.opt.check(d)){ return d }
  215. var meta = (Gun.obj.ify(d)||noop), sig = meta['~'];
  216. return sig? {m: {'#':s,'.':k,':':meta[':'],'>':Gun.state.is(n, k)}, s: sig} : d;
  217. }
  218. SEA.opt.unpack = function(d, k, n){ var tmp;
  219. if(u === d){ return }
  220. if(d && (u !== (tmp = d[':']))){ return tmp }
  221. if(!k || !n){ return }
  222. if(d === n[k]){ return d }
  223. if(!SEA.opt.check(n[k])){ return d }
  224. var soul = Gun.node.soul(n), s = Gun.state.is(n, k);
  225. if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
  226. return d[2];
  227. }
  228. if(s < SEA.opt.shuffle_attack){
  229. return d;
  230. }
  231. }
  232. SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019
  233. var noop = function(){}, u;
  234. var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
  235. var rel_is = Gun.val.rel.is;
  236. // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.