diff --git a/src/acl.js b/src/acl.js index 44d63ab..8a02c26 100644 --- a/src/acl.js +++ b/src/acl.js @@ -3,20 +3,20 @@ const fs = require('fs') const ROLES = { owner: { - description: 'Read and write to all branches, and ACL management', + description: 'Read and write to all branches, and ACL management' }, admin: { - description: 'Read and write to all branches', + description: 'Read and write to all branches' }, contributor: { - description: 'Read and write to all branches except protected ones', + description: 'Read and write to all branches except protected ones' }, viewer: { - description: 'Read all branches', - }, + description: 'Read all branches' + } } const DEFAULT_ACL = { - visibility: 'public', // public|private + visibility: 'public', // public|private protectedBranches: ['master', 'main'], ACL: {} } @@ -127,5 +127,5 @@ module.exports = { getAdmins, getContributors, getViewers, - revokeAccessFromUser, + revokeAccessFromUser } diff --git a/src/auth/index.js b/src/auth/index.js index 54037db..0c81c33 100644 --- a/src/auth/index.js +++ b/src/auth/index.js @@ -1,13 +1,13 @@ -function getId(data) { - if (!process.env.GIT_PEAR_AUTH) return payload +function getId (data) { + if (!process.env.GIT_PEAR_AUTH) return data if (process.env.GIT_PEAR_AUTH === 'nip98') { const nip98 = require('./nip98') return nip98.getId(data) } } -async function getToken(payload) { - if (!process.env.GIT_PEAR_AUTH) return userId +async function getToken (payload) { + if (!process.env.GIT_PEAR_AUTH) return payload if (process.env.GIT_PEAR_AUTH === 'nip98') { const nip98 = require('./nip98') return nip98.getToken(payload) diff --git a/src/auth/nip98.js b/src/auth/nip98.js index 2f00b31..f693dc0 100644 --- a/src/auth/nip98.js +++ b/src/auth/nip98.js @@ -1,6 +1,6 @@ const { nip98, nip19, finalizeEvent } = require('nostr-tools') -async function getToken({ url, method, data }) { +async function getToken ({ url, method, data }) { if (!process.env.GIT_PEAR_AUTH_NSEC) throw new Error('Missing NSEC') const { data: sK } = nip19.decode(process.env.GIT_PEAR_AUTH_NSEC) return nip98.getToken( @@ -13,11 +13,11 @@ async function getToken({ url, method, data }) { } // FIXME -async function getId({ payload, url, method, data }) { +async function getId ({ payload, url, method, data }) { const event = JSON.parse(Buffer.from(payload, 'base64').toString()) const isValid = await nip98.validateEvent(event, url, method, data) if (!isValid) throw new Error('Invalid event') - return { + return { ...event, userId: nip19.npubEncode(event.pubkey) } diff --git a/src/cli.js b/src/cli.js index 8e92544..5d9d9a1 100755 --- a/src/cli.js +++ b/src/cli.js @@ -6,7 +6,6 @@ const commander = require('commander') const program = new commander.Command() const path = require('path') -const fs = require('fs') const home = require('./home') const acl = require('./acl') @@ -25,10 +24,9 @@ program program .command('init') .description('initialize a gitpear repo') - .option('-p, --path [path]', 'paht to the git repo', '.') .option('-s, --share [branch]', 'share the repo as public, default false, default branch is current', '') .action(async (options) => { - const fullPath = path.resolve(options.path) + const fullPath = path.resolve('.') checkIfGitRepo(fullPath) const name = fullPath.split(path.sep).pop() @@ -42,7 +40,7 @@ program } try { - home.createAppFolder(name) + home.createAppFolder(name) console.log(`Added project "${name}" to gitpear`) } catch (e) { } try { @@ -67,9 +65,8 @@ program .description('share a gitpear repo') .option('-b, --branch [b]', 'branch to share, default is current branch', '') .option('-v, --visibility [v]', 'visibility of the repo', 'public') - .option('-p, --path [path]', 'path to the repo', '.') .action(async (options) => { - const fullPath = path.resolve(options.path) + const fullPath = path.resolve('.') checkIfGitRepo(fullPath) const name = fullPath.split(path.sep).pop() @@ -115,7 +112,6 @@ program } } }) - program .command('unshare') @@ -184,7 +180,7 @@ program opts.stdio = 'inherit' } else { opts.detached = true - opts.stdio = [ 'ignore', home.getOutStream(), home.getErrStream() ] + opts.stdio = ['ignore', home.getOutStream(), home.getErrStream()] } const daemon = spawn('git-peard', opts) @@ -211,7 +207,7 @@ program } }) -function localBranchProtectionRules(a, b, p, options) { +function localBranchProtectionRules (a, b, p, options) { const fullPath = path.resolve(p) checkIfGitRepo(fullPath) @@ -238,8 +234,7 @@ function localBranchProtectionRules(a, b, p, options) { } } -function localACL(a, u, p, options) { - console.log('localACL', { a, u, p, options }) +function localACL (a, u, p, options) { const fullPath = path.resolve(p) checkIfGitRepo(fullPath) @@ -266,7 +261,7 @@ function localACL(a, u, p, options) { process.exit(1) } - const [ userId, role ] = u.split(':') + const [userId, role] = u.split(':') if (repoACL.ACL[userId]) { console.error(`${userId} already has access to ${name} as ${repoACL.ACL[userId]}`) process.exit(1) @@ -290,11 +285,10 @@ function localACL(a, u, p, options) { acl.revokeAccessFromUser(name, u) console.log(`Removed ${u} from ${name}`) - return } } -async function remoteBranchProtectionRules(a, b, p, options) { +async function remoteBranchProtectionRules (a, b, p, options) { if (a === 'list') { await aclRemote.list(p, b, { branch: true }) } else if (a === 'add') { @@ -314,7 +308,7 @@ async function remoteBranchProtectionRules(a, b, p, options) { } } -async function remoteACL(a, b, p, options) { +async function remoteACL (a, b, p, options) { if (a === 'list') { await aclRemote.list(p, b) } else if (a === 'add') { @@ -338,7 +332,7 @@ async function remoteACL(a, b, p, options) { } } -async function share(name, branchToShare, options) { +async function share (name, branchToShare, options) { let aclOptions let message = `Shared "${name}" project, ${branchToShare} branch` if (options?.visibility) { diff --git a/src/git-remote-pear.js b/src/git-remote-pear.js index 68e7852..848fc9a 100755 --- a/src/git-remote-pear.js +++ b/src/git-remote-pear.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -const { spawn } = require('child_process') const ProtomuxRPC = require('protomux-rpc') const RAM = require('random-access-memory') @@ -14,8 +13,6 @@ const home = require('./home') const auth = require('./auth') const acl = require('./acl') -const fs = require('fs') - const url = process.argv[3] const matches = url.match(/pear:\/\/([a-f0-9]{64})\/(.*)/) @@ -76,13 +73,13 @@ swarm.on('connection', async (socket) => { await drive.core.update({ wait: true }) - payload = { body: { url, method: 'get-refs', data: repoName }} + payload = { body: { url, method: 'get-refs', data: repoName } } if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') { payload.header = await auth.getToken(payload.body) } const refsRes = await rpc.request('get-refs', Buffer.from(JSON.stringify(payload))) - let commit + let commit try { commit = await git.getCommit() } catch (e) { } @@ -99,7 +96,7 @@ async function talkToGit (refs, drive, repoName, rpc, commit) { process.stdout.write('push\n') process.stdout.write('fetch\n\n') } else if (chunk && chunk.search(/^push/) !== -1) { - const [_command, path] = chunk.split(' ') + const path = chunk.split(' ')[1] let [src, dst] = path.split(':') const isDelete = !src @@ -107,13 +104,6 @@ async function talkToGit (refs, drive, repoName, rpc, commit) { dst = dst.replace('refs/heads/', '').replace('\n\n', '') - try { home.createAppFolder(repoName) } catch (e) { } - try { await git.createBareRepo(repoName) } catch (e) { } - try { await git.addRemote(repoName) } catch (e) { } - try { await git.push(dst) } catch (e) { } - try { home.shareAppFolder(repoName) } catch (e) { } - try { acl.setACL(repoName, acl.getACL(repoName)) } catch (e) { } - let method if (isDelete) { method = 'd-branch' @@ -128,16 +118,25 @@ async function talkToGit (refs, drive, repoName, rpc, commit) { method = 'push' } + try { home.createAppFolder(repoName) } catch (e) { } + try { await git.createBareRepo(repoName) } catch (e) { } + try { await git.addRemote(repoName, { quite: true }) } catch (e) { } + try { await git.push(dst, isForce) } catch (e) { } + try { home.shareAppFolder(repoName) } catch (e) { } + try { acl.setACL(repoName, acl.getACL(repoName)) } catch (e) { } + const publicKey = home.readPk() - let payload = { body: { - url: `pear://${publicKey}/${repoName}`, - data: `${dst}#${commit}`, - method - } } + const payload = { + body: { + url: `pear://${publicKey}/${repoName}`, + data: `${dst}#${commit}`, + method + } + } if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') { payload.header = await auth.getToken(payload.body) } - const res = await rpc.request(method, Buffer.from(JSON.stringify(payload))) + await rpc.request(method, Buffer.from(JSON.stringify(payload))) process.stdout.write('\n\n') process.exit(0) diff --git a/src/git.js b/src/git.js index 789b373..ada5d08 100644 --- a/src/git.js +++ b/src/git.js @@ -64,9 +64,9 @@ async function createBareRepo (name) { return await doGit(init) } -async function addRemote (name) { +async function addRemote (name, opts = { quiet: false }) { const init = spawn('git', ['remote', 'add', 'pear', getCodePath(name)]) - return await doGit(init) + return await doGit(init, opts) } async function push (branch = 'master', force = false) { @@ -76,8 +76,8 @@ async function push (branch = 'master', force = false) { return await doGit(push) } -async function doGit (child) { - child.stderr.pipe(process.stderr) +async function doGit (child, opts = { quiet: false }) { + if (!opts.quiet) child.stderr.pipe(process.stderr) return new Promise((resolve, reject) => { child.on('close', (code) => { if (code) { @@ -229,5 +229,5 @@ module.exports = { addRemote, push, getCommit, - getCurrentBranch, + getCurrentBranch } diff --git a/src/rpc-handlers/acl.js b/src/rpc-handlers/acl.js index 5ec8aaf..1df3019 100644 --- a/src/rpc-handlers/acl.js +++ b/src/rpc-handlers/acl.js @@ -2,14 +2,14 @@ const ACL = require('../acl') const home = require('../home') async function getACLHandler (publicKey, req) { - const { repoName, userId, acl } = await parseACLRequest.bind(this)(publicKey, req) + const { repoName } = await parseACLRequest.bind(this)(publicKey, req) const repoACL = ACL.getACL(repoName) return Buffer.from(JSON.stringify(repoACL)) } async function addACLHandler (publicKey, req) { - const { repoName, userId, acl, isBranch, name } = await parseACLRequest.bind(this)(publicKey, req) + const { repoName, isBranch, name } = await parseACLRequest.bind(this)(publicKey, req) isBranch ? ACL.addProtectedBranch(repoName, name) : ACL.grantAccessToUser(repoName, ...name.split(':')) @@ -18,7 +18,7 @@ async function addACLHandler (publicKey, req) { } async function delACLHandler (publicKey, req) { - const { repoName, userId, acl, isBranch, name } = await parseACLRequest.bind(this)(publicKey, req) + const { repoName, isBranch, name } = await parseACLRequest.bind(this)(publicKey, req) isBranch ? ACL.removeProtectedBranch(repoName, name) : ACL.revokeAccessFromUser(repoName, name) @@ -26,7 +26,7 @@ async function delACLHandler (publicKey, req) { return Buffer.from(JSON.stringify(repoACL)) } -async function parseACLRequest(publicKey, req) { +async function parseACLRequest (publicKey, req) { if (!req) throw new Error('Request is empty') const request = JSON.parse(req.toString()) const userId = await this.authenticate(publicKey, request) @@ -42,12 +42,12 @@ async function parseACLRequest(publicKey, req) { name: request.body.name, userId, acl: request.body.acl, - isBranch: !!request.body.branch, + isBranch: !!request.body.branch } } module.exports = { getACLHandler, addACLHandler, - delACLHandler, + delACLHandler } diff --git a/src/rpc-handlers/git.js b/src/rpc-handlers/git.js index 33c8413..5e83ce2 100644 --- a/src/rpc-handlers/git.js +++ b/src/rpc-handlers/git.js @@ -3,12 +3,10 @@ const home = require('../home') const { spawn } = require('child_process') async function getReposHandler (publicKey, req) { - const { branch, url, userId } = await parseReq.bind(this)(publicKey, req) + const { userId } = await parseReq.bind(this)(publicKey, req) const res = {} for (const repoName in this.repositories) { - // TODO: add only public repos and those which are shared with the peer - // Alternatively return only requested repo const isPublic = (ACL.getACL(repoName).visibility === 'public') if (isPublic || ACL.getViewers(repoName).includes(userId)) { res[repoName] = this.drives[repoName].key.toString('hex') @@ -18,7 +16,7 @@ async function getReposHandler (publicKey, req) { } async function getRefsHandler (publicKey, req) { - const { repoName, branch, url, userId } = await parseReq.bind(this)(publicKey, req) + const { repoName, userId } = await parseReq.bind(this)(publicKey, req) const res = this.repositories[repoName] const isPublic = (ACL.getACL(repoName).visibility === 'public') @@ -43,14 +41,7 @@ async function pushHandler (publicKey, req) { return await new Promise((resolve, reject) => { const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) } const child = spawn('git', ['fetch', url, `${branch}:${branch}`], { env }) - let errBuffer = Buffer.from('') - child.stderr.on('data', data => { - errBuffer = Buffer.concat([errBuffer, data]) - }) - - child.on('close', code => { - return code === 0 ? resolve(errBuffer) : reject(errBuffer) - }) + return doGit(child, resolve, reject) }) } @@ -68,19 +59,12 @@ async function forcePushHandler (publicKey, req) { return await new Promise((resolve, reject) => { const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) } const child = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env }) - let errBuffer = Buffer.from('') - child.stderr.on('data', data => { - errBuffer = Buffer.concat([errBuffer, data]) - }) - - child.on('close', code => { - return code === 0 ? resolve(errBuffer) : reject(errBuffer) - }) + return doGit(child, resolve, reject) }) } async function deleteBranchHandler (publicKey, req) { - const { url, repoName, branch, userId } = await parseReq.bind(this)(publicKey, req) + const { repoName, branch, userId } = await parseReq.bind(this)(publicKey, req) const isContributor = ACL.getContributors(repoName).includes(userId) if (!isContributor) throw new Error('You are not allowed to push to this repo') @@ -93,34 +77,46 @@ async function deleteBranchHandler (publicKey, req) { return await new Promise((resolve, reject) => { const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) } const child = spawn('git', ['branch', '-D', branch], { env }) - let errBuffer = Buffer.from('') - child.stderr.on('data', data => { - errBuffer = Buffer.concat([errBuffer, data]) - }) - - child.on('close', code => { - return code === 0 ? resolve(errBuffer) : reject(errBuffer) - }) + return doGit(child, resolve, reject) }) } -async function parseReq(publicKey, req) { +async function parseReq (publicKey, req) { if (!req) throw new Error('Request is empty') const request = JSON.parse(req.toString()) const parsed = { repoName: request.body.url?.split('/')?.pop(), branch: request.body.data?.split('#')[0], url: request.body.url, - userId: await this.authenticate(publicKey, request), + userId: await this.authenticate(publicKey, request) } return parsed } +function doGit (child, resolve, reject) { + let errBuffer = Buffer.from('') + let outBuffer = Buffer.from('') + + child.stdout.on('data', data => { + outBuffer = Buffer.concat([outBuffer, data]) + }) + + child.stderr.on('data', data => { + errBuffer = Buffer.concat([errBuffer, data]) + }) + + child.on('close', code => { + console.error('errBuffer', errBuffer.toString()) + console.log('outBuffer', outBuffer.toString()) + + return code === 0 ? resolve(outBuffer) : reject(errBuffer) + }) +} + module.exports = { getReposHandler, getRefsHandler, pushHandler, forcePushHandler, - deleteBranchHandler, + deleteBranchHandler } - diff --git a/src/rpc-handlers/index.js b/src/rpc-handlers/index.js index 4027ecb..7cbc02a 100644 --- a/src/rpc-handlers/index.js +++ b/src/rpc-handlers/index.js @@ -3,5 +3,5 @@ const acl = require('./acl') module.exports = { git, - acl, + acl } diff --git a/src/rpc-requests/acl-remote.js b/src/rpc-requests/acl-remote.js index df448f3..22ec067 100644 --- a/src/rpc-requests/acl-remote.js +++ b/src/rpc-requests/acl-remote.js @@ -8,7 +8,7 @@ const auth = require('../auth') const { printACL, printACLForUser, logBranches } = require('../utils') -async function list(url, name, rpc, opts) { +async function list (url, name, rpc, opts) { const payload = { body: { url, method: 'get-acl' } } if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') { payload.header = await auth.getToken(payload.body) @@ -20,15 +20,15 @@ async function list(url, name, rpc, opts) { process.exit(0) } -function listACLUser(repoACL, u) { +function listACLUser (repoACL, u) { u ? printACLForUser(repoACL, u) : printACL(repoACL) } -function listACLBranch(repoACL) { +function listACLBranch (repoACL) { logBranches(repoACL) } -async function add(url, name, rpc, opts) { +async function add (url, name, rpc, opts) { const payload = { body: { url, method: 'add-acl', name } } if (opts.branch) payload.body.branch = true @@ -42,7 +42,7 @@ async function add(url, name, rpc, opts) { process.exit(0) } -async function del(url, name, rpc, opts) { +async function del (url, name, rpc, opts) { const payload = { body: { url, method: 'del-acl', name } } if (opts.branch) payload.body.branch = true @@ -84,7 +84,7 @@ async function wrapper (url, name, opts = {}, cb) { swarm.on('connection', async (socket) => { const rpc = new ProtomuxRPC(socket) - let payload = { body: { url, method: 'get-repos' } } + const payload = { body: { url, method: 'get-repos' } } if (!process.env.GIT_PEAR_AUTH) { console.debug('Retreiving data using un-authenticated access') } else { @@ -112,5 +112,5 @@ async function wrapper (url, name, opts = {}, cb) { module.exports = { list: (url, name, opts) => wrapper(url, name, opts, list), add: (url, name, opts) => wrapper(url, name, opts, add), - remove: (url, name, opts) => wrapper(url, name, opts, del), + remove: (url, name, opts) => wrapper(url, name, opts, del) } diff --git a/src/rpc-requests/index.js b/src/rpc-requests/index.js index 01e7653..e13e2f1 100644 --- a/src/rpc-requests/index.js +++ b/src/rpc-requests/index.js @@ -3,5 +3,5 @@ const aclRemote = require('./acl-remote') module.exports = { listRemote, - aclRemote, + aclRemote } diff --git a/src/rpc-requests/list-remote.js b/src/rpc-requests/list-remote.js index 9f15898..1ab0d37 100644 --- a/src/rpc-requests/list-remote.js +++ b/src/rpc-requests/list-remote.js @@ -28,7 +28,7 @@ module.exports = async function listRemote (url) { swarm.on('connection', async (socket) => { const rpc = new ProtomuxRPC(socket) - let payload = { body: { url, method: 'get-repos' } } + const payload = { body: { url, method: 'get-repos' } } if (!process.env.GIT_PEAR_AUTH) { console.debug('Retreiving data using un-authenticated access') } else { diff --git a/src/rpc.js b/src/rpc.js index 7a58453..c400d45 100755 --- a/src/rpc.js +++ b/src/rpc.js @@ -1,7 +1,4 @@ const ProtomuxRPC = require('protomux-rpc') -const SecretStream = require('@hyperswarm/secret-stream') -const { spawn } = require('child_process') -const home = require('./home') const auth = require('./auth') const { git, acl } = require('./rpc-handlers') @@ -26,13 +23,13 @@ module.exports = class RPC { /* -- PULL HANDLERS -- */ rpc.respond('get-repos', async req => await git.getReposHandler.bind(this)(socket.remotePublicKey, req)) - rpc.respond('get-refs', async req => await git.getRefsHandler.bind(this)(socket.remotePublicKey, req)) + rpc.respond('get-refs', async req => await git.getRefsHandler.bind(this)(socket.remotePublicKey, req)) if (!process.env.GIT_PEAR_AUTH) return /* -- PUSH HANDLERS -- */ - rpc.respond('push', async req => await git.pushHandler.bind(this)(socket.remotePublicKey, req)) - rpc.respond('f-push', async req => await git.forcePushHandler.bind(this)(socket.remotePublicKey, req)) + rpc.respond('push', async req => await git.pushHandler.bind(this)(socket.remotePublicKey, req)) + rpc.respond('f-push', async req => await git.forcePushHandler.bind(this)(socket.remotePublicKey, req)) rpc.respond('d-branch', async req => await git.deleteBranchHandler.bind(this)(socket.remotePublicKey, req)) /* -- REPO ADMINISTRATION HANDLERS -- */ diff --git a/src/utils.js b/src/utils.js index f7c8e7d..9037a57 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,7 @@ const fs = require('fs') const path = require('path') -function printACL(repoACL) { +function printACL (repoACL) { console.log('Repo Visibility:', '\t', repoACL.visibility) console.log('Protected Branch(s):', '\t', repoACL.protectedBranches.join(', ')) console.log('User:', '\t', 'Role:') @@ -10,20 +10,20 @@ function printACL(repoACL) { } } -function printACLForUser(repoACL, u) { - console.log('Repo Visibility:', '\t', repoACL.visibility) - console.log('Protected Branch(s):', '\t', repoACL.protectedBranches.join(', ')) - console.log('User:', u, '\t', repoACL.ACL[u]) +function printACLForUser (repoACL, u) { + console.log('Repo Visibility:', '\t', repoACL.visibility) + console.log('Protected Branch(s):', '\t', repoACL.protectedBranches.join(', ')) + console.log('User:', u, '\t', repoACL.ACL[u]) } -function checkIfGitRepo(p) { +function checkIfGitRepo (p) { if (!fs.existsSync(path.join(p, '.git'))) { console.error(` ${p} is not a git repo`) process.exit(1) } } -function logBranches(repoACL) { +function logBranches (repoACL) { console.log('Repo Visibility:', '\t', repoACL.visibility) console.log('Protected Branch(s):', '\t', repoACL.protectedBranches.join(', ')) } @@ -32,5 +32,5 @@ module.exports = { printACL, printACLForUser, checkIfGitRepo, - logBranches, + logBranches } diff --git a/test/rpc.test.js b/test/rpc.test.js index fc90b99..7061b18 100644 --- a/test/rpc.test.js +++ b/test/rpc.test.js @@ -56,7 +56,7 @@ test('e2e', async t => { await drive.core.update({ wait: true }) - payload = Buffer.from(JSON.stringify({ body: { url, method: 'get-refs', data: repoName }})) + payload = Buffer.from(JSON.stringify({ body: { url, method: 'get-refs', data: repoName } })) const refsRes = await rpc.request('get-refs', payload) t.ok(refsRes)