diff --git a/.gitignore b/.gitignore index 5960db5..0416303 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ node_modules pnpm-lock.yaml yarn.lock TODO -keypair \ No newline at end of file +data \ No newline at end of file diff --git a/lib/members.cjs b/lib/members.cjs new file mode 100644 index 0000000..3a0d887 --- /dev/null +++ b/lib/members.cjs @@ -0,0 +1,71 @@ +const Crypto = require('node:crypto') +const Path = require('node:path') +const AtomicFileRW = require('atomic-file-rw') +const Base58 = require('bs58') + +class Tokens { + static #filePath + + /** + * @type {Set} + */ + static #set + + static #loaded = false + + static #save(cb) { + const json = JSON.stringify([...this.#set]) + AtomicFileRW.writeFile(this.#filePath, json, cb) + } + + static load(parentPath) { + if (this.#loaded) return + this.#filePath = Path.join(parentPath, 'members.json') + this.#set = new Set() + + AtomicFileRW.readFile(this.#filePath, (err, buf) => { + if (err) { + if (err.code === 'ENOENT') { + this.#loaded = true + } else { + console.warn('Problem loading members file:', err) + } + return + } + const json = typeof buf === 'string' ? buf : buf.toString('utf-8') + const arr = JSON.parse(json) + for (const token of arr) { + this.#set.add(token) + } + this.#loaded = true + }) + } + + /** + * @param {string} token + * @returns {boolean} + */ + static has(token) { + if (!this.#loaded) { + throw new Error('Members not loaded yet, cannot call has()') + } + return this.#set.has(token) + } + + static create() { + if (!this.#loaded) { + throw new Error('Members not loaded yet, cannot call create()') + } + let token + do { + token = Base58.encode(Crypto.randomBytes(32)) + } while (this.#set.has(token)) + this.#set.add(token) + this.#save((err, _) => { + if (err) console.warn('Problem saving members file:', err) + }) + return token + } +} + +module.exports = Tokens diff --git a/lib/peer.cjs b/lib/peer.cjs index 3a889d7..c0ee0a8 100644 --- a/lib/peer.cjs +++ b/lib/peer.cjs @@ -1,8 +1,12 @@ +const Path = require('node:path') const Keypair = require('ppppp-keypair') +const caps = require('ppppp-caps') const SSAPI = require('secret-stack/lib/api') module.exports = function startPeer() { - const keypair = Keypair.loadOrCreateSync('./keypair') + const path = Path.join(__dirname, '..', 'data') + const keypairPath = Path.join(path, 'keypair') + const keypair = Keypair.loadOrCreateSync(keypairPath) SSAPI([], {}) .use(require('secret-stack/lib/core')) @@ -11,7 +15,8 @@ module.exports = function startPeer() { .use(require('ssb-conn')) .use(require('./plugin-hub.cjs')) .call(null, { - caps: { shse: 'p2pLq5VZKvNWaaafMUEcxH9BKm2WjNBCxsc8TRQV5gS' }, + path, + caps, keypair, conn: { autostart: false, diff --git a/lib/plugin-hub.cjs b/lib/plugin-hub.cjs index d79862a..c381d9c 100644 --- a/lib/plugin-hub.cjs +++ b/lib/plugin-hub.cjs @@ -2,6 +2,8 @@ const cat = require('pull-cat') const Notify = require('pull-notify') const pull = require('pull-stream') const debug = require('debug')('ppppp:hub') +const Tokens = require('./tokens.cjs') +const Members = require('./members.cjs') function ErrorDuplex(message) { const err = new Error(message) @@ -22,32 +24,36 @@ module.exports = { createTunnel: 'duplex', ping: 'sync', attendants: 'source', - createToken: 'async', + createToken: 'sync', }, permissions: { anonymous: { allow: ['createTunnel', 'ping', 'attendants', 'createToken'], }, }, - init(sstack) { + init(sstack, config) { if (!sstack.conn || !sstack.conn.connect) { throw new Error('tunnel plugin is missing the required ssb-conn plugin') } debug('running multiserver at %s', sstack.getAddress('public')) - // Ensure that incoming connections are only from members - sstack.auth.hook(function (fn, args) { - const [incomingId, cb] = args - cb(null, true) - // fn.apply(this, args) + Tokens.load(config.path) + Members.load(config.path) - // FIXME: - // if (members.has(incomingId)) { - // fn.apply(this, args); - // } else { - // debug('prevented stranger %s from connecting to us', incomingId); - // cb(new Error('client is a stranger')); - // } + // Ensure that client connections are only from members or to-be members + sstack.auth.hook(function (fn, args) { + const [clientMetadata, cb] = args + const {pubkey, extra} = clientMetadata + if (Members.has(pubkey)) { + cb(null, true) + } else if (extra && Tokens.has(extra)) { + Tokens.delete(extra) + Members.add(pubkey) + cb(null, true) + } else { + debug('prevented stranger %s from connecting to us', pubkey) + cb(new Error('client is a stranger')) + } }) const attendants = new Map() @@ -101,8 +107,8 @@ module.exports = { return Date.now() }, - createToken(cb) { - cb(new Error('not implemented')) + createToken() { + return Tokens.create() }, } }, diff --git a/lib/tokens.cjs b/lib/tokens.cjs new file mode 100644 index 0000000..9c1d9b4 --- /dev/null +++ b/lib/tokens.cjs @@ -0,0 +1,81 @@ +const Crypto = require('node:crypto') +const Path = require('node:path') +const AtomicFileRW = require('atomic-file-rw') +const Base58 = require('bs58') + +class Tokens { + static #filePath + + /** + * @type {Set} + */ + static #set + + static #loaded = false + + static #save(cb) { + const json = JSON.stringify([...this.#set]) + AtomicFileRW.writeFile(this.#filePath, json, cb) + } + + static load(parentPath) { + if (this.#loaded) return + this.#filePath = Path.join(parentPath, 'tokens.json') + this.#set = new Set() + + AtomicFileRW.readFile(this.#filePath, (err, buf) => { + if (err) { + if (err.code === 'ENOENT') { + this.#loaded = true + } else { + console.log('Problem loading tokens file:', err) + } + return + } + const json = typeof buf === 'string' ? buf : Buffer.toString(buf, 'utf-8') + const arr = JSON.parse(json) + for (const token of arr) { + this.#set.add(token) + } + this.#loaded = true + }) + } + + /** + * @param {string} token + * @returns {boolean} + */ + static has(token) { + if (!this.#loaded) { + throw new Error('Tokens not loaded yet, cannot call has()') + } + return this.#set.has(token) + } + + static create() { + if (!this.#loaded) { + throw new Error('Tokens not loaded yet, cannot call create()') + } + let token + do { + token = Base58.encode(Crypto.randomBytes(32)) + } while (this.#set.has(token)) + this.#set.add(token) + this.#save((err, _) => { + if (err) console.log('Problem saving tokens file:', err) + }) + return token + } + + static delete(token) { + if (!this.#loaded) { + throw new Error('Tokens not loaded yet, cannot call delete()') + } + this.#set.delete(token) + this.#save((err, _) => { + if (err) console.log('Problem saving tokens file:', err) + }) + } +} + +module.exports = Tokens \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 136309f..5bb55ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,13 @@ "dependencies": { "@fastify/static": "6.10.2", "@fastify/view": "7.4.1", + "atomic-file-rw": "~0.3.0", + "bs58": "5.0.0", "debug": "4.3.4", "ejs": "3.1.9", "fastify": "4.17.0", "pino": "8.14.1", + "ppppp-caps": "github:staltz/ppppp-caps", "ppppp-keypair": "github:staltz/ppppp-keypair", "pull-cat": "1.1.11", "pull-notify": "0.1.2", @@ -2347,6 +2350,11 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.1.tgz", "integrity": "sha512-wHuWB+CvSVb2XqXM0W/WOYUkVSPbiJb9S5fNB7TBhd8s892Xq910bRxwHtC4l71hgztObTjXL6ZheZXFjhDrDQ==" }, + "node_modules/ppppp-caps": { + "version": "0.0.1", + "resolved": "git+ssh://git@github.com/staltz/ppppp-caps.git#a2111355a1d2bddfc4d5f82267257fe99c14f608", + "license": "CC0-1.0" + }, "node_modules/ppppp-keypair": { "version": "0.0.1", "resolved": "git+ssh://git@github.com/staltz/ppppp-keypair.git#2d0cd86dae6df2fa33eb14c836ab706244807f43", @@ -5533,6 +5541,10 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.1.tgz", "integrity": "sha512-wHuWB+CvSVb2XqXM0W/WOYUkVSPbiJb9S5fNB7TBhd8s892Xq910bRxwHtC4l71hgztObTjXL6ZheZXFjhDrDQ==" }, + "ppppp-caps": { + "version": "git+ssh://git@github.com/staltz/ppppp-caps.git#a2111355a1d2bddfc4d5f82267257fe99c14f608", + "from": "ppppp-caps@github:staltz/ppppp-caps" + }, "ppppp-keypair": { "version": "git+ssh://git@github.com/staltz/ppppp-keypair.git#2d0cd86dae6df2fa33eb14c836ab706244807f43", "from": "ppppp-keypair@github:staltz/ppppp-keypair", diff --git a/package.json b/package.json index 37a8f03..0e5bf22 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,12 @@ "dependencies": { "@fastify/static": "6.10.2", "@fastify/view": "7.4.1", + "atomic-file-rw": "~0.3.0", + "bs58": "5.0.0", "debug": "4.3.4", "ejs": "3.1.9", "fastify": "4.17.0", + "ppppp-caps": "github:staltz/ppppp-caps", "ppppp-keypair": "github:staltz/ppppp-keypair", "pino": "8.14.1", "pull-cat": "1.1.11",