mirror of
https://github.com/BillyOutlast/drop.git
synced 2026-02-04 08:41:17 +01:00
Merge branch 'Huskydog9988-db-store' into develop
This commit is contained in:
@@ -39,7 +39,7 @@ export default defineNuxtConfig({
|
||||
},
|
||||
|
||||
scheduledTasks: {
|
||||
"0 * * * *": ["cleanup:invitations"],
|
||||
"0 * * * *": ["cleanup:invitations", "cleanup:sessions"],
|
||||
},
|
||||
|
||||
compressPublicAssets: true,
|
||||
|
||||
@@ -28,6 +28,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.15.4",
|
||||
@@ -56,7 +57,9 @@
|
||||
"h3": "^1.13.0",
|
||||
"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"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
||||
"overrides": {
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
@@ -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;
|
||||
@@ -42,3 +42,22 @@ model APIToken {
|
||||
|
||||
@@index([token])
|
||||
}
|
||||
|
||||
model Certificate {
|
||||
id String @id @default(uuid())
|
||||
|
||||
privateKey String
|
||||
certificate String
|
||||
|
||||
blacklisted Boolean @default(false)
|
||||
}
|
||||
|
||||
model Session {
|
||||
token String @id
|
||||
expiresAt DateTime
|
||||
|
||||
userId String
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
|
||||
data Json // misc extra data
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ model User {
|
||||
collections Collection[]
|
||||
articles Article[]
|
||||
|
||||
tokens APIToken[]
|
||||
tokens APIToken[]
|
||||
sessions Session[]
|
||||
|
||||
saves SaveSlot[]
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<void>;
|
||||
@@ -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<boolean> {
|
||||
const result = await prisma.certificate.findUnique({
|
||||
where: {
|
||||
id: name,
|
||||
},
|
||||
select: {
|
||||
blacklisted: true,
|
||||
},
|
||||
});
|
||||
if (result === null) return false;
|
||||
return result.blacklisted;
|
||||
},
|
||||
};
|
||||
return store;
|
||||
};
|
||||
|
||||
69
server/internal/session/db.ts
Normal file
69
server/internal/session/db.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
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<string, Session>({
|
||||
max: 50, // number of items
|
||||
ttl: 30 * 100, // 30s (in ms)
|
||||
});
|
||||
|
||||
return {
|
||||
async setSession(token, session) {
|
||||
cache.set(token, session);
|
||||
|
||||
// const strData = JSON.stringify(data);
|
||||
await prisma.session.upsert({
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
create: {
|
||||
token,
|
||||
...session,
|
||||
},
|
||||
update: session,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
async updateSession(token, data) {
|
||||
return await this.setSession(token, data);
|
||||
},
|
||||
async getSession<T extends Session>(token: string) {
|
||||
const cached = cache.get(token);
|
||||
if (cached !== undefined) return cached as T;
|
||||
|
||||
const result = await prisma.session.findUnique({
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
});
|
||||
if (result === null) return undefined;
|
||||
|
||||
// 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 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,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
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";
|
||||
import createDBSessionHandler from "./db";
|
||||
|
||||
/*
|
||||
This implementation may need work.
|
||||
@@ -13,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"];
|
||||
|
||||
@@ -24,88 +22,97 @@ 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");
|
||||
const cookieHeader = request.headers.get("Cookie");
|
||||
if (!cookieHeader) return undefined;
|
||||
const cookies = parseCookies(cookieHeader);
|
||||
const cookie = cookies[dropTokenCookie];
|
||||
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, {});
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
getDropTokenCookie() {
|
||||
return dropTokenCookie;
|
||||
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<T extends Session>(request: MinimumRequestObject) {
|
||||
const token = this.getSessionToken(request);
|
||||
if (!token) return undefined;
|
||||
const data = await this.sessionProvider.getSession<{ [userSessionKey]: T }>(
|
||||
token
|
||||
);
|
||||
if (!data) return undefined;
|
||||
// TODO: should validate if session is expired or not here, not in application code
|
||||
|
||||
return data[userSessionKey];
|
||||
const data = await this.sessionProvider.getSession<T>(token);
|
||||
return data;
|
||||
}
|
||||
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);
|
||||
/**
|
||||
* Signout session associated with request and deauthenticates it
|
||||
* @param request
|
||||
* @returns
|
||||
*/
|
||||
async signout(h3: H3Event) {
|
||||
const token = this.getSessionToken(h3);
|
||||
if (!token) return false;
|
||||
await this.sessionProvider.clearSession(token);
|
||||
const res = await this.sessionProvider.removeSession(token);
|
||||
if (!res) return false;
|
||||
deleteCookie(h3, dropTokenCookieName);
|
||||
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 cleanupSessions() {
|
||||
await this.sessionProvider.cleanupSessions();
|
||||
}
|
||||
|
||||
async setUserId(h3: H3Event, userId: string, extend = false) {
|
||||
const token =
|
||||
this.getSessionToken(h3) ?? (await this.createSession(h3, extend));
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
const result = await this.sessionProvider.updateSession(
|
||||
token,
|
||||
userIdKey,
|
||||
userId
|
||||
);
|
||||
// ---------------------- 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[dropTokenCookieName];
|
||||
return cookie;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T extends Session>(token: string): Promise<T | undefined> {
|
||||
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);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
15
server/internal/session/types.d.ts
vendored
15
server/internal/session/types.d.ts
vendored
@@ -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<boolean>;
|
||||
updateSession: (token: string, key: string, data: any) => Promise<boolean>;
|
||||
getSession: <T extends Session>(token: string) => Promise<T | undefined>;
|
||||
clearSession: (token: string) => Promise<void>;
|
||||
setSession: (token: string, data: Session) => Promise<boolean>;
|
||||
updateSession: (token: string, data: Session) => Promise<boolean>;
|
||||
removeSession: (token: string) => Promise<boolean>;
|
||||
cleanupSessions: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
|
||||
@@ -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)}`
|
||||
|
||||
12
server/tasks/cleanup/sessions.ts
Normal file
12
server/tasks/cleanup/sessions.ts
Normal file
@@ -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 };
|
||||
},
|
||||
});
|
||||
94
yarn.lock
94
yarn.lock
@@ -1855,6 +1855,27 @@
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz#71a8fc82d4d2e425af304c35bf389506f674d89b"
|
||||
integrity sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==
|
||||
|
||||
"@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.16.1":
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue-macros/common/-/common-1.16.1.tgz#dac7ebc57ded4d6fb19d7f9a83d2973971d9fa65"
|
||||
@@ -1909,7 +1930,7 @@
|
||||
estree-walker "^2.0.2"
|
||||
source-map-js "^1.2.0"
|
||||
|
||||
"@vue/compiler-dom@3.5.13", "@vue/compiler-dom@^3.3.4":
|
||||
"@vue/compiler-dom@3.5.13", "@vue/compiler-dom@^3.3.4", "@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==
|
||||
@@ -1940,6 +1961,14 @@
|
||||
"@vue/compiler-dom" "3.5.13"
|
||||
"@vue/shared" "3.5.13"
|
||||
|
||||
"@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"
|
||||
@@ -1990,6 +2019,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.13":
|
||||
version "3.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f"
|
||||
@@ -2023,7 +2066,7 @@
|
||||
"@vue/compiler-ssr" "3.5.13"
|
||||
"@vue/shared" "3.5.13"
|
||||
|
||||
"@vue/shared@3.5.13", "@vue/shared@^3.5.13":
|
||||
"@vue/shared@3.5.13", "@vue/shared@^3.5.0", "@vue/shared@^3.5.13":
|
||||
version "3.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f"
|
||||
integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==
|
||||
@@ -2068,6 +2111,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-escapes@^4.3.0:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
|
||||
@@ -2900,6 +2948,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"
|
||||
@@ -3787,6 +3840,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"
|
||||
@@ -4447,6 +4505,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"
|
||||
@@ -4778,7 +4841,7 @@ 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==
|
||||
@@ -4878,6 +4941,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"
|
||||
@@ -5335,6 +5403,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"
|
||||
@@ -6774,6 +6847,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"
|
||||
@@ -7189,7 +7267,7 @@ vscode-languageserver@^7.0.0:
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "3.16.0"
|
||||
|
||||
vscode-uri@^3.0.2:
|
||||
vscode-uri@^3.0.2, 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==
|
||||
@@ -7213,6 +7291,14 @@ vue-router@^4.5.0, 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"
|
||||
|
||||
Reference in New Issue
Block a user