From 1f50bc3752d355c07f80ac9e7f90c09ccdb7f6f0 Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 19 Oct 2025 21:53:00 -0700 Subject: [PATCH] Add logActionAudit and query endpoint --- server/db/pg/schema/privateSchema.ts | 25 ++- server/db/pg/schema/schema.ts | 29 +++- server/db/sqlite/schema/privateSchema.ts | 22 +++ server/db/sqlite/schema/schema.ts | 28 +++- server/middlewares/logActionAudit.ts | 12 ++ server/private/routers/auditLogs/index.ts | 0 .../routers/auditLogs/queryActionAuditLog.ts | 147 ++++++++++++++++++ server/private/routers/external.ts | 34 ++-- server/private/routers/integration.ts | 7 +- server/routers/auditLogs/types.ts | 14 ++ server/routers/external.ts | 135 ++++++++++------ server/routers/integration.ts | 143 +++++++++++------ 12 files changed, 488 insertions(+), 108 deletions(-) create mode 100644 server/middlewares/logActionAudit.ts create mode 100644 server/private/routers/auditLogs/index.ts create mode 100644 server/private/routers/auditLogs/queryActionAuditLog.ts create mode 100644 server/routers/auditLogs/types.ts diff --git a/server/db/pg/schema/privateSchema.ts b/server/db/pg/schema/privateSchema.ts index f33b88e1..d2bb8841 100644 --- a/server/db/pg/schema/privateSchema.ts +++ b/server/db/pg/schema/privateSchema.ts @@ -230,6 +230,28 @@ export const actionAuditLog = pgTable("actionAuditLog", { index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp) ])); +export const identityAuditLog = pgTable("identityAuditLog", { + id: serial("id").primaryKey(), + timestamp: bigint("timestamp", { mode: "number" }).notNull(), // this is EPOCH time in seconds + orgId: varchar("orgId") + .notNull() + .references(() => orgs.orgId, { onDelete: "cascade" }), + actorType: varchar("actorType", { length: 50 }).notNull(), + actor: varchar("actor", { length: 255 }).notNull(), + actorId: varchar("actorId", { length: 255 }).notNull(), + resourceId: integer("resourceId"), + ip: varchar("ip", { length: 45 }).notNull(), + type: varchar("type", { length: 100 }).notNull(), + action: varchar("action", { length: 100 }).notNull(), + location: text("location"), + path: text("path"), + userAgent: text("userAgent"), + metadata: text("details") +}, (table) => ([ + index("idx_identityAuditLog_timestamp").on(table.timestamp), + index("idx_identityAuditLog_org_timestamp").on(table.orgId, table.timestamp) +])); + export type Limit = InferSelectModel; export type Account = InferSelectModel; export type Certificate = InferSelectModel; @@ -247,4 +269,5 @@ export type RemoteExitNodeSession = InferSelectModel< >; export type ExitNodeOrg = InferSelectModel; export type LoginPage = InferSelectModel; -export type ActionAuditLog = InferSelectModel; \ No newline at end of file +export type ActionAuditLog = InferSelectModel; +export type IdentityAuditLog = InferSelectModel; \ No newline at end of file diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 4bed23f8..2dc937d9 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -6,7 +6,8 @@ import { integer, bigint, real, - text + text, + index } from "drizzle-orm/pg-core"; import { InferSelectModel } from "drizzle-orm"; import { randomUUID } from "crypto"; @@ -671,6 +672,28 @@ export const setupTokens = pgTable("setupTokens", { dateUsed: varchar("dateUsed") }); +export const requestAuditLog = pgTable("requestAuditLog", { + id: serial("id").primaryKey(), + timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds + orgId: varchar("orgId") + .notNull() + .references(() => orgs.orgId, { onDelete: "cascade" }), + actorType: varchar("actorType").notNull(), + actor: varchar("actor").notNull(), + actorId: varchar("actorId").notNull(), + resourceId: integer("resourceId"), + ip: varchar("ip").notNull(), + type: varchar("type").notNull(), + action: varchar("action").notNull(), + event: varchar("event").notNull(), + location: varchar("location"), + userAgent: varchar("userAgent"), + metadata: text("details") +}, (table) => ([ + index("idx_actionAuditLog_timestamp").on(table.timestamp), + index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp) +])); + export type Org = InferSelectModel; export type User = InferSelectModel; export type Site = InferSelectModel; @@ -722,3 +745,7 @@ export type SetupToken = InferSelectModel; export type HostMeta = InferSelectModel; export type TargetHealthCheck = InferSelectModel; export type IdpOidcConfig = InferSelectModel; +export type LicenseKey = InferSelectModel; +export type SecurityKey = InferSelectModel; +export type WebauthnChallenge = InferSelectModel; +export type RequestAuditLog = InferSelectModel; \ No newline at end of file diff --git a/server/db/sqlite/schema/privateSchema.ts b/server/db/sqlite/schema/privateSchema.ts index cbf61cc1..bbd0aa3e 100644 --- a/server/db/sqlite/schema/privateSchema.ts +++ b/server/db/sqlite/schema/privateSchema.ts @@ -225,6 +225,28 @@ export const actionAuditLog = sqliteTable("actionAuditLog", { index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp) ])); +export const identityAuditLog = sqliteTable("identityAuditLog", { + id: integer("id").primaryKey({ autoIncrement: true }), + timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds + orgId: text("orgId") + .notNull() + .references(() => orgs.orgId, { onDelete: "cascade" }), + actorType: text("actorType").notNull(), + actor: text("actor").notNull(), + actorId: text("actorId").notNull(), + resourceId: integer("resourceId"), + ip: text("ip").notNull(), + type: text("type").notNull(), + action: text("action").notNull(), + location: text("location"), + path: text("path"), + userAgent: text("userAgent"), + metadata: text("details") +}, (table) => ([ + index("idx_actionAuditLog_timestamp").on(table.timestamp), + index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp) +])); + export type Limit = InferSelectModel; export type Account = InferSelectModel; export type Certificate = InferSelectModel; diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 3d6c6b0d..59b9bc2e 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -1,6 +1,6 @@ import { randomUUID } from "crypto"; import { InferSelectModel } from "drizzle-orm"; -import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; +import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; export const domains = sqliteTable("domains", { domainId: text("domainId").primaryKey(), @@ -710,6 +710,28 @@ export const idpOrg = sqliteTable("idpOrg", { orgMapping: text("orgMapping") }); +export const requestAuditLog = sqliteTable("requestAuditLog", { + id: integer("id").primaryKey({ autoIncrement: true }), + timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds + orgId: text("orgId") + .notNull() + .references(() => orgs.orgId, { onDelete: "cascade" }), + actorType: text("actorType").notNull(), + actor: text("actor").notNull(), + actorId: text("actorId").notNull(), + resourceId: integer("resourceId"), + ip: text("ip").notNull(), + type: text("type").notNull(), + action: text("action").notNull(), + event: text("event").notNull(), + location: text("location"), + userAgent: text("userAgent"), + metadata: text("details") +}, (table) => ([ + index("idx_actionAuditLog_timestamp").on(table.timestamp), + index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp) +])); + export type Org = InferSelectModel; export type User = InferSelectModel; export type Site = InferSelectModel; @@ -761,3 +783,7 @@ export type SetupToken = InferSelectModel; export type HostMeta = InferSelectModel; export type TargetHealthCheck = InferSelectModel; export type IdpOidcConfig = InferSelectModel; +export type LicenseKey = InferSelectModel; +export type SecurityKey = InferSelectModel; +export type WebauthnChallenge = InferSelectModel; +export type RequestAuditLog = InferSelectModel; \ No newline at end of file diff --git a/server/middlewares/logActionAudit.ts b/server/middlewares/logActionAudit.ts new file mode 100644 index 00000000..a1521883 --- /dev/null +++ b/server/middlewares/logActionAudit.ts @@ -0,0 +1,12 @@ +import { ActionsEnum } from "@server/auth/actions"; +import { Request, Response, NextFunction } from "express"; + +export function logActionAudit(action: ActionsEnum) { + return async function ( + req: Request, + res: Response, + next: NextFunction + ): Promise { + next(); + }; +} diff --git a/server/private/routers/auditLogs/index.ts b/server/private/routers/auditLogs/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/server/private/routers/auditLogs/queryActionAuditLog.ts b/server/private/routers/auditLogs/queryActionAuditLog.ts new file mode 100644 index 00000000..4c10a3f0 --- /dev/null +++ b/server/private/routers/auditLogs/queryActionAuditLog.ts @@ -0,0 +1,147 @@ +import { actionAuditLog, db } from "@server/db"; +import { registry } from "@server/openApi"; +import { NextFunction } from "express"; +import { Request, Response } from "express"; +import { eq, gt, lt, and, count } from "drizzle-orm"; +import { OpenAPITags } from "@server/openApi"; +import { z } from "zod"; +import createHttpError from "http-errors"; +import HttpCode from "@server/types/HttpCode"; +import { fromError } from "zod-validation-error"; +import { QueryActionAuditLogResponse } from "@server/routers/auditLogs/types"; +import response from "@server/lib/response"; +import logger from "@server/logger"; + +export const queryAccessAuditLogsQuery = z.object({ + // iso string just validate its a parseable date + timeStart: z + .string() + .refine((val) => !isNaN(Date.parse(val)), { + message: "timeStart must be a valid ISO date string" + }) + .transform((val) => Math.floor(new Date(val).getTime() / 1000)), + timeEnd: z + .string() + .refine((val) => !isNaN(Date.parse(val)), { + message: "timeEnd must be a valid ISO date string" + }) + .transform((val) => Math.floor(new Date(val).getTime() / 1000)) + .optional() + .default(new Date().toISOString()), + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.number().int().positive()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.number().int().nonnegative()) +}); + +export const queryAccessAuditLogsParams = z.object({ + orgId: z.string() +}); + +function querySites(timeStart: number, timeEnd: number, orgId: string) { + return db + .select({ + orgId: actionAuditLog.orgId, + action: actionAuditLog.action, + actorType: actionAuditLog.actorType, + timestamp: actionAuditLog.timestamp, + actor: actionAuditLog.actor + }) + .from(actionAuditLog) + .where( + and( + gt(actionAuditLog.timestamp, timeStart), + lt(actionAuditLog.timestamp, timeEnd), + eq(actionAuditLog.orgId, orgId) + ) + ) + .orderBy(actionAuditLog.timestamp); +} + +registry.registerPath({ + method: "get", + path: "/org/{orgId}/logs/action", + description: "Query the action audit log for an organization", + tags: [OpenAPITags.Org], + request: { + query: queryAccessAuditLogsQuery, + params: queryAccessAuditLogsParams + }, + responses: {} +}); + +export async function queryAccessAuditLogs( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedQuery.error) + ) + ); + } + const { timeStart, timeEnd, limit, offset } = parsedQuery.data; + + const parsedParams = queryAccessAuditLogsParams.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error) + ) + ); + } + const { orgId } = parsedParams.data; + + const baseQuery = querySites(timeStart, timeEnd, orgId); + + const log = await baseQuery.limit(limit).offset(offset); + + const countQuery = db + .select({ count: count() }) + .from(actionAuditLog) + .where( + and( + gt(actionAuditLog.timestamp, timeStart), + lt(actionAuditLog.timestamp, timeEnd), + eq(actionAuditLog.orgId, orgId) + ) + ); + + const totalCountResult = await countQuery; + const totalCount = totalCountResult[0].count; + + return response(res, { + data: { + log: log, + pagination: { + total: totalCount, + limit, + offset + } + }, + success: true, + error: false, + message: "Action audit logs retrieved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index 74cd6b0c..a7c927f2 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -31,6 +31,7 @@ import { } from "@server/middlewares"; import { ActionsEnum } from "@server/auth/actions"; import { + logActionAudit, verifyCertificateAccess, verifyIdpAccess, verifyLoginPageAccess, @@ -72,7 +73,8 @@ authenticated.put( verifyValidLicense, verifyOrgAccess, verifyUserHasAction(ActionsEnum.createIdp), - orgIdp.createOrgOidcIdp + orgIdp.createOrgOidcIdp, + logActionAudit(ActionsEnum.createIdp) ); authenticated.post( @@ -81,7 +83,8 @@ authenticated.post( verifyOrgAccess, verifyIdpAccess, verifyUserHasAction(ActionsEnum.updateIdp), - orgIdp.updateOrgOidcIdp + orgIdp.updateOrgOidcIdp, + logActionAudit(ActionsEnum.updateIdp) ); authenticated.delete( @@ -90,7 +93,8 @@ authenticated.delete( verifyOrgAccess, verifyIdpAccess, verifyUserHasAction(ActionsEnum.deleteIdp), - orgIdp.deleteOrgIdp + orgIdp.deleteOrgIdp, + logActionAudit(ActionsEnum.deleteIdp) ); authenticated.get( @@ -127,7 +131,8 @@ authenticated.post( verifyOrgAccess, verifyCertificateAccess, verifyUserHasAction(ActionsEnum.restartCertificate), - certificates.restartCertificate + certificates.restartCertificate, + logActionAudit(ActionsEnum.restartCertificate) ); if (build === "saas") { @@ -152,14 +157,16 @@ if (build === "saas") { "/org/:orgId/billing/create-checkout-session", verifyOrgAccess, verifyUserHasAction(ActionsEnum.billing), - billing.createCheckoutSession + billing.createCheckoutSession, + logActionAudit(ActionsEnum.billing) ); authenticated.post( "/org/:orgId/billing/create-portal-session", verifyOrgAccess, verifyUserHasAction(ActionsEnum.billing), - billing.createPortalSession + billing.createPortalSession, + logActionAudit(ActionsEnum.billing) ); authenticated.get( @@ -206,7 +213,8 @@ authenticated.put( verifyValidLicense, verifyOrgAccess, verifyUserHasAction(ActionsEnum.createRemoteExitNode), - remoteExitNode.createRemoteExitNode + remoteExitNode.createRemoteExitNode, + logActionAudit(ActionsEnum.createRemoteExitNode) ); authenticated.get( @@ -240,7 +248,8 @@ authenticated.delete( verifyOrgAccess, verifyRemoteExitNodeAccess, verifyUserHasAction(ActionsEnum.deleteRemoteExitNode), - remoteExitNode.deleteRemoteExitNode + remoteExitNode.deleteRemoteExitNode, + logActionAudit(ActionsEnum.deleteRemoteExitNode) ); authenticated.put( @@ -248,7 +257,8 @@ authenticated.put( verifyValidLicense, verifyOrgAccess, verifyUserHasAction(ActionsEnum.createLoginPage), - loginPage.createLoginPage + loginPage.createLoginPage, + logActionAudit(ActionsEnum.createLoginPage) ); authenticated.post( @@ -257,7 +267,8 @@ authenticated.post( verifyOrgAccess, verifyLoginPageAccess, verifyUserHasAction(ActionsEnum.updateLoginPage), - loginPage.updateLoginPage + loginPage.updateLoginPage, + logActionAudit(ActionsEnum.updateLoginPage) ); authenticated.delete( @@ -266,7 +277,8 @@ authenticated.delete( verifyOrgAccess, verifyLoginPageAccess, verifyUserHasAction(ActionsEnum.deleteLoginPage), - loginPage.deleteLoginPage + loginPage.deleteLoginPage, + logActionAudit(ActionsEnum.deleteLoginPage) ); authenticated.get( diff --git a/server/private/routers/integration.ts b/server/private/routers/integration.ts index d767424a..00bc167a 100644 --- a/server/private/routers/integration.ts +++ b/server/private/routers/integration.ts @@ -23,6 +23,7 @@ import { import { ActionsEnum } from "@server/auth/actions"; import { unauthenticated as ua, authenticated as a } from "@server/routers/integration"; +import { logActionAudit } from "#private/middlewares"; export const unauthenticated = ua; export const authenticated = a; @@ -31,12 +32,14 @@ authenticated.post( `/org/:orgId/send-usage-notification`, verifyApiKeyIsRoot, // We are the only ones who can use root key so its fine verifyApiKeyHasAction(ActionsEnum.sendUsageNotification), - org.sendUsageNotification + org.sendUsageNotification, + logActionAudit(ActionsEnum.sendUsageNotification) ); authenticated.delete( "/idp/:idpId", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.deleteIdp), - orgIdp.deleteOrgIdp + orgIdp.deleteOrgIdp, + logActionAudit(ActionsEnum.deleteIdp) ); \ No newline at end of file diff --git a/server/routers/auditLogs/types.ts b/server/routers/auditLogs/types.ts new file mode 100644 index 00000000..4530de79 --- /dev/null +++ b/server/routers/auditLogs/types.ts @@ -0,0 +1,14 @@ +export type QueryActionAuditLogResponse = { + log: { + orgId: string; + action: string; + actorType: string; + timestamp: number; + actor: string; + }[]; + pagination: { + total: number; + limit: number; + offset: number; + }; +}; diff --git a/server/routers/external.ts b/server/routers/external.ts index 8bd72f62..55b98242 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -44,6 +44,7 @@ import rateLimit, { ipKeyGenerator } from "express-rate-limit"; import createHttpError from "http-errors"; import { build } from "@server/build"; import { createStore } from "#dynamic/lib/rateLimitStore"; +import { logActionAudit } from "#dynamic/middlewares"; // Root routes export const unauthenticated = Router(); @@ -75,7 +76,8 @@ authenticated.post( "/org/:orgId", verifyOrgAccess, verifyUserHasAction(ActionsEnum.updateOrg), - org.updateOrg + org.updateOrg, + logActionAudit(ActionsEnum.updateOrg) ); if (build !== "saas") { @@ -84,7 +86,8 @@ if (build !== "saas") { verifyOrgAccess, verifyUserIsOrgOwner, verifyUserHasAction(ActionsEnum.deleteOrg), - org.deleteOrg + org.deleteOrg, + logActionAudit(ActionsEnum.deleteOrg) ); } @@ -92,7 +95,8 @@ authenticated.put( "/org/:orgId/site", verifyOrgAccess, verifyUserHasAction(ActionsEnum.createSite), - site.createSite + site.createSite, + logActionAudit(ActionsEnum.createSite) ); authenticated.get( "/org/:orgId/sites", @@ -149,7 +153,8 @@ authenticated.put( verifyClientsEnabled, verifyOrgAccess, verifyUserHasAction(ActionsEnum.createClient), - client.createClient + client.createClient, + logActionAudit(ActionsEnum.createClient) ); authenticated.delete( @@ -157,7 +162,8 @@ authenticated.delete( verifyClientsEnabled, verifyClientAccess, verifyUserHasAction(ActionsEnum.deleteClient), - client.deleteClient + client.deleteClient, + logActionAudit(ActionsEnum.deleteClient) ); authenticated.post( @@ -165,7 +171,8 @@ authenticated.post( verifyClientsEnabled, verifyClientAccess, // this will check if the user has access to the client verifyUserHasAction(ActionsEnum.updateClient), // this will check if the user has permission to update the client - client.updateClient + client.updateClient, + logActionAudit(ActionsEnum.updateClient) ); // authenticated.get( @@ -178,15 +185,18 @@ authenticated.post( "/site/:siteId", verifySiteAccess, verifyUserHasAction(ActionsEnum.updateSite), - site.updateSite + site.updateSite, + logActionAudit(ActionsEnum.updateSite) ); authenticated.delete( "/site/:siteId", verifySiteAccess, verifyUserHasAction(ActionsEnum.deleteSite), - site.deleteSite + site.deleteSite, + logActionAudit(ActionsEnum.deleteSite) ); +// TODO: BREAK OUT THESE ACTIONS SO THEY ARE NOT ALL "getSite" authenticated.get( "/site/:siteId/docker/status", verifySiteAccess, @@ -203,13 +213,15 @@ authenticated.post( "/site/:siteId/docker/check", verifySiteAccess, verifyUserHasAction(ActionsEnum.getSite), - site.checkDockerSocket + site.checkDockerSocket, + // logActionAudit(ActionsEnum.getSite) ); authenticated.post( "/site/:siteId/docker/trigger", verifySiteAccess, verifyUserHasAction(ActionsEnum.getSite), - site.triggerFetchContainers + site.triggerFetchContainers, + // logActionAudit(ActionsEnum.getSite) ); authenticated.get( "/site/:siteId/docker/containers", @@ -224,7 +236,8 @@ authenticated.put( verifyOrgAccess, verifySiteAccess, verifyUserHasAction(ActionsEnum.createSiteResource), - siteResource.createSiteResource + siteResource.createSiteResource, + logActionAudit(ActionsEnum.createSiteResource) ); authenticated.get( @@ -257,7 +270,8 @@ authenticated.post( verifySiteAccess, verifySiteResourceAccess, verifyUserHasAction(ActionsEnum.updateSiteResource), - siteResource.updateSiteResource + siteResource.updateSiteResource, + logActionAudit(ActionsEnum.updateSiteResource) ); authenticated.delete( @@ -266,14 +280,16 @@ authenticated.delete( verifySiteAccess, verifySiteResourceAccess, verifyUserHasAction(ActionsEnum.deleteSiteResource), - siteResource.deleteSiteResource + siteResource.deleteSiteResource, + logActionAudit(ActionsEnum.deleteSiteResource) ); authenticated.put( "/org/:orgId/resource", verifyOrgAccess, verifyUserHasAction(ActionsEnum.createResource), - resource.createResource + resource.createResource, + logActionAudit(ActionsEnum.createResource) ); authenticated.get( @@ -313,15 +329,18 @@ authenticated.delete( "/org/:orgId/invitations/:inviteId", verifyOrgAccess, verifyUserHasAction(ActionsEnum.removeInvitation), - user.removeInvitation + user.removeInvitation, + logActionAudit(ActionsEnum.removeInvitation) ); authenticated.post( "/org/:orgId/create-invite", verifyOrgAccess, verifyUserHasAction(ActionsEnum.inviteUser), - user.inviteUser + user.inviteUser, + logActionAudit(ActionsEnum.inviteUser) ); // maybe make this /invite/create instead + unauthenticated.post("/invite/accept", user.acceptInvite); // this is supposed to be unauthenticated authenticated.get( @@ -354,20 +373,23 @@ authenticated.post( "/resource/:resourceId", verifyResourceAccess, verifyUserHasAction(ActionsEnum.updateResource), - resource.updateResource + resource.updateResource, + logActionAudit(ActionsEnum.updateResource) ); authenticated.delete( "/resource/:resourceId", verifyResourceAccess, verifyUserHasAction(ActionsEnum.deleteResource), - resource.deleteResource + resource.deleteResource, + logActionAudit(ActionsEnum.deleteResource) ); authenticated.put( "/resource/:resourceId/target", verifyResourceAccess, verifyUserHasAction(ActionsEnum.createTarget), - target.createTarget + target.createTarget, + logActionAudit(ActionsEnum.createTarget) ); authenticated.get( "/resource/:resourceId/targets", @@ -380,7 +402,8 @@ authenticated.put( "/resource/:resourceId/rule", verifyResourceAccess, verifyUserHasAction(ActionsEnum.createResourceRule), - resource.createResourceRule + resource.createResourceRule, + logActionAudit(ActionsEnum.createResourceRule) ); authenticated.get( "/resource/:resourceId/rules", @@ -392,13 +415,15 @@ authenticated.post( "/resource/:resourceId/rule/:ruleId", verifyResourceAccess, verifyUserHasAction(ActionsEnum.updateResourceRule), - resource.updateResourceRule + resource.updateResourceRule, + logActionAudit(ActionsEnum.updateResourceRule) ); authenticated.delete( "/resource/:resourceId/rule/:ruleId", verifyResourceAccess, verifyUserHasAction(ActionsEnum.deleteResourceRule), - resource.deleteResourceRule + resource.deleteResourceRule, + logActionAudit(ActionsEnum.deleteResourceRule) ); authenticated.get( @@ -411,20 +436,23 @@ authenticated.post( "/target/:targetId", verifyTargetAccess, verifyUserHasAction(ActionsEnum.updateTarget), - target.updateTarget + target.updateTarget, + logActionAudit(ActionsEnum.updateTarget) ); authenticated.delete( "/target/:targetId", verifyTargetAccess, verifyUserHasAction(ActionsEnum.deleteTarget), - target.deleteTarget + target.deleteTarget, + logActionAudit(ActionsEnum.deleteTarget) ); authenticated.put( "/org/:orgId/role", verifyOrgAccess, verifyUserHasAction(ActionsEnum.createRole), - role.createRole + role.createRole, + logActionAudit(ActionsEnum.createRole) ); authenticated.get( "/org/:orgId/roles", @@ -449,14 +477,16 @@ authenticated.delete( "/role/:roleId", verifyRoleAccess, verifyUserHasAction(ActionsEnum.deleteRole), - role.deleteRole + role.deleteRole, + logActionAudit(ActionsEnum.deleteRole) ); authenticated.post( "/role/:roleId/add/:userId", verifyRoleAccess, verifyUserAccess, verifyUserHasAction(ActionsEnum.addUserRole), - user.addUserRole + user.addUserRole, + logActionAudit(ActionsEnum.addUserRole) ); authenticated.post( @@ -464,7 +494,8 @@ authenticated.post( verifyResourceAccess, verifyRoleAccess, verifyUserHasAction(ActionsEnum.setResourceRoles), - resource.setResourceRoles + resource.setResourceRoles, + logActionAudit(ActionsEnum.setResourceRoles) ); authenticated.post( @@ -472,35 +503,40 @@ authenticated.post( verifyResourceAccess, verifySetResourceUsers, verifyUserHasAction(ActionsEnum.setResourceUsers), - resource.setResourceUsers + resource.setResourceUsers, + logActionAudit(ActionsEnum.setResourceUsers) ); authenticated.post( `/resource/:resourceId/password`, verifyResourceAccess, verifyUserHasAction(ActionsEnum.setResourcePassword), - resource.setResourcePassword + resource.setResourcePassword, + logActionAudit(ActionsEnum.setResourcePassword) ); authenticated.post( `/resource/:resourceId/pincode`, verifyResourceAccess, verifyUserHasAction(ActionsEnum.setResourcePincode), - resource.setResourcePincode + resource.setResourcePincode, + logActionAudit(ActionsEnum.setResourcePincode) ); authenticated.post( `/resource/:resourceId/header-auth`, verifyResourceAccess, verifyUserHasAction(ActionsEnum.setResourceHeaderAuth), - resource.setResourceHeaderAuth + resource.setResourceHeaderAuth, + logActionAudit(ActionsEnum.setResourceHeaderAuth) ); authenticated.post( `/resource/:resourceId/whitelist`, verifyResourceAccess, verifyUserHasAction(ActionsEnum.setResourceWhitelist), - resource.setResourceWhitelist + resource.setResourceWhitelist, + logActionAudit(ActionsEnum.setResourceWhitelist) ); authenticated.get( @@ -514,14 +550,16 @@ authenticated.post( `/resource/:resourceId/access-token`, verifyResourceAccess, verifyUserHasAction(ActionsEnum.generateAccessToken), - accessToken.generateAccessToken + accessToken.generateAccessToken, + logActionAudit(ActionsEnum.generateAccessToken) ); authenticated.delete( `/access-token/:accessTokenId`, verifyAccessTokenAccess, verifyUserHasAction(ActionsEnum.deleteAcessToken), - accessToken.deleteAccessToken + accessToken.deleteAccessToken, + logActionAudit(ActionsEnum.deleteAcessToken) ); authenticated.get( @@ -594,7 +632,8 @@ authenticated.put( "/org/:orgId/user", verifyOrgAccess, verifyUserHasAction(ActionsEnum.createOrgUser), - user.createOrgUser + user.createOrgUser, + logActionAudit(ActionsEnum.createOrgUser) ); authenticated.post( @@ -602,7 +641,8 @@ authenticated.post( verifyOrgAccess, verifyUserAccess, verifyUserHasAction(ActionsEnum.updateOrgUser), - user.updateOrgUser + user.updateOrgUser, + logActionAudit(ActionsEnum.updateOrgUser) ); authenticated.get("/org/:orgId/user/:userId", verifyOrgAccess, user.getOrgUser); @@ -624,7 +664,8 @@ authenticated.delete( verifyOrgAccess, verifyUserAccess, verifyUserHasAction(ActionsEnum.removeUser), - user.removeUserOrg + user.removeUserOrg, + logActionAudit(ActionsEnum.removeUser) ); // authenticated.put( @@ -757,7 +798,8 @@ authenticated.post( verifyOrgAccess, verifyApiKeyAccess, verifyUserHasAction(ActionsEnum.setApiKeyActions), - apiKeys.setApiKeyActions + apiKeys.setApiKeyActions, + logActionAudit(ActionsEnum.setApiKeyActions) ); authenticated.get( @@ -772,7 +814,8 @@ authenticated.put( `/org/:orgId/api-key`, verifyOrgAccess, verifyUserHasAction(ActionsEnum.createApiKey), - apiKeys.createOrgApiKey + apiKeys.createOrgApiKey, + logActionAudit(ActionsEnum.createApiKey) ); authenticated.delete( @@ -780,7 +823,8 @@ authenticated.delete( verifyOrgAccess, verifyApiKeyAccess, verifyUserHasAction(ActionsEnum.deleteApiKey), - apiKeys.deleteOrgApiKey + apiKeys.deleteOrgApiKey, + logActionAudit(ActionsEnum.deleteApiKey) ); authenticated.get( @@ -795,7 +839,8 @@ authenticated.put( `/org/:orgId/domain`, verifyOrgAccess, verifyUserHasAction(ActionsEnum.createOrgDomain), - domain.createOrgDomain + domain.createOrgDomain, + logActionAudit(ActionsEnum.createOrgDomain) ); authenticated.post( @@ -803,7 +848,8 @@ authenticated.post( verifyOrgAccess, verifyDomainAccess, verifyUserHasAction(ActionsEnum.restartOrgDomain), - domain.restartOrgDomain + domain.restartOrgDomain, + logActionAudit(ActionsEnum.restartOrgDomain) ); authenticated.delete( @@ -811,7 +857,8 @@ authenticated.delete( verifyOrgAccess, verifyDomainAccess, verifyUserHasAction(ActionsEnum.deleteOrgDomain), - domain.deleteAccountDomain + domain.deleteAccountDomain, + logActionAudit(ActionsEnum.deleteOrgDomain) ); // Auth routes diff --git a/server/routers/integration.ts b/server/routers/integration.ts index 8808c931..f54189e6 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -29,7 +29,7 @@ import { import HttpCode from "@server/types/HttpCode"; import { Router } from "express"; import { ActionsEnum } from "@server/auth/actions"; -import { build } from "@server/build"; +import { logActionAudit } from "#dynamic/middlewares"; export const unauthenticated = Router(); @@ -51,7 +51,8 @@ authenticated.put( "/org", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.createOrg), - org.createOrg + org.createOrg, + logActionAudit(ActionsEnum.createOrg) ); authenticated.get( @@ -72,21 +73,24 @@ authenticated.post( "/org/:orgId", verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.updateOrg), - org.updateOrg + org.updateOrg, + logActionAudit(ActionsEnum.updateOrg) ); authenticated.delete( "/org/:orgId", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.deleteOrg), - org.deleteOrg + org.deleteOrg, + logActionAudit(ActionsEnum.deleteOrg) ); authenticated.put( "/org/:orgId/site", verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.createSite), - site.createSite + site.createSite, + logActionAudit(ActionsEnum.createSite) ); authenticated.get( @@ -121,14 +125,16 @@ authenticated.post( "/site/:siteId", verifyApiKeySiteAccess, verifyApiKeyHasAction(ActionsEnum.updateSite), - site.updateSite + site.updateSite, + logActionAudit(ActionsEnum.updateSite) ); authenticated.delete( "/site/:siteId", verifyApiKeySiteAccess, verifyApiKeyHasAction(ActionsEnum.deleteSite), - site.deleteSite + site.deleteSite, + logActionAudit(ActionsEnum.deleteSite) ); authenticated.get( @@ -142,7 +148,8 @@ authenticated.put( verifyApiKeyOrgAccess, verifyApiKeySiteAccess, verifyApiKeyHasAction(ActionsEnum.createSiteResource), - siteResource.createSiteResource + siteResource.createSiteResource, + logActionAudit(ActionsEnum.createSiteResource) ); authenticated.get( @@ -175,7 +182,8 @@ authenticated.post( verifyApiKeySiteAccess, verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.updateSiteResource), - siteResource.updateSiteResource + siteResource.updateSiteResource, + logActionAudit(ActionsEnum.updateSiteResource) ); authenticated.delete( @@ -184,21 +192,24 @@ authenticated.delete( verifyApiKeySiteAccess, verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.deleteSiteResource), - siteResource.deleteSiteResource + siteResource.deleteSiteResource, + logActionAudit(ActionsEnum.deleteSiteResource) ); authenticated.put( "/org/:orgId/resource", verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.createResource), - resource.createResource + resource.createResource, + logActionAudit(ActionsEnum.createResource) ); authenticated.put( "/org/:orgId/site/:siteId/resource", verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.createResource), - resource.createResource + resource.createResource, + logActionAudit(ActionsEnum.createResource) ); authenticated.get( @@ -233,7 +244,8 @@ authenticated.post( "/org/:orgId/create-invite", verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.inviteUser), - user.inviteUser + user.inviteUser, + logActionAudit(ActionsEnum.inviteUser) ); authenticated.get( @@ -261,21 +273,24 @@ authenticated.post( "/resource/:resourceId", verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.updateResource), - resource.updateResource + resource.updateResource, + logActionAudit(ActionsEnum.updateResource) ); authenticated.delete( "/resource/:resourceId", verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.deleteResource), - resource.deleteResource + resource.deleteResource, + logActionAudit(ActionsEnum.deleteResource) ); authenticated.put( "/resource/:resourceId/target", verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.createTarget), - target.createTarget + target.createTarget, + logActionAudit(ActionsEnum.createTarget) ); authenticated.get( @@ -289,7 +304,8 @@ authenticated.put( "/resource/:resourceId/rule", verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.createResourceRule), - resource.createResourceRule + resource.createResourceRule, + logActionAudit(ActionsEnum.createResourceRule) ); authenticated.get( @@ -303,14 +319,16 @@ authenticated.post( "/resource/:resourceId/rule/:ruleId", verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.updateResourceRule), - resource.updateResourceRule + resource.updateResourceRule, + logActionAudit(ActionsEnum.updateResourceRule) ); authenticated.delete( "/resource/:resourceId/rule/:ruleId", verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.deleteResourceRule), - resource.deleteResourceRule + resource.deleteResourceRule, + logActionAudit(ActionsEnum.deleteResourceRule) ); authenticated.get( @@ -324,21 +342,24 @@ authenticated.post( "/target/:targetId", verifyApiKeyTargetAccess, verifyApiKeyHasAction(ActionsEnum.updateTarget), - target.updateTarget + target.updateTarget, + logActionAudit(ActionsEnum.updateTarget) ); authenticated.delete( "/target/:targetId", verifyApiKeyTargetAccess, verifyApiKeyHasAction(ActionsEnum.deleteTarget), - target.deleteTarget + target.deleteTarget, + logActionAudit(ActionsEnum.deleteTarget) ); authenticated.put( "/org/:orgId/role", verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.createRole), - role.createRole + role.createRole, + logActionAudit(ActionsEnum.createRole) ); authenticated.get( @@ -352,7 +373,8 @@ authenticated.delete( "/role/:roleId", verifyApiKeyRoleAccess, verifyApiKeyHasAction(ActionsEnum.deleteRole), - role.deleteRole + role.deleteRole, + logActionAudit(ActionsEnum.deleteRole) ); authenticated.get( @@ -367,7 +389,8 @@ authenticated.post( verifyApiKeyRoleAccess, verifyApiKeyUserAccess, verifyApiKeyHasAction(ActionsEnum.addUserRole), - user.addUserRole + user.addUserRole, + logActionAudit(ActionsEnum.addUserRole) ); authenticated.post( @@ -375,7 +398,8 @@ authenticated.post( verifyApiKeyResourceAccess, verifyApiKeyRoleAccess, verifyApiKeyHasAction(ActionsEnum.setResourceRoles), - resource.setResourceRoles + resource.setResourceRoles, + logActionAudit(ActionsEnum.setResourceRoles) ); authenticated.post( @@ -383,35 +407,40 @@ authenticated.post( verifyApiKeyResourceAccess, verifyApiKeySetResourceUsers, verifyApiKeyHasAction(ActionsEnum.setResourceUsers), - resource.setResourceUsers + resource.setResourceUsers, + logActionAudit(ActionsEnum.setResourceUsers) ); authenticated.post( `/resource/:resourceId/password`, verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.setResourcePassword), - resource.setResourcePassword + resource.setResourcePassword, + logActionAudit(ActionsEnum.setResourcePassword) ); authenticated.post( `/resource/:resourceId/pincode`, verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.setResourcePincode), - resource.setResourcePincode + resource.setResourcePincode, + logActionAudit(ActionsEnum.setResourcePincode) ); authenticated.post( `/resource/:resourceId/header-auth`, verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.setResourceHeaderAuth), - resource.setResourceHeaderAuth + resource.setResourceHeaderAuth, + logActionAudit(ActionsEnum.setResourceHeaderAuth) ); authenticated.post( `/resource/:resourceId/whitelist`, verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.setResourceWhitelist), - resource.setResourceWhitelist + resource.setResourceWhitelist, + logActionAudit(ActionsEnum.setResourceWhitelist) ); authenticated.get( @@ -439,14 +468,16 @@ authenticated.post( `/resource/:resourceId/access-token`, verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.generateAccessToken), - accessToken.generateAccessToken + accessToken.generateAccessToken, + logActionAudit(ActionsEnum.generateAccessToken) ); authenticated.delete( `/access-token/:accessTokenId`, verifyApiKeyAccessTokenAccess, verifyApiKeyHasAction(ActionsEnum.deleteAcessToken), - accessToken.deleteAccessToken + accessToken.deleteAccessToken, + logActionAudit(ActionsEnum.deleteAcessToken) ); authenticated.get( @@ -474,7 +505,8 @@ authenticated.post( "/user/:userId/2fa", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.updateUser), - user.updateUser2FA + user.updateUser2FA, + logActionAudit(ActionsEnum.updateUser) ); authenticated.get( @@ -495,7 +527,8 @@ authenticated.put( "/org/:orgId/user", verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.createOrgUser), - user.createOrgUser + user.createOrgUser, + logActionAudit(ActionsEnum.createOrgUser) ); authenticated.post( @@ -503,7 +536,8 @@ authenticated.post( verifyApiKeyOrgAccess, verifyApiKeyUserAccess, verifyApiKeyHasAction(ActionsEnum.updateOrgUser), - user.updateOrgUser + user.updateOrgUser, + logActionAudit(ActionsEnum.updateOrgUser) ); authenticated.delete( @@ -511,7 +545,8 @@ authenticated.delete( verifyApiKeyOrgAccess, verifyApiKeyUserAccess, verifyApiKeyHasAction(ActionsEnum.removeUser), - user.removeUserOrg + user.removeUserOrg, + logActionAudit(ActionsEnum.removeUser) ); // authenticated.put( @@ -531,7 +566,8 @@ authenticated.post( `/org/:orgId/api-key/:apiKeyId/actions`, verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.setApiKeyActions), - apiKeys.setApiKeyActions + apiKeys.setApiKeyActions, + logActionAudit(ActionsEnum.setApiKeyActions) ); authenticated.get( @@ -545,28 +581,32 @@ authenticated.put( `/org/:orgId/api-key`, verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.createApiKey), - apiKeys.createOrgApiKey + apiKeys.createOrgApiKey, + logActionAudit(ActionsEnum.createApiKey) ); authenticated.delete( `/org/:orgId/api-key/:apiKeyId`, verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.deleteApiKey), - apiKeys.deleteApiKey + apiKeys.deleteApiKey, + logActionAudit(ActionsEnum.deleteApiKey) ); authenticated.put( "/idp/oidc", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.createIdp), - idp.createOidcIdp + idp.createOidcIdp, + logActionAudit(ActionsEnum.createIdp) ); authenticated.post( "/idp/:idpId/oidc", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.updateIdp), - idp.updateOidcIdp + idp.updateOidcIdp, + logActionAudit(ActionsEnum.updateIdp) ); authenticated.get( @@ -587,21 +627,24 @@ authenticated.put( "/idp/:idpId/org/:orgId", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.createIdpOrg), - idp.createIdpOrgPolicy + idp.createIdpOrgPolicy, + logActionAudit(ActionsEnum.createIdpOrg) ); authenticated.post( "/idp/:idpId/org/:orgId", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.updateIdpOrg), - idp.updateIdpOrgPolicy + idp.updateIdpOrgPolicy, + logActionAudit(ActionsEnum.updateIdpOrg) ); authenticated.delete( "/idp/:idpId/org/:orgId", verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.deleteIdpOrg), - idp.deleteIdpOrgPolicy + idp.deleteIdpOrgPolicy, + logActionAudit(ActionsEnum.deleteIdpOrg) ); authenticated.get( @@ -640,7 +683,8 @@ authenticated.put( verifyClientsEnabled, verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.createClient), - client.createClient + client.createClient, + logActionAudit(ActionsEnum.createClient) ); authenticated.delete( @@ -648,7 +692,8 @@ authenticated.delete( verifyClientsEnabled, verifyApiKeyClientAccess, verifyApiKeyHasAction(ActionsEnum.deleteClient), - client.deleteClient + client.deleteClient, + logActionAudit(ActionsEnum.deleteClient) ); authenticated.post( @@ -656,12 +701,14 @@ authenticated.post( verifyClientsEnabled, verifyApiKeyClientAccess, verifyApiKeyHasAction(ActionsEnum.updateClient), - client.updateClient + client.updateClient, + logActionAudit(ActionsEnum.updateClient) ); authenticated.put( "/org/:orgId/blueprint", verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.applyBlueprint), - org.applyBlueprint + org.applyBlueprint, + logActionAudit(ActionsEnum.applyBlueprint) );