From 74019e97a6640e053e04d10f61ef6788be83f6bb Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Feb 2025 02:20:07 +0100 Subject: [PATCH] session upgrades work --- src/errors.js | 24 ++++++---------- src/middlewares/sessionMiddleware.js | 14 +++++++-- src/models/SessionRelatedToPublickey.js | 27 ++++++++++++++++++ src/routes/apiRoutes.js | 30 ++++++++++++++----- src/routes/webRoutes.js | 24 ++++++++++++++++ src/services/invitesService.js | 29 +++++++++++++------ src/services/sessionService.js | 38 +++++++++++++++++++++---- 7 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 src/models/SessionRelatedToPublickey.js diff --git a/src/errors.js b/src/errors.js index 023ba59..8c9c0b4 100644 --- a/src/errors.js +++ b/src/errors.js @@ -1,10 +1,3 @@ -class ChallengedUsedError extends Error { - constructor(message) { - super(message); - this.name = "ChallengeUsedError"; - } -} - class AlreadyUsedError extends Error { constructor(message) { super(message); @@ -19,13 +12,6 @@ class InvalidSignatureError extends Error { } } -class AppInvitedUsedError extends Error { - constructor(message) { - super(message); - this.name = "AppInvitedUsedError"; - } -} - class NotFoundError extends Error { constructor(message) { super(message); @@ -33,8 +19,16 @@ class NotFoundError extends Error { } } +class ExpiredError extends Error { + constructor(message) { + super(message); + this.name = 'ExpiredError'; + } +} + module.exports = { AlreadyUsedError, InvalidSignatureError, - NotFoundError + NotFoundError, + ExpiredError }; \ No newline at end of file diff --git a/src/middlewares/sessionMiddleware.js b/src/middlewares/sessionMiddleware.js index 42a4895..0fe3d32 100644 --- a/src/middlewares/sessionMiddleware.js +++ b/src/middlewares/sessionMiddleware.js @@ -11,16 +11,24 @@ async function setAndPersistNewSession(res) { async function createSessionMiddleware(req, res, next) { - if (!req.cookies.sessionUuid) { + const sessionUuid = req.cookies.sessionUuid; + + console.log("Running cookie middleware") + + if (!sessionUuid) { + console.log("Found no cookie") await setAndPersistNewSession(res); } - if (req.cookies.sessionUuid) { - if (await sessionService.isSessionExpired(req.cookies.sessionUuid)) { + if (sessionUuid) { + console.log(`Found a cookie ${sessionUuid}`) + if (!(await sessionService.isSessionValid(sessionUuid))) { + console.log("But it's not valid") await setAndPersistNewSession(res); } } + console.log("Moving on...") next(); } diff --git a/src/models/SessionRelatedToPublickey.js b/src/models/SessionRelatedToPublickey.js new file mode 100644 index 0000000..3a218e6 --- /dev/null +++ b/src/models/SessionRelatedToPublickey.js @@ -0,0 +1,27 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../database'); + +const SessionRelatedToPublickey = sequelize.define('SessionRelatedToPublickey', { + uuid: { + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true + }, + session_uuid: { + type: DataTypes.UUID, + allowNull: false, + }, + public_key: { + type: DataTypes.STRING, + allowNull: false + }, + created_at: { + type: DataTypes.DATE, + allowNull: false + } +}, { + tableName: 'session_related_to_public_key' +}); + +module.exports = SessionRelatedToPublickey; \ No newline at end of file diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 384de44..c2d8eeb 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -2,6 +2,7 @@ const express = require('express'); const invitesService = require('../services/invitesService'); const nostrService = require('../services/nostrService'); +const sessionService = require('../services/sessionService'); const { TimeoutError } = require('sequelize'); const errors = require('../errors'); @@ -55,22 +56,37 @@ router.get('/signup/nostr-challenge', async (req, res) => { router.post("/signup/nostr-verify", async (req, res) => { const signedEvent = req.body; + const sessionUuid = req.cookies.sessionUuid; + let completedSignUpChallenge; try { - console.log(`Starting nostr-verify with event: ${signedEvent}`); - const completedSignUpChallenge = await invitesService.verifySignUpChallenge(signedEvent); - console.log(`Finished nostr-verify`); + completedSignUpChallenge = await invitesService.verifySignUpChallenge(signedEvent); } catch (error) { - if (error instanceof TimeoutError) { - console.error('The challenge is outdated.'); + if (error instanceof errors.ExpiredError) { + return res.status(410).json({ + success: false, + message: 'The challenge has expired, request a new one.' + }) } if (error instanceof errors.AlreadyUsedError) { - console.error('The challenge was already used, request a new one.'); + return res.status(410).json({ + success: false, + message: 'The challenge has been used, request a new one.' + }) } if (error instanceof errors.InvalidSignatureError) { - console.error('Signature is not valid.') + return res.status(400).json({ + success: false, + message: 'The challenge signature is not valid.' + }) } } + + await sessionService.relateSessionToPublicKey( + sessionUuid, + completedSignUpChallenge.public_key + ) + return res.status(200).json({ success: true }); }); diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index fbff9d6..4fa5ecd 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -32,6 +32,30 @@ router.get('/invite/:inviteUuid', async (req, res) => { return res.render('invite', { invite }); }); +router.get('/invite/:inviteUuid', async (req, res) => { + const { inviteUuid } = req.params; + + res.cookie('inviteUuid', inviteUuid, { httpOnly: true, maxAge: 86400000 }); + + let invite; + try { + invite = await invitesService.getAppInvite(inviteUuid); + if (!invite) { + return res.status(404).render('error', { message: 'Invite not found.' }); + } + + if (await invitesService.isAppInviteSpent(inviteUuid)) { + return res.status(410).render('invite_spent', { invite }) + } + + } catch (error) { + console.error('Error fetching invite:', error); + return res.status(500).render('error', { message: 'An error occurred' }); + } + + return res.render('invite', { invite }); +}); + router.get('/private', authMiddleware, (req, res) => { res.render('private', {}); }); diff --git a/src/services/invitesService.js b/src/services/invitesService.js index 1bacbd5..b161496 100644 --- a/src/services/invitesService.js +++ b/src/services/invitesService.js @@ -73,22 +73,17 @@ async function verifySignUpChallenge(signedEvent) { null, challenge ); - console.log(`Found this nostr challenge: ${nostrChallenge}`); - const signUpChallenge = await SignUpChallengeCreated.findOne({ where: { nostr_challenge_uuid: nostrChallenge.uuid } }) - console.log(`Found this signup challenge: ${signUpChallenge}`); - if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { throw new errors.AlreadyUsedError("This challenge has already been used."); } - console.log(`I'm gonna verify the nostr challenge`); + const completedNostrChallenge = await nostrService.verifyNostrChallenge(signedEvent); - console.log(`Verified the NostrChallenge: ${completedNostrChallenge}`); const completedSignUpChallenge = await SignUpChallengeCompleted.create( { @@ -99,17 +94,33 @@ async function verifySignUpChallenge(signedEvent) { created_at: new Date().toISOString() } ); - console.log(`Verified the SignUpChallenge: ${completedSignUpChallenge}`); - return completedSignUpChallenge; } +async function isPublicKeySignedUp(publicKey) { + const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne( + { + where: { + public_key: publicKey + } + } + ) + + if (signUpChallengeCompleted) { + return true; + } + + return false; + +} + module.exports = { appInviteExists, getAppInvite, isAppInviteSpent, createAppInvite, createSignUpChallenge, - verifySignUpChallenge + verifySignUpChallenge, + isPublicKeySignedUp }; \ No newline at end of file diff --git a/src/services/sessionService.js b/src/services/sessionService.js index 82641cc..bdd601d 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -1,4 +1,9 @@ +const uuid = require('uuid'); + const SessionCreated = require('../models/SessionCreated'); +const SessionRelatedToPublickey = require('../models/SessionRelatedToPublickey'); + +const invitesService = require('./invitesService'); const constants = require('../constants'); @@ -14,7 +19,7 @@ async function createSession(sessionUuid) { }); } -async function isSessionExpired(sessionUuid) { +async function isSessionValid(sessionUuid) { const currentSession = await SessionCreated.findOne({ where: { 'uuid': sessionUuid @@ -22,15 +27,36 @@ async function isSessionExpired(sessionUuid) { }); if (!currentSession) { - return true; + return false; } if (currentSession.expires_at <= new Date()) { - return true; + return false; } - return false; + return true; } -exports.createSession = createSession; -exports.isSessionExpired = isSessionExpired; +async function relateSessionToPublicKey(sessionUuid, publicKey) { + if (!(await isSessionValid(sessionUuid))) { + throw Error("Session is not valid anymore."); + } + + if (!(await invitesService.isPublicKeySignedUp(publicKey))) { + throw Error("Public key is not signed up."); + } + + return SessionRelatedToPublickey.create({ + 'uuid': uuid.v7(), + session_uuid: sessionUuid, + public_key: publicKey, + created_at: new Date().toISOString() + + }); +} + +module.exports = { + createSession, + isSessionValid, + relateSessionToPublicKey +} \ No newline at end of file