Merge branch 'master' of github.com:amark/gun

This commit is contained in:
Jussi Rytkönen 2020-11-23 13:10:16 +02:00
commit 227685cedf
7 changed files with 404 additions and 21 deletions

75
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,75 @@
name: ci
on: [push, pull_request]
env:
project: 'release-node'
jobs:
test:
strategy:
matrix:
node-version: [14.x] # [12.x, 14.x]
os: [ubuntu-latest] #, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
# verify the version in package.json matches the release tag
- name: Version
uses: tcurdt/action-verify-version-npm@master
- name: Setup Node ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Cache
id: cache-modules
uses: actions/cache@v2
with:
path: node_modules
key: ${{ matrix.node-version }}-${{ runner.OS }}-build-${{ hashFiles('package.json') }}
- name: Install
if: steps.cache-modules.outputs.cache-hit != 'true'
run: npm install
- name: Test
run: npm test
# create release artifacts to publish as github release
# - name: Upload
# if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
# uses: actions/upload-artifact@v2
# with:
# name: ${{ env.project }}_${{ matrix.os }}_${{ matrix.node-version }}
# path: |
# !.git
# !.github
# !node_modules
# .
release:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
needs: [test]
runs-on: ubuntu-latest
steps:
# - name: Download
# uses: actions/download-artifact@v2
# with:
# path: artifacts
# - name: Archives
# run: find artifacts -mindepth 1 -maxdepth 1 -exec tar -C {} -cvzf {}.tgz . \;
- name: Release
uses: softprops/action-gh-release@v1
# with:
# files: |
# artifacts/*.tgz
env:
GITHUB_TOKEN: ${{ secrets.PAT }}

17
.github/workflows/cleanup.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: cleanup
on:
schedule:
- cron: '1 1 * * 1' # once a week clean out old artifacts
jobs:
expire:
runs-on: ubuntu-latest
steps:
- name: Expire Artifacts
uses: kolpav/purge-artifacts-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
expire-in: 1hour

35
.github/workflows/dockerhub.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: dockerhub
on:
release:
types: [published]
env:
project: 'release-node'
jobs:
dockerhub:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build
run: |
docker build -t ${{ env.project }} .
- name: Login
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
- name: Tag
run: |
docker tag ${{ env.project }} ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.project }}:${GITHUB_REF/refs\/tags\/v/}
docker tag ${{ env.project }} ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.project }}:latest
- name: Push
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.project }}

22
.github/workflows/npm.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: npm
on:
release:
types: [published]
jobs:
npm:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Publish
env:
NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
run: |
npm config set //registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN
npm install
npm publish --access=public

View File

@ -168,7 +168,8 @@
}
}
var $m = $('<div>').attr('id', 'meta');
$m.append($('<span>').html('&#9776;').addClass('meta-start'));
//$m.append($('<span>').html('&#9776;').addClass('meta-start'));
$m.append($('<span>').html('+').addClass('meta-start'));
$m.append($('<div>').addClass('meta-menu meta-none').append('<ul>'));
$m.on('mouseenter', function(){
if (meta.flip.active || meta.flip.is()) return;
@ -186,7 +187,6 @@
position: 'fixed',
bottom: '2em',
right: '2em',
background: 'white',
'font-size': '18pt',
'font-family': 'Tahoma, arial',
'border-radius': '1em',
@ -197,15 +197,15 @@
width: '2em',
height: '2em',
outline: 'none',
color: '#000044',
overflow: 'visible',
background: 'rgba(0,0,0,0.5)', color: 'white',
transition: 'all 0.2s ease-in'
},
'#meta *': {outline: 'none'},
'#meta .meta-none': {display: 'none'},
'#meta span': {'line-height': '2em'},
'#meta .meta-menu': {
background: 'rgba(0,0,0,0.1)',
background: 'rgba(0,0,0,0.2)',
width: '12em',
right: '-2em',
bottom: '-2em',
@ -224,16 +224,15 @@
'#meta .meta-menu ul li': {
display: 'block',
'float': 'right',
background: 'white',
opacity: 0.7,
padding: '0.5em 1em',
'border-radius': '1em',
'margin-left': '0.25em',
'margin-top': '0.25em',
background: 'rgba(0,0,0,0.2)', 'backdrop-filter': 'blur(10px)', color: 'white',
'cursor': 'pointer'
},
'#meta .meta-menu ul li:hover': {
opacity: 1
background: 'rgba(0,0,0,0.5)'
},
'#meta a': {color: 'black'},
'#meta .meta-menu ul:before': {
@ -289,4 +288,4 @@
meta.flip()
})
})(USE, './metaEvents');
}());
}());

146
sea.js
View File

@ -650,6 +650,67 @@
module.exports = SEA.secret;
})(USE, './secret');
;USE(function(module){
var SEA = USE('./root');
// This is to certify that a group of "certificants" can "put" anything at a group of matched "paths" to the certificate issuer's graph
SEA.certify = SEA.certify || (async (certificants, patterns, issuer, cb, opt = {}) => { try {
/*
IMPORTANT: A Certificate is like a Signature. No one knows who (issuer) created/signed a cert until you put it into their graph.
"certificants": A string (~Bobpub) || a pair || an array of pubs/pairs. These people will have the rights.
"patterns": A string (^inbox.*), or an array of strings [^inbox.*, ^secret\-group.*]. These patterns will be used to check against soul+'/'+key
"issuer": Key pair or priv of the certificate issuer
"cb": A callback function after all things are done
"opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry
*/
// We need some logic here to verify that all params are valid
console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.')
certificants = (() => {
var data = []
if (certificants) {
if (typeof certificants === 'string') {
data.push(certificants)
}
if (Array.isArray(certificants)) {
certificants.map(person => {
if (typeof person ==='string') data.push(person)
else if (typeof person === 'object' && person.pub) data.push(person.pub)
})
}
if (typeof certificants === 'object' && certificants.pub) data.push(certificants.pub)
}
return data
})()
patterns = patterns ? typeof patterns === 'string' ? [patterns] : Array.isArray(patterns) ? patterns : null : null
const data = JSON.stringify({
c: certificants,
p: patterns,
...(opt.expiry && typeof opt.expiry === 'number' ? {e: opt.expiry} : {}) // inject expiry if possible
})
const certificate = await SEA.sign(data, issuer, null, {raw:1})
var r = certificate
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
} catch(e) {
SEA.err = e;
if(SEA.throw){ throw e }
if(cb){ cb() }
return;
}});
module.exports = SEA.certify;
})(USE, './certify');
;USE(function(module){
var shim = USE('./shim');
// Practical examples about usage found in tests.
@ -659,6 +720,7 @@
SEA.verify = USE('./verify');
SEA.encrypt = USE('./encrypt');
SEA.decrypt = USE('./decrypt');
SEA.certify = USE('./certify');
//SEA.opt.aeskey = USE('./aeskey'); // not official! // this causes problems in latest WebCrypto.
SEA.random = SEA.random || shim.random;
@ -1137,6 +1199,7 @@
;USE(function(module){
var SEA = USE('./sea')
var S = USE('./settings')
var Gun = SEA.Gun;
// After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
@ -1217,6 +1280,7 @@
return; // omit!
}
}
if('~@' === soul){ // special case for shared system data, the list of aliases.
check.alias(eve, msg, val, key, soul, at, no); return;
}
@ -1249,27 +1313,83 @@
if(key === link_is(val)){ return eve.to.next(msg) } // and the ID must be EXACTLY equal to its property
no("Alias not same!"); // that way nobody can tamper with the list of public keys.
};
check.pub = function(eve, msg, val, key, soul, at, no, user, pub){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
if('pub' === key && '~'+pub === soul){
if(val === pub){ return eve.to.next(msg) } // the account MUST match `pub` property that equals the ID of the public key.
return no("Account not same!");
check.pub = function(eve, msg, val, key, soul, at, no, user, pub){ var tmp // Example: {_:#~asdf, hello:'world'~fdsa}}
const raw = S.parse(val) || {}
const verify = (certificate, certificant, cb) => {
if (certificate['m'] && certificate['s'] && certificant && pub) {
// now verify certificate
return SEA.verify(certificate, pub, data => { // check if "pub" (of the graph owner) really issued this cert
if (u !== data && u !== data.e && msg.put['>'] && msg.put['>'] > parseFloat(data.e)) return no("Certificate expired.")
// "data.c" = a list of certificants/certified users, "data.p" = a list of allowed patterns
if (u !== data && data.c && data.p && (data.c.indexOf('*') || data.c.indexOf(certificant))) {
// ok, now "certificant" is in the "certificants" list, but is "path" allowed? Check path
let path = soul + '/' + key
path = path.replace(path.substring(0, path.indexOf('/') + 1), '')
for (p of data.p) {
if (new RegExp(p).test(path)) {
return cb(data)
}
}
return no("Certificate verification fail.")
}
})
}
return
}
if((tmp = user.is) && pub === tmp.pub){
if ('pub' === key && '~'+pub === soul) {
if(val === pub){ return eve.to.next(msg) } // the account MUST match `pub` property that equals the ID of the public key.
return no("Account not same!")
}
if ((tmp = user.is) && tmp.pub && !raw['*'] && !raw['+'] && (pub === tmp.pub || (pub !== tmp.pub && ((msg._.out || {}).opt || {}).cert))){
SEA.sign(SEA.opt.pack(msg.put), (user._).sea, function(data){
if(u === data){ return no(SEA.err || 'Signature fail.') }
if(tmp = link_is(val)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
msg.put[':'] = JSON.stringify({':': tmp = SEA.opt.unpack(data.m), '~': data.s});
msg.put['='] = tmp;
eve.to.next(msg);
}, {raw: 1});
msg.put[':'] = {':': tmp = SEA.opt.unpack(data.m), '~': data.s}
msg.put['='] = tmp
// if writing to own graph, just allow it
if (pub === user.is.pub) {
if (tmp = link_is(val)) { (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
msg.put[':'] = JSON.stringify(msg.put[':'])
return eve.to.next(msg)
}
// if writing to other's graph, check if cert exists then try to inject cert into put, also inject self pub so that everyone can verify the put
if (pub !== user.is.pub && ((msg._.out || {}).opt || {}).cert) {
const cert = S.parse(msg._.out.opt.cert)
// even if cert exists, we must verify it
if (cert && cert.m && cert.s) {
verify(cert, user.is.pub, _ => {
msg.put[':']['+'] = cert // '+' is a certificate
msg.put[':']['*'] = user.is.pub // '*' is pub of the user who puts
msg.put[':'] = JSON.stringify(msg.put[':'])
return eve.to.next(msg)
})
}
}
}, {raw: 1})
return;
}
SEA.verify(SEA.opt.pack(msg.put), pub, function(data){ var tmp;
SEA.verify(SEA.opt.pack(msg.put), raw['*'] || pub, function(data){ var tmp;
data = SEA.opt.unpack(data);
if(u === data){ return no("Unverified data.") } // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account.
if((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
msg.put['='] = data;
eve.to.next(msg);
// check if cert ('+') and putter's pub ('*') exist
if (raw['+'] && raw['+']['m'] && raw['+']['s'] && raw['*']) {
// now verify certificate
verify(raw['+'], raw['*'], _ => {
msg.put['='] = data;
return eve.to.next(msg);
})
}
else {
msg.put['='] = data;
return eve.to.next(msg);
}
});
};
check.any = function(eve, msg, val, key, soul, at, no, user){ var tmp, pub;

View File

@ -1,3 +1,5 @@
const expect = require('../expect')
var root;
var Gun;
(function(){
@ -523,6 +525,119 @@ describe('SEA', function(){
gun.user().auth(alice);
});
});
var alice = {
epriv: "Odtnqn-gng-NCLAULCdhxcG7KE26WSWdnNTBSYf8Dsw",
epub:
"rOWulaGGaNOKhrS9XtZUbdWjcIfTM5k5pImolyNwLe0.9Ks7JRrOQl3e401dSgCGlNWgvIC_DQm0EA9jGKXBDg0",
priv: "ijke9inZcbIpNUy5p3wiMRxUvqM12xU8WLewGzUXj8E",
pub:
"Zpf4KFmDmxNnHbRcTkZcAvPnke8_4hLv_FtNhBLcSps.ICAIjzky_T0ENNFIC5cjE-dN87dWp7cb88y0Rb3Nbvo"
}
var bob = {
epriv: "z5OC6iWYPVZO-sNqxd20t_qAPsA5nn9d-_yg5uW2mZM",
epub:
"bHUUjC-xP9QoTEyY5rubZJwft_szXgvetGOGUPOT8Mw.5J2j9SBZ8lqSHKgeFRbMZDs0EuNgM-VVWgMHE3YMFSI",
priv: "dWDbrbKUinmSxrlilKmyPzKAgmZCzm9i14bTydUf0kQ",
pub:
"naP2o7Ebn5tFF2V-z8pDFwOgOazduoiKogWnZ0cTtEE.K-sa7v6DXkb_saMFlCepqPUH--C-6rv6cO1t3wEo6-M"
}
var dave = {
epriv: "1eBCIIk30bzfTN50uqSTIN10TWP2AqkqExioV5P-oCE",
epub:
"PneRg1oMRw3mrrnjbRq8YSADLrdbg8BGEBoC0_P6It4.Vhv8QIkLhurxU5-LhHctaaNn5u3LNujqMNRdh6JbzvU",
priv: "XKJrGFWoERdHfhXfhc-VY0nIWeI2eAIDAfkX0nu_O0A",
pub:
"AXEGD2GABu1lnzTKML_wXHSlznwI5ZFebF-MLPKxI8Y.0Sx-Sd5GpF_1kzrUcliWRdhppq7FFehQi41oZ-WOJmI"
}
it('certify + the good', function(done){(async function(){
var cert = await SEA.certify(bob, ["^private.*"], alice)
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, () => {
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty").once(_data=>{
expect(_data).to.be(data)
user.leave()
done()
})
}, { opt: { cert } })
})
}())})
it('certify + the public', function(done){(async function(){
var cert = await SEA.certify(bob, ["^private.*"], alice)
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, () => {
user.leave()
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty").once(_data=>{
expect(_data).to.be(data)
done()
})
}, { opt: { cert } })
})
}())})
it('certify + someone', function(done){(async function(){
var cert = await SEA.certify(bob, ["^private.*"], alice)
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, () => {
user.leave()
user.auth(dave, () => {
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty").once(_data=>{
expect(_data).to.be(data)
done()
})
})
}, { opt: { cert } })
})
}())})
it('certify + the evil', function(done){(async function(){
var cert = await SEA.certify(bob, ["^private.*"], alice)
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("wrongway")
.get("asdf")
.get("qwerty")
.put(data, ack => {
expect(ack.err).to.be.ok()
done()
}, { opt: { cert } })
})
}())})
});
})