sea.js 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665
  1. ;(function(){
  2. /* UNBUILD */
  3. var root;
  4. if(typeof window !== "undefined"){ root = window }
  5. if(typeof global !== "undefined"){ root = global }
  6. root = root || {};
  7. var console = root.console || {log: function(){}};
  8. function USE(arg, req){
  9. return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
  10. arg(mod = {exports: {}});
  11. USE[R(path)] = mod.exports;
  12. }
  13. function R(p){
  14. return p.split('/').slice(-1).toString().replace('.js','');
  15. }
  16. }
  17. if(typeof module !== "undefined"){ var common = module }
  18. /* UNBUILD */
  19. ;USE(function(module){
  20. // Security, Encryption, and Authorization: SEA.js
  21. // MANDATORY READING: https://gun.eco/explainers/data/security.html
  22. // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH.
  23. // THIS IS AN EARLY ALPHA!
  24. if(typeof window !== "undefined"){ module.window = window }
  25. var tmp = module.window || module;
  26. var SEA = tmp.SEA || function(){};
  27. if(SEA.window = module.window){ try{
  28. SEA.window.SEA = SEA;
  29. tmp = document.createEvent('CustomEvent');
  30. tmp.initCustomEvent('extension', false, false, {type: "SEA"});
  31. (window.dispatchEvent || window.fireEvent)(tmp);
  32. window.postMessage({type: "SEA"}, '*');
  33. } catch(e){} }
  34. try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
  35. module.exports = SEA;
  36. })(USE, './root');
  37. ;USE(function(module){
  38. var SEA = USE('./root');
  39. if(SEA.window){
  40. if(location.protocol.indexOf('s') < 0
  41. && location.host.indexOf('localhost') < 0
  42. && location.protocol.indexOf('file:') < 0){
  43. location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS!
  44. }
  45. }
  46. })(USE, './https');
  47. ;USE(function(module){
  48. // This is Array extended to have .toString(['utf8'|'hex'|'base64'])
  49. function SeaArray() {}
  50. Object.assign(SeaArray, { from: Array.from })
  51. SeaArray.prototype = Object.create(Array.prototype)
  52. SeaArray.prototype.toString = function(enc, start, end) { enc = enc || 'utf8'; start = start || 0;
  53. const length = this.length
  54. if (enc === 'hex') {
  55. const buf = new Uint8Array(this)
  56. return [ ...Array(((end && (end + 1)) || length) - start).keys()]
  57. .map((i) => buf[ i + start ].toString(16).padStart(2, '0')).join('')
  58. }
  59. if (enc === 'utf8') {
  60. return Array.from(
  61. { length: (end || length) - start },
  62. (_, i) => String.fromCharCode(this[ i + start])
  63. ).join('')
  64. }
  65. if (enc === 'base64') {
  66. return btoa(this)
  67. }
  68. }
  69. module.exports = SeaArray;
  70. })(USE, './array');
  71. ;USE(function(module){
  72. // This is Buffer implementation used in SEA. Functionality is mostly
  73. // compatible with NodeJS 'safe-buffer' and is used for encoding conversions
  74. // between binary and 'hex' | 'utf8' | 'base64'
  75. // See documentation and validation for safe implementation in:
  76. // https://github.com/feross/safe-buffer#update
  77. var SeaArray = USE('./array');
  78. function SafeBuffer(...props) {
  79. console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()')
  80. return SafeBuffer.from(...props)
  81. }
  82. SafeBuffer.prototype = Object.create(Array.prototype)
  83. Object.assign(SafeBuffer, {
  84. // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
  85. from() {
  86. if (!Object.keys(arguments).length) {
  87. throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
  88. }
  89. const input = arguments[0]
  90. let buf
  91. if (typeof input === 'string') {
  92. const enc = arguments[1] || 'utf8'
  93. if (enc === 'hex') {
  94. const bytes = input.match(/([\da-fA-F]{2})/g)
  95. .map((byte) => parseInt(byte, 16))
  96. if (!bytes || !bytes.length) {
  97. throw new TypeError('Invalid first argument for type \'hex\'.')
  98. }
  99. buf = SeaArray.from(bytes)
  100. } else if (enc === 'utf8') {
  101. const length = input.length
  102. const words = new Uint16Array(length)
  103. Array.from({ length: length }, (_, i) => words[i] = input.charCodeAt(i))
  104. buf = SeaArray.from(words)
  105. } else if (enc === 'base64') {
  106. const dec = atob(input)
  107. const length = dec.length
  108. const bytes = new Uint8Array(length)
  109. Array.from({ length: length }, (_, i) => bytes[i] = dec.charCodeAt(i))
  110. buf = SeaArray.from(bytes)
  111. } else if (enc === 'binary') {
  112. buf = SeaArray.from(input)
  113. } else {
  114. console.info('SafeBuffer.from unknown encoding: '+enc)
  115. }
  116. return buf
  117. }
  118. const byteLength = input.byteLength
  119. const length = input.byteLength ? input.byteLength : input.length
  120. if (length) {
  121. let buf
  122. if (input instanceof ArrayBuffer) {
  123. buf = new Uint8Array(input)
  124. }
  125. return SeaArray.from(buf || input)
  126. }
  127. },
  128. // This is 'safe-buffer.alloc' sans encoding support
  129. alloc(length, fill = 0 /*, enc*/ ) {
  130. return SeaArray.from(new Uint8Array(Array.from({ length: length }, () => fill)))
  131. },
  132. // This is normal UNSAFE 'buffer.alloc' or 'new Buffer(length)' - don't use!
  133. allocUnsafe(length) {
  134. return SeaArray.from(new Uint8Array(Array.from({ length : length })))
  135. },
  136. // This puts together array of array like members
  137. concat(arr) { // octet array
  138. if (!Array.isArray(arr)) {
  139. throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.')
  140. }
  141. return SeaArray.from(arr.reduce((ret, item) => ret.concat(Array.from(item)), []))
  142. }
  143. })
  144. SafeBuffer.prototype.from = SafeBuffer.from
  145. SafeBuffer.prototype.toString = SeaArray.prototype.toString
  146. module.exports = SafeBuffer;
  147. })(USE, './buffer');
  148. ;USE(function(module){
  149. const Buffer = USE('./buffer')
  150. const api = {Buffer: Buffer}
  151. if (typeof window !== 'undefined') {
  152. var crypto = window.crypto || window.msCrypto;
  153. var subtle = crypto.subtle || crypto.webkitSubtle;
  154. const TextEncoder = window.TextEncoder
  155. const TextDecoder = window.TextDecoder
  156. Object.assign(api, {
  157. crypto,
  158. subtle,
  159. TextEncoder,
  160. TextDecoder,
  161. random: (len) => Buffer.from(crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))))
  162. })
  163. } else {
  164. try{
  165. var crypto = USE('crypto', 1);
  166. const { subtle } = USE('@trust/webcrypto', 1) // All but ECDH
  167. const { TextEncoder, TextDecoder } = USE('text-encoding', 1)
  168. Object.assign(api, {
  169. crypto,
  170. subtle,
  171. TextEncoder,
  172. TextDecoder,
  173. random: (len) => Buffer.from(crypto.randomBytes(len))
  174. });
  175. //try{
  176. const WebCrypto = USE('node-webcrypto-ossl', 1)
  177. api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
  178. //}catch(e){
  179. //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
  180. //}
  181. }catch(e){
  182. console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!");
  183. 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).");
  184. TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;
  185. }
  186. }
  187. module.exports = api
  188. })(USE, './shim');
  189. ;USE(function(module){
  190. const SEA = USE('./root');
  191. const Buffer = USE('./buffer')
  192. const settings = {}
  193. // Encryption parameters
  194. const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 }
  195. const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } }
  196. const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' }
  197. const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' }
  198. const _initial_authsettings = {
  199. validity: 12 * 60 * 60, // internally in seconds : 12 hours
  200. hook: (props) => props // { iat, exp, alias, remember }
  201. // or return new Promise((resolve, reject) => resolve(props)
  202. }
  203. // These are used to persist user's authentication "session"
  204. const authsettings = Object.assign({}, _initial_authsettings)
  205. // This creates Web Cryptography API compliant JWK for sign/verify purposes
  206. const keysToEcdsaJwk = (pub, d) => { // d === priv
  207. //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old
  208. const [ x, y ] = pub.split('.') // new
  209. var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true }
  210. jwk.key_ops = d ? ['sign'] : ['verify'];
  211. if(d){ jwk.d = d }
  212. return jwk;
  213. }
  214. Object.assign(settings, {
  215. pbkdf2: pbkdf2,
  216. ecdsa: {
  217. pair: ecdsaKeyProps,
  218. sign: ecdsaSignProps
  219. },
  220. ecdh: ecdhKeyProps,
  221. jwk: keysToEcdsaJwk,
  222. recall: authsettings
  223. })
  224. SEA.opt = settings;
  225. module.exports = settings
  226. })(USE, './settings');
  227. ;USE(function(module){
  228. module.exports = (props) => {
  229. try {
  230. if(props.slice && 'SEA{' === props.slice(0,4)){
  231. props = props.slice(3);
  232. }
  233. return props.slice ? JSON.parse(props) : props
  234. } catch (e) {} //eslint-disable-line no-empty
  235. return props
  236. }
  237. })(USE, './parse');
  238. ;USE(function(module){
  239. const shim = USE('./shim');
  240. const Buffer = USE('./buffer')
  241. const parse = USE('./parse')
  242. const { pbkdf2 } = USE('./settings')
  243. // This internal func returns SHA-256 hashed data for signing
  244. const sha256hash = async (mm) => {
  245. const m = parse(mm)
  246. const hash = await shim.subtle.digest({name: pbkdf2.hash}, new shim.TextEncoder().encode(m))
  247. return Buffer.from(hash)
  248. }
  249. module.exports = sha256hash
  250. })(USE, './sha256');
  251. ;USE(function(module){
  252. // This internal func returns SHA-1 hashed data for KeyID generation
  253. const __shim = USE('./shim')
  254. const subtle = __shim.subtle
  255. const ossl = __shim.ossl ? __shim.__ossl : subtle
  256. const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b))
  257. module.exports = sha1hash
  258. })(USE, './sha1');
  259. ;USE(function(module){
  260. var SEA = USE('./root');
  261. var shim = USE('./shim');
  262. var S = USE('./settings');
  263. var sha = USE('./sha256');
  264. var u;
  265. SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof`
  266. var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random!
  267. var opt = opt || {};
  268. if(salt instanceof Function){
  269. cb = salt;
  270. salt = u;
  271. }
  272. salt = salt || shim.random(9);
  273. if('SHA-256' === opt.name){
  274. var rsha = shim.Buffer.from(await sha(data), 'binary').toString('utf8')
  275. if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
  276. return rsha;
  277. }
  278. const key = await (shim.ossl || shim.subtle).importKey(
  279. 'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits']
  280. )
  281. const result = await (shim.ossl || shim.subtle).deriveBits({
  282. name: opt.name || 'PBKDF2',
  283. iterations: opt.iterations || S.pbkdf2.iter,
  284. salt: new shim.TextEncoder().encode(opt.salt || salt),
  285. hash: opt.hash || S.pbkdf2.hash,
  286. }, key, opt.length || (S.pbkdf2.ks * 8))
  287. data = shim.random(data.length) // Erase data in case of passphrase
  288. const r = shim.Buffer.from(result, 'binary').toString('utf8')
  289. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  290. return r;
  291. } catch(e) {
  292. SEA.err = e;
  293. if(cb){ cb() }
  294. return;
  295. }});
  296. module.exports = SEA.work;
  297. })(USE, './work');
  298. ;USE(function(module){
  299. var SEA = USE('./root');
  300. var shim = USE('./shim');
  301. var S = USE('./settings');
  302. var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer;
  303. //SEA.pair = async (data, proof, cb) => { try {
  304. SEA.pair = SEA.pair || (async (cb) => { try {
  305. const ecdhSubtle = shim.ossl || shim.subtle
  306. // First: ECDSA keys for signing/verifying...
  307. var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ])
  308. .then(async (keys) => {
  309. // privateKey scope doesn't leak out from here!
  310. //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
  311. const key = {};
  312. key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d;
  313. const pub = await shim.subtle.exportKey('jwk', keys.publicKey)
  314. //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old
  315. key.pub = pub.x+'.'+pub.y // new
  316. // x and y are already base64
  317. // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt)
  318. // but split on a non-base64 letter.
  319. return key;
  320. })
  321. // To include PGPv4 kind of keyId:
  322. // const pubId = await SEA.keyid(keys.pub)
  323. // Next: ECDH keys for encryption/decryption...
  324. try{
  325. var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey'])
  326. .then(async (keys) => {
  327. // privateKey scope doesn't leak out from here!
  328. const key = {};
  329. key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d;
  330. const pub = await ecdhSubtle.exportKey('jwk', keys.publicKey)
  331. //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old
  332. key.epub = pub.x+'.'+pub.y // new
  333. // ex and ey are already base64
  334. // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt)
  335. // but split on a non-base64 letter.
  336. return key;
  337. })
  338. }catch(e){
  339. if(SEA.window){ throw e }
  340. if(e == 'Error: ECDH is not a supported algorithm'){ console.log('Ignoring ECDH...') }
  341. else { throw e }
  342. } dh = dh || {};
  343. const r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv }
  344. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  345. return r;
  346. } catch(e) {
  347. console.log(e);
  348. SEA.err = e;
  349. if(cb){ cb() }
  350. return;
  351. }});
  352. module.exports = SEA.pair;
  353. })(USE, './pair');
  354. ;USE(function(module){
  355. var SEA = USE('./root');
  356. var shim = USE('./shim');
  357. var S = USE('./settings');
  358. var sha256hash = USE('./sha256');
  359. SEA.sign = SEA.sign || (async (data, pair, cb) => { try {
  360. if(data && data.slice
  361. && 'SEA{' === data.slice(0,4)
  362. && '"m":' === data.slice(4,8)){
  363. // TODO: This would prevent pair2 signing pair1's signature.
  364. // So we may want to change this in the future.
  365. // but for now, we want to prevent duplicate double signature.
  366. if(cb){ try{ cb(data) }catch(e){console.log(e)} }
  367. return data;
  368. }
  369. const pub = pair.pub
  370. const priv = pair.priv
  371. const jwk = S.jwk(pub, priv)
  372. const msg = JSON.stringify(data)
  373. const hash = await sha256hash(msg)
  374. const sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign'])
  375. .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
  376. const r = 'SEA'+JSON.stringify({m: msg, s: shim.Buffer.from(sig, 'binary').toString('utf8')});
  377. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  378. return r;
  379. } catch(e) {
  380. console.log(e);
  381. SEA.err = e;
  382. if(cb){ cb() }
  383. return;
  384. }});
  385. module.exports = SEA.sign;
  386. })(USE, './sign');
  387. ;USE(function(module){
  388. var SEA = USE('./root');
  389. var shim = USE('./shim');
  390. var S = USE('./settings');
  391. var sha256hash = USE('./sha256');
  392. var parse = USE('./parse');
  393. var u;
  394. SEA.verify = SEA.verify || (async (data, pair, cb) => { try {
  395. const json = parse(data)
  396. if(false === pair){ // don't verify!
  397. const raw = (json !== data)?
  398. (json.s && json.m)? parse(json.m) : data
  399. : json;
  400. if(cb){ try{ cb(raw) }catch(e){console.log(e)} }
  401. return raw;
  402. }
  403. const pub = pair.pub || pair
  404. const jwk = S.jwk(pub)
  405. const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify'])
  406. const hash = await sha256hash(json.m)
  407. const sig = new Uint8Array(shim.Buffer.from(json.s, 'utf8'))
  408. const check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash))
  409. if(!check){ throw "Signature did not match." }
  410. const r = check? parse(json.m) : u;
  411. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  412. return r;
  413. } catch(e) {
  414. console.log(e);
  415. SEA.err = e;
  416. if(cb){ cb() }
  417. return;
  418. }});
  419. module.exports = SEA.verify;
  420. })(USE, './verify');
  421. ;USE(function(module){
  422. var shim = USE('./shim');
  423. var sha256hash = USE('./sha256');
  424. const importGen = async (key, salt, opt) => {
  425. //const combo = shim.Buffer.concat([shim.Buffer.from(key, 'utf8'), salt || shim.random(8)]).toString('utf8') // old
  426. var opt = opt || {};
  427. const combo = key + (salt || shim.random(8)).toString('utf8'); // new
  428. const hash = shim.Buffer.from(await sha256hash(combo), 'binary')
  429. return await shim.subtle.importKey('raw', new Uint8Array(hash), opt.name || 'AES-GCM', false, ['encrypt', 'decrypt'])
  430. }
  431. module.exports = importGen;
  432. })(USE, './aeskey');
  433. ;USE(function(module){
  434. var SEA = USE('./root');
  435. var shim = USE('./shim');
  436. var S = USE('./settings');
  437. var aeskey = USE('./aeskey');
  438. SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
  439. var opt = opt || {};
  440. const key = pair.epriv || pair;
  441. const msg = JSON.stringify(data)
  442. const rand = {s: shim.random(8), iv: shim.random(16)};
  443. const ct = await aeskey(key, rand.s, opt)
  444. .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible...
  445. name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv)
  446. }, aes, new shim.TextEncoder().encode(msg)))
  447. const r = 'SEA'+JSON.stringify({
  448. ct: shim.Buffer.from(ct, 'binary').toString('utf8'),
  449. iv: rand.iv.toString('utf8'),
  450. s: rand.s.toString('utf8')
  451. });
  452. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  453. return r;
  454. } catch(e) {
  455. SEA.err = e;
  456. if(cb){ cb() }
  457. return;
  458. }});
  459. module.exports = SEA.encrypt;
  460. })(USE, './encrypt');
  461. ;USE(function(module){
  462. var SEA = USE('./root');
  463. var shim = USE('./shim');
  464. var S = USE('./settings');
  465. var aeskey = USE('./aeskey');
  466. var parse = USE('./parse');
  467. SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
  468. var opt = opt || {};
  469. const key = pair.epriv || pair;
  470. const json = parse(data)
  471. const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt)
  472. .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible...
  473. name: opt.name || 'AES-GCM', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8'))
  474. }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8'))))
  475. const r = parse(new shim.TextDecoder('utf8').decode(ct))
  476. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  477. return r;
  478. } catch(e) {
  479. SEA.err = e;
  480. if(cb){ cb() }
  481. return;
  482. }});
  483. module.exports = SEA.decrypt;
  484. })(USE, './decrypt');
  485. ;USE(function(module){
  486. var SEA = USE('./root');
  487. var shim = USE('./shim');
  488. var S = USE('./settings');
  489. // Derive shared secret from other's pub and my epub/epriv
  490. SEA.secret = SEA.secret || (async (key, pair, cb) => { try {
  491. const pub = key.epub || key
  492. const epub = pair.epub
  493. const epriv = pair.epriv
  494. const ecdhSubtle = shim.ossl || shim.subtle
  495. const pubKeyData = keysToEcdhJwk(pub)
  496. const props = Object.assign(
  497. S.ecdh,
  498. { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }
  499. )
  500. const privKeyData = keysToEcdhJwk(epub, epriv)
  501. const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey'])
  502. .then(async (privKey) => {
  503. // privateKey scope doesn't leak out from here!
  504. const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ])
  505. return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k)
  506. })
  507. const r = derived;
  508. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  509. return r;
  510. } catch(e) {
  511. SEA.err = e;
  512. if(cb){ cb() }
  513. return;
  514. }});
  515. const keysToEcdhJwk = (pub, d) => { // d === priv
  516. //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old
  517. const [ x, y ] = pub.split('.') // new
  518. const jwk = d ? { d: d } : {}
  519. return [ // Use with spread returned value...
  520. 'jwk',
  521. Object.assign(
  522. jwk,
  523. { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
  524. ), // ??? refactor
  525. S.ecdh
  526. ]
  527. }
  528. module.exports = SEA.secret;
  529. })(USE, './secret');
  530. ;USE(function(module){
  531. // Old Code...
  532. const __gky10 = USE('./shim')
  533. const crypto = __gky10.crypto
  534. const subtle = __gky10.subtle
  535. const ossl = __gky10.ossl
  536. const TextEncoder = __gky10.TextEncoder
  537. const TextDecoder = __gky10.TextDecoder
  538. const getRandomBytes = __gky10.random
  539. const EasyIndexedDB = USE('./indexed')
  540. const Buffer = USE('./buffer')
  541. var settings = USE('./settings');
  542. const __gky11 = USE('./settings')
  543. const pbKdf2 = __gky11.pbkdf2
  544. const ecdsaKeyProps = __gky11.ecdsa.pair
  545. const ecdsaSignProps = __gky11.ecdsa.sign
  546. const ecdhKeyProps = __gky11.ecdh
  547. const keysToEcdsaJwk = __gky11.jwk
  548. const sha1hash = USE('./sha1')
  549. const sha256hash = USE('./sha256')
  550. const recallCryptoKey = USE('./remember')
  551. const parseProps = USE('./parse')
  552. // Practical examples about usage found from ./test/common.js
  553. const SEA = USE('./root');
  554. SEA.work = USE('./work');
  555. SEA.sign = USE('./sign');
  556. SEA.verify = USE('./verify');
  557. SEA.encrypt = USE('./encrypt');
  558. SEA.decrypt = USE('./decrypt');
  559. SEA.random = SEA.random || getRandomBytes;
  560. // This is easy way to use IndexedDB, all methods are Promises
  561. // Note: Not all SEA interfaces have to support this.
  562. SEA.EasyIndexedDB = EasyIndexedDB;
  563. // This is Buffer used in SEA and usable from Gun/SEA application also.
  564. // For documentation see https://nodejs.org/api/buffer.html
  565. SEA.Buffer = SEA.Buffer || Buffer;
  566. // These SEA functions support now ony Promises or
  567. // async/await (compatible) code, use those like Promises.
  568. //
  569. // Creates a wrapper library around Web Crypto API
  570. // for various AES, ECDSA, PBKDF2 functions we called above.
  571. // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string)
  572. SEA.keyid = SEA.keyid || (async (pub) => {
  573. try {
  574. // base64('base64(x):base64(y)') => Buffer(xy)
  575. const pb = Buffer.concat(
  576. Buffer.from(pub, 'base64').toString('utf8').split(':')
  577. .map((t) => Buffer.from(t, 'base64'))
  578. )
  579. // id is PGPv4 compliant raw key
  580. const id = Buffer.concat([
  581. Buffer.from([0x99, pb.length / 0x100, pb.length % 0x100]), pb
  582. ])
  583. const sha1 = await sha1hash(id)
  584. const hash = Buffer.from(sha1, 'binary')
  585. return hash.toString('hex', hash.length - 8) // 16-bit ID as hex
  586. } catch (e) {
  587. console.log(e)
  588. throw e
  589. }
  590. });
  591. // all done!
  592. // Obviously it is missing MANY necessary features. This is only an alpha release.
  593. // Please experiment with it, audit what I've done so far, and complain about what needs to be added.
  594. // SEA should be a full suite that is easy and seamless to use.
  595. // Again, scroll naer the top, where I provide an EXAMPLE of how to create a user and sign in.
  596. // Once logged in, the rest of the code you just read handled automatically signing/validating data.
  597. // But all other behavior needs to be equally easy, like opinionated ways of
  598. // Adding friends (trusted public keys), sending private messages, etc.
  599. // Cheers! Tell me what you think.
  600. var Gun = (SEA.window||{}).Gun || USE('./gun', 1);
  601. Gun.SEA = SEA;
  602. SEA.Gun = Gun;
  603. module.exports = SEA
  604. })(USE, './sea');
  605. ;USE(function(module){
  606. var SEA = USE('./sea');
  607. var Gun = SEA.Gun;
  608. // This is internal func queries public key(s) for alias.
  609. const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => {
  610. // load all public keys associated with the username alias we want to log in with.
  611. gunRoot.get('~@'+alias).once((data, key) => {
  612. //rev.off();
  613. if (!data) {
  614. // if no user, don't do anything.
  615. const err = 'No user!'
  616. Gun.log(err)
  617. return reject({ err })
  618. }
  619. // then figuring out all possible candidates having matching username
  620. const aliases = []
  621. let c = 0
  622. // TODO: how about having real chainable map without callback ?
  623. Gun.obj.map(data, (at, pub) => {
  624. if (!pub.slice || '~' !== pub.slice(0, 1)) {
  625. // TODO: ... this would then be .filter((at, pub))
  626. return
  627. }
  628. ++c
  629. // grab the account associated with this public key.
  630. gunRoot.get(pub).once(data => {
  631. pub = pub.slice(1)
  632. --c
  633. if (data){
  634. aliases.push({ pub, put: data })
  635. }
  636. if (!c && (c = -1)) {
  637. resolve(aliases)
  638. }
  639. })
  640. })
  641. if (!c) {
  642. reject({ err: 'Public key does not exist!' })
  643. }
  644. })
  645. })
  646. module.exports = queryGunAliases
  647. })(USE, './query');
  648. ;USE(function(module){
  649. var SEA = USE('./sea');
  650. var Gun = SEA.Gun;
  651. const queryGunAliases = USE('./query')
  652. const parseProps = USE('./parse')
  653. // This is internal User authentication func.
  654. const authenticate = async (alias, pass, gunRoot) => {
  655. // load all public keys associated with the username alias we want to log in with.
  656. const aliases = (await queryGunAliases(alias, gunRoot))
  657. .filter(a => !!a.pub && !!a.put)
  658. // Got any?
  659. if (!aliases.length) {
  660. throw { err: 'Public key does not exist!' }
  661. }
  662. let err
  663. // then attempt to log into each one until we find ours!
  664. // (if two users have the same username AND the same password... that would be bad)
  665. const users = await Promise.all(aliases.map(async (a, i) => {
  666. // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
  667. const auth = parseProps(a.put.auth)
  668. // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here.
  669. // SEA.verify(at.put.auth, pub).then(function(auth){
  670. try {
  671. const proof = await SEA.work(pass, auth.s)
  672. //const props = { pub: pub, proof: proof, at: at }
  673. // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
  674. /*
  675. MARK TO @mhelander : pub vs epub!???
  676. */
  677. const salt = auth.salt
  678. const sea = await SEA.decrypt(auth.ek, proof)
  679. if (!sea) {
  680. err = 'Failed to decrypt secret! ' + (i+1) +'/'+aliases.length;
  681. return
  682. }
  683. // now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
  684. // if we were successful, then that meanswe're logged in!
  685. const priv = sea.priv
  686. const epriv = sea.epriv
  687. const epub = a.put.epub
  688. // TODO: 'salt' needed?
  689. err = null
  690. if(SEA.window){
  691. var tmp = SEA.window.sessionStorage;
  692. if(tmp && gunRoot._.opt.remember){
  693. SEA.window.sessionStorage.alias = alias;
  694. SEA.window.sessionStorage.tmp = pass;
  695. }
  696. }
  697. return {priv: priv, pub: a.put.pub, salt: salt, epub: epub, epriv: epriv };
  698. } catch (e) {
  699. err = 'Failed to decrypt secret!'
  700. throw { err }
  701. }
  702. }))
  703. var user = Gun.list.map(users, function(acc){ if(acc){ return acc } })
  704. if (!user) {
  705. throw { err: err || 'Public key does not exist!' }
  706. }
  707. return user
  708. }
  709. module.exports = authenticate;
  710. })(USE, './authenticate');
  711. ;USE(function(module){
  712. const authsettings = USE('./settings')
  713. const SEA = USE('./sea');
  714. const Gun = SEA.Gun;
  715. //const { scope: seaIndexedDb } = USE('./indexed')
  716. // This updates sessionStorage & IndexedDB to persist authenticated "session"
  717. const updateStorage = (proof, key, pin) => async (props) => {
  718. if (!Gun.obj.has(props, 'alias')) {
  719. return // No 'alias' - we're done.
  720. }
  721. if (authsettings.validity && proof && Gun.obj.has(props, 'iat')) {
  722. props.proof = proof
  723. delete props.remember // Not stored if present
  724. const alias = props.alias
  725. const id = props.alias
  726. const remember = { alias: alias, pin: pin }
  727. try {
  728. const signed = await SEA.sign(JSON.stringify(remember), key)
  729. sessionStorage.setItem('user', alias)
  730. sessionStorage.setItem('remember', signed)
  731. const encrypted = await SEA.encrypt(props, pin)
  732. if (encrypted) {
  733. const auth = await SEA.sign(encrypted, key)
  734. await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out.
  735. await seaIndexedDb.put(id, { auth: auth })
  736. }
  737. return props
  738. } catch (err) {
  739. throw { err: 'Session persisting failed!' }
  740. }
  741. }
  742. // Wiping IndexedDB completely when using random PIN
  743. await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out.
  744. // And remove sessionStorage data
  745. sessionStorage.removeItem('user')
  746. sessionStorage.removeItem('remember')
  747. return props
  748. }
  749. module.exports = updateStorage
  750. })(USE, './update');
  751. ;USE(function(module){
  752. const SEA = USE('./sea');
  753. const Gun = SEA.Gun;
  754. const Buffer = USE('./buffer')
  755. const authsettings = USE('./settings')
  756. const updateStorage = USE('./update')
  757. // This internal func persists User authentication if so configured
  758. const authPersist = async (user, proof, opts) => {
  759. // opts = { pin: 'string' }
  760. // no opts.pin then uses random PIN
  761. // How this works:
  762. // called when app bootstraps, with wanted options
  763. // IF authsettings.validity === 0 THEN no remember-me, ever
  764. // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
  765. const pin = Buffer.from(
  766. (Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10),
  767. 'utf8'
  768. ).toString('base64')
  769. const alias = user.alias
  770. const exp = authsettings.validity // seconds // @mhelander what is `exp`???
  771. if (proof && alias && exp) {
  772. const iat = Math.ceil(Date.now() / 1000) // seconds
  773. const remember = Gun.obj.has(opts, 'pin') || undefined // for hook - not stored
  774. const props = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
  775. const pub = user.pub
  776. const epub = user.epub
  777. const priv = user.sea.priv
  778. const epriv = user.sea.epriv
  779. const key = { pub: pub, priv: priv, epub: epub, epriv: epriv }
  780. if (props instanceof Promise) {
  781. const asyncProps = await props.then()
  782. return await updateStorage(proof, key, pin)(asyncProps)
  783. }
  784. return await updateStorage(proof, key, pin)(props)
  785. }
  786. return await updateStorage()({ alias: 'delete' })
  787. }
  788. module.exports = authPersist
  789. })(USE, './persist');
  790. ;USE(function(module){
  791. const authPersist = USE('./persist')
  792. // This internal func finalizes User authentication
  793. const finalizeLogin = async (alias, key, gunRoot, opts) => {
  794. const user = gunRoot._.user
  795. // add our credentials in-memory only to our root gun instance
  796. var tmp = user._.tag;
  797. var opt = user._.opt;
  798. user._ = gunRoot.get('~'+key.pub)._;
  799. user._.opt = opt;
  800. var tags = user._.tag;
  801. /*Object.values && Object.values(tmp).forEach(function(tag){
  802. // TODO: This is ugly & buggy code, it needs to be refactored & tested into a event "merge" utility.
  803. var t = tags[tag.tag];
  804. console.log("hm??", tag, t);
  805. if(!t){
  806. tags[tag.tag] = tag;
  807. return;
  808. }
  809. if(tag.last){
  810. tag.last.to = t.to;
  811. t.last = tag.last = t.last || tag.last;
  812. }
  813. t.to = tag.to;
  814. })*/
  815. //user._.tag = tmp || user._.tag;
  816. // so that way we can use the credentials to encrypt/decrypt data
  817. // that is input/output through gun (see below)
  818. const pub = key.pub
  819. const priv = key.priv
  820. const epub = key.epub
  821. const epriv = key.epriv
  822. user._.is = user.is = {alias: alias, pub: pub};
  823. Object.assign(user._, { alias: alias, pub: pub, epub: epub, sea: { pub: pub, priv: priv, epub: epub, epriv: epriv } })
  824. //console.log("authorized", user._);
  825. // persist authentication
  826. //await authPersist(user._, key.proof, opts) // temporarily disabled
  827. // emit an auth event, useful for page redirects and stuff.
  828. try {
  829. gunRoot._.on('auth', user._) // TODO: Deprecate this, emit on user instead! Update docs when you do.
  830. //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.
  831. } catch (e) {
  832. console.log('Your \'auth\' callback crashed with:', e)
  833. }
  834. // returns success with the user data credentials.
  835. return user._
  836. }
  837. module.exports = finalizeLogin
  838. })(USE, './login');
  839. ;USE(function(module){
  840. const Buffer = USE('./buffer')
  841. const authsettings = USE('./settings')
  842. //const { scope: seaIndexedDb } = USE('./indexed')
  843. const queryGunAliases = USE('./query')
  844. const parseProps = USE('./parse')
  845. const updateStorage = USE('./update')
  846. const SEA = USE('./sea')
  847. const Gun = SEA.Gun;
  848. const finalizeLogin = USE('./login')
  849. // This internal func recalls persisted User authentication if so configured
  850. const authRecall = async (gunRoot, authprops) => {
  851. // window.sessionStorage only holds signed { alias, pin } !!!
  852. const remember = authprops || sessionStorage.getItem('remember')
  853. const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} // @mhelander what is pIn?
  854. const pin = pIn && Buffer.from(pIn, 'utf8').toString('base64')
  855. // Checks for existing proof, matching alias and expiration:
  856. const checkRememberData = async ({ proof, alias: aLias, iat, exp, remember }) => {
  857. if (!!proof && alias === aLias) {
  858. const checkNotExpired = (args) => {
  859. if (Math.floor(Date.now() / 1000) < (iat + args.exp)) {
  860. // No way hook to update 'iat'
  861. return Object.assign(args, { iat: iat, proof: proof })
  862. } else {
  863. Gun.log('Authentication expired!')
  864. }
  865. }
  866. // We're not gonna give proof to hook!
  867. const hooked = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
  868. return ((hooked instanceof Promise)
  869. && await hooked.then(checkNotExpired)) || checkNotExpired(hooked)
  870. }
  871. }
  872. const readAndDecrypt = async (data, pub, key) =>
  873. parseProps(await SEA.decrypt(await SEA.verify(data, pub), key))
  874. // Already authenticated?
  875. if (gunRoot._.user
  876. && Gun.obj.has(gunRoot._.user._, 'pub')
  877. && Gun.obj.has(gunRoot._.user._, 'sea')) {
  878. return gunRoot._.user._ // Yes, we're done here.
  879. }
  880. // No, got persisted 'alias'?
  881. if (!alias) {
  882. throw { err: 'No authentication session found!' }
  883. }
  884. // Yes, got persisted 'remember'?
  885. if (!remember) {
  886. throw { // And return proof if for matching alias
  887. err: (await seaIndexedDb.get(alias, 'auth') && authsettings.validity
  888. && 'Missing PIN and alias!') || 'No authentication session found!'
  889. }
  890. }
  891. // Yes, let's get (all?) matching aliases
  892. const aliases = (await queryGunAliases(alias, gunRoot))
  893. .filter(({ pub } = {}) => !!pub)
  894. // Got any?
  895. if (!aliases.length) {
  896. throw { err: 'Public key does not exist!' }
  897. }
  898. let err
  899. // Yes, then attempt to log into each one until we find ours!
  900. // (if two users have the same username AND the same password... that would be bad)
  901. const [ { key, at, proof, pin: newPin } = {} ] = await Promise
  902. .all(aliases.filter(({ at: { put } = {} }) => !!put)
  903. .map(async ({ at: at, pub: pub }) => {
  904. const readStorageData = async (args) => {
  905. const props = args || parseProps(await SEA.verify(remember, pub, true))
  906. let pin = props.pin
  907. let aLias = props.alias
  908. const data = (!pin && alias === aLias)
  909. // No PIN, let's try short-term proof if for matching alias
  910. ? await checkRememberData(props)
  911. // Got PIN so get IndexedDB secret if signature is ok
  912. : await checkRememberData(await readAndDecrypt(await seaIndexedDb.get(alias, 'auth'), pub, pin))
  913. pin = pin || data.pin
  914. delete data.pin
  915. return { pin: pin, data: data }
  916. }
  917. // got pub, try auth with pin & alias :: or unwrap Storage data...
  918. const __gky20 = await readStorageData(pin && { pin, alias })
  919. const data = __gky20.data
  920. const newPin = __gky20.pin
  921. const proof = data.proof
  922. if (!proof) {
  923. if (!data) {
  924. err = 'No valid authentication session found!'
  925. return
  926. }
  927. try { // Wipes IndexedDB silently
  928. await updateStorage()(data)
  929. } catch (e) {} //eslint-disable-line no-empty
  930. err = 'Expired session!'
  931. return
  932. }
  933. try { // auth parsing or decryption fails or returns empty - silently done
  934. const auth= at.put.auth.auth
  935. const sea = await SEA.decrypt(auth, proof)
  936. if (!sea) {
  937. err = 'Failed to decrypt private key!'
  938. return
  939. }
  940. const priv = sea.priv
  941. const epriv = sea.epriv
  942. const epub = at.put.epub
  943. // Success! we've found our private data!
  944. err = null
  945. return { proof: proof, at: at, pin: newPin, key: { pub: pub, priv: priv, epriv: epriv, epub: epub } }
  946. } catch (e) {
  947. err = 'Failed to decrypt private key!'
  948. return
  949. }
  950. }).filter((props) => !!props))
  951. if (!key) {
  952. throw { err: err || 'Public key does not exist!' }
  953. }
  954. // now we have AES decrypted the private key,
  955. // if we were successful, then that means we're logged in!
  956. try {
  957. await updateStorage(proof, key, newPin || pin)(key)
  958. const user = Object.assign(key, { at: at, proof: proof })
  959. const pIN = newPin || pin
  960. const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') }
  961. return await finalizeLogin(alias, user, gunRoot, pinProp)
  962. } catch (e) { // TODO: right log message ?
  963. Gun.log('Failed to finalize login with new password!')
  964. const { err = '' } = e || {}
  965. throw { err: 'Finalizing new password login failed! Reason: '+err }
  966. }
  967. }
  968. module.exports = authRecall
  969. })(USE, './recall');
  970. ;USE(function(module){
  971. const authPersist = USE('./persist')
  972. const authsettings = USE('./settings')
  973. //const { scope: seaIndexedDb } = USE('./indexed')
  974. // This internal func executes logout actions
  975. const authLeave = async (gunRoot, alias = gunRoot._.user._.alias) => {
  976. var user = gunRoot._.user._ || {};
  977. [ 'get', 'soul', 'ack', 'put', 'is', 'alias', 'pub', 'epub', 'sea' ].map((key) => delete user[key])
  978. if(user.$){
  979. delete user.$.is;
  980. }
  981. // Let's use default
  982. gunRoot.user();
  983. // Removes persisted authentication & CryptoKeys
  984. try {
  985. await authPersist({ alias: alias })
  986. } catch (e) {} //eslint-disable-line no-empty
  987. return { ok: 0 }
  988. }
  989. module.exports = authLeave
  990. })(USE, './leave');
  991. ;USE(function(module){
  992. var Gun = USE('./sea').Gun;
  993. Gun.chain.then = function(cb){
  994. var gun = this, p = (new Promise(function(res, rej){
  995. gun.once(res);
  996. }));
  997. return cb? p.then(cb) : p;
  998. }
  999. })(USE, './then');
  1000. ;USE(function(module){
  1001. var SEA = USE('./sea');
  1002. var Gun = SEA.Gun;
  1003. var then = USE('./then');
  1004. function User(root){
  1005. this._ = {$: this};
  1006. }
  1007. User.prototype = (function(){ function F(){}; F.prototype = Gun.chain; return new F() }()) // Object.create polyfill
  1008. User.prototype.constructor = User;
  1009. // let's extend the gun chain with a `user` function.
  1010. // only one user can be logged in at a time, per gun instance.
  1011. Gun.chain.user = function(pub){
  1012. var gun = this, root = gun.back(-1), user;
  1013. if(pub){ return root.get('~'+pub) }
  1014. if(user = root.back('user')){ return user }
  1015. var root = (root._), at = root, uuid = at.opt.uuid || Gun.state.lex;
  1016. (at = (user = at.user = gun.chain(new User))._).opt = {};
  1017. at.opt.uuid = function(cb){
  1018. var id = uuid(), pub = root.user;
  1019. if(!pub || !(pub = (pub._).sea) || !(pub = pub.pub)){ return id }
  1020. id = id + '~' + pub + '.';
  1021. if(cb && cb.call){ cb(null, id) }
  1022. return id;
  1023. }
  1024. return user;
  1025. }
  1026. Gun.User = User;
  1027. module.exports = User;
  1028. })(USE, './user');
  1029. ;USE(function(module){
  1030. // TODO: This needs to be split into all separate functions.
  1031. // Not just everything thrown into 'create'.
  1032. const SEA = USE('./sea')
  1033. const User = USE('./user')
  1034. const authRecall = USE('./recall')
  1035. const authsettings = USE('./settings')
  1036. const authenticate = USE('./authenticate')
  1037. const finalizeLogin = USE('./login')
  1038. const authLeave = USE('./leave')
  1039. const _initial_authsettings = USE('./settings').recall
  1040. const Gun = SEA.Gun;
  1041. var u;
  1042. // Well first we have to actually create a user. That is what this function does.
  1043. User.prototype.create = function(username, pass, cb, opt){
  1044. // TODO: Needs to be cleaned up!!!
  1045. const gunRoot = this.back(-1)
  1046. var gun = this, cat = (gun._);
  1047. cb = cb || function(){};
  1048. if(cat.ing){
  1049. cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
  1050. return gun;
  1051. }
  1052. cat.ing = true;
  1053. opt = opt || {};
  1054. var resolve = function(){}, reject = resolve;
  1055. // Because more than 1 user might have the same username, we treat the alias as a list of those users.
  1056. if(cb){ resolve = reject = cb }
  1057. gunRoot.get('~@'+username).get(async (at, ev) => {
  1058. ev.off()
  1059. if (at.put && !opt.already) {
  1060. // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
  1061. const err = 'User already created!'
  1062. Gun.log(err)
  1063. cat.ing = false;
  1064. gun.leave();
  1065. return reject({ err: err })
  1066. }
  1067. const salt = Gun.text.random(64)
  1068. // pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it.
  1069. try {
  1070. const proof = await SEA.work(pass, salt)
  1071. // this will take some short amount of time to produce a proof, which slows brute force attacks.
  1072. const pairs = await SEA.pair()
  1073. // now we have generated a brand new ECDSA key pair for the user account.
  1074. const pub = pairs.pub
  1075. const priv = pairs.priv
  1076. const epriv = pairs.epriv
  1077. // the user's public key doesn't need to be signed. But everything else needs to be signed with it!
  1078. const alias = await SEA.sign(username, pairs)
  1079. if(u === alias){ throw SEA.err }
  1080. const epub = await SEA.sign(pairs.epub, pairs)
  1081. if(u === epub){ throw SEA.err }
  1082. // to keep the private key safe, we AES encrypt it with the proof of work!
  1083. const auth = await SEA.encrypt({ priv: priv, epriv: epriv }, proof)
  1084. .then((auth) => // TODO: So signedsalt isn't needed?
  1085. // SEA.sign(salt, pairs).then((signedsalt) =>
  1086. SEA.sign({ek: auth, s: salt}, pairs)
  1087. // )
  1088. ).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); cat.ing = false; gun.leave(); reject(e) })
  1089. const user = { alias: alias, pub: pub, epub: epub, auth: auth }
  1090. const tmp = '~'+pairs.pub;
  1091. // awesome, now we can actually save the user with their public key as their ID.
  1092. try{
  1093. gunRoot.get(tmp).put(user)
  1094. }catch(e){console.log(e)}
  1095. // next up, we want to associate the alias with the public key. So we add it to the alias list.
  1096. gunRoot.get('~@'+username).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp)))
  1097. // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
  1098. 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.
  1099. } catch (e) {
  1100. Gun.log('SEA.create failed!')
  1101. cat.ing = false;
  1102. gun.leave();
  1103. reject(e)
  1104. }
  1105. })
  1106. return gun; // gun chain commands must return gun chains!
  1107. }
  1108. // now that we have created a user, we want to authenticate them!
  1109. User.prototype.auth = function(alias, pass, cb, opt){
  1110. // TODO: Needs to be cleaned up!!!!
  1111. const opts = opt || (typeof cb !== 'function' && cb)
  1112. let pin = opts && opts.pin
  1113. let newpass = opts && opts.newpass
  1114. const gunRoot = this.back(-1)
  1115. cb = typeof cb === 'function' ? cb : () => {}
  1116. newpass = newpass || (opts||{}).change;
  1117. var gun = this, cat = (gun._);
  1118. if(cat.ing){
  1119. cb({err: "User is already being created or authenticated!", wait: true});
  1120. return gun;
  1121. }
  1122. cat.ing = true;
  1123. if (!pass && pin) { (async function(){
  1124. try {
  1125. var r = await authRecall(gunRoot, { alias: alias, pin: pin })
  1126. return cat.ing = false, cb(r), gun;
  1127. } catch (e) {
  1128. var err = { err: 'Auth attempt failed! Reason: No session data for alias & PIN' }
  1129. return cat.ing = false, gun.leave(), cb(err), gun;
  1130. }}())
  1131. return gun;
  1132. }
  1133. const putErr = (msg) => (e) => {
  1134. const { message, err = message || '' } = e
  1135. Gun.log(msg)
  1136. var error = { err: msg+' Reason: '+err }
  1137. return cat.ing = false, gun.leave(), cb(error), gun;
  1138. }
  1139. (async function(){ try {
  1140. const keys = await authenticate(alias, pass, gunRoot)
  1141. if (!keys) {
  1142. return putErr('Auth attempt failed!')({ message: 'No keys' })
  1143. }
  1144. const pub = keys.pub
  1145. const priv = keys.priv
  1146. const epub = keys.epub
  1147. const epriv = keys.epriv
  1148. // we're logged in!
  1149. if (newpass) {
  1150. // password update so encrypt private key using new pwd + salt
  1151. try {
  1152. const salt = Gun.text.random(64);
  1153. const encSigAuth = await SEA.work(newpass, salt)
  1154. .then((key) =>
  1155. SEA.encrypt({ priv: priv, epriv: epriv }, key)
  1156. .then((auth) => SEA.sign({ek: auth, s: salt}, keys))
  1157. )
  1158. const signedEpub = await SEA.sign(epub, keys)
  1159. const signedAlias = await SEA.sign(alias, keys)
  1160. const user = {
  1161. pub: pub,
  1162. alias: signedAlias,
  1163. auth: encSigAuth,
  1164. epub: signedEpub
  1165. }
  1166. // awesome, now we can update the user using public key ID.
  1167. gunRoot.get('~'+user.pub).put(user)
  1168. // then we're done
  1169. const login = finalizeLogin(alias, keys, gunRoot, { pin })
  1170. login.catch(putErr('Failed to finalize login with new password!'))
  1171. return cat.ing = false, cb(await login), gun
  1172. } catch (e) {
  1173. return putErr('Password set attempt failed!')(e)
  1174. }
  1175. } else {
  1176. const login = finalizeLogin(alias, keys, gunRoot, { pin: pin })
  1177. login.catch(putErr('Finalizing login failed!'))
  1178. return cat.ing = false, cb(await login), gun;
  1179. }
  1180. } catch (e) {
  1181. return putErr('Auth attempt failed!')(e)
  1182. } }());
  1183. return gun;
  1184. }
  1185. User.prototype.pair = function(){
  1186. var user = this;
  1187. if(!user.is){ return false }
  1188. return user._.sea;
  1189. }
  1190. User.prototype.leave = async function(){
  1191. var gun = this, user = (gun.back(-1)._).user;
  1192. if(user){
  1193. delete user.is;
  1194. delete user._.is;
  1195. delete user._.sea;
  1196. }
  1197. if(typeof window !== 'undefined'){
  1198. var tmp = window.sessionStorage;
  1199. delete tmp.alias;
  1200. delete tmp.tmp;
  1201. }
  1202. return await authLeave(this.back(-1))
  1203. }
  1204. // If authenticated user wants to delete his/her account, let's support it!
  1205. User.prototype.delete = async function(alias, pass){
  1206. const gunRoot = this.back(-1)
  1207. try {
  1208. const __gky40 = await authenticate(alias, pass, gunRoot)
  1209. const pub = __gky40.pub
  1210. await authLeave(gunRoot, alias)
  1211. // Delete user data
  1212. gunRoot.get('~'+pub).put(null)
  1213. // Wipe user data from memory
  1214. const { user = { _: {} } } = gunRoot._;
  1215. // TODO: is this correct way to 'logout' user from Gun.User ?
  1216. [ 'alias', 'sea', 'pub' ].map((key) => delete user._[key])
  1217. user._.is = user.is = {}
  1218. gunRoot.user()
  1219. return { ok: 0 } // TODO: proper return codes???
  1220. } catch (e) {
  1221. Gun.log('User.delete failed! Error:', e)
  1222. throw e // TODO: proper error codes???
  1223. }
  1224. }
  1225. // If authentication is to be remembered over reloads or browser closing,
  1226. // set validity time in minutes.
  1227. User.prototype.recall = function(setvalidity, options){
  1228. var gun = this;
  1229. const gunRoot = this.back(-1)
  1230. let validity
  1231. let opts
  1232. var o = setvalidity;
  1233. if(o && o.sessionStorage){
  1234. if(typeof window !== 'undefined'){
  1235. var tmp = window.sessionStorage;
  1236. if(tmp){
  1237. gunRoot._.opt.remember = true;
  1238. if(tmp.alias && tmp.tmp){
  1239. gunRoot.user().auth(tmp.alias, tmp.tmp);
  1240. }
  1241. }
  1242. }
  1243. return gun;
  1244. }
  1245. if (!Gun.val.is(setvalidity)) {
  1246. opts = setvalidity
  1247. validity = _initial_authsettings.validity
  1248. } else {
  1249. opts = options
  1250. validity = setvalidity * 60 // minutes to seconds
  1251. }
  1252. try {
  1253. // opts = { hook: function({ iat, exp, alias, proof }) }
  1254. // iat == Date.now() when issued, exp == seconds to expire from iat
  1255. // How this works:
  1256. // called when app bootstraps, with wanted options
  1257. // IF authsettings.validity === 0 THEN no remember-me, ever
  1258. // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
  1259. authsettings.validity = typeof validity !== 'undefined'
  1260. ? validity : _initial_authsettings.validity
  1261. authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
  1262. ? opts.hook : _initial_authsettings.hook
  1263. // All is good. Should we do something more with actual recalled data?
  1264. (async function(){ await authRecall(gunRoot) }());
  1265. return gun;
  1266. } catch (e) {
  1267. const err = 'No session!'
  1268. Gun.log(err)
  1269. // NOTE! It's fine to resolve recall with reason why not successful
  1270. // instead of rejecting...
  1271. //return { err: (e && e.err) || err }
  1272. return gun;
  1273. }
  1274. }
  1275. User.prototype.alive = async function(){
  1276. const gunRoot = this.back(-1)
  1277. try {
  1278. // All is good. Should we do something more with actual recalled data?
  1279. await authRecall(gunRoot)
  1280. return gunRoot._.user._
  1281. } catch (e) {
  1282. const err = 'No session!'
  1283. Gun.log(err)
  1284. throw { err }
  1285. }
  1286. }
  1287. User.prototype.trust = async function(user){
  1288. // TODO: BUG!!! SEA `node` read listener needs to be async, which means core needs to be async too.
  1289. //gun.get('alice').get('age').trust(bob);
  1290. if (Gun.is(user)) {
  1291. user.get('pub').get((ctx, ev) => {
  1292. console.log(ctx, ev)
  1293. })
  1294. }
  1295. }
  1296. User.prototype.grant = function(to, cb){
  1297. console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
  1298. var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
  1299. gun.back(function(at){ if(at.pub){ return } path += (at.get||'') });
  1300. (async function(){
  1301. var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
  1302. sec = await SEA.decrypt(sec, pair);
  1303. if(!sec){
  1304. sec = SEA.random(16).toString();
  1305. enc = await SEA.encrypt(sec, pair);
  1306. user.get('trust').get(pair.pub).get(path).put(enc);
  1307. }
  1308. var pub = to.get('pub').then();
  1309. var epub = to.get('epub').then();
  1310. pub = await pub; epub = await epub;
  1311. var dh = await SEA.secret(epub, pair);
  1312. enc = await SEA.encrypt(sec, dh);
  1313. user.get('trust').get(pub).get(path).put(enc, cb);
  1314. }());
  1315. return gun;
  1316. }
  1317. User.prototype.secret = function(data, cb){
  1318. console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
  1319. var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
  1320. gun.back(function(at){ if(at.pub){ return } path += (at.get||'') });
  1321. (async function(){
  1322. var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
  1323. sec = await SEA.decrypt(sec, pair);
  1324. if(!sec){
  1325. sec = SEA.random(16).toString();
  1326. enc = await SEA.encrypt(sec, pair);
  1327. user.get('trust').get(pair.pub).get(path).put(enc);
  1328. }
  1329. enc = await SEA.encrypt(data, sec);
  1330. gun.put(enc, cb);
  1331. }());
  1332. return gun;
  1333. }
  1334. module.exports = User
  1335. })(USE, './create');
  1336. ;USE(function(module){
  1337. const SEA = USE('./sea')
  1338. const Gun = SEA.Gun;
  1339. // After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
  1340. // We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
  1341. Gun.on('opt', function(at){
  1342. if(!at.sea){ // only add SEA once per instance, on the "at" context.
  1343. at.sea = {own: {}};
  1344. at.on('in', security, at); // now listen to all input data, acting as a firewall.
  1345. at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
  1346. at.on('node', each, at);
  1347. }
  1348. this.to.next(at); // make sure to call the "next" middleware adapter.
  1349. });
  1350. // Alright, this next adapter gets run at the per node level in the graph database.
  1351. // This will let us verify that every property on a node has a value signed by a public key we trust.
  1352. // If the signature does not match, the data is just `undefined` so it doesn't get passed on.
  1353. // If it does match, then we transform the in-memory "view" of the data into its plain value (without the signature).
  1354. // Now NOTE! Some data is "system" data, not user data. Example: List of public keys, aliases, etc.
  1355. // This data is self-enforced (the value can only match its ID), but that is handled in the `security` function.
  1356. // From the self-enforced data, we can see all the edges in the graph that belong to a public key.
  1357. // Example: ~ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and
  1358. // its encrypted private key, but it might also have other signed values on it like `profile = <ID>` edge.
  1359. // Using that directed edge's ID, we can then track (in memory) which IDs belong to which keys.
  1360. // Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous!
  1361. // This means we should ONLY trust our "friends" (our key ring) public keys, not any ones.
  1362. // I have not yet added that to SEA yet in this alpha release. That is coming soon, but beware in the meanwhile!
  1363. function each(msg){ // TODO: Warning: Need to switch to `gun.on('node')`! Do not use `Gun.on('node'` in your apps!
  1364. // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!!
  1365. // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT.
  1366. var to = this.to, vertex = (msg.$._).put, c = 0, d;
  1367. Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node
  1368. // TODO: consider async/await use here...
  1369. SEA.verify(val, false, function(data){ c--; // false just extracts the plain data.
  1370. node[key] = val = data; // transform to plain value.
  1371. if(d && !c && (c = -1)){ to.next(msg) }
  1372. });
  1373. });
  1374. d = true;
  1375. if(d && !c){ to.next(msg) }
  1376. return;
  1377. }
  1378. // signature handles data output, it is a proxy to the security function.
  1379. function signature(msg){
  1380. if(msg.user){
  1381. return this.to.next(msg);
  1382. }
  1383. var ctx = this.as;
  1384. msg.user = ctx.user;
  1385. security.call(this, msg);
  1386. }
  1387. // okay! The security function handles all the heavy lifting.
  1388. // It needs to deal read and write of input and output of system data, account/public key data, and regular data.
  1389. // This is broken down into some pretty clear edge cases, let's go over them:
  1390. function security(msg){
  1391. var at = this.as, sea = at.sea, to = this.to;
  1392. if(msg.get){
  1393. // if there is a request to read data from us, then...
  1394. var soul = msg.get['#'];
  1395. if(soul){ // for now, only allow direct IDs to be read.
  1396. if(soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
  1397. if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
  1398. return to.next(msg); // yes.
  1399. } else
  1400. if('~@' === soul.slice(0,2)){ // Allow reading the list of public keys associated with an alias?
  1401. return to.next(msg); // yes.
  1402. } else { // Allow reading everything?
  1403. return to.next(msg); // yes // TODO: No! Make this a callback/event that people can filter on.
  1404. }
  1405. }
  1406. }
  1407. if(msg.put){
  1408. // potentially parallel async operations!!!
  1409. var check = {}, each = {}, u;
  1410. each.node = function(node, soul){
  1411. if(Gun.obj.empty(node, '_')){ return check['node'+soul] = 0 } // ignore empty updates, don't reject them.
  1412. Gun.obj.map(node, each.way, {soul: soul, node: node});
  1413. };
  1414. each.way = function(val, key){
  1415. var soul = this.soul, node = this.node, tmp;
  1416. if('_' === key){ return } // ignore meta data
  1417. if('~@' === soul){ // special case for shared system data, the list of aliases.
  1418. each.alias(val, key, node, soul); return;
  1419. }
  1420. if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
  1421. each.pubs(val, key, node, soul); return;
  1422. }
  1423. if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
  1424. each.pub(val, key, node, soul, tmp, msg.user); return;
  1425. }
  1426. each.any(val, key, node, soul, msg.user); return;
  1427. return each.end({err: "No other data allowed!"});
  1428. };
  1429. each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}}
  1430. if(!val){ return each.end({err: "Data must exist!"}) } // data MUST exist
  1431. if('~@'+key === Gun.val.link.is(val)){ return check['alias'+key] = 0 } // in fact, it must be EXACTLY equal to itself
  1432. each.end({err: "Mismatching alias."}); // if it isn't, reject.
  1433. };
  1434. each.pubs = function(val, key, node, soul){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
  1435. if(!val){ return each.end({err: "Alias must exist!"}) } // data MUST exist
  1436. if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property
  1437. each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys.
  1438. };
  1439. each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}}
  1440. if('pub' === key){
  1441. if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key.
  1442. return each.end({err: "Account must match!"});
  1443. }
  1444. check['user'+soul+key] = 1;
  1445. if(user && (user = user._) && user.sea && pub === user.pub){
  1446. //var id = Gun.text.random(3);
  1447. SEA.sign(val, user.sea, function(data){ var rel;
  1448. if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) }
  1449. if(rel = Gun.val.link.is(val)){
  1450. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  1451. }
  1452. node[key] = data;
  1453. check['user'+soul+key] = 0;
  1454. each.end({ok: 1});
  1455. });
  1456. // TODO: Handle error!!!!
  1457. return;
  1458. }
  1459. SEA.verify(val, pub, function(data){ var rel, tmp;
  1460. if(u === data){ // make sure the signature matches the account it claims to be on.
  1461. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
  1462. }
  1463. if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
  1464. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  1465. }
  1466. check['user'+soul+key] = 0;
  1467. each.end({ok: 1});
  1468. });
  1469. };
  1470. function relpub(s){
  1471. if(!s){ return }
  1472. s = s.split('~');
  1473. if(!s || !(s = s[1])){ return }
  1474. s = s.split('.');
  1475. if(!s || 2 > s.length){ return }
  1476. s = s.slice(0,2).join('.');
  1477. return s;
  1478. }
  1479. each.any = function(val, key, node, soul, user){ var tmp, pub;
  1480. if(!user || !(user = user._) || !(user = user.sea)){
  1481. if(tmp = relpub(soul)){
  1482. check['any'+soul+key] = 1;
  1483. SEA.verify(val, pub = tmp, function(data){ var rel;
  1484. if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
  1485. if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
  1486. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  1487. }
  1488. check['any'+soul+key] = 0;
  1489. each.end({ok: 1});
  1490. });
  1491. return;
  1492. }
  1493. check['any'+soul+key] = 1;
  1494. at.on('secure', function(msg){ this.off();
  1495. check['any'+soul+key] = 0;
  1496. if(at.opt.secure){ msg = null }
  1497. each.end(msg || {err: "Data cannot be modified."});
  1498. }).on.on('secure', msg);
  1499. //each.end({err: "Data cannot be modified."});
  1500. return;
  1501. }
  1502. if(!(tmp = relpub(soul))){
  1503. if(at.opt.secure){
  1504. each.end({err: "Soul is missing public key at '" + key + "'."});
  1505. return;
  1506. }
  1507. if(val && val.slice && 'SEA{' === (val).slice(0,4)){
  1508. check['any'+soul+key] = 0;
  1509. each.end({ok: 1});
  1510. return;
  1511. }
  1512. //check['any'+soul+key] = 1;
  1513. //SEA.sign(val, user, function(data){
  1514. // if(u === data){ return each.end({err: 'Any signature failed.'}) }
  1515. // node[key] = data;
  1516. check['any'+soul+key] = 0;
  1517. each.end({ok: 1});
  1518. //});
  1519. return;
  1520. }
  1521. var pub = tmp;
  1522. if(pub !== user.pub){
  1523. each.any(val, key, node, soul);
  1524. return;
  1525. }
  1526. /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
  1527. if(user.pub !== p){ return p }
  1528. });
  1529. if(other){
  1530. each.any(val, key, node, soul);
  1531. return;
  1532. }*/
  1533. check['any'+soul+key] = 1;
  1534. SEA.sign(val, user, function(data){
  1535. if(u === data){ return each.end({err: 'My signature fail.'}) }
  1536. node[key] = data;
  1537. check['any'+soul+key] = 0;
  1538. each.end({ok: 1});
  1539. });
  1540. }
  1541. each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb?
  1542. if(each.err){ return }
  1543. if((each.err = ctx.err) || ctx.no){
  1544. console.log('NO!', each.err, msg.put);
  1545. return;
  1546. }
  1547. if(!each.end.ed){ return }
  1548. if(Gun.obj.map(check, function(no){
  1549. if(no){ return true }
  1550. })){ return }
  1551. to.next(msg);
  1552. };
  1553. Gun.obj.map(msg.put, each.node);
  1554. each.end({end: each.end.ed = true});
  1555. return; // need to manually call next after async.
  1556. }
  1557. to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
  1558. }
  1559. })(USE, './index');
  1560. }());