From 1f9bb34853adb1436f1b50529e141a30d7a64f1e Mon Sep 17 00:00:00 2001 From: Cameron Redmore Date: Fri, 25 Apr 2025 13:03:25 +0100 Subject: [PATCH] Changes sessions to be stored in the DB, this ensures that sessions persist after a restart! --- package.json | 1 + pnpm-lock.yaml | 43 ++++++++++++ .../migration.sql | 12 ++++ prisma/schema.prisma | 8 +++ src-server/server.js | 69 +++++++++++-------- 5 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 prisma/migrations/20250425115643_add_session_table/migration.sql diff --git a/package.json b/package.json index ad6070b..95f1ebe 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@google/genai": "^0.9.0", "@prisma/client": "^6.6.0", "@quasar/extras": "^1.16.4", + "@quixo3/prisma-session-store": "^3.1.13", "@simplewebauthn/browser": "^13.1.0", "@simplewebauthn/server": "^13.1.1", "axios": "^1.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c0ac2a..43f92b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@quasar/extras': specifier: ^1.16.4 version: 1.16.17 + '@quixo3/prisma-session-store': + specifier: ^3.1.13 + version: 3.1.13(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.8.3))(typescript@5.8.3))(express-session@1.18.1) '@simplewebauthn/browser': specifier: ^13.1.0 version: 13.1.0 @@ -406,6 +409,10 @@ packages: '@levischuck/tiny-cbor@0.2.11': resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -418,6 +425,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@paralleldrive/cuid2@2.2.2': + resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@peculiar/asn1-android@2.3.16': resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==} @@ -519,6 +529,13 @@ packages: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 vue: ^3.0.0 + '@quixo3/prisma-session-store@3.1.13': + resolution: {integrity: sha512-EAuOvYAaAsQ0OqxkdJG/Qs3cxlT4VV8SFHjtsA3G01uB1b6r7xftX3oeg7mcG0HN/DI1qOqwvy3YFoJ38ls0iA==} + engines: {node: '>=12.0'} + peerDependencies: + '@prisma/client': '>=2.16.1' + express-session: '>=1.17.1' + '@rollup/rollup-android-arm-eabi@4.40.0': resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} cpu: [arm] @@ -2749,6 +2766,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + ts-essentials@9.4.2: resolution: {integrity: sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==} peerDependencies: @@ -2771,6 +2792,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + type-fest@4.40.0: resolution: {integrity: sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==} engines: {node: '>=16'} @@ -3273,6 +3298,8 @@ snapshots: '@levischuck/tiny-cbor@0.2.11': {} + '@noble/hashes@1.8.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3285,6 +3312,10 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@paralleldrive/cuid2@2.2.2': + dependencies: + '@noble/hashes': 1.8.0 + '@peculiar/asn1-android@2.3.16': dependencies: '@peculiar/asn1-schema': 2.3.15 @@ -3435,6 +3466,14 @@ snapshots: vite: 6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0) vue: 3.5.13(typescript@5.8.3) + '@quixo3/prisma-session-store@3.1.13(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.8.3))(typescript@5.8.3))(express-session@1.18.1)': + dependencies: + '@paralleldrive/cuid2': 2.2.2 + '@prisma/client': 6.6.0(prisma@6.6.0(typescript@5.8.3))(typescript@5.8.3) + express-session: 1.18.1 + ts-dedent: 2.2.0 + type-fest: 2.19.0 + '@rollup/rollup-android-arm-eabi@4.40.0': optional: true @@ -5805,6 +5844,8 @@ snapshots: dependencies: typescript: 5.8.3 + ts-dedent@2.2.0: {} + ts-essentials@9.4.2(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 @@ -5821,6 +5862,8 @@ snapshots: type-fest@0.21.3: {} + type-fest@2.19.0: {} + type-fest@4.40.0: {} type-is@1.6.18: diff --git a/prisma/migrations/20250425115643_add_session_table/migration.sql b/prisma/migrations/20250425115643_add_session_table/migration.sql new file mode 100644 index 0000000..84f9be6 --- /dev/null +++ b/prisma/migrations/20250425115643_add_session_table/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "sid" TEXT NOT NULL, + "data" TEXT NOT NULL, + "expires_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sid_key" ON "Session"("sid"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ed23db9..09371f9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -121,3 +121,11 @@ model ChatMessage { createdAt DateTime @default(now()) @map("created_at") thread ChatThread @relation(fields: [threadId], references: [id], onDelete: Cascade) } + +// Add this model for session storage +model Session { + id String @id + sid String @unique + data String + expiresAt DateTime @map("expires_at") +} diff --git a/src-server/server.js b/src-server/server.js index fe40e40..9180c1c 100644 --- a/src-server/server.js +++ b/src-server/server.js @@ -9,9 +9,12 @@ * Make sure to yarn add / npm install (in your project root) * anything you import here (except for express and compression). */ +import dotenv from 'dotenv'; import express from 'express'; import compression from 'compression'; import session from 'express-session'; // Added for session management +import { PrismaSessionStore } from '@quixo3/prisma-session-store'; // Import Prisma session store +import { PrismaClient } from '@prisma/client'; // Import Prisma Client import { v4 as uuidv4 } from 'uuid'; // Added for generating session IDs import apiRoutes from './routes/api.js'; import authRoutes from './routes/auth.js'; // Added for WebAuthn routes @@ -19,6 +22,8 @@ import chatRoutes from './routes/chat.js'; // Added for Chat routes import cron from 'node-cron'; import { generateAndStoreMantisSummary } from './services/mantisSummarizer.js'; +dotenv.config(); + // Define Relying Party details (Update with your actual details) export const rpID = process.env.NODE_ENV === 'production' ? 'your-production-domain.com' : 'localhost'; export const rpName = 'StylePoint'; @@ -27,14 +32,30 @@ export const origin = process.env.NODE_ENV === 'production' ? `https://${rpID}` // In-memory store for challenges (Replace with a persistent store in production) export const challengeStore = new Map(); +const prisma = new PrismaClient(); // Instantiate Prisma Client + const app = express(); +if(!process.env.SESSION_SECRET) +{ + console.error('SESSION_SECRET environment variable is not set. Please set it to a strong secret key.'); + process.exit(1); // Exit the process if the secret is not set +} + // Session middleware configuration app.use(session({ genid: (req) => uuidv4(), // Use UUIDs for session IDs - secret: process.env.SESSION_SECRET || 'a-very-strong-secret-key', // Use an environment variable for the secret + secret: process.env.SESSION_SECRET, // Use an environment variable for the secret resave: false, - saveUninitialized: true, + saveUninitialized: false, // Changed to false as recommended for session stores + store: new PrismaSessionStore( // Use PrismaSessionStore + prisma, + { + checkPeriod: 2 * 60 * 1000, //ms + dbRecordIdIsSessionId: true, + dbRecordIdFunction: undefined, + } + ), cookie: { secure: process.env.NODE_ENV === 'production', // Use secure cookies in production httpOnly: true, @@ -42,36 +63,24 @@ app.use(session({ } })); -// Initialize the database (now synchronous) -try +// Schedule the Mantis summary task +// Run daily at 1:00 AM server time (adjust as needed) +cron.schedule('0 1 * * *', async() => { - console.log('Prisma Client is ready.'); // Log Prisma readiness - - // Schedule the Mantis summary task after DB initialization - // Run daily at 1:00 AM server time (adjust as needed) - cron.schedule('0 1 * * *', async() => + console.log('Running scheduled Mantis summary task...'); + try { - console.log('Running scheduled Mantis summary task...'); - try - { - await generateAndStoreMantisSummary(); - console.log('Scheduled Mantis summary task completed.'); - } - catch (error) - { - console.error('Error running scheduled Mantis summary task:', error); - } - }, { - scheduled: true, - timezone: 'Europe/London' // Example: Set to your server's timezone - }); -} -catch (error) -{ - console.error('Error during server setup:', error); - // Optionally handle the error more gracefully, e.g., prevent server start - process.exit(1); // Exit if setup fails -} + await generateAndStoreMantisSummary(); + console.log('Scheduled Mantis summary task completed.'); + } + catch (error) + { + console.error('Error running scheduled Mantis summary task:', error); + } +}, { + scheduled: true, + timezone: 'Europe/London' // Example: Set to your server's timezone +}); // attackers can use this header to detect apps running Express // and then launch specifically-targeted attacks