MetaMask start, AXE start, SEA refactor, etc.

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

80
axe.js Normal file
View File

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

View File

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

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

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

4
gun.min.js vendored

File diff suppressed because one or more lines are too long

139
lib/normalize.js Normal file
View File

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

19
lib/reboot.js Normal file
View File

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

View File

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

View File

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

909
sea.js

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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