From 9d66b66221d49f6a9f07de5f528139807473689f Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 25 Jan 2024 17:22:17 +0000 Subject: [PATCH] ACL prep Signed-off-by: dzdidi --- Readme.md | 4 +- npm-shrinkwrap.json | 202 ++++++++++++++++++++++++++++++++++++++++- package.json | 1 + src/acl/index.js | 20 ++++ src/acl/nip98.js | 49 ++++++++++ src/cli.js | 2 + src/git-remote-pear.js | 41 +++++++-- src/git.js | 21 ++++- src/home.js | 7 +- src/rpc-request.js | 0 src/rpc.js | 79 ++++++++-------- 11 files changed, 376 insertions(+), 50 deletions(-) create mode 100644 src/acl/index.js create mode 100644 src/acl/nip98.js create mode 100644 src/rpc-request.js diff --git a/Readme.md b/Readme.md index dbdd75b..4b4fc19 100644 --- a/Readme.md +++ b/Readme.md @@ -48,7 +48,7 @@ All data will be persisted in application directory (default `~/.gitpear`). To c * `git pear list [-s, --shared]` - list all or (only shared) repositories -## Usage example +## Usage example (NO PUSH) Please not this is only remote helper and its intention is only to enable direct `clone|fetch|pull` of repository hosted on private computer. @@ -94,3 +94,5 @@ git checkout master git fetch origin git pull ``` + +## Usage example (PUSH) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 1700503..82ec373 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -15,6 +15,7 @@ "corestore": "^6.15.13", "hyperdrive": "^11.6.3", "hyperswarm": "^4.7.13", + "nostr-tools": "^2.1.5", "protomux-rpc": "^1.5.1", "random-access-memory": "^6.2.0" }, @@ -239,6 +240,47 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@noble/ciphers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.2.0.tgz", + "integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -274,6 +316,53 @@ "node": ">= 8" } }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -3008,6 +3097,36 @@ "node": ">=0.10.0" } }, + "node_modules/nostr-tools": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.1.5.tgz", + "integrity": "sha512-Gug/j54YGQ0ewB09dZW3mS9qfXWFlcOQMlyb1MmqQsuNO/95mfNOQSBi+jZ61O++Y+jG99SzAUPFLopUsKf0MA==", + "dependencies": { + "@noble/ciphers": "0.2.0", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "optionalDependencies": { + "nostr-wasm": "v0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nostr-wasm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "optional": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4390,7 +4509,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4838,6 +4957,31 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@noble/ciphers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.2.0.tgz", + "integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==" + }, + "@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "requires": { + "@noble/hashes": "1.3.2" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" + } + } + }, + "@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4864,6 +5008,40 @@ "fastq": "^1.6.0" } }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, + "@scure/bip32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "requires": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "dependencies": { + "@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "requires": { + "@noble/hashes": "1.3.1" + } + } + } + }, + "@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "requires": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -6982,6 +7160,26 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, + "nostr-tools": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.1.5.tgz", + "integrity": "sha512-Gug/j54YGQ0ewB09dZW3mS9qfXWFlcOQMlyb1MmqQsuNO/95mfNOQSBi+jZ61O++Y+jG99SzAUPFLopUsKf0MA==", + "requires": { + "@noble/ciphers": "0.2.0", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1", + "nostr-wasm": "v0.1.0" + } + }, + "nostr-wasm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "optional": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8028,7 +8226,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true + "devOptional": true }, "udx-native": { "version": "1.7.12", diff --git a/package.json b/package.json index 5299b9b..ed34376 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "corestore": "^6.15.13", "hyperdrive": "^11.6.3", "hyperswarm": "^4.7.13", + "nostr-tools": "^2.1.5", "protomux-rpc": "^1.5.1", "random-access-memory": "^6.2.0" } diff --git a/src/acl/index.js b/src/acl/index.js new file mode 100644 index 0000000..54037db --- /dev/null +++ b/src/acl/index.js @@ -0,0 +1,20 @@ +function getId(data) { + if (!process.env.GIT_PEAR_AUTH) return payload + 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 + if (process.env.GIT_PEAR_AUTH === 'nip98') { + const nip98 = require('./nip98') + return nip98.getToken(payload) + } +} + +module.exports = { + getId, + getToken +} diff --git a/src/acl/nip98.js b/src/acl/nip98.js new file mode 100644 index 0000000..f9f48b7 --- /dev/null +++ b/src/acl/nip98.js @@ -0,0 +1,49 @@ +const { nip98, nip19, finalizeEvent } = require('nostr-tools') + +async function getToken({ url, method, data }) { + const { data: sK } = nip19.decode(process.env.GIT_PEAR_AUTH_NSEC) + return nip98.getToken( + url, + method, + (e) => finalizeEvent(e, sK), + false, + data + ) +} + +// FIXME +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 { + ...event, + userId: nip19.npubEncode(event.pubkey) + } +} + +module.exports = { + getId, + getToken +} + +// ;(async () => { +// const repo = 'gitpear' +// const url = `pear://d1672d338b8e24223cd0dc6c6b5e04ebabf091fc2b470204abdb98fa5fc59072/${repo}` +// const commit = '1837a4bae8497f71fb8f01305c3ace1e3dedcdba' +// const method = 'push' +// const branch = 'test' +// const data = `${branch}#${commit}` +// +// let payload +// let npub +// +// payload = await getToken({ url, method, data }) +// npub = await getId({ payload, url, method, data }) +// +// payload = await getToken({url, method: 'get-repos'}) +// npub = await getId({ payload, url, method: 'get-repos' }) +// +// payload = await getToken({url, method: 'get-refs', data: { repo }}) +// npub = await getId({ payload, url, method: 'get-refs', data: { repo }}) +// })() diff --git a/src/cli.js b/src/cli.js index 71ab6e8..d6ad6b5 100755 --- a/src/cli.js +++ b/src/cli.js @@ -32,6 +32,8 @@ program const name = fullPath.split(path.sep).pop() if ((home.isInitialized(name))) { console.error(`${name} is already initialized`) + await git.addRemote(name) + console.log(`Added git remote for "${name}" as "pear"`) process.exit(1) } diff --git a/src/git-remote-pear.js b/src/git-remote-pear.js index f140492..0cc08b3 100755 --- a/src/git-remote-pear.js +++ b/src/git-remote-pear.js @@ -11,6 +11,7 @@ const crypto = require('hypercore-crypto') const git = require('./git.js') const home = require('./home') +const acl = require('./acl') const fs = require('fs') @@ -39,7 +40,12 @@ swarm.on('connection', async (socket) => { store.replicate(socket) const rpc = new ProtomuxRPC(socket) - const reposRes = await rpc.request('get-repos') + let payload = { body: { url, method: 'get-repos' } } + if (process.env.GIT_PEAR_AUTH) { + payload.header = await acl.getToken(payload.body) + } + + const reposRes = await rpc.request('get-repos', Buffer.from(JSON.stringify(payload))) const repositories = JSON.parse(reposRes.toString()) if (!repositories) { console.error('Failed to retrieve repositories') @@ -65,12 +71,21 @@ swarm.on('connection', async (socket) => { await drive.core.update({ wait: true }) - const refsRes = await rpc.request('get-refs', Buffer.from(repoName)) + // TODO: ACL + payload = { body: { url, method: 'get-refs', data: repoName }} + if (process.env.GIT_PEAR_AUTH) { + payload.header = await acl.getToken(payload.body) + } + const refsRes = await rpc.request('get-refs', Buffer.from(JSON.stringify(payload))) - await talkToGit(JSON.parse(refsRes.toString()), drive, repoName, rpc) + let commit + try { + commit = await git.getCommit() + } catch (e) { } + await talkToGit(JSON.parse(refsRes.toString()), drive, repoName, rpc, commit) }) -async function talkToGit (refs, drive, repoName, rpc) { +async function talkToGit (refs, drive, repoName, rpc, commit) { process.stdin.setEncoding('utf8') const didFetch = false process.stdin.on('readable', async function () { @@ -92,22 +107,30 @@ async function talkToGit (refs, drive, repoName, rpc) { dst = dst.replace('refs/heads/', '').replace('\n\n', '') - let command + let method if (isDelete) { - command = 'd-branch' + method = 'd-branch' } else if (isForce) { console.warn('To', url) await git.push(src, isForce) src = src.replace('+', '') - command = 'f-push' + method = 'f-push' } else { console.warn('To', url) await git.push(src) - command = 'push' + method = 'push' } const publicKey = home.readPk() - const res = await rpc.request(command, Buffer.from(`${publicKey}/${repoName}:${dst}`)) + let payload = { body: { + url: `pear://${publicKey}/${repoName}`, + data: `${dst}#${commit}`, + method + } } + if (process.env.GIT_PEAR_AUTH) { + payload.header = await acl.getToken(payload.body) + } + const res = 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 3cb7c69..41bf38d 100644 --- a/src/git.js +++ b/src/git.js @@ -1,6 +1,25 @@ const { getCodePath } = require('./home') const { spawn } = require('child_process') +async function getCommit () { + return await new Promise((resolve, reject) => { + const process = spawn('git', ['rev-parse', 'HEAD']) + let outBuffer = Buffer.from('') + process.stdout.on('data', data => { + outBuffer = Buffer.concat([outBuffer, data]) + }) + + let errBuffer = Buffer.from('') + process.stderr.on('err', data => { + errBuffer = Buffer.concat([errBuffer, data]) + }) + + process.on('close', code => { + return code === 0 ? resolve(outBuffer.toString().replace('\n', '')) : reject(errBuffer) + }) + }) +} + async function lsPromise (url) { const ls = spawn('git', ['ls-remote', url]) const res = {} @@ -182,4 +201,4 @@ async function unpackStream (packStream) { }) } -module.exports = { lsPromise, uploadPack, unpackFile, unpackStream, createBareRepo, addRemote, push } +module.exports = { lsPromise, uploadPack, unpackFile, unpackStream, createBareRepo, addRemote, push, getCommit } diff --git a/src/home.js b/src/home.js index 921c4bc..a7db197 100644 --- a/src/home.js +++ b/src/home.js @@ -14,6 +14,10 @@ function shareAppFolder (name) { fs.openSync(`${APP_HOME}/${name}/.git-daemon-export-ok`, 'w') } +function shareWith (userId, branch = '*', permissions = 'rw') { + fs.appendFileSync(`${APP_HOME}/.git-daemon-export-ok`, `${userId}:${branch}:${permissions}\n`) +} + function unshareAppFolder (name) { fs.unlinkSync(`${APP_HOME}/${name}/.git-daemon-export-ok`) } @@ -119,5 +123,6 @@ module.exports = { storeDaemonPid, getDaemonPid, isDaemonRunning, - removeDaemonPid + removeDaemonPid, + shareWith, } diff --git a/src/rpc-request.js b/src/rpc-request.js new file mode 100644 index 0000000..e69de29 diff --git a/src/rpc.js b/src/rpc.js index 8ab4fd4..9b67540 100755 --- a/src/rpc.js +++ b/src/rpc.js @@ -1,6 +1,7 @@ const ProtomuxRPC = require('protomux-rpc') const { spawn } = require('child_process') const home = require('./home') +const acl = require('./acl') module.exports = class RPC { constructor (announcedRefs, repositories, drives) { @@ -19,92 +20,98 @@ module.exports = class RPC { // which can in turn be stored in a .git-daemon-export-ok file /* -- PULL HANDLERS -- */ - rpc.respond('get-repos', req => this.getReposHandler(req)) - rpc.respond('get-refs', async req => await this.getRefsHandler(req)) + rpc.respond('get-repos', async req => await this.getReposHandler(req)) + rpc.respond('get-refs', async req => await this.getRefsHandler(req)) /* -- PUSH HANDLERS -- */ - rpc.respond('push', async req => await this.pushHandler(req)) - rpc.respond('f-push', async req => await this.forcePushHandler(req)) + rpc.respond('push', async req => await this.pushHandler(req)) + rpc.respond('f-push', async req => await this.forcePushHandler(req)) rpc.respond('d-branch', async req => await this.deleteBranchHandler(req)) this.connections[peerInfo.publicKey] = rpc } - getReposHandler (_req) { + async getReposHandler (req) { + const { branch, url } = await this.parseReq(req) + const res = {} - for (const repo in this.repositories) { - res[repo] = this.drives[repo].key.toString('hex') + for (const repoName in this.repositories) { + // TODO: add only public repos and those which are shared with the peer + // Alternatively return only requested repo + res[repoName] = this.drives[repoName].key.toString('hex') } return Buffer.from(JSON.stringify(res)) } - getRefsHandler (req) { - const res = this.repositories[req.toString()] + async getRefsHandler (req) { + const { repoName, branch, url } = await this.parseReq(req) + const res = this.repositories[repoName] return Buffer.from(JSON.stringify(res)) } async pushHandler (req) { - const { url, repo, key, branch } = this.parsePushCommand(req) - // TODO: check ACL + const { url, repoName, branch } = await this.parseReq(req) return await new Promise((resolve, reject) => { - const process = spawn('git', ['fetch', url, `${branch}:${branch}`], { env: { GIT_DIR: home.getCodePath(repo) } }) + const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) } + const child = spawn('git', ['fetch', url, `${branch}:${branch}`], { env }) let errBuffer = Buffer.from('') - process.stderr.on('data', data => { + child.stderr.on('data', data => { errBuffer = Buffer.concat([errBuffer, data]) }) - process.on('close', code => { + child.on('close', code => { return code === 0 ? resolve(errBuffer) : reject(errBuffer) }) }) } async forcePushHandler (req) { - const { url, repo, key, branch } = this.parsePushCommand(req) - // TODO: check ACL + const { url, repoName, branch } = await this.parseReq(req) return await new Promise((resolve, reject) => { - const process = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env: { GIT_DIR: home.getCodePath(repo) } }) + const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) } + const child = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env }) let errBuffer = Buffer.from('') - process.stderr.on('data', data => { + child.stderr.on('data', data => { errBuffer = Buffer.concat([errBuffer, data]) }) - process.on('close', code => { + child.on('close', code => { return code === 0 ? resolve(errBuffer) : reject(errBuffer) }) }) } async deleteBranchHandler (req) { - const { url, repo, key, branch } = this.parsePushCommand(req) - // TODO: check ACL + const { url, repoName, branch } = await this.parseReq(req) return await new Promise((resolve, reject) => { - const process = spawn('git', ['branch', '-D', branch], { env: { GIT_DIR: home.getCodePath(repo) } }) + const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) } + const child = spawn('git', ['branch', '-D', branch], { env }) let errBuffer = Buffer.from('') - process.stderr.on('data', data => { + child.stderr.on('data', data => { errBuffer = Buffer.concat([errBuffer, data]) }) - process.on('close', code => { + child.on('close', code => { return code === 0 ? resolve(errBuffer) : reject(errBuffer) }) }) } - parsePushCommand(req) { - const [url, branch] = req.toString().split(':') - const [key, repo] = url.split('/') + async parseReq(req) { + let payload + let request = JSON.parse(req.toString()) + if (process.env.GIT_PEAR_AUTH) { + payload = await acl.getId({ + ...request.body, + payload: request.header + }) + } + return { - url: `pear://${url}`, - repo, - key, - branch + repoName: request.body.url?.split('/')?.pop(), + branch: request.body.data?.split('#')[0], + url: request.body.url } } - - loadACL(repoName) { - // TODO: read contact of .git-daemon-export-ok - // find key and its permissions - } }