mirror of
https://github.com/amark/gun.git
synced 2025-06-06 06:06:50 +00:00
MetaMask start, AXE start, SEA refactor, etc.
This commit is contained in:
parent
7f4b77a454
commit
16e64e1de5
80
axe.js
Normal file
80
axe.js
Normal 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');
|
||||
|
||||
}());
|
@ -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
203
examples/move/index.html
Normal 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: '© <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
4
gun.min.js
vendored
File diff suppressed because one or more lines are too long
139
lib/normalize.js
Normal file
139
lib/normalize.js
Normal 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
19
lib/reboot.js
Normal 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());
|
||||
|
||||
}());
|
@ -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') }
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
418
sea/create.js
418
sea/create.js
@ -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);
|
||||
|
@ -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...
|
||||
|
@ -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)
|
||||
|
20
sea/index.js
20
sea/index.js
@ -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 = {};
|
||||
|
||||
|
||||
|
21
sea/leave.js
21
sea/leave.js
@ -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
|
||||
|
49
sea/login.js
49
sea/login.js
@ -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
|
||||
|
12
sea/pair.js
12
sea/pair.js
@ -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...
|
||||
|
@ -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
|
||||
|
43
sea/query.js
43
sea/query.js
@ -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
|
||||
|
141
sea/recall.js
141
sea/recall.js
@ -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
|
||||
|
10
sea/root.js
10
sea/root.js
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
57
sea/shim.js
57
sea/shim.js
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user