From 2974bd3c5e63369fdc54a2eef0854768c438401b Mon Sep 17 00:00:00 2001 From: 7bit <662932+sevenbitbyte@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:55:38 -0700 Subject: [PATCH 1/6] Update match-maker-client.js --- src/party/peer/match-maker-client.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/party/peer/match-maker-client.js b/src/party/peer/match-maker-client.js index 3ebd7fa..c5c3791 100644 --- a/src/party/peer/match-maker-client.js +++ b/src/party/peer/match-maker-client.js @@ -12,7 +12,7 @@ const WebsocketComms = require('../../comms/websocket-comms') const PeerInvite = require('./peer-invite') class MatchMakerClient extends EventEmitter { - constructor(identity, contacts, urlOrParty = 'https://postquantum.one/api/', wsUrlOrParty = 'wss://postquantum.one/ws'){ + constructor(identity, contacts, urlOrParty = 'https://postquantum.one/api/', wsUrlOrParty = 'wss://postquantum.one/ws', billingIdentity=null){ super() @@ -22,6 +22,7 @@ class MatchMakerClient extends EventEmitter { this.identity = identity this.wsParty = null this.restParty = null + this.billingIdentity = null if(typeof urlOrParty == 'string'){ this.restUrl = urlOrParty @@ -157,15 +158,23 @@ class MatchMakerClient extends EventEmitter { debug('calling onInviteMsg') await pending.onInviteMsg(msg.invite) - + } } + async announceBillingKey({stripeCheckoutSession}={}){ + this.announcePublicKeys(true, { + stripe: stripeCheckoutSession + }) + } + async announcePublicKeys(useBillingKeyAsActor=false, billingMethodDetails=null){ - async announcePublicKeys(){ + let currentActor = useBillingKeyAsActor == true ? this.billingIdentity : this.identity + const announceData = { annoucement: { + type: useBillingKeyAsActor ? 'billing_identity' : 'user_identity' created: Date.now(), expiry: Date.now() + 24*60*60*1000, //! Set session expiry to 24hr from now sessionKey: { @@ -174,9 +183,9 @@ class MatchMakerClient extends EventEmitter { public: this.sessionKey.key.public }, actorKey: { - type: this.identity.key.type, - hash: this.identity.key.hash, - public: this.identity.key.public + type: currentActor.key.type, + hash: currentActor.key.hash, + public: currentActor.key.public } }, trust: { @@ -185,8 +194,10 @@ class MatchMakerClient extends EventEmitter { } } + if( + - const actorSigMsg = await this.identity.sign(announceData.annoucement, true) + const actorSigMsg = await currentActor.sign(announceData.annoucement, true) const sessionSigMsg = await this.sessionKey.sign(announceData.annoucement, true) debug('actorSigMsg', actorSigMsg) From 34d7484c894fa3cf34ee79e77a441ceb90088b78 Mon Sep 17 00:00:00 2001 From: sevenbitbyte Date: Mon, 18 May 2026 20:14:04 +0000 Subject: [PATCH 2/6] add runner to EndpointContext --- src/service/endpoint-context.js | 7 ++++++- src/service/service-runner-node.js | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/service/endpoint-context.js b/src/service/endpoint-context.js index 80c725a..272a550 100644 --- a/src/service/endpoint-context.js +++ b/src/service/endpoint-context.js @@ -15,7 +15,7 @@ class EndpointContext { * @param {Debug} options.debug Debug constructor (defaults to npm:Debug) * @param {boolean} options.sendFullErrors Enables sending full stack traces to client (defaults to false) */ - constructor({party, endpoint, req, res, input, debug=Debug, sendFullErrors=false}){ + constructor({party, endpoint, runner, req, res, input, debug=Debug, sendFullErrors=false}){ /** * @member module:Service.EndpointContext.debug @@ -27,6 +27,11 @@ class EndpointContext { */ this.endpoint = endpoint + /** + * @member module:Service.EndpointContext.runner + */ + this.runner = runner + /** * @member module:Service.EndpointContext.MiddlewareConfig */ diff --git a/src/service/service-runner-node.js b/src/service/service-runner-node.js index 5df899c..b251c2e 100644 --- a/src/service/service-runner-node.js +++ b/src/service/service-runner-node.js @@ -516,6 +516,7 @@ class ServiceRunnerNode { req: event.request, res: event.response, endpoint, party: this.party, + runner: this, input: event.request.body, debug: Debug, sendFullErrors: this.sendFullErrors From 3b407d6981ef1049c64070cd03683bc360986ffd Mon Sep 17 00:00:00 2001 From: sevenbitbyte Date: Mon, 18 May 2026 20:54:54 +0000 Subject: [PATCH 3/6] key management and ephemeral sessions --- package.json | 1 + src/party/peer/match-maker-client.js | 8 +- src/venue/endpoints/key-announce.js | 204 +++++++++++++++++++ src/venue/middleware/pre/ephmeral-session.js | 101 +++++++++ src/venue/schema/public-key.js | 47 +++++ src/venue/schema/session-key.js | 69 +++++++ src/venue/venue-service.js | 5 + 7 files changed, 431 insertions(+), 4 deletions(-) create mode 100644 src/venue/endpoints/key-announce.js create mode 100644 src/venue/middleware/pre/ephmeral-session.js create mode 100644 src/venue/schema/public-key.js create mode 100644 src/venue/schema/session-key.js diff --git a/package.json b/package.json index 5caa37b..4687a73 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "devDependencies": { "@dataparty/bouncer-model": "1.4.3", "@hapi/code": "^9.0.1", + "@hapi/joi": "^17.1.1", "@hapi/lab": "^25.0.1", "argon2": "^0.30.3", "argon2-browser": "^1.18.0", diff --git a/src/party/peer/match-maker-client.js b/src/party/peer/match-maker-client.js index c5c3791..65969a5 100644 --- a/src/party/peer/match-maker-client.js +++ b/src/party/peer/match-maker-client.js @@ -174,7 +174,7 @@ class MatchMakerClient extends EventEmitter { const announceData = { annoucement: { - type: useBillingKeyAsActor ? 'billing_identity' : 'user_identity' + //type: 'guest',//useBillingKeyAsActor ? 'billing_identity' : 'user_identity', created: Date.now(), expiry: Date.now() + 24*60*60*1000, //! Set session expiry to 24hr from now sessionKey: { @@ -194,8 +194,6 @@ class MatchMakerClient extends EventEmitter { } } - if( - const actorSigMsg = await currentActor.sign(announceData.annoucement, true) const sessionSigMsg = await this.sessionKey.sign(announceData.annoucement, true) @@ -208,7 +206,9 @@ class MatchMakerClient extends EventEmitter { debug('announcePublicKeys', announceData) - const announceResult = await this.restParty.comms.call('key/announce', announceData, { + let callPath = useBillingKeyAsActor ? 'billing/key/announce' : 'key/announce' + + const announceResult = await this.restParty.comms.call(callPath, announceData, { expectClearTextReply: false, sendClearTextRequest: false, useSessions: false diff --git a/src/venue/endpoints/key-announce.js b/src/venue/endpoints/key-announce.js new file mode 100644 index 0000000..fca2d26 --- /dev/null +++ b/src/venue/endpoints/key-announce.js @@ -0,0 +1,204 @@ +const Joi = require('@hapi/joi') +const Hoek = require('@hapi/hoek') +const debug = require('debug')('dataparty.endpoint.key-announce') + +const {Identity, Message, Routines} = require('@dataparty/crypto') + +//const IEndpoint = require('@dataparty/api/src/service/iendpoint') +const IEndpoint = require('../../service/iendpoint') + +const KeyVerifier = Joi.object().keys({ + id: Joi.string().max(100), + type: Joi.string().max(300).required(), + hash: Joi.string().max(200).required(), + public: { + box: Joi.string().max(60).required(), + sign: Joi.string().max(60).required(), + pqkem: Joi.string().max(8000).required(), + pqsign_ml: Joi.string().max(8000).required(), + pqsign_slh: Joi.string().max(800).required() + } +}) + +module.exports = class KeyAnnounceEndpoint extends IEndpoint { + + static get Name(){ + return 'key/announce' + } + + static get Description(){ + return 'announce a public key' + } + + static get MiddlewareConfig(){ + return { + pre: { + decrypt: true, + validate: Joi.object().keys({ + + annoucement: { + created: Joi.number().required(), + expiry: Joi.number().required(), + actorKey: KeyVerifier.required(), + sessionKey: KeyVerifier.required(), + }, + trust:{ + actorSig: Joi.string().required().description('actor signature of the annoucement in base64'), + sessionSig: Joi.string().required().description('session signature of the annoucement in base64') + } + + }).description('key to announce') + }, + post: { + encrypt: true, + validate: Joi.object().keys({ + done: Joi.boolean(), + }).description('public key') + } + } + } + + static async run(ctx, {Package}){ + + ctx.debug('hello key/announce') + + ctx.debug('ip', ctx.req.ip) + ctx.debug('input', ctx.input) + ctx.debug('sender', ctx.senderKey) + + const inputActorKey = { + type: ctx.input.annoucement.actorKey.type, + hash: ctx.input.annoucement.actorKey.hash, + public: ctx.input.annoucement.actorKey.public + } + + const inputSessionKey = { + type: ctx.input.annoucement.sessionKey.type, + hash: ctx.input.annoucement.sessionKey.hash, + public: ctx.input.annoucement.sessionKey.public + } + + + const computedActorHash = await Routines.hashKey( inputActorKey ) + const computedSessionHash = await Routines.hashKey( inputSessionKey ) + + ctx.debug('computed hash -', computedSessionHash) + + // verify keys are self consistent + if( + computedActorHash != inputActorKey.hash || + computedSessionHash != inputSessionKey.hash + ) { + ctx.debug('invalid actor or session key hash') + return {done: false} + } + + // ensure sender is connected using session key mentioned in annoucement + if(computedSessionHash == inputSessionKey.hash && + inputSessionKey.public.sign == ctx.senderKey.public.sign && + inputSessionKey.public.box == ctx.senderKey.public.box + ){ + + const actorSigBson = Routines.Utils.base64.decode( ctx.input.trust.actorSig ) + const sessionSigBson = Routines.Utils.base64.decode( ctx.input.trust.sessionSig ) + + const actorSigMsg = new Message({ msg: ctx.input.annoucement }) + const sessionSigMsg = new Message({ msg: ctx.input.annoucement }) + + actorSigMsg.sig = actorSigBson + sessionSigMsg.sig = sessionSigBson + + const actorIdentity = Identity.fromJSON({ + id: '', + key: inputActorKey + }) + + const sessionIdentity = Identity.fromJSON({ + id: '', + key: inputSessionKey + }) + + //verify actor & session signature. Require postquantum signing + await actorSigMsg.assertVerified( actorIdentity, true ) + await sessionSigMsg.assertVerified( sessionIdentity, true ) + + let sessionKeyDoc = (await ctx.party.find() + .type('session_key') + .where('annoucement.sessionKey.hash').equals(computedSessionHash) + .exec())[0] + + if(!sessionKeyDoc){ + // create session document + + const now = Date.now() + const tomorrow = now + 24*60*60*1000 + + const fiveMinAgo = now - (5*1000*60) + const fiveMinFromNow = now + (5*1000*60) + + // verify start time is valid + if( + ctx.input.annoucement.created < fiveMinAgo || + ctx.input.annoucement.created > fiveMinFromNow + ) { + ctx.debug('invalid start time') + return {done: false} + } + + // verify expiry time is valid + if( + ctx.input.annoucement.expiry < now || + ctx.input.annoucement.expiry > tomorrow + ) { + ctx.debug('invalid expiry time') + return {done: false} + } + + ctx.debug('opening session -', computedSessionHash) + + let sessionDoc = await ctx.party.createDocument('session_key', { + created: now, + expiry: ctx.input.annoucement.expiry, + annoucement: ctx.input.annoucement, + trust: ctx.input.trust + }) + + ctx.debug('session created - ', computedSessionHash) + + } else { + ctx.debug('session key already known - ', computedSessionHash) + return {done: false} + } + + let publicKey = (await ctx.party.find() + .type('public_key') + .where('annoucement.actorKey.hash').equals(computedActorHash) + .exec())[0] + + if(!publicKey){ + + ctx.debug('annoucing key -', computedActorHash) + + let keyDoc = await ctx.party.createDocument('public_key', { + created: Date.now(), + role: 'guest', + owner: computedActorHash, + ...inputActorKey + }) + + ctx.debug('actor annouced key -', computedActorHash) + } else { + ctx.debug('key already known -', computedActorHash) + } + + return {done: true} + + } else { + + ctx.debug('announce ERROR - BAD KEY HASH') + + return {done: false} + } + + } +} diff --git a/src/venue/middleware/pre/ephmeral-session.js b/src/venue/middleware/pre/ephmeral-session.js new file mode 100644 index 0000000..11f862e --- /dev/null +++ b/src/venue/middleware/pre/ephmeral-session.js @@ -0,0 +1,101 @@ +const Joi = require('joi') +const Hoek = require('@hapi/hoek') +const {Identity} = require('@dataparty/crypto') +const debug = require('debug')('dataparty.middleware.pre.ephemeral-session') + +const IMiddleware = require('../../service/imiddleware') + +module.exports = class Decrypt extends IMiddleware { + + static get Name(){ + return 'ephemeral_session' + } + + static get Type(){ + return 'pre' + } + + static get Description(){ + return 'Decrypt inbound data' + } + + static get ConfigSchema(){ + return Joi.boolean() + } + + static async start(party){ + + } + + static async run(ctx, {Config}){ + + if (!Config){ return } + + if(!ctx.input_session_id){ + throw new Error('no session id') + } + + ctx.debug('looking up session -', ctx.input_session_id) + + let sessionKeyDoc = (await ctx.party.find() + .type('session_key') + .where('annoucement.sessionKey.hash') + .equals(ctx.input_session_id) + .exec() + )[0] + + if(!sessionKeyDoc){ + throw new Error('invalid session') + } + + ctx.debug('located session - ', ctx.input_session_id) + + const sessionKey = sessionKeyDoc.data.annoucement.sessionKey + const actorKey = sessionKeyDoc.data.annoucement.actorKey + + // ensure sender is connected using session key mentioned in db + if(sessionKey.hash == ctx.input_session_id && + sessionKey.public.sign == ctx.senderKey.public.sign && + sessionKey.public.box == ctx.senderKey.public.box + ){ + + const now = Date.now() + + if( sessionKeyDoc.data.expiry < now ){ + throw new Error('session expired!') + } + + const actorIdentity = Identity.fromJSON({ + id: 'actor', + key: actorKey + }) + + const sessionIdentity = Identity.fromJSON({ + id: 'ephemeral-session', + key: sessionKey + }) + + ctx.debug('looking up actor - ', actorKey.hash) + + let actorDoc = (await ctx.party.find() + .type('public_key') + .where('hash') + .equals(actorKey.hash) + .exec() + )[0] + + if(!actorDoc){ + throw new Error('failed to find actor') + } + + ctx.setSession( sessionKeyDoc ) + ctx.setIdentity( actorIdentity ) + ctx.setActor( actorDoc ) + + ctx.debug('session ready') + } else { + throw new Error('invalid sender key for this session') + } + + } +} \ No newline at end of file diff --git a/src/venue/schema/public-key.js b/src/venue/schema/public-key.js new file mode 100644 index 0000000..597601b --- /dev/null +++ b/src/venue/schema/public-key.js @@ -0,0 +1,47 @@ +'use strict' + +const ISchema = require('../../bouncer/ischema') + +class PublicKey extends ISchema { + + static get Type () { return 'public_key' } + + static get Schema(){ + return { + created: { + type: Number, + required: true + }, + + role: String, // [ guest, billing, service, wallet ] + + owner: {required: true, index: true, type: String}, // public key hash + + type: String, + hash: {required: true, index: true, type: String, unique: true}, + public: { + box: String, + sign: String, + pqkem: String, + pqsign_ml: String, + pqsign_slh: String + } + } + } + + static setupSchema(schema){ + //schema.index({ 'hash': 1 }, {unique: true}) + return schema + } + + static permissions (context) { + return { + read: false, + new: false, + change: false + } + } +} + + +module.exports = PublicKey diff --git a/src/venue/schema/session-key.js b/src/venue/schema/session-key.js new file mode 100644 index 0000000..a0da93c --- /dev/null +++ b/src/venue/schema/session-key.js @@ -0,0 +1,69 @@ +'use strict' + +const ISchema = require('../../bouncer/ischema') + + +function PublicKeySchema(unique=true){ + return { + type: {required: true, type: String}, + hash: {required: true, index: true, type: String, unique}, + public: { + box: String, + sign: String, + pqkem: String, + pqsign_ml: String, + pqsign_slh: String + } + } +} + + +class SessionKey extends ISchema { + + static get Type () { return 'session_key' } + + static get Schema(){ + return { + created: { + type: Number, + required: true + }, + expiry: { + type: Number, + index: true, + required: true + }, + annoucement: { + created: { + type: Number, + required: true + }, + expiry: { + type: Number, + required: true + }, + sessionKey: PublicKeySchema(true), + actorKey: PublicKeySchema(false) + }, + trust: { + actorSig: {required: true, type: String}, //! base64 of BSON signature + sessionSig: {required: true, type: String} //! base64 of BSON signature + } + } + } + + static setupSchema(schema){ + return schema + } + + static permissions (context) { + return { + read: false, + new: false, + change: false + } + } +} + + +module.exports = SessionKey \ No newline at end of file diff --git a/src/venue/venue-service.js b/src/venue/venue-service.js index f5c7ad3..47eaad7 100644 --- a/src/venue/venue-service.js +++ b/src/venue/venue-service.js @@ -11,6 +11,9 @@ class VenueService extends DatapartySrv.IService { let builder = new DatapartySrv.ServiceBuilder(this) + + builder.addSchema(Path.join(__dirname, './schema/public-key.js')) + builder.addSchema(Path.join(__dirname, './schema/session-key.js')) builder.addSchema(Path.join(__dirname, './schema/venue_service.js')) builder.addMiddleware(DatapartySrv.middleware_paths.pre.decrypt) @@ -22,6 +25,8 @@ class VenueService extends DatapartySrv.IService { builder.addEndpoint(DatapartySrv.endpoint_paths.identity) builder.addEndpoint(DatapartySrv.endpoint_paths.version) + builder.addEndpoint(Path.join(__dirname, './endpoints/key-announce.js')) + builder.addEndpoint(Path.join(__dirname, './endpoints/create-service.js')) builder.addAuth(Path.join(__dirname, './auth.js')) } From 6df2d7079c14b0cea040b80840acf7bc8eab8033 Mon Sep 17 00:00:00 2001 From: sevenbitbyte Date: Mon, 18 May 2026 21:07:08 +0000 Subject: [PATCH 4/6] cleanup ephemeral sessions every 15 minutes --- src/venue/tasks/cleanup-ephemeral-sessions.js | 86 +++++++++++++++++++ src/venue/venue-service.js | 4 + 2 files changed, 90 insertions(+) create mode 100644 src/venue/tasks/cleanup-ephemeral-sessions.js diff --git a/src/venue/tasks/cleanup-ephemeral-sessions.js b/src/venue/tasks/cleanup-ephemeral-sessions.js new file mode 100644 index 0000000..435a48b --- /dev/null +++ b/src/venue/tasks/cleanup-ephemeral-sessions.js @@ -0,0 +1,86 @@ +const debug = require('debug')('dataparty.task.cleanup-ephemeral-sessions') + +//const ITask = require('@dataparty/api/src/service/itask') +const ITask = require('../../service/itask') + +class CleanupEphemeralSessionsTask extends ITask { + + constructor(options){ + super({ + name: CleanupEphemeralSessionsTask.name, + background: CleanupEphemeralSessionsTask.Config.background, + ...options + }) + + debug('new') + + this.duration = Math.round(1000*60*15) + this.timeout = null + } + + static get Config(){ + return { + background: true, + autostart: true + } + } + + async exec(){ + + this.setTimer() + + return this.detach() + } + + + async lookupSessions(){ + + let now = Date.now() + + return (await this.context.party.find() + .type('session_key') + .where('expiry').lt(now) + .exec()) + } + + setTimer(){ + this.timeout = setTimeout(this.onTimeout.bind(this), this.duration) + } + + async onTimeout(){ + this.timeout = null + + debug('cleanup ephemeral sessions task') + + try{ + let sessions = await this.lookupSessions() + + if(sessions && sessions.length > 0){ + debug('expired sessions ', sessions.length) + let list = sessions.map(i=>{return i.data}) + await this.context.party.remove(...list) + } + } catch (err){ + debug(err) + } + + this.setTimer() + } + + stop(){ + if(this.timeout !== null){ + clearTimeout(this.timeout) + this.timeout = null + } + } + + static get Name(){ + return 'cleanup-ephemeral-sessions' + } + + static get Description(){ + return 'Cleanup Ephemeral sessions' + } +} + +module.exports = CleanupEphemeralSessionsTask \ No newline at end of file diff --git a/src/venue/venue-service.js b/src/venue/venue-service.js index 47eaad7..dc56ea1 100644 --- a/src/venue/venue-service.js +++ b/src/venue/venue-service.js @@ -28,6 +28,10 @@ class VenueService extends DatapartySrv.IService { builder.addEndpoint(Path.join(__dirname, './endpoints/key-announce.js')) builder.addEndpoint(Path.join(__dirname, './endpoints/create-service.js')) + + + builder.addTask(Path.join(__dirname,'./tasks/cleanup-ephemeral-sessions.js')) + builder.addAuth(Path.join(__dirname, './auth.js')) } } From 8ddc422009b0ed69fa1f169887dce0e5e0232ef4 Mon Sep 17 00:00:00 2001 From: sevenbitbyte Date: Mon, 25 May 2026 17:13:08 +0000 Subject: [PATCH 5/6] admin only access to API --- src/comms/peer-comms.js | 7 ++-- src/config/json-file.js | 13 +++++++ src/party/peer/match-maker-client.js | 6 ++- src/service/service-host.js | 2 +- src/venue/auth.js | 32 ++++++++++++++-- src/venue/bin/add-admin.js | 40 ++++++++++++++++++++ src/venue/bin/chill.js | 0 src/venue/endpoints/key-announce.js | 16 +++++++- src/venue/public/p2p-test.html | 7 +++- src/venue/schema/venue_package.js | 56 ++++++++++++++++++++++++++++ src/venue/schema/venue_service.js | 1 - src/venue/venue-host.js | 12 +++--- 12 files changed, 176 insertions(+), 16 deletions(-) create mode 100755 src/venue/bin/add-admin.js create mode 100644 src/venue/bin/chill.js create mode 100644 src/venue/schema/venue_package.js diff --git a/src/comms/peer-comms.js b/src/comms/peer-comms.js index c19a07c..b64c98a 100644 --- a/src/comms/peer-comms.js +++ b/src/comms/peer-comms.js @@ -385,13 +385,13 @@ class PeerComms extends ISocketComms { if(this.party.hostRunner){ const actor = await this.party.hostRunner.auth.lookupIdentity(offer.sender) - const verified = await Routines.verifyDataPQ(actor, signature, offerBSON) + const verified = await Routines.verifyDataPQ(offer.sender, signature, offerBSON) if(!verified){ throw new Error('DENY(hostRunner) - auth op signature is not valid') } - if(this.discoverRemoteIdentity){ this.remoteIdentity = actor } + if(this.discoverRemoteIdentity){ this.remoteIdentity = offer.sender } const authorized = await this.party.hostRunner.auth.isSocketConnectionAllowed(actor) if(!authorized){ @@ -406,6 +406,7 @@ class PeerComms extends ISocketComms { await this.stop() debug('DENY - client not allowed - ', this.remoteIdentity) + throw new Error('DENY - client not allowed') } } else { const actor = offer.sender @@ -420,7 +421,7 @@ class PeerComms extends ISocketComms { } } - debug('clienr auth op offer -', offer) + debug('client auth op offer -', offer) debug('ALLOW - allowing client - ', this.remoteIdentity) this.aesStream = await AESStream.recoverStream( diff --git a/src/config/json-file.js b/src/config/json-file.js index b5280ab..f2f1bec 100644 --- a/src/config/json-file.js +++ b/src/config/json-file.js @@ -22,6 +22,7 @@ class JsonFileConfig extends IConfig { this.path = this.basePath +'/config.json' this.defaults = defaults || {} this.content = Object.assign({}, this.defaults) + this.writing = false } async load(){ @@ -49,6 +50,8 @@ class JsonFileConfig extends IConfig { async start () { await this.touchDir('') await this.load() + + fs.watchFile(this.path, this.handleFileChange.bind(this)) logger('started') } @@ -79,7 +82,9 @@ class JsonFileConfig extends IConfig { } async save(){ + this.writing = true fs.writeFileSync(this.path, JSON.stringify(this.content, null, 2)) + this.writing = false } async touchDir (path) { @@ -98,6 +103,14 @@ class JsonFileConfig extends IConfig { }) }) } + + async handleFileChange(current, previous){ + if(this.writing){ return } + + logger('config changed, reloading') + + await this.load() + } } module.exports = JsonFileConfig \ No newline at end of file diff --git a/src/party/peer/match-maker-client.js b/src/party/peer/match-maker-client.js index 65969a5..cf2f6bd 100644 --- a/src/party/peer/match-maker-client.js +++ b/src/party/peer/match-maker-client.js @@ -83,7 +83,7 @@ class MatchMakerClient extends EventEmitter { await this.announcePublicKeys() } - if(!this.wsParty){ + if(!this.wsParty && this.wsUrl){ this.wsParty = new PeerParty({ comms: new WebsocketComms({ uri: this.wsUrl, @@ -213,6 +213,10 @@ class MatchMakerClient extends EventEmitter { sendClearTextRequest: false, useSessions: false }) + + if(announceResult.done != true){ + throw new Error('annoucement request failed - '+callPath) + } } diff --git a/src/service/service-host.js b/src/service/service-host.js index 6fd1c68..a03a5dd 100644 --- a/src/service/service-host.js +++ b/src/service/service-host.js @@ -307,7 +307,7 @@ class ServiceHost { this.apiApp.use((err, req, res, _next) => { console.log('Error handler', err) if (err instanceof IpDeniedError) { - //res.status(401) + res.status(401) } else { res.status(err.status || 500) } diff --git a/src/venue/auth.js b/src/venue/auth.js index c2ff01c..7b8074c 100644 --- a/src/venue/auth.js +++ b/src/venue/auth.js @@ -1,6 +1,7 @@ const debug = require('debug')('dataparty.auth.venue-auth') const IAuth = require('../service/iauth') +const {Identity} = require('@dataparty/crypto') module.exports = class IAuth { @@ -36,12 +37,28 @@ module.exports = class IAuth { } async lookupIdentity(identity){ + + let sessionKeyDoc = (await this.context.party.find() + .type('session_key') + .where('annoucement.sessionKey.hash') + .equals(identity.key.hash) + .exec() + )[0] + + if(sessionKeyDoc){ + const actorIdentity = Identity.fromJSON({ + id: 'actor', + key: sessionKeyDoc.data.annoucement.actorKey + }) + + return actorIdentity + } + return identity } async isSocketConnectionAllowed(identity){ - //throw new Error('not implemented') - return true + return await this.isAdmin(identity) } async isInternal(identity){ @@ -49,7 +66,16 @@ module.exports = class IAuth { } async isAdmin(identity){ - return false + + // verify key-hash is an admin + const admins = (await this.context.party.config.read('admins')) || [] + + if(admins.indexOf(identity.key.hash) == -1){ + debug('non-admin user', identity.key.hash) + return false + } + + return true } async canReadDb(identity){ diff --git a/src/venue/bin/add-admin.js b/src/venue/bin/add-admin.js new file mode 100755 index 0000000..f09375e --- /dev/null +++ b/src/venue/bin/add-admin.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +const Dataparty = require('../../index') + + +async function main(){ + + const path = '/data/dataparty/venue-service' + + let config = new Dataparty.Config.JsonFileConfig({ + basePath: path+'/config' + }) + + await config.start() + + console.log(process.argv) + + let admins = (await config.read('admins')) || [] + + const newAdmin = process.argv[2] + + console.log(await config.readAll()) + console.log(admins) + + if(admins.indexOf(newAdmin) != -1){ return } + + admins.push(newAdmin) + + await config.write('admins', admins) + + console.log('admin added -', newAdmin) + +} + + +main().catch(err=>{ + console.error(err) +}).finally(()=>{ + process.exit() +}) diff --git a/src/venue/bin/chill.js b/src/venue/bin/chill.js new file mode 100644 index 0000000..e69de29 diff --git a/src/venue/endpoints/key-announce.js b/src/venue/endpoints/key-announce.js index fca2d26..de1e7ec 100644 --- a/src/venue/endpoints/key-announce.js +++ b/src/venue/endpoints/key-announce.js @@ -93,6 +93,7 @@ module.exports = class KeyAnnounceEndpoint extends IEndpoint { return {done: false} } + // ensure sender is connected using session key mentioned in annoucement if(computedSessionHash == inputSessionKey.hash && inputSessionKey.public.sign == ctx.senderKey.public.sign && @@ -122,6 +123,19 @@ module.exports = class KeyAnnounceEndpoint extends IEndpoint { await actorSigMsg.assertVerified( actorIdentity, true ) await sessionSigMsg.assertVerified( sessionIdentity, true ) + // verify key-hash is an admin + //const admins = (await ctx.party.config.read('admins')) || [] + + //if(admins.indexOf(computedActorHash) == -1){ + const isAdmin = await ctx.runner.auth.isAdmin(actorIdentity) + if(!isAdmin){ + ctx.debug('non-admin user') + return {done: false} + + process.exit(1) + } + + let sessionKeyDoc = (await ctx.party.find() .type('session_key') .where('annoucement.sessionKey.hash').equals(computedSessionHash) @@ -172,7 +186,7 @@ module.exports = class KeyAnnounceEndpoint extends IEndpoint { let publicKey = (await ctx.party.find() .type('public_key') - .where('annoucement.actorKey.hash').equals(computedActorHash) + .where('hash').equals(computedActorHash) .exec())[0] if(!publicKey){ diff --git a/src/venue/public/p2p-test.html b/src/venue/public/p2p-test.html index 6c57aa8..a45f222 100644 --- a/src/venue/public/p2p-test.html +++ b/src/venue/public/p2p-test.html @@ -256,7 +256,12 @@

Offers recieved

await hostLocal.start() - matchMaker = new Dataparty.MatchMakerClient(hostLocal.privateIdentity, null) + matchMaker = new Dataparty.MatchMakerClient( + hostLocal.privateIdentity, + null, + 'https://api.dataparty.xyz/api', + 'wss://api.dataparty.xyz/ws' + ) await matchMaker.start() diff --git a/src/venue/schema/venue_package.js b/src/venue/schema/venue_package.js new file mode 100644 index 0000000..261f55e --- /dev/null +++ b/src/venue/schema/venue_package.js @@ -0,0 +1,56 @@ +'use strict' + +const debug = require('debug')('venue.venue_pkg') + +const ISchema = require('../../bouncer/ischema') + +const Utils = ISchema.Utils + + +class VenuePkg extends ISchema { + + static get Type () { return 'venue_pkg' } + + static get Schema(){ + return { + owner: {type: String, required: true, index: true}, //public_key.key.hash + created: {type: Number, required: true}, + changed: {type: Number}, + venue: {type: String}, + settings: { + enabled: {type: Boolean, required: true}, + sendFullErrors: {type: Boolean, required: true}, + useNative: {type: Boolean, required: true}, + defaultConfig: {type: Object} + }, + package: { + name: {type: String, required: true, index: true}, + version: {type: String, required: true}, + githash: {type: String, required: true}, + branch: {type: String, required: true} + }, + compressedBuild: {type: String, required: true}, //! zlib compressed + signature: { + timestamp: {type: Number, required: true}, + type: {type: String, required: true, maxlength: 10}, + value: {type: String, required: true} + } + } + } + + static setupSchema(schema){ + //schema.index({ 'package.name': 1 }, {unique: true}) + return schema + } + + static permissions (context) { + return { + read: false, + new: false, + change: false + } + } +} + + +module.exports = VenuePkg \ No newline at end of file diff --git a/src/venue/schema/venue_service.js b/src/venue/schema/venue_service.js index f5f8d08..f66ff9e 100644 --- a/src/venue/schema/venue_service.js +++ b/src/venue/schema/venue_service.js @@ -2,7 +2,6 @@ const debug = require('debug')('venue.venue_srv') - const ISchema = require('../../bouncer/ischema') const Utils = ISchema.Utils diff --git a/src/venue/venue-host.js b/src/venue/venue-host.js index fef4471..b3f8ab2 100644 --- a/src/venue/venue-host.js +++ b/src/venue/venue-host.js @@ -55,7 +55,8 @@ async function main(){ const CloudFlareIpFilter = { options: { - mode: 'allow' + mode: 'allow', + //trustProxy: true }, ips: [ '173.245.48.0/20', @@ -79,7 +80,8 @@ async function main(){ '2405:b500::/32', '2405:8100::/32', '2a06:98c0::/29', - '2c0f:f248::/32' + '2c0f:f248::/32', + '10.115.68.55/32' ] } @@ -114,7 +116,7 @@ async function main(){ trust_proxy: true, wsEnabled: true, ssl_key, ssl_cert, - listenUri: 'https://0.0.0.0:443', + listenUri: 'https://0.0.0.0:3000', staticPath: Path.join(__dirname,'public'), staticPrefix: '/venue/', ipFilter: CloudFlareIpFilter @@ -126,7 +128,7 @@ async function main(){ debug('started') console.log('partying') - +/* await loadService(runnerRouter, { enabled: true, domain: 'postquantum.one', @@ -135,7 +137,7 @@ async function main(){ sendFullErrors: true, useNative: false - }, '/home/ubuntu/match-maker/dataparty/@datapartyjs-match-maker.dataparty-service.json') + }, '/home/ubuntu/match-maker/dataparty/@datapartyjs-match-maker.dataparty-service.json')*/ } From 015a46ba8cf4d22d4ca8e046bc5941d3afd94058 Mon Sep 17 00:00:00 2001 From: sevenbitbyte Date: Mon, 25 May 2026 17:17:31 +0000 Subject: [PATCH 6/6] debounce start --- src/config/json-file.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/config/json-file.js b/src/config/json-file.js index f2f1bec..d52eb4f 100644 --- a/src/config/json-file.js +++ b/src/config/json-file.js @@ -23,6 +23,7 @@ class JsonFileConfig extends IConfig { this.defaults = defaults || {} this.content = Object.assign({}, this.defaults) this.writing = false + this.started = false } async load(){ @@ -48,11 +49,16 @@ class JsonFileConfig extends IConfig { } async start () { + + if(this.started){return} + await this.touchDir('') await this.load() fs.watchFile(this.path, this.handleFileChange.bind(this)) logger('started') + + this.started = true } async clear () {