From 2a644c3f88353e0b3e6b89233370da7649f87c38 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 24 Oct 2025 10:51:32 -0700 Subject: [PATCH] Working on settings --- server/db/pg/schema/schema.ts | 16 +++++-- server/db/sqlite/schema/schema.ts | 16 +++++-- server/private/lib/logAccessAudit.ts | 31 ++++++++++-- server/private/middlewares/logActionAudit.ts | 37 ++++++++++++++- server/routers/badger/logRequestAudit.ts | 50 ++++++++++++++++++-- 5 files changed, 133 insertions(+), 17 deletions(-) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index dab128f9..601290e0 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -26,7 +26,16 @@ export const orgs = pgTable("orgs", { orgId: varchar("orgId").primaryKey(), name: varchar("name").notNull(), subnet: varchar("subnet"), - createdAt: text("createdAt") + createdAt: text("createdAt"), + settingsLogRetentionDaysRequest: integer("settingsLogRetentionDaysRequest") // where 0 = dont keep logs and -1 = keep forever + .notNull() + .default(15), + settingsLogRetentionDaysAccess: integer("settingsLogRetentionDaysAccess") + .notNull() + .default(15), + settingsLogRetentionDaysAction: integer("settingsLogRetentionDaysAction") + .notNull() + .default(15), }); export const orgDomains = pgTable("orgDomains", { @@ -676,8 +685,9 @@ export const requestAuditLog = pgTable( { id: serial("id").primaryKey(), timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds - orgId: text("orgId") - .references(() => orgs.orgId, { onDelete: "cascade" }), + orgId: text("orgId").references(() => orgs.orgId, { + onDelete: "cascade" + }), action: boolean("action").notNull(), reason: integer("reason").notNull(), actorType: text("actorType"), diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 4ef12a14..ede2e754 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -19,7 +19,16 @@ export const orgs = sqliteTable("orgs", { orgId: text("orgId").primaryKey(), name: text("name").notNull(), subnet: text("subnet"), - createdAt: text("createdAt") + createdAt: text("createdAt"), + settingsLogRetentionDaysRequest: integer("settingsLogRetentionDaysRequest") // where 0 = dont keep logs and -1 = keep forever + .notNull() + .default(15), + settingsLogRetentionDaysAccess: integer("settingsLogRetentionDaysAccess") + .notNull() + .default(15), + settingsLogRetentionDaysAction: integer("settingsLogRetentionDaysAction") + .notNull() + .default(15) }); export const userDomains = sqliteTable("userDomains", { @@ -721,8 +730,9 @@ export const requestAuditLog = sqliteTable( { id: integer("id").primaryKey({ autoIncrement: true }), timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds - orgId: text("orgId") - .references(() => orgs.orgId, { onDelete: "cascade" }), + orgId: text("orgId").references(() => orgs.orgId, { + onDelete: "cascade" + }), action: integer("action", { mode: "boolean" }).notNull(), reason: integer("reason").notNull(), actorType: text("actorType"), diff --git a/server/private/lib/logAccessAudit.ts b/server/private/lib/logAccessAudit.ts index 9b3ae4d5..97aef0ee 100644 --- a/server/private/lib/logAccessAudit.ts +++ b/server/private/lib/logAccessAudit.ts @@ -1,11 +1,32 @@ -import { accessAuditLog, db } from "@server/db"; +import { accessAuditLog, db, orgs } from "@server/db"; import { getCountryCodeForIp } from "@server/lib/geoip"; import logger from "@server/logger"; -import NodeCache from "node-cache"; +import { eq } from "drizzle-orm"; -const cache = new NodeCache({ - stdTTL: 5 // seconds -}); +async function getAccessDays(orgId: string): Promise { + // check cache first + const cached = cache.get(`org_${orgId}_accessDays`); + if (cached !== undefined) { + return cached; + } + + const [org] = await db + .select({ + settingsLogRetentionDaysAction: orgs.settingsLogRetentionDaysAction + }) + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + return 0; + } + + // store the result in cache + cache.set(`org_${orgId}_accessDays`, org.settingsLogRetentionDaysAction); + + return org.settingsLogRetentionDaysAction; +} export async function logAccessAudit(data: { action: boolean; diff --git a/server/private/middlewares/logActionAudit.ts b/server/private/middlewares/logActionAudit.ts index 488aff88..af7759fc 100644 --- a/server/private/middlewares/logActionAudit.ts +++ b/server/private/middlewares/logActionAudit.ts @@ -12,11 +12,39 @@ */ import { ActionsEnum } from "@server/auth/actions"; -import { actionAuditLog, db } from "@server/db"; +import { actionAuditLog, db, orgs } from "@server/db"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import { Request, Response, NextFunction } from "express"; import createHttpError from "http-errors"; +import NodeCache from "node-cache"; +import { eq } from "drizzle-orm"; + +const cache = new NodeCache({ stdTTL: 300 }); // cache for 5 minutes +async function getActionDays(orgId: string): Promise { + // check cache first + const cached = cache.get(`org_${orgId}_actionDays`); + if (cached !== undefined) { + return cached; + } + + const [org] = await db + .select({ + settingsLogRetentionDaysAction: orgs.settingsLogRetentionDaysAction + }) + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + return 0; + } + + // store the result in cache + cache.set(`org_${orgId}_actionDays`, org.settingsLogRetentionDaysAction); + + return org.settingsLogRetentionDaysAction; +} export function logActionAudit(action: ActionsEnum) { return async function ( @@ -57,6 +85,12 @@ export function logActionAudit(action: ActionsEnum) { return next(); } + const retentionDays = await getActionDays(orgId); + if (retentionDays === 0) { + // do not log + return next(); + } + const timestamp = Math.floor(Date.now() / 1000); let metadata = null; @@ -86,3 +120,4 @@ export function logActionAudit(action: ActionsEnum) { } }; } + diff --git a/server/routers/badger/logRequestAudit.ts b/server/routers/badger/logRequestAudit.ts index b9adc161..77b97d96 100644 --- a/server/routers/badger/logRequestAudit.ts +++ b/server/routers/badger/logRequestAudit.ts @@ -1,5 +1,7 @@ -import { db, requestAuditLog } from "@server/db"; +import { db, orgs, requestAuditLog } from "@server/db"; import logger from "@server/logger"; +import { eq } from "drizzle-orm"; +import NodeCache from "node-cache"; /** @@ -22,6 +24,32 @@ Reasons: */ +const cache = new NodeCache({ stdTTL: 300 }); // cache for 5 minutes +async function getRetentionDays(orgId: string): Promise { + // check cache first + const cached = cache.get(`org_${orgId}_retentionDays`); + if (cached !== undefined) { + return cached; + } + + const [org] = await db + .select({ + settingsLogRetentionDaysRequest: orgs.settingsLogRetentionDaysRequest + }) + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + return 0; + } + + // store the result in cache + cache.set(`org_${orgId}_retentionDays`, org.settingsLogRetentionDaysRequest); + + return org.settingsLogRetentionDaysRequest; +} + export async function logRequestAudit( data: { action: boolean; @@ -29,8 +57,8 @@ export async function logRequestAudit( resourceId?: number; orgId?: string; location?: string; - user?: { username: string; userId: string; }; - apiKey?: { name: string | null; apiKeyId: string; }; + user?: { username: string; userId: string }; + apiKey?: { name: string | null; apiKeyId: string }; metadata?: any; // userAgent?: string; }, @@ -43,11 +71,20 @@ export async function logRequestAudit( tls: boolean; sessions?: Record; headers?: Record; - query?: Record; + query?: Record; requestIp?: string; } ) { try { + + if (data.orgId) { + const retentionDays = await getRetentionDays(data.orgId); + if (retentionDays === 0) { + // do not log + return; + } + } + let actorType: string | undefined; let actor: string | undefined; let actorId: string | undefined; @@ -79,7 +116,10 @@ export async function logRequestAudit( const clientIp = body.requestIp ? (() => { - if (body.requestIp.startsWith("[") && body.requestIp.includes("]")) { + if ( + body.requestIp.startsWith("[") && + body.requestIp.includes("]") + ) { // if brackets are found, extract the IPv6 address from between the brackets const ipv6Match = body.requestIp.match(/\[(.*?)\]/); if (ipv6Match) {