-
-
Save jabis/a3b7d5b475ef23fb859d3acabe9325cb to your computer and use it in GitHub Desktop.
/** | |
* get and put encrypted and/or signed material to paths in Gun | |
* FIXME: Investigate why root level put doesn't work | |
* | |
* DONE: Add signing to make objects unwritable by others | |
* Changes: | |
* - 25.02.2020 | |
* - added mergedeep to better merge deeper objects between themselves | |
* - added pair.osign option to only sign not encrypt when passing existing pairs | |
**/ | |
(async function(){ | |
var Nug = this.Nug = null; | |
var atoob = this.atoob = function atoob(arr){ | |
var obj = {}; | |
Gun.list.map(arr, function(v,f,t){ | |
if(Gun.list.is(v) || Gun.obj.is(v)){ | |
obj[f] = atoob(v); | |
return; | |
} | |
obj[f] = v; | |
}); | |
return obj; | |
}; | |
const getNug = this.getNug = function(){ | |
if(Nug) return Nug.back(-1); | |
if(Gun) Nug = new Gun(location.protocol+"//"+location.host+"/gun"); | |
return Nug.back(-1); | |
} | |
const mrgdeep = this.mrgdeep = function mrgdeep(...objects) { | |
const isObject = obj => obj && typeof obj === 'object'; | |
return objects.reduce((prev, obj) => { | |
Object.keys(obj).forEach(key => { | |
const pVal = prev[key]; | |
const oVal = obj[key]; | |
if (Array.isArray(pVal) && Array.isArray(oVal)) { | |
prev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index); | |
} | |
else if (isObject(pVal) && isObject(oVal)) { | |
prev[key] = mrgdeep(pVal, oVal); | |
} | |
else { | |
prev[key] = oVal; | |
} | |
}); | |
return prev; | |
}, {}); | |
} | |
const decEnc = this.decEnc = async(...args)=> { | |
console.log(args); | |
let data = args.length > 0 ? args.shift() : false; // data as first argument | |
let pair = args.length > 0 ? args.shift() : false; // pair to use in decrypting/encrypting as second argument | |
let mode = args.length > 0 ? args.shift() : "decrypt"; // mode optional encrypt/decrypt on SEA defaults to decrypt | |
//TODO return Promise.reject if we don't have all mandatory arguments passed | |
let secret = await SEA.secret(pair.epub, pair); | |
let it = await SEA[mode](data, secret); | |
return it; | |
}; | |
const createRWEResource = this.createRWEResource = async(...args)=> { | |
let key = args.shift(); | |
let data = args.shift(); | |
let encIt = args.length>0 ? args.shift() : false; | |
let addUuid = args.length>0 ? args.shift() : false; | |
const uuid = gun._.opt.uuid(); | |
let pair = await SEA.pair(); | |
if(typeOf(encIt) == "object" && encIt.priv) { | |
pair = encIt; | |
var onlysign = pair.osign? true:false; | |
if(pair.hasOwnProperty('osign')) delete pair["osign"]; | |
if(onlysign) encIt = false; | |
} | |
let id = "~"+pair.pub; | |
if(addUuid) id = id + "." + uuid; | |
let nug = getNug(); | |
if(!encIt){ | |
let datax = {"#":id,'.':key,':':data,'>':Gun.state()} | |
let signed = await SEA.sign(datax,pair); | |
let putsi = await nug.get(id).get(key).put(signed).then(); | |
console.log("putsi",putsi); | |
return {id:id,key:key,ref:nug.get(id).get(key),pair:pair}; | |
} | |
else { | |
return await decEnc(data,pair,"encrypt").then(async(enc)=>{ | |
let datax = {"#":id,'.':key,':':'a'+enc,'>':Gun.state()} | |
let signed = await SEA.sign(datax,pair); | |
console.log(pair,signed); | |
let putsi = await nug.get(id).get(key).put(signed).then() | |
console.log("putsi",putsi); | |
return {id:id,key:key,ref:nug.get(id).get(key),pair:pair}; | |
}); | |
} | |
}; | |
Gun.chain.putsenc = async function(){ | |
let args = Array.from(arguments); | |
let self = this; | |
let gun = this.back(-1); | |
let data = args.shift(); | |
let pair = args.length > 0 ? args.shift() : (gun.user().is ? gun.user()._.sea : null); | |
let genuuid = args.length > 0 ? args.shift() : false; | |
let onlysign = args.length > 0 ? args.shift() : false; | |
if(!pair) return Promise.reject("No keypair provided or not logged in"); | |
if(onlysign) pair.osign=true; | |
return self.once(async function(olddata,key) { | |
return await createRWEResource(key, data, pair, genuuid); | |
}); | |
}; | |
Gun.chain.putenc = async function() { | |
let args = Array.from(arguments); | |
let self = this; | |
let gun = this.back(-1); | |
let nug = getNug(); | |
let data = args.shift(); | |
let me = gun.user().is; | |
let pair = args.length > 0 ? args.shift() : me ? gun.user()._.sea : null; | |
let onlysign = args.length > 0 ? args.shift() : false; | |
//TODO: Implement checking with old pair as well as throw if error | |
if (!pair) return Promise.reject("No keypair provided or not logged in"); | |
if(onlysign) pair.osign = true; | |
return this.once(async function(olddata, key) { | |
console.log("old data", olddata); | |
//TODO: Check the onlysign and skip decrypt and encrypt in that case | |
if(typeof olddata === "string" && /^(aSEA)/.test(olddata)) olddata = olddata.slice(1) | |
return await decEnc(olddata, pair) | |
.then(async old => { | |
console.log("old", old); | |
var nd = null; | |
if (!old) { | |
nd = data; | |
} else { | |
if (typeof old === "object" && data && typeof data ==="object") { | |
// trying to simulate regular put | |
nd = mrgdeep(old, data); | |
} else { | |
nd = data; | |
} | |
} | |
console.log("new data", nd); | |
return await decEnc(nd, pair, "encrypt").then(async (enc) => { | |
console.log("encrypted", enc); | |
if (!me && pair && pair.priv) { | |
let id = "~"+pair.pub; | |
let signed = await SEA.sign( | |
{ | |
"#": id, | |
".": key, | |
":": "a"+enc, | |
">": Gun.state() | |
}, | |
pair | |
); | |
console.log("signed",signed); | |
return await nug.get(id).get(key).put(signed).then(); | |
} else { | |
return await self.put("a"+enc).then(); | |
} | |
}); | |
}) | |
.catch(err => { | |
console.log("whoops", err); | |
return gun; | |
}); | |
}); | |
}; | |
Gun.chain.getenc = async function(){ | |
let args = Array.from(arguments); | |
let self = this; | |
let gun = this.back(-1); | |
let path = args.length > 0 ? args.shift() : false; | |
let pair = args.length > 0 ? args.shift() : (gun.user().is ? gun.user()._.sea : null) | |
if(!pair) return Promise.reject("No keypair provided or not logged in"); | |
let data; | |
if(!path) { | |
data = await self.once().then(); | |
} else { | |
data = await this.get(path).then(); | |
} | |
//console.log(data,pair,path); | |
if(typeof data === "string" && /^(aSEA)/.test(data)) data = data.slice(1) | |
let dec = await decEnc(data, pair); | |
return dec ? dec : null; | |
}; | |
const putMyDataEnc = this.putMyDataEnc = async (...args) => { | |
//let args = arguments; Array.from(arguments); | |
console.log(args); | |
let cb = false; | |
if(args && args.length > 0 && typeof args[args.length-1] === "function"){ | |
cb = args.pop(); // if we have a cb function in last of the list, then pop that | |
} | |
let path = args.length > 0 ? args.shift() : false; // path as first argument | |
let data = args.length > 0 ? args.shift() : false; // data as second argument | |
let pair = args.length > 0 ? args.shift() : false; // pair optional pair to use in encrypting | |
if(!path || !data) return Promise.reject("No path or data!"); | |
const me = gun.user(); | |
if (me.is) { | |
//authenticated | |
const mypair = me._.sea; | |
let usePair = mypair; | |
if(pair && pair.epub && pair.epriv) usePair = pair; | |
let enc = await decEnc(data, usePair,"encrypt"); | |
if (cb) { | |
return me | |
.get(path) | |
.put(enc,cb) | |
} else { | |
return await me | |
.get(path) | |
.put(enc) | |
.then(); | |
} | |
} else { | |
return Promise.reject("Not authenticated"); | |
} | |
}; | |
const getMyDataEnc = this.getMyDataEnc = async (...args)=>{ | |
console.log(args); | |
let path = args.shift(); // path as first argument | |
let pair = args.length > 0 ? args.shift() : false; // optional pair to decrypt with | |
const me = gun.user(); | |
if(me.is) { //authenticated | |
const mypair = me._.sea; | |
let usePair = mypair; | |
if(pair && pair.epub && pair.epriv) usePair = pair; | |
let data = await me | |
.get(path) | |
.then() | |
//console.log(data); | |
let dec = await decEnc(data, usePair) | |
return dec; | |
} else { | |
return Promise.reject("Not authenticated") | |
} | |
}; | |
// USAGE: | |
// creating a new resource with generated keypair signature: {keyname} to store to, {data} to house {pair} a pair or boolean, {uuid} whether to append random uuid to pubkey | |
let testresource1 = await createRWEResource("testing/encryption/321",{'data':'I','want':'to',encrypt:'true'},true,true); | |
console.log(testresource1); // {id:created id, key: referenced key, ref:gun node, pair: if not your user-pair save this if you ever want to touch this node again} | |
// creating/overwriting a resource with your user keypair directly to gun.user().get("testing/encryption/321") | |
let mypair = gun.user()._.sea; | |
testresource2 = await createRWEResource("testing/encryption/321",{'data':'I','dontwant':'to',encrypt:'true'},mypair); | |
console.log(testresource2); | |
//decode with decEnc | |
let id = testresource2.id; | |
let key = testresource2.key; | |
let setti = testresource2.ref; | |
let pair = testresource2.pair; | |
let val = await setti.once(Gun.log).then(); | |
let dec = await decEnc(val.slice(1),testresource2.pair) | |
console.log(dec) | |
//{data: "I", dontwant: "to", encrypt: "true"} | |
let example = await gun.get("testing/encryption/123").get("example").putsenc({"encrypted":"stuff"}); // overwrite whole example | |
console.log(example); | |
let readexample = await gun.get("testing/encryption/123").getenc("example"); | |
console.log(readexample); | |
let updateexample = await gun.get("testing/encryption/123").get("example").putenc({"stuff":"encrypted stuff"}); | |
console.log(updateexample); | |
setTimeout(async()=>{ | |
let x = await gun.get("testing/encryption/123").getenc("example"); | |
console.log("some wait time so we wont resolve from localstorage",x); | |
},250) | |
putMyDataEnc("testing/encryption/123",{socks:"wet"}).then((d)=>{ console.log(d); }).catch((err)=>{ console.log(err); }) | |
getMyDataEnc("testing/encryption/123").then((d)=>{ console.log(d); }).catch((err)=>{ console.log(err); }) | |
//testing decryption of the node: | |
gun.user().get("testing/encryption/123").once(function(data){ | |
decEnc(data,gun.user()._.sea).then((d)=>{console.log(d)}).catch((err)=>{ console.log(err); }) | |
}) | |
})(); |
I'm refactoring some aspects of this example script as I'm wanting it to be a bit more generic, so you would have a choice of
- encrypt the whole node
- encrypt only values inside the object
And to chaining in general, returning without the put...then()
calls seems to keep the chaining functional, but I'm working purely with async/await/promises so in my use case I kept the current setting with the .then()
ables, for where I want the encryption/decryption I simply want the decrypted value or ack
returned in the process
Also additional note, these objects are encrypted, but not write protected, without signing them and including the pubkey in the public graph, so it's only partial solution so far :)
@jabis that sounds pretty cool !
When do you think it's functionality will be totally finished, including write permissions ?
Best regards Zilveer
It's on my to-do list, but can't promise exact timeframe, as I'm working on big project, where this functionality is not the biggest priority :)
I'm thinking maybe I'll throw a new revision by christmas - but if sooner I'll update the gist so you'll get notified if you follow it :)
Hi,
Just a friendly reminder about what I am waiting for :)
I am looking forward to use this in my project :))
Best regards Zilveer
Handling a family pet emergency, so will take a little longer unfortunately
@jabis sorry to hear, hope everything is going good.
Just eager to your helper class released soon so I can use it in my app :)
Best regards Zilveer
@jabis That is great to hear, hope you pet is better now!
Would you mind to add some kind of callback
functionaltity to putenc
, putsenc
as it works in put(obj, cb)
?
It would also be awesome if you can add the callback
feature to the standalone functionalities!
Best regards Zilveer
I'm not a fan of callback-hell but I'll see if I can make it somewhat compatible. The thing is I'm plucking this out of the framework and replacing things that I rely on elsewhere with vanilla, so it's a little chore to do :D
@zilveer https://gist.github.com/jabis/a3b7d5b475ef23fb859d3acabe9325cb#file-myhelper-js-L24-L65 here I'm now trying upon creation to return the ref to the gun chain for further use, well we'll see at the end of the week if I can wrap my head around how to mold the Gun.chain to somewhat sane implementation ^^
Note: that's now write protected and put(s)enc can't yet touch it - have to solve the signing (Nug to use) there, but gun.get('~createdpubkeystuff').getenc("testing/encryption/321",pair)
is indeed working already
@zilveer, all my methods work with await or .then(function(woot){ console.log(woot); })
instead of adding callbacks
Now you can manipulate data with
createRWEResource/getenc/put(s)enc like:
testresource = await createRWEResource("testing/encryption/321",{'data':'I','want':'to',encrypt:'true'},gun.user()._.sea);
console.log(testresource); // {id,key,gunref,pair}
//and directly read with getenc (well might need a little setTimeout if like me it takes awhile to push changes to peers)
await gun.user().getenc("testing/encryption/321");
//and directly write with putenc
await gun.user().get("testing/encryption/321").putenc({some:"more",data:"here"});
Note createRWEResource currenctly acts like .putsenc
overwriting the data
tidbit: the method createRWEResource comes from create ReadWriteprotectedEncryptedResource - gonna come up with a better name soon :D
@jabis wow such improvement over the current coding. You should indeed have this as a repo instead for GUN or else it is somehow "hidden" from public.
I really like what you have achieved so far, thumbs up !
Regards Zilveer
is this correct:
.putsenc overwrites whole object where .putenc tries to update the object, if its an object?
.get("order").putsenc({ stuff:"here"})
.get("order").putenc({something:"updated"}})
.getenc("order") // {stuff:"here",something:"updated"}
also did you fix the chaining promptly on this?
thanks for reply,
regards