diff --git a/package-lock.json b/package-lock.json index 504ac59..34403a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,14 @@ "dayjs": "^1.11.6", "defu": "^6.1.0", "h3": "^1.0.1", + "node-cron": "^3.0.2", "unstorage": "^1.0.1" }, "devDependencies": { "@nuxt/module-builder": "^0.2.1", "@nuxt/schema": "^3.0.0", "@nuxtjs/eslint-config-typescript": "^12.0.0", + "@types/node-cron": "^3.0.6", "eslint": "8.28", "nuxt": "^3.0.0" } @@ -1325,6 +1327,12 @@ "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", "dev": true }, + "node_modules/@types/node-cron": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.6.tgz", + "integrity": "sha512-Qu9dpjkgj2JmzRmDMVzpt2dFKuJ7wma0mxEvbbgomwkhAdHKT2LpSLYHawzd9OeeP4HsyhmcV9o/xLgJyPNcgw==", + "dev": true + }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", @@ -6526,6 +6534,17 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" }, + "node_modules/node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -9422,6 +9441,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -11012,6 +11039,12 @@ "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", "dev": true }, + "@types/node-cron": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.6.tgz", + "integrity": "sha512-Qu9dpjkgj2JmzRmDMVzpt2dFKuJ7wma0mxEvbbgomwkhAdHKT2LpSLYHawzd9OeeP4HsyhmcV9o/xLgJyPNcgw==", + "dev": true + }, "@types/normalize-package-data": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", @@ -14755,6 +14788,14 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" }, + "node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "requires": { + "uuid": "8.3.2" + } + }, "node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -16858,6 +16899,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 88e65d1..9616b30 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,14 @@ "dayjs": "^1.11.6", "defu": "^6.1.0", "h3": "^1.0.1", + "node-cron": "^3.0.2", "unstorage": "^1.0.1" }, "devDependencies": { "@nuxt/module-builder": "^0.2.1", "@nuxt/schema": "^3.0.0", "@nuxtjs/eslint-config-typescript": "^12.0.0", + "@types/node-cron": "^3.0.6", "eslint": "8.28", "nuxt": "^3.0.0" } diff --git a/src/runtime/server/middleware/session/index.ts b/src/runtime/server/middleware/session/index.ts index 6ee76e1..53dd7c1 100644 --- a/src/runtime/server/middleware/session/index.ts +++ b/src/runtime/server/middleware/session/index.ts @@ -28,7 +28,7 @@ const safeSetCookie = (event: H3Event, name: string, value: string, createdAt: D }) } -const checkSessionExpirationTime = (session: Session, sessionExpiryInSeconds: number) => { +export const checkSessionExpirationTime = (session: Session, sessionExpiryInSeconds: number) => { const now = dayjs() if (now.diff(dayjs(session.createdAt), 'seconds') > sessionExpiryInSeconds) { throw new SessionExpired() diff --git a/src/runtime/server/middleware/session/sessionStorage.ts b/src/runtime/server/middleware/session/sessionStorage.ts index f537fca..cf76385 100644 --- a/src/runtime/server/middleware/session/sessionStorage.ts +++ b/src/runtime/server/middleware/session/sessionStorage.ts @@ -1,8 +1,32 @@ import { createStorage, prefixStorage } from 'unstorage' +import { schedule } from 'node-cron' +import { Session, SessionOptions } from '../../../../types' +import { checkSessionExpirationTime } from './index' +// @ts-ignore import { useRuntimeConfig } from '#imports' // @ts-ignore import sessionDriver from '#session-driver' -const sessionConfig = useRuntimeConfig().session.session -const driver = sessionDriver(sessionConfig.storageOptions.options) -const storage = createStorage({ driver }).mount(sessionConfig.storePrefix, driver) -export const sessionStorage = prefixStorage(storage, sessionConfig.storePrefix) + +const CLEANUP_INTERVAL = '* * * * *' + +const sessionOptions = useRuntimeConfig().session.session as SessionOptions +const driver = sessionDriver(sessionOptions.storageOptions.options) +const storage = createStorage({ driver }).mount(sessionOptions.storePrefix, driver) +const sessionStorage = prefixStorage(storage, sessionOptions.storePrefix) + +// Cleanup expired sessions at a fixed interval +schedule(CLEANUP_INTERVAL, async () => { + const keys = await sessionStorage.getKeys() + keys.forEach(async (key) => { + const session = await sessionStorage.getItem(key) as Session + try { + if (sessionOptions.expiryInSeconds) { + checkSessionExpirationTime(session, sessionOptions.expiryInSeconds) + } + } catch { + await sessionStorage.removeItem(key) + } + }) +}) + +export { sessionStorage }