diff --git a/backend/src/ee/services/audit-log/audit-log-dal.ts b/backend/src/ee/services/audit-log/audit-log-dal.ts index bcef06e10..21f785835 100644 --- a/backend/src/ee/services/audit-log/audit-log-dal.ts +++ b/backend/src/ee/services/audit-log/audit-log-dal.ts @@ -39,11 +39,13 @@ export const auditLogDALFactory = (db: TDbClient) => { offset = 0, actorId, actorType, + secretPath, eventType, eventMetadata }: Omit & { actorId?: string; actorType?: ActorType; + secretPath?: string; eventType?: EventType[]; eventMetadata?: Record; }, @@ -88,6 +90,10 @@ export const auditLogDALFactory = (db: TDbClient) => { }); } + if (projectId && secretPath) { + void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object('secretPath', ?::text)`, [secretPath]); + } + // Filter by actor type if (actorType) { void sqlQuery.where("actor", actorType); diff --git a/backend/src/ee/services/audit-log/audit-log-service.ts b/backend/src/ee/services/audit-log/audit-log-service.ts index ff7dede5f..3b860864f 100644 --- a/backend/src/ee/services/audit-log/audit-log-service.ts +++ b/backend/src/ee/services/audit-log/audit-log-service.ts @@ -46,10 +46,6 @@ export const auditLogServiceFactory = ({ actorOrgId ); - /** - * NOTE (dangtony98): Update this to organization-level audit log permission check once audit logs are moved - * to the organization level ✅ - */ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs); } @@ -64,6 +60,7 @@ export const auditLogServiceFactory = ({ actorId: filter.auditLogActorId, actorType: filter.actorType, eventMetadata: filter.eventMetadata, + secretPath: filter.secretPath, ...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId }) }); diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 9c19cd3cc..6e8314731 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -32,6 +32,7 @@ export type TListProjectAuditLogDTO = { projectId?: string; auditLogActorId?: string; actorType?: ActorType; + secretPath?: string; eventMetadata?: Record; }; } & Omit; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 800788179..eca11e983 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -828,6 +828,8 @@ export const AUDIT_LOGS = { projectId: "Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.", eventType: "The type of the event to export.", + secretPath: + "The path of the secret to query audit logs for. Note that the projectId parameter must also be provided.", userAgentType: "Choose which consuming application to export audit logs for.", eventMetadata: "Filter by event metadata key-value pairs. Formatted as `key1=value1,key2=value2`, with comma-separation.", diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 104898099..db0008ebe 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -11,7 +11,7 @@ import { } from "@app/db/schemas"; 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 { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -113,6 +113,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { querystring: z.object({ projectId: z.string().optional().describe(AUDIT_LOGS.EXPORT.projectId), actorType: z.nativeEnum(ActorType).optional(), + secretPath: z + .string() + .optional() + .transform((val) => (!val ? val : removeTrailingSlash(val))) + .describe(AUDIT_LOGS.EXPORT.secretPath), + // eventType is split with , for multiple values, we need to transform it to array eventType: z .string() diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index 3a5070ef5..338671cab 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -10,6 +10,7 @@ export type TGetAuditLogsFilter = { actorType?: ActorType; projectId?: string; actor?: string; // user ID format + secretPath?: string; startDate?: Date; endDate?: Date; limit: number; diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx index 3a21958ae..5c7e020b7 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx @@ -14,6 +14,7 @@ import { DropdownMenuTrigger, FilterableSelect, FormControl, + Input, Select, SelectItem } from "@app/components/v2"; @@ -22,6 +23,7 @@ import { useGetAuditLogActorFilterOpts, useGetUserWorkspaces } from "@app/hooks/ import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants"; import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums"; import { Actor } from "@app/hooks/api/auditLogs/types"; +import { ProjectType } from "@app/hooks/api/workspace/types"; import { AuditLogFilterFormData } from "./types"; @@ -50,6 +52,7 @@ export const LogsFilter = ({ className, control, reset, + setValue, watch }: Props) => { const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false); @@ -101,6 +104,7 @@ export const LogsFilter = ({ }; const selectedEventTypes = watch("eventType") as EventType[] | undefined; + const selectedProject = watch("project"); return (
- {isOrgAuditLogs && workspacesInOrg.length > 0 && ( - ( - - ({ name, id }))} - getOptionValue={(option) => option.id} - getOptionLabel={(option) => option.name} - /> - - )} - /> - )} +
+ {isOrgAuditLogs && workspacesInOrg.length > 0 && ( + ( + + { + if (e === null) { + setValue("secretPath", ""); + } + onChange(e); + }} + placeholder="Select a project..." + options={workspacesInOrg.map(({ name, id, type }) => ({ name, id, type }))} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + + )} + /> + )} + {selectedProject?.type === ProjectType.SecretManager && ( + ( + + onChange(e.target.value)} /> + + )} + /> + )} +
(secretPath!, 500); + return (
{showFilters && ( @@ -90,6 +94,7 @@ export const LogsSection = withPermission( isOrgAuditLogs={isOrgAuditLogs} showActorColumn={!!showActorColumn} filter={{ + secretPath: debouncedSecretPath || undefined, eventMetadata: presets?.eventMetadata, projectId, actorType: presets?.actorType, diff --git a/frontend/src/pages/organization/AuditLogsPage/components/types.tsx b/frontend/src/pages/organization/AuditLogsPage/components/types.tsx index 05c44fa4d..17dcb80fd 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/types.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/types.tsx @@ -1,14 +1,19 @@ import { z } from "zod"; import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; +import { ProjectType } from "@app/hooks/api/workspace/types"; export const auditLogFilterFormSchema = z .object({ eventMetadata: z.object({}).optional(), - project: z.object({ id: z.string(), name: z.string() }).optional().nullable(), + project: z + .object({ id: z.string(), name: z.string(), type: z.nativeEnum(ProjectType) }) + .optional() + .nullable(), eventType: z.nativeEnum(EventType).array(), actor: z.string().optional(), userAgentType: z.nativeEnum(UserAgentType), + secretPath: z.string().optional(), startDate: z.date().optional(), endDate: z.date().optional(), page: z.coerce.number().optional(),