From 7b19d2aa6a1e93c354a394f80a1e48da38084a23 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sun, 8 Sep 2024 19:24:04 +0400 Subject: [PATCH] feat: audit logs on organization-level support --- .../server/routes/v1/organization-router.ts | 68 ++++++++++++++++++- frontend/src/hooks/api/auditLogs/queries.tsx | 28 ++++---- frontend/src/hooks/api/auditLogs/types.tsx | 4 ++ 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 808f125bb..2e49d5b8e 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { + AuditLogsSchema, GroupsSchema, IncidentContactsSchema, OrganizationsSchema, @@ -8,7 +9,9 @@ import { OrgRolesSchema, UsersSchema } from "@app/db/schemas"; -import { ORGANIZATIONS } from "@app/lib/api-docs"; +import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; +import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs"; +import { getLastMidnightDateISO } from "@app/lib/fn"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -62,6 +65,69 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "GET", + url: "/audit-logs", + config: { + rateLimit: readLimit + }, + schema: { + description: "Get all audit logs for an organization", + querystring: z.object({ + eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType), + userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType), + startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate), + endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate), + offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset), + limit: z.coerce.number().default(20).describe(AUDIT_LOGS.EXPORT.limit), + actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor) + }), + + response: { + 200: z.object({ + auditLogs: AuditLogsSchema.omit({ + eventMetadata: true, + eventType: true, + actor: true, + actorMetadata: true + }) + .merge( + z.object({ + project: z.object({ + name: z.string(), + slug: z.string() + }), + event: z.object({ + type: z.string(), + metadata: z.any() + }), + actor: z.object({ + type: z.string(), + metadata: z.any() + }) + }) + ) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const auditLogs = await server.services.auditLog.listAuditLogs({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + ...req.query, + endDate: req.query.endDate, + startDate: req.query.startDate || getLastMidnightDateISO(), + auditLogActor: req.query.actor, + actor: req.permission.type, + projectId: null + }); + return { auditLogs }; + } + }); + server.route({ method: "GET", url: "/:organizationId/users", diff --git a/frontend/src/hooks/api/auditLogs/queries.tsx b/frontend/src/hooks/api/auditLogs/queries.tsx index 3517d4435..68fd3890c 100644 --- a/frontend/src/hooks/api/auditLogs/queries.tsx +++ b/frontend/src/hooks/api/auditLogs/queries.tsx @@ -5,27 +5,29 @@ import { apiRequest } from "@app/config/request"; import { Actor, AuditLog, AuditLogFilters } from "./types"; export const workspaceKeys = { - getAuditLogs: (workspaceId: string, filters: AuditLogFilters) => + getAuditLogs: (filters: AuditLogFilters, workspaceId: string | null) => [{ workspaceId, filters }, "audit-logs"] as const, getAuditLogActorFilterOpts: (workspaceId: string) => [{ workspaceId }, "audit-log-actor-filters"] as const }; -export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) => { +export const useGetAuditLogs = (filters: AuditLogFilters, workspaceId: string | null) => { return useInfiniteQuery({ - queryKey: workspaceKeys.getAuditLogs(workspaceId, filters), + queryKey: workspaceKeys.getAuditLogs(filters, workspaceId), + enabled: workspaceId !== "", + queryFn: async ({ pageParam }) => { - const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>( - `/api/v1/workspace/${workspaceId}/audit-logs`, - { - params: { - ...filters, - offset: pageParam, - startDate: filters?.startDate?.toISOString(), - endDate: filters?.endDate?.toISOString() - } + const auditLogEndpoint = workspaceId + ? `/api/v1/workspace/${workspaceId}/audit-logs` + : "/api/v1/organization/audit-logs"; + const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(auditLogEndpoint, { + params: { + ...filters, + offset: pageParam, + startDate: filters?.startDate?.toISOString(), + endDate: filters?.endDate?.toISOString() } - ); + }); return data.auditLogs; }, getNextPageParam: (lastPage, pages) => diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index f9b53d037..5d300fdcb 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -830,6 +830,10 @@ export type AuditLog = { userAgentType: UserAgentType; createdAt: string; updatedAt: string; + project: { + name: string; + slug: string; + }; }; export type AuditLogFilters = {