From c57cd8afa2b8ca61f6cd834844a351b1b9736e8f Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Fri, 14 Mar 2025 10:53:37 -0400 Subject: [PATCH 1/8] store certs in db --- server/prisma/schema/auth.prisma | 9 ++++ server/server/internal/clients/ca-store.ts | 61 ++++++++++++++++++++++ server/server/plugins/ca.ts | 13 +++-- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/server/prisma/schema/auth.prisma b/server/prisma/schema/auth.prisma index bf862ddb..6ebd4e05 100644 --- a/server/prisma/schema/auth.prisma +++ b/server/prisma/schema/auth.prisma @@ -41,3 +41,12 @@ model APIToken { @@index([token]) } + +model Certificate { + id String @id @default(uuid()) + + privateKey String + certificate String + + blacklisted Boolean @default(false) +} diff --git a/server/server/internal/clients/ca-store.ts b/server/server/internal/clients/ca-store.ts index 483108a2..8a2b4963 100644 --- a/server/server/internal/clients/ca-store.ts +++ b/server/server/internal/clients/ca-store.ts @@ -1,6 +1,7 @@ import path from "path"; import fs from "fs"; import { CertificateBundle } from "./ca"; +import prisma from "../db/database"; export type CertificateStore = { store(name: string, data: CertificateBundle): Promise; @@ -33,3 +34,63 @@ export const fsCertificateStore = (base: string) => { }; return store; }; + +export const dbCertificateStore = () => { + const store: CertificateStore = { + async store(name: string, data: CertificateBundle) { + await prisma.certificate.upsert({ + where: { + id: name, + }, + create: { + id: name, + privateKey: data.priv, + certificate: data.cert, + }, + update: { + privateKey: data.priv, + certificate: data.cert, + }, + }); + }, + async fetch(name: string) { + const result = await prisma.certificate.findUnique({ + where: { + id: name, + }, + select: { + privateKey: true, + certificate: true, + }, + }); + if (result === null) return undefined; + return { + priv: result.privateKey, + cert: result.certificate, + }; + }, + async blacklistCertificate(name: string) { + await prisma.certificate.update({ + where: { + id: name, + }, + data: { + blacklisted: true, + }, + }); + }, + async checkBlacklistCertificate(name: string): Promise { + const result = await prisma.certificate.findUnique({ + where: { + id: name, + }, + select: { + blacklisted: true, + }, + }); + if (result === null) return false; + return result.blacklisted; + }, + }; + return store; +}; diff --git a/server/server/plugins/ca.ts b/server/server/plugins/ca.ts index 000764ae..6a90d643 100644 --- a/server/server/plugins/ca.ts +++ b/server/server/plugins/ca.ts @@ -1,6 +1,9 @@ import { CertificateAuthority } from "../internal/clients/ca"; import fs from "fs"; -import { fsCertificateStore } from "../internal/clients/ca-store"; +import { + dbCertificateStore, + fsCertificateStore, +} from "../internal/clients/ca-store"; let ca: CertificateAuthority | undefined; @@ -10,9 +13,9 @@ export const useCertificateAuthority = () => { }; export default defineNitroPlugin(async (nitro) => { - const basePath = process.env.CLIENT_CERTIFICATES ?? "./certs"; - fs.mkdirSync(basePath, { recursive: true }); - const store = fsCertificateStore(basePath); + // const basePath = process.env.CLIENT_CERTIFICATES ?? "./certs"; + // fs.mkdirSync(basePath, { recursive: true }); + // const store = fsCertificateStore(basePath); - ca = await CertificateAuthority.new(store); + ca = await CertificateAuthority.new(dbCertificateStore()); }); From 032147cad95c527a9634fa64f9c19a02d3eceadb Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:35:12 -0400 Subject: [PATCH 2/8] store sessions in db --- server/prisma/schema/auth.prisma | 5 ++ server/server/internal/session/db.ts | 79 +++++++++++++++++++++++++ server/server/internal/session/index.ts | 6 +- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 server/server/internal/session/db.ts diff --git a/server/prisma/schema/auth.prisma b/server/prisma/schema/auth.prisma index 6ebd4e05..df4a8af0 100644 --- a/server/prisma/schema/auth.prisma +++ b/server/prisma/schema/auth.prisma @@ -50,3 +50,8 @@ model Certificate { blacklisted Boolean @default(false) } + +model Session { + token String @id + data Json +} diff --git a/server/server/internal/session/db.ts b/server/server/internal/session/db.ts new file mode 100644 index 00000000..6a850680 --- /dev/null +++ b/server/server/internal/session/db.ts @@ -0,0 +1,79 @@ +import { json } from "stream/consumers"; +import prisma from "../db/database"; +import { Session, SessionProvider } from "./types"; +import { Prisma } from "@prisma/client"; + +export default function createDBSessionHandler(): SessionProvider { + return { + async setSession(token, data) { + // const strData = JSON.stringify(data); + await prisma.session.upsert({ + where: { + token, + }, + create: { + token, + data, + }, + update: { + data, + }, + }); + return true; + }, + async updateSession(token, key, data) { + const newObj: { [key: string]: any } = {}; + newObj[key] = data; + + const session = await prisma.session.upsert({ + where: { + token, + }, + create: { + token, + data: newObj, + }, + update: {}, + }); + + // if json object and not arrary, update session + if ( + typeof session.data === "object" && + !Array.isArray(session.data) && + session.data !== null + ) { + // means we set it above + if (session.data[key] === data) return true; + + // else we need to set it ourselves + (session.data as Prisma.JsonObject)[key] = data; + await prisma.session.update({ + where: { + token, + }, + data: { + data: session.data, + }, + }); + return true; + } + return false; + }, + async getSession(token: string) { + const result = await prisma.session.findUnique({ + where: { + token, + }, + }); + if (result === null) return undefined; + return result.data as T; + }, + async clearSession(token) { + await prisma.session.delete({ + where: { + token, + }, + }); + }, + }; +} diff --git a/server/server/internal/session/index.ts b/server/server/internal/session/index.ts index 3049b221..d4d10578 100644 --- a/server/server/internal/session/index.ts +++ b/server/server/internal/session/index.ts @@ -6,6 +6,7 @@ import { v4 as uuidv4 } from "uuid"; import moment from "moment"; import { parse as parseCookies } from "cookie-es"; import { MinimumRequestObject } from "~/server/h3"; +import createDBSessionHandler from "./db"; /* This implementation may need work. @@ -24,11 +25,12 @@ export class SessionHandler { constructor() { // Create a new provider - this.sessionProvider = createMemorySessionProvider(); + this.sessionProvider = createDBSessionHandler(); + // this.sessionProvider = createMemorySessionProvider(); } private getSessionToken(request: MinimumRequestObject | undefined) { - if(!request) throw new Error("Native web request not available"); + if (!request) throw new Error("Native web request not available"); const cookieHeader = request.headers.get("Cookie"); if (!cookieHeader) return undefined; const cookies = parseCookies(cookieHeader); From 8b13a7e46b2bac32bed9c9f0e8152b49231e6b89 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:37:31 -0400 Subject: [PATCH 3/8] add migrations to store ca and session in db --- .../migration.sql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 server/prisma/migrations/20250314153636_store_ca_and_session_in_db/migration.sql diff --git a/server/prisma/migrations/20250314153636_store_ca_and_session_in_db/migration.sql b/server/prisma/migrations/20250314153636_store_ca_and_session_in_db/migration.sql new file mode 100644 index 00000000..b342bf4e --- /dev/null +++ b/server/prisma/migrations/20250314153636_store_ca_and_session_in_db/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE "Certificate" ( + "id" TEXT NOT NULL, + "privateKey" TEXT NOT NULL, + "certificate" TEXT NOT NULL, + "blacklisted" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "Certificate_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Session" ( + "token" TEXT NOT NULL, + "data" JSONB NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("token") +); From 2524e42abdd5133aa79d7823dcf79bf924e23fa2 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Tue, 1 Apr 2025 21:32:13 -0400 Subject: [PATCH 4/8] feat: cache for session store in db --- server/package.json | 1 + server/server/internal/session/db.ts | 14 +++++++++++++- server/yarn.lock | 5 +++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/server/package.json b/server/package.json index f0a426e8..fc550ff6 100644 --- a/server/package.json +++ b/server/package.json @@ -24,6 +24,7 @@ "fast-fuzzy": "^1.12.0", "file-type-mime": "^0.4.3", "jdenticon": "^3.3.0", + "lru-cache": "^11.1.0", "micromark": "^4.0.1", "moment": "^2.30.1", "nuxt": "^3.13.2", diff --git a/server/server/internal/session/db.ts b/server/server/internal/session/db.ts index 6a850680..d514791d 100644 --- a/server/server/internal/session/db.ts +++ b/server/server/internal/session/db.ts @@ -1,11 +1,18 @@ -import { json } from "stream/consumers"; +import { LRUCache } from "lru-cache"; import prisma from "../db/database"; import { Session, SessionProvider } from "./types"; import { Prisma } from "@prisma/client"; export default function createDBSessionHandler(): SessionProvider { + const cache = new LRUCache({ + max: 50, // number of items + ttl: 30 * 100, // 30s (in ms) + }); + return { async setSession(token, data) { + cache.set(token, data); + // const strData = JSON.stringify(data); await prisma.session.upsert({ where: { @@ -24,6 +31,7 @@ export default function createDBSessionHandler(): SessionProvider { async updateSession(token, key, data) { const newObj: { [key: string]: any } = {}; newObj[key] = data; + cache.set(token, newObj); const session = await prisma.session.upsert({ where: { @@ -60,6 +68,9 @@ export default function createDBSessionHandler(): SessionProvider { return false; }, async getSession(token: string) { + const cached = cache.get(token); + if (cached !== undefined) return cached as T; + const result = await prisma.session.findUnique({ where: { token, @@ -69,6 +80,7 @@ export default function createDBSessionHandler(): SessionProvider { return result.data as T; }, async clearSession(token) { + cache.delete(token); await prisma.session.delete({ where: { token, diff --git a/server/yarn.lock b/server/yarn.lock index 7817ed92..e221510b 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -4282,6 +4282,11 @@ lru-cache@^10.2.0, lru-cache@^10.4.3: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117" + integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" From d50a2e7f0d40d3d3defb684c62bc63f59fb3a643 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:15:39 -0400 Subject: [PATCH 5/8] fix: add missing dev deps --- server/package.json | 4 +- server/yarn.lock | 112 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/server/package.json b/server/package.json index 3f3ce6bd..7eb5e3b9 100644 --- a/server/package.json +++ b/server/package.json @@ -55,7 +55,9 @@ "nitropack": "2.11.6", "postcss": "^8.4.47", "sass": "^1.79.4", - "tailwindcss": "^4.0.0" + "tailwindcss": "^4.0.0", + "typescript": "^5.8.2", + "vue-tsc": "^2.2.8" }, "optionalDependencies": { "@drop/droplet-darwin-arm64": "^0.7.0", diff --git a/server/yarn.lock b/server/yarn.lock index 38282eb7..316e8fc3 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -2234,6 +2234,27 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz#72b8b705cfce36b00b59af196195146e356500c4" integrity sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A== +"@volar/language-core@2.4.12", "@volar/language-core@~2.4.11": + version "2.4.12" + resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.4.12.tgz#98c8424f8d81a9cad1760a587b1c6db27d05f0cc" + integrity sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA== + dependencies: + "@volar/source-map" "2.4.12" + +"@volar/source-map@2.4.12": + version "2.4.12" + resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.4.12.tgz#7cc8c6b1b134a2215f06c91ad011d94eef81b0ed" + integrity sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw== + +"@volar/typescript@~2.4.11": + version "2.4.12" + resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.4.12.tgz#8c638c23cab89ab131cdcd2d6f2a51768caaa015" + integrity sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g== + dependencies: + "@volar/language-core" "2.4.12" + path-browserify "^1.0.1" + vscode-uri "^3.0.8" + "@vue-macros/common@^1.12.2": version "1.15.0" resolved "https://registry.yarnpkg.com/@vue-macros/common/-/common-1.15.0.tgz#42a97458698beca552a08c89e9680c3b6c5bb71b" @@ -2289,6 +2310,17 @@ estree-walker "^2.0.2" source-map-js "^1.2.0" +"@vue/compiler-core@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05" + integrity sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q== + dependencies: + "@babel/parser" "^7.25.3" + "@vue/shared" "3.5.13" + entities "^4.5.0" + estree-walker "^2.0.2" + source-map-js "^1.2.0" + "@vue/compiler-dom@3.5.12", "@vue/compiler-dom@^3.3.4": version "3.5.12" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz#456d631d11102535b7ee6fd954cf2c93158d0354" @@ -2297,6 +2329,14 @@ "@vue/compiler-core" "3.5.12" "@vue/shared" "3.5.12" +"@vue/compiler-dom@^3.5.0": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58" + integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA== + dependencies: + "@vue/compiler-core" "3.5.13" + "@vue/shared" "3.5.13" + "@vue/compiler-sfc@3.5.12", "@vue/compiler-sfc@^3.5.12", "@vue/compiler-sfc@^3.5.3": version "3.5.12" resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz#6688120d905fcf22f7e44d3cb90f8dabc4dd3cc8" @@ -2320,6 +2360,14 @@ "@vue/compiler-dom" "3.5.12" "@vue/shared" "3.5.12" +"@vue/compiler-vue2@^2.7.16": + version "2.7.16" + resolved "https://registry.yarnpkg.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz#2ba837cbd3f1b33c2bc865fbe1a3b53fb611e249" + integrity sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A== + dependencies: + de-indent "^1.0.2" + he "^1.2.0" + "@vue/devtools-api@^6.6.4": version "6.6.4" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343" @@ -2370,6 +2418,20 @@ dependencies: rfdc "^1.4.1" +"@vue/language-core@2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.2.8.tgz#05befa390399fbd4409bc703ee0520b8ac1b7088" + integrity sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ== + dependencies: + "@volar/language-core" "~2.4.11" + "@vue/compiler-dom" "^3.5.0" + "@vue/compiler-vue2" "^2.7.16" + "@vue/shared" "^3.5.0" + alien-signals "^1.0.3" + minimatch "^9.0.3" + muggle-string "^0.4.1" + path-browserify "^1.0.1" + "@vue/reactivity@3.5.12": version "3.5.12" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.12.tgz#a2815d91842ed7b9e7e7936c851923caf6b6e603" @@ -2408,6 +2470,11 @@ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.12.tgz#f9e45b7f63f2c3f40d84237b1194b7f67de192e3" integrity sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg== +"@vue/shared@3.5.13", "@vue/shared@^3.5.0": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" + integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -2462,6 +2529,11 @@ agent-base@^7.1.2: resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== +alien-signals@^1.0.3: + version "1.0.13" + resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-1.0.13.tgz#8d6db73462f742ee6b89671fbd8c37d0b1727a7e" + integrity sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg== + ansi-colors@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -3367,6 +3439,11 @@ db0@^0.3.1: resolved "https://registry.yarnpkg.com/db0/-/db0-0.3.1.tgz#84366f06cd9a154545b077be5cb955e4ac278314" integrity sha512-3RogPLE2LLq6t4YiFCREyl572aBjkfMvfwPyN51df00TbPbryL3XqBYuJ/j6mgPssPK8AKfYdLxizaO5UG10sA== +de-indent@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" + integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4336,6 +4413,11 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + hookable@^5.5.3: version "5.5.3" resolved "https://registry.yarnpkg.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d" @@ -5451,7 +5533,7 @@ minimatch@^5.0.1, minimatch@^5.1.0: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.4: +minimatch@^9.0.3, minimatch@^9.0.4: version "9.0.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== @@ -5568,6 +5650,11 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +muggle-string@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328" + integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ== + mz@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -6170,6 +6257,11 @@ parseurl@^1.3.2, parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-is-absolute@1.0.1, path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -7732,6 +7824,11 @@ type-level-regexp@~0.1.17: resolved "https://registry.yarnpkg.com/type-level-regexp/-/type-level-regexp-0.1.17.tgz#ec1bf7dd65b85201f9863031d6f023bdefc2410f" integrity sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg== +typescript@^5.8.2: + version "5.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" + integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== + ufo@^1.1.2, ufo@^1.3.2, ufo@^1.4.0, ufo@^1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754" @@ -8190,6 +8287,11 @@ vscode-uri@^3.0.2: resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f" integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== +vscode-uri@^3.0.8: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c" + integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== + vue-bundle-renderer@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/vue-bundle-renderer/-/vue-bundle-renderer-2.1.1.tgz#77147f96d865729828b3a5dff2bccffa8370dde9" @@ -8209,6 +8311,14 @@ vue-router@^4.4.5, vue-router@latest: dependencies: "@vue/devtools-api" "^6.6.4" +vue-tsc@^2.2.8: + version "2.2.8" + resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.2.8.tgz#7c8e1bd9333d25241a7f9988eedf08c65483158c" + integrity sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ== + dependencies: + "@volar/typescript" "~2.4.11" + "@vue/language-core" "2.2.8" + vue3-carousel-nuxt@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/vue3-carousel-nuxt/-/vue3-carousel-nuxt-1.1.5.tgz#c12d521d2ab16da7cbe3778d097262cfca1117c0" From a8d681f99d78966fc66b982731f18ffc6427eb1a Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Thu, 3 Apr 2025 19:03:41 -0400 Subject: [PATCH 6/8] fix: register page validation --- server/middleware/require-user.global.ts | 7 ++++++- server/pages/auth/register.vue | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/server/middleware/require-user.global.ts b/server/middleware/require-user.global.ts index dbe218cc..55532e02 100644 --- a/server/middleware/require-user.global.ts +++ b/server/middleware/require-user.global.ts @@ -1,4 +1,9 @@ -const whitelistedPrefixes = ["/auth/signin", "/register", "/api", "/setup"]; +const whitelistedPrefixes = [ + "/auth/signin", + "/auth/register", + "/api", + "/setup", +]; const requireAdmin = ["/admin"]; export default defineNuxtRouteMiddleware(async (to, from) => { diff --git a/server/pages/auth/register.vue b/server/pages/auth/register.vue index 5574ab44..13165359 100644 --- a/server/pages/auth/register.vue +++ b/server/pages/auth/register.vue @@ -214,7 +214,7 @@ const validEmail = computed( () => !(emailValidator(email.value) instanceof type.errors) ); -const usernameValidator = type("string.lower.preformatted >= 5"); +const usernameValidator = type("string.alphanumeric >= 5").to("string.lower"); const validUsername = computed( () => !(usernameValidator(username.value) instanceof type.errors) ); From 3a50e3ebae227885d4f538e20988439fa607a805 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Thu, 3 Apr 2025 19:15:33 -0400 Subject: [PATCH 7/8] refactor: session handler --- server/nuxt.config.ts | 2 +- server/prisma/schema/auth.prisma | 9 +- server/prisma/schema/user.prisma | 3 +- .../server/api/v1/auth/signin/simple.post.ts | 33 ++-- .../api/v1/client/auth/callback/index.get.ts | 10 +- .../api/v1/client/auth/callback/index.post.ts | 4 +- server/server/internal/acls/index.ts | 12 +- server/server/internal/session/db.ts | 72 +++----- server/server/internal/session/index.ts | 159 +++++++++--------- server/server/internal/session/memory.ts | 22 ++- server/server/internal/session/types.d.ts | 15 +- server/server/plugins/redirect.ts | 4 +- server/server/tasks/cleanup/sessions.ts | 12 ++ 13 files changed, 189 insertions(+), 168 deletions(-) create mode 100644 server/server/tasks/cleanup/sessions.ts diff --git a/server/nuxt.config.ts b/server/nuxt.config.ts index 9b4f5742..edb39c88 100644 --- a/server/nuxt.config.ts +++ b/server/nuxt.config.ts @@ -45,7 +45,7 @@ export default defineNuxtConfig({ }, scheduledTasks: { - "0 * * * *": ["cleanup:invitations"], + "0 * * * *": ["cleanup:invitations", "cleanup:sessions"], }, compressPublicAssets: true, diff --git a/server/prisma/schema/auth.prisma b/server/prisma/schema/auth.prisma index 1f38aa78..e8505f9e 100644 --- a/server/prisma/schema/auth.prisma +++ b/server/prisma/schema/auth.prisma @@ -53,6 +53,11 @@ model Certificate { } model Session { - token String @id - data Json + token String @id + expiresAt DateTime + + userId String + user User? @relation(fields: [userId], references: [id]) + + data Json // misc extra data } diff --git a/server/prisma/schema/user.prisma b/server/prisma/schema/user.prisma index b450163a..2b3905f8 100644 --- a/server/prisma/schema/user.prisma +++ b/server/prisma/schema/user.prisma @@ -14,7 +14,8 @@ model User { collections Collection[] articles Article[] - tokens APIToken[] + tokens APIToken[] + sessions Session[] } model Notification { diff --git a/server/server/api/v1/auth/signin/simple.post.ts b/server/server/api/v1/auth/signin/simple.post.ts index dcc22386..c3358f44 100644 --- a/server/server/api/v1/auth/signin/simple.post.ts +++ b/server/server/api/v1/auth/signin/simple.post.ts @@ -8,24 +8,30 @@ import { } from "~/server/internal/security/simple"; import sessionHandler from "~/server/internal/session"; -export default defineEventHandler(async (h3) => { - const body = await readBody(h3); +const signinValidator = type({ + username: "string", + password: "string", + "rememberMe?": "boolean | undefined", +}); + +export default defineEventHandler(async (h3) => { + const body = signinValidator(await readBody(h3)); + if (body instanceof type.errors) { + // hover out.summary to see validation errors + console.error(body.summary); - const username = body.username; - const password = body.password; - const rememberMe = body.rememberMe ?? false; - if (username === undefined || password === undefined) throw createError({ - statusCode: 403, - statusMessage: "Username or password missing from request.", + statusCode: 400, + statusMessage: body.summary, }); + } const authMek = await prisma.linkedAuthMec.findFirst({ where: { mec: AuthMec.Simple, enabled: true, user: { - username, + username: body.username, }, }, include: { @@ -62,14 +68,14 @@ export default defineEventHandler(async (h3) => { "Invalid password state. Please contact the server administrator.", }); - if (!(await checkHashBcrypt(password, hash))) + if (!(await checkHashBcrypt(body.password, hash))) throw createError({ statusCode: 401, statusMessage: "Invalid username or password.", }); // TODO: send user to forgot password screen or something to force them to change their password to new system - await sessionHandler.setUserId(h3, authMek.userId, rememberMe); + await sessionHandler.signin(h3, authMek.userId, body.rememberMe); return { result: true, userId: authMek.userId }; } @@ -82,13 +88,12 @@ export default defineEventHandler(async (h3) => { "Invalid password state. Please contact the server administrator.", }); - if (!(await checkHashArgon2(password, hash))) + if (!(await checkHashArgon2(body.password, hash))) throw createError({ statusCode: 401, statusMessage: "Invalid username or password.", }); - await sessionHandler.setUserId(h3, authMek.userId, rememberMe); - + await sessionHandler.signin(h3, authMek.userId, body.rememberMe); return { result: true, userId: authMek.userId }; }); diff --git a/server/server/api/v1/client/auth/callback/index.get.ts b/server/server/api/v1/client/auth/callback/index.get.ts index ea8e4b31..d9545347 100644 --- a/server/server/api/v1/client/auth/callback/index.get.ts +++ b/server/server/api/v1/client/auth/callback/index.get.ts @@ -2,8 +2,8 @@ import clientHandler from "~/server/internal/clients/handler"; import sessionHandler from "~/server/internal/session"; export default defineEventHandler(async (h3) => { - const userId = await sessionHandler.getUserId(h3); - if (!userId) throw createError({ statusCode: 403 }); + const user = await sessionHandler.getSession(h3); + if (!user) throw createError({ statusCode: 403 }); const query = getQuery(h3); const providedClientId = query.id?.toString(); @@ -13,16 +13,14 @@ export default defineEventHandler(async (h3) => { statusMessage: "Provide client ID in request params as 'id'", }); - const data = await clientHandler.fetchClientMetadata( - providedClientId - ); + const data = await clientHandler.fetchClientMetadata(providedClientId); if (!data) throw createError({ statusCode: 404, statusMessage: "Request not found.", }); - await clientHandler.attachUserId(providedClientId, userId); + await clientHandler.attachUserId(providedClientId, user.userId); return data; }); diff --git a/server/server/api/v1/client/auth/callback/index.post.ts b/server/server/api/v1/client/auth/callback/index.post.ts index 46eb0b34..b08a57bf 100644 --- a/server/server/api/v1/client/auth/callback/index.post.ts +++ b/server/server/api/v1/client/auth/callback/index.post.ts @@ -2,8 +2,8 @@ import clientHandler from "~/server/internal/clients/handler"; import sessionHandler from "~/server/internal/session"; export default defineEventHandler(async (h3) => { - const userId = await sessionHandler.getUserId(h3); - if (!userId) throw createError({ statusCode: 403 }); + const user = await sessionHandler.getSession(h3); + if (!user) throw createError({ statusCode: 403 }); const body = await readBody(h3); const clientId = await body.id; diff --git a/server/server/internal/acls/index.ts b/server/server/internal/acls/index.ts index 089bb8fb..b75f07ac 100644 --- a/server/server/internal/acls/index.ts +++ b/server/server/internal/acls/index.ts @@ -81,8 +81,8 @@ class ACLManager { if (!request) throw new Error("Native web requests not available - weird deployment?"); // Sessions automatically have all ACLs - const userId = await sessionHandler.getUserId(request); - if (userId) return userId; + const user = await sessionHandler.getSession(request); + if (user) return user.userId; const authorizationToken = this.getAuthorizationToken(request); if (!authorizationToken) return undefined; @@ -116,9 +116,11 @@ class ACLManager { ) { if (!request) throw new Error("Native web requests not available - weird deployment?"); - const userId = await sessionHandler.getUserId(request); - if (userId) { - const user = await prisma.user.findUnique({ where: { id: userId } }); + const userSession = await sessionHandler.getSession(request); + if (userSession) { + const user = await prisma.user.findUnique({ + where: { id: userSession.userId }, + }); if (!user) return false; if (user.admin) return true; return false; diff --git a/server/server/internal/session/db.ts b/server/server/internal/session/db.ts index d514791d..12ce90b3 100644 --- a/server/server/internal/session/db.ts +++ b/server/server/internal/session/db.ts @@ -10,8 +10,8 @@ export default function createDBSessionHandler(): SessionProvider { }); return { - async setSession(token, data) { - cache.set(token, data); + async setSession(token, session) { + cache.set(token, session); // const strData = JSON.stringify(data); await prisma.session.upsert({ @@ -20,54 +20,16 @@ export default function createDBSessionHandler(): SessionProvider { }, create: { token, - data, - }, - update: { - data, + ...session, }, + update: session, }); return true; }, - async updateSession(token, key, data) { - const newObj: { [key: string]: any } = {}; - newObj[key] = data; - cache.set(token, newObj); - - const session = await prisma.session.upsert({ - where: { - token, - }, - create: { - token, - data: newObj, - }, - update: {}, - }); - - // if json object and not arrary, update session - if ( - typeof session.data === "object" && - !Array.isArray(session.data) && - session.data !== null - ) { - // means we set it above - if (session.data[key] === data) return true; - - // else we need to set it ourselves - (session.data as Prisma.JsonObject)[key] = data; - await prisma.session.update({ - where: { - token, - }, - data: { - data: session.data, - }, - }); - return true; - } - return false; + async updateSession(token, data) { + return await this.setSession(token, data); }, - async getSession(token: string) { + async getSession(token: string) { const cached = cache.get(token); if (cached !== undefined) return cached as T; @@ -77,15 +39,31 @@ export default function createDBSessionHandler(): SessionProvider { }, }); if (result === null) return undefined; - return result.data as T; + + // i hate casting + // need to cast to unknown since result.data can be an N deep json object technically + // ts doesn't like that be cast down to the more constraining session type + return result as unknown as T; }, - async clearSession(token) { + async removeSession(token) { cache.delete(token); await prisma.session.delete({ where: { token, }, }); + return true; + }, + async cleanupSessions() { + const now = new Date(); + + await prisma.session.deleteMany({ + where: { + expiresAt: { + lt: now, + }, + }, + }); }, }; } diff --git a/server/server/internal/session/index.ts b/server/server/internal/session/index.ts index d4d10578..cda80f0f 100644 --- a/server/server/internal/session/index.ts +++ b/server/server/internal/session/index.ts @@ -1,8 +1,7 @@ -import { H3Event, Session } from "h3"; +import { H3Event } from "h3"; import createMemorySessionProvider from "./memory"; -import { SessionProvider } from "./types"; -import prisma from "../db/database"; -import { v4 as uuidv4 } from "uuid"; +import { Session, SessionProvider } from "./types"; +import { randomUUID } from "node:crypto"; import moment from "moment"; import { parse as parseCookies } from "cookie-es"; import { MinimumRequestObject } from "~/server/h3"; @@ -14,9 +13,7 @@ This implementation may need work. It exposes an API that should stay static, but there are plenty of opportunities for optimisation/organisation under the hood */ -const userSessionKey = "_userSession"; -const userIdKey = "_userId"; -const dropTokenCookie = "drop-token"; +const dropTokenCookieName = "drop-token"; const normalSessionLength = [31, "days"]; const extendedSessionLength = [1, "year"]; @@ -29,86 +26,94 @@ export class SessionHandler { // this.sessionProvider = createMemorySessionProvider(); } - private getSessionToken(request: MinimumRequestObject | undefined) { + async signin(h3: H3Event, userId: string, rememberMe: boolean = false) { + const expiresAt = this.createExipreAt(rememberMe); + const token = this.createSessionCookie(h3, expiresAt); + return await this.sessionProvider.setSession(token, { + userId, + expiresAt, + data: {}, + }); + } + + /** + * Get a session associated with a request + * @returns session + */ + async getSession(request: MinimumRequestObject) { + const token = this.getSessionToken(request); + if (!token) return undefined; + // TODO: should validate if session is expired or not here, not in application code + + const data = await this.sessionProvider.getSession(token); + return data; + } + + /** + * Signout session associated with request and deauthenticates it + * @param request + * @returns + */ + async signout(h3: H3Event) { + const token = this.getSessionToken(h3); + if (!token) return false; + const res = await this.sessionProvider.removeSession(token); + if (!res) return false; + deleteCookie(h3, dropTokenCookieName); + return true; + } + + async cleanupSessions() { + await this.sessionProvider.cleanupSessions(); + } + + /** + * Update session info + * @param token session token + * @param data new session data + * @returns success or not + */ + private async updateSession(token: string, data: Session) { + return await this.sessionProvider.updateSession(token, data); + } + + // ---------------------- Private API Below ------------------------ + + /** + * Get session token on a request + * @param request + * @returns session token + */ + private getSessionToken( + request: MinimumRequestObject | undefined + ): string | undefined { if (!request) throw new Error("Native web request not available"); const cookieHeader = request.headers.get("Cookie"); if (!cookieHeader) return undefined; const cookies = parseCookies(cookieHeader); - const cookie = cookies[dropTokenCookie]; + const cookie = cookies[dropTokenCookieName]; return cookie; } - private async createSession(h3: H3Event, extend = false) { - const token = uuidv4(); - const expiry = moment().add( - ...(extend ? extendedSessionLength : normalSessionLength) - ); - - setCookie(h3, dropTokenCookie, token, { expires: expiry.toDate() }); - - this.sessionProvider.setSession(dropTokenCookie, {}); + private createExipreAt(rememberMe: boolean) { + return moment() + .add(...(rememberMe ? extendedSessionLength : normalSessionLength)) + .toDate(); + } + /** + * Creates cookie that represents user session + * @param h3 + * @param extend + * @returns + */ + private createSessionCookie(h3: H3Event, expiresAt: Date) { + const token = randomUUID(); + // TODO: we should probably switch to jwts to minimize possibility of someone + // trying to guess a session id (jwts let us sign + encrypt stuff in a std way) + setCookie(h3, dropTokenCookieName, token, { expires: expiresAt }); return token; } - - getDropTokenCookie() { - return dropTokenCookie; - } - - async getSession(request: MinimumRequestObject) { - const token = this.getSessionToken(request); - if (!token) return undefined; - const data = await this.sessionProvider.getSession<{ [userSessionKey]: T }>( - token - ); - if (!data) return undefined; - - return data[userSessionKey]; - } - async setSession(h3: H3Event, data: any, extend = false) { - const token = - this.getSessionToken(h3) ?? (await this.createSession(h3, extend)); - const result = await this.sessionProvider.updateSession( - token, - userSessionKey, - data - ); - - return result; - } - async clearSession(request: MinimumRequestObject) { - const token = this.getSessionToken(request); - if (!token) return false; - await this.sessionProvider.clearSession(token); - return true; - } - - async getUserId(h3: MinimumRequestObject) { - const token = this.getSessionToken(h3); - if (!token) return undefined; - - return await this.getUserIdRaw(token); - } - async getUserIdRaw(token: string) { - const session = await this.sessionProvider.getSession<{ - [userIdKey]: string | undefined; - }>(token); - - if (!session) return undefined; - - return session[userIdKey]; - } - - async setUserId(h3: H3Event, userId: string, extend = false) { - const token = - this.getSessionToken(h3) ?? (await this.createSession(h3, extend)); - - const result = await this.sessionProvider.updateSession( - token, - userIdKey, - userId - ); - } } export const sessionHandler = new SessionHandler(); diff --git a/server/server/internal/session/memory.ts b/server/server/internal/session/memory.ts index f4ad75e6..2b35d487 100644 --- a/server/server/internal/session/memory.ts +++ b/server/server/internal/session/memory.ts @@ -8,15 +8,23 @@ export default function createMemorySessionHandler() { sessions[token] = data; return true; }, - async updateSession(token, key, data) { - sessions[token] = Object.assign({}, sessions[token], { [key]: data }); + async getSession(token: string): Promise { + const session = sessions[token]; + return session ? (session as T) : undefined; // Ensure undefined is returned if session is not found + }, + async updateSession(token, data) { + return this.setSession(token, data); + }, + async removeSession(token) { + delete sessions[token]; return true; }, - async getSession(token) { - return sessions[token] as any; // Wild type cast because we let the user specify types if they want - }, - async clearSession(token) { - delete sessions[token]; + async cleanupSessions() { + const now = new Date(); + for (let token in sessions) { + // if expires at time is before now, the session is expired + if (sessions[token].expiresAt < now) await this.removeSession(token); + } }, }; diff --git a/server/server/internal/session/types.d.ts b/server/server/internal/session/types.d.ts index 5977e7ca..c832b41a 100644 --- a/server/server/internal/session/types.d.ts +++ b/server/server/internal/session/types.d.ts @@ -1,10 +1,17 @@ import { H3Event } from "h3"; -export type Session = { [key: string]: any }; +export type Session = { + userId: string; + expiresAt: Date; + data: { + [key: string]: any; + }; +}; export interface SessionProvider { - setSession: (token: string, data: Session) => Promise; - updateSession: (token: string, key: string, data: any) => Promise; getSession: (token: string) => Promise; - clearSession: (token: string) => Promise; + setSession: (token: string, data: Session) => Promise; + updateSession: (token: string, data: Session) => Promise; + removeSession: (token: string) => Promise; + cleanupSessions: () => Promise; } diff --git a/server/server/plugins/redirect.ts b/server/server/plugins/redirect.ts index a8751535..a531b543 100644 --- a/server/server/plugins/redirect.ts +++ b/server/server/plugins/redirect.ts @@ -14,8 +14,8 @@ export default defineNitroPlugin((nitro) => { switch (error.statusCode) { case 401: case 403: - const userId = await sessionHandler.getUserId(event); - if (userId) break; + const user = await sessionHandler.getSession(event); + if (user) break; return sendRedirect( event, `/auth/signin?redirect=${encodeURIComponent(event.path)}` diff --git a/server/server/tasks/cleanup/sessions.ts b/server/server/tasks/cleanup/sessions.ts new file mode 100644 index 00000000..07cbf8b2 --- /dev/null +++ b/server/server/tasks/cleanup/sessions.ts @@ -0,0 +1,12 @@ +import sessionHandler from "~/server/internal/session"; + +export default defineTask({ + meta: { + name: "cleanup:invitations", + }, + async run({}) { + await sessionHandler.cleanupSessions(); + + return { result: true }; + }, +}); From f8ecc4a10ce13d246f3a28b944380bb45fce9011 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Fri, 4 Apr 2025 10:34:58 +1100 Subject: [PATCH 8/8] chore: apply schema changes to db --- .../migration.sql | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 server/prisma/migrations/20250403233442_apply_store_changes/migration.sql diff --git a/server/prisma/migrations/20250403233442_apply_store_changes/migration.sql b/server/prisma/migrations/20250403233442_apply_store_changes/migration.sql new file mode 100644 index 00000000..2a710995 --- /dev/null +++ b/server/prisma/migrations/20250403233442_apply_store_changes/migration.sql @@ -0,0 +1,13 @@ +/* + Warnings: + + - Added the required column `expiresAt` to the `Session` table without a default value. This is not possible if the table is not empty. + - Added the required column `userId` to the `Session` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Session" ADD COLUMN "expiresAt" TIMESTAMP(3) NOT NULL, +ADD COLUMN "userId" TEXT NOT NULL; + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;