From f8a185e879b3ef4676a9ba119a9fa7dc4771c827 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 02:19:23 +0100 Subject: [PATCH] nostr service and fix usage in invites --- src/commands/createAppInvite.js | 3 +- src/services/index.js | 3 +- src/services/invitesService.js | 17 ++- src/services/nostrService.js | 211 +++++++++++++++++--------------- 4 files changed, 124 insertions(+), 110 deletions(-) diff --git a/src/commands/createAppInvite.js b/src/commands/createAppInvite.js index f4407bb..6b36583 100644 --- a/src/commands/createAppInvite.js +++ b/src/commands/createAppInvite.js @@ -1,4 +1,5 @@ -const nostrService = require('../services/nostrService'); +const NostrServiceProvider = require('../services/nostrService'); +const nostrService = new NostrServiceProvider().provide(); const InvitesServiceProvider = require('../services/invitesService'); const invitesService = new InvitesServiceProvider({ nostrService, diff --git a/src/services/index.js b/src/services/index.js index efb197d..deaaa8b 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -2,9 +2,10 @@ class ServicesProvider { constructor() {} provide() { - const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); + const NostrServiceProvider = require('../services/nostrService'); + const nostrService = new NostrServiceProvider().provide(); const InvitesServiceProvider = require('../services/invitesService'); const invitesService = new InvitesServiceProvider({ nostrService, diff --git a/src/services/invitesService.js b/src/services/invitesService.js index 75e96f1..6b3fde0 100644 --- a/src/services/invitesService.js +++ b/src/services/invitesService.js @@ -1,6 +1,5 @@ const uuid = require('uuid'); -const nostrService = require('./nostrService'); const models = require('../models'); const errors = require('../errors'); @@ -49,7 +48,7 @@ class InvitesServiceProvider { }); } - async function createSignUpChallenge(appInviteUuid) { + const createSignUpChallenge = async (appInviteUuid) => { if (!(await appInviteExists(appInviteUuid))) { throw new errors.NotFoundError("Invite doesn't exist."); } @@ -58,7 +57,7 @@ class InvitesServiceProvider { throw new errors.AlreadyUsedError('Invite has already been used.'); } - const nostrChallenge = await nostrService.createNostrChallenge(); + const nostrChallenge = await this.nostrService.createNostrChallenge(); return await models.SignUpChallengeCreated.create({ uuid: uuid.v7(), @@ -66,15 +65,15 @@ class InvitesServiceProvider { app_invite_uuid: appInviteUuid, created_at: new Date().toISOString(), }); - } + }; - async function verifySignUpChallenge(signedEvent) { + const verifySignUpChallenge = async (signedEvent) => { const challengeTag = signedEvent.tags.find( (tag) => tag[0] === 'challenge' ); const challenge = challengeTag[1]; - const nostrChallenge = await nostrService.getNostrChallenge( + const nostrChallenge = await this.nostrService.getNostrChallenge( null, challenge ); @@ -85,14 +84,14 @@ class InvitesServiceProvider { }, }); - if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { + if (await this.nostrService.hasNostrChallengeBeenCompleted(challenge)) { throw new errors.AlreadyUsedError( 'This challenge has already been used.' ); } const completedNostrChallenge = - await nostrService.verifyNostrChallenge(signedEvent); + await this.nostrService.verifyNostrChallenge(signedEvent); const completedSignUpChallenge = await models.SignUpChallengeCompleted.create({ @@ -104,7 +103,7 @@ class InvitesServiceProvider { }); return completedSignUpChallenge; - } + }; async function isPublicKeySignedUp(publicKey) { const signUpChallengeCompleted = diff --git a/src/services/nostrService.js b/src/services/nostrService.js index 412d0ba..51cb0d3 100644 --- a/src/services/nostrService.js +++ b/src/services/nostrService.js @@ -8,105 +8,118 @@ const models = require('../models'); const constants = require('../constants'); const errors = require('../errors'); -async function createNostrChallenge() { - const currentTimestamp = new Date(); - const expiryTimestamp = new Date(currentTimestamp.getTime()); - expiryTimestamp.setSeconds( - expiryTimestamp.getSeconds() + - constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS - ); +class NostrServiceProvider { + constructor() {} - const nostrChallenge = await models.NostrChallengeCreated.create({ - uuid: uuid.v7(), - challenge: crypto.randomBytes(32).toString('hex'), - expires_at: expiryTimestamp.toISOString(), - created_at: currentTimestamp.toISOString(), - }); + provide() { + async function createNostrChallenge() { + const currentTimestamp = new Date(); + const expiryTimestamp = new Date(currentTimestamp.getTime()); + expiryTimestamp.setSeconds( + expiryTimestamp.getSeconds() + + constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS + ); - return nostrChallenge; + const nostrChallenge = await models.NostrChallengeCreated.create({ + uuid: uuid.v7(), + challenge: crypto.randomBytes(32).toString('hex'), + expires_at: expiryTimestamp.toISOString(), + created_at: currentTimestamp.toISOString(), + }); + + return nostrChallenge; + } + + async function getNostrChallenge( + nostrChallengeUuid = null, + challenge = null + ) { + if (nostrChallengeUuid) { + return await models.NostrChallengeCreated.findOne({ + where: { + uuid: nostrChallengeUuid, + }, + }); + } + + if (challenge) { + return await models.NostrChallengeCreated.findOne({ + where: { + challenge, + }, + }); + } + + throw Error('You need to pass a uuid or a challenge.'); + } + + async function verifyNostrChallenge(signedEvent) { + const challengeTag = signedEvent.tags.find( + (tag) => tag[0] === 'challenge' + ); + const challenge = challengeTag[1]; + + if (!(await isNostrChallengeFresh(challenge))) { + throw TimeoutError('Challenge expired, request new one.'); + } + + if (await hasNostrChallengeBeenCompleted(challenge)) { + throw new errors.AlreadyUsedError( + 'Challenge already used, request new one.' + ); + } + + const isSignatureValid = verifyEvent(signedEvent); + if (!isSignatureValid) { + throw new errors.InvalidSignatureError('Signature is not valid.'); + } + + return await models.NostrChallengeCompleted.create({ + uuid: uuid.v7(), + challenge: challenge, + signed_event: signedEvent, + public_key: signedEvent.pubkey, + created_at: new Date().toISOString(), + }); + } + + async function isNostrChallengeFresh(challengeString) { + const nostrChallenge = await models.NostrChallengeCreated.findOne({ + where: { + challenge: challengeString, + expires_at: { + [Op.gt]: new Date(), + }, + }, + }); + + if (nostrChallenge) { + return true; + } + return false; + } + + async function hasNostrChallengeBeenCompleted(challengeString) { + const completedNostrChallenge = + await models.NostrChallengeCompleted.findOne({ + where: { + challenge: challengeString, + }, + }); + + if (completedNostrChallenge) { + return true; + } + return false; + } + + return { + createNostrChallenge, + getNostrChallenge, + verifyNostrChallenge, + isNostrChallengeFresh, + hasNostrChallengeBeenCompleted, + }; + } } - -async function getNostrChallenge(nostrChallengeUuid = null, challenge = null) { - if (nostrChallengeUuid) { - return await models.NostrChallengeCreated.findOne({ - where: { - uuid: nostrChallengeUuid, - }, - }); - } - - if (challenge) { - return await models.NostrChallengeCreated.findOne({ - where: { - challenge, - }, - }); - } - - throw Error('You need to pass a uuid or a challenge.'); -} - -async function verifyNostrChallenge(signedEvent) { - const challengeTag = signedEvent.tags.find((tag) => tag[0] === 'challenge'); - const challenge = challengeTag[1]; - - if (!(await isNostrChallengeFresh(challenge))) { - throw TimeoutError('Challenge expired, request new one.'); - } - - if (await hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError( - 'Challenge already used, request new one.' - ); - } - - const isSignatureValid = verifyEvent(signedEvent); - if (!isSignatureValid) { - throw new errors.InvalidSignatureError('Signature is not valid.'); - } - - return await models.NostrChallengeCompleted.create({ - uuid: uuid.v7(), - challenge: challenge, - signed_event: signedEvent, - public_key: signedEvent.pubkey, - created_at: new Date().toISOString(), - }); -} - -async function isNostrChallengeFresh(challengeString) { - const nostrChallenge = await models.NostrChallengeCreated.findOne({ - where: { - challenge: challengeString, - expires_at: { - [Op.gt]: new Date(), - }, - }, - }); - - if (nostrChallenge) { - return true; - } - return false; -} - -async function hasNostrChallengeBeenCompleted(challengeString) { - const completedNostrChallenge = await models.NostrChallengeCompleted.findOne({ - where: { - challenge: challengeString, - }, - }); - - if (completedNostrChallenge) { - return true; - } - return false; -} - -module.exports = { - createNostrChallenge, - getNostrChallenge, - verifyNostrChallenge, - isNostrChallengeFresh, - hasNostrChallengeBeenCompleted, -}; +module.exports = NostrServiceProvider;