feat(audit-logs): filter audit logs by secret key

This commit is contained in:
Daniel Hougaard
2025-03-26 05:54:33 +04:00
parent 6482e88dfc
commit 4654a17e5f
9 changed files with 50 additions and 5 deletions

View File

@@ -40,12 +40,14 @@ export const auditLogDALFactory = (db: TDbClient) => {
actorId,
actorType,
secretPath,
secretKey,
eventType,
eventMetadata
}: Omit<TFindQuery, "actor" | "eventType"> & {
actorId?: string;
actorType?: ActorType;
secretPath?: string;
secretKey?: string;
eventType?: EventType[];
eventMetadata?: Record<string, string>;
},
@@ -90,8 +92,24 @@ export const auditLogDALFactory = (db: TDbClient) => {
});
}
if (projectId && secretPath) {
void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object('secretPath', ?::text)`, [secretPath]);
if (projectId) {
if (secretPath) {
void sqlQuery.whereRaw(`"eventMetadata"->>'secretPath' = ?`, [secretPath]);
}
if (secretKey) {
void sqlQuery.whereRaw(
`(
"eventMetadata"->>'secretKey' = ?
OR
EXISTS (
SELECT 1
FROM jsonb_array_elements("eventMetadata"->'secrets') AS element
WHERE element->>'secretKey' = ?
)
)`,
[secretKey, secretKey]
);
}
}
// Filter by actor type

View File

@@ -63,6 +63,7 @@ export const auditLogServiceFactory = ({
actorType: filter.actorType,
eventMetadata: filter.eventMetadata,
secretPath: filter.secretPath,
secretKey: filter.secretKey,
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
});

View File

@@ -36,6 +36,7 @@ export type TListProjectAuditLogDTO = {
auditLogActorId?: string;
actorType?: ActorType;
secretPath?: string;
secretKey?: string;
eventMetadata?: Record<string, string>;
};
} & Omit<TProjectPermission, "projectId">;

View File

@@ -843,6 +843,8 @@ export const AUDIT_LOGS = {
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.",
secretKey:
"The key 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.",

View File

@@ -118,6 +118,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
.optional()
.transform((val) => (!val ? val : removeTrailingSlash(val)))
.describe(AUDIT_LOGS.EXPORT.secretPath),
secretKey: z.string().optional().describe(AUDIT_LOGS.EXPORT.secretKey),
// eventType is split with , for multiple values, we need to transform it to array
eventType: z

View File

@@ -11,6 +11,7 @@ export type TGetAuditLogsFilter = {
projectId?: string;
actor?: string; // user ID format
secretPath?: string;
secretKey?: string;
startDate?: Date;
endDate?: Date;
limit: number;

View File

@@ -157,7 +157,24 @@ export const LogsFilter = ({
control={control}
name="secretPath"
render={({ field: { onChange, value, ...field } }) => (
<FormControl label="Secret path" className="w-40">
<FormControl label="Secret Path" className="w-40">
<Input
placeholder="/folder"
{...field}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
)}
{selectedProject?.type === ProjectType.SecretManager && (
<Controller
control={control}
name="secretKey"
render={({ field: { onChange, value, ...field } }) => (
<FormControl label="Secret Key" className="w-40">
<Input {...field} value={value} onChange={(e) => onChange(e.target.value)} />
</FormControl>
)}
@@ -289,7 +306,7 @@ export const LogsFilter = ({
control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return (
<FormControl label="Start date" errorText={error?.message} isError={Boolean(error)}>
<FormControl label="Start Date" errorText={error?.message} isError={Boolean(error)}>
<DatePicker
value={field.value || undefined}
onChange={onChange}
@@ -309,7 +326,7 @@ export const LogsFilter = ({
control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return (
<FormControl label="End date" errorText={error?.message} isError={Boolean(error)}>
<FormControl label="End Date" errorText={error?.message} isError={Boolean(error)}>
<DatePicker
value={field.value || undefined}
onChange={onChange}

View File

@@ -58,11 +58,13 @@ export const LogsSection = withPermission(
const actor = watch("actor");
const projectId = watch("project")?.id;
const secretPath = watch("secretPath");
const secretKey = watch("secretKey");
const startDate = watch("startDate");
const endDate = watch("endDate");
const [debouncedSecretPath] = useDebounce<string>(secretPath!, 500);
const [debouncedSecretKey] = useDebounce<string>(secretKey!, 500);
return (
<div>
@@ -81,6 +83,7 @@ export const LogsSection = withPermission(
refetchInterval={refetchInterval}
filter={{
secretPath: debouncedSecretPath || undefined,
secretKey: debouncedSecretKey || undefined,
eventMetadata: presets?.eventMetadata,
projectId,
actorType: presets?.actorType,

View File

@@ -14,6 +14,7 @@ export const auditLogFilterFormSchema = z
actor: z.string().optional(),
userAgentType: z.nativeEnum(UserAgentType),
secretPath: z.string().optional(),
secretKey: z.string().optional(),
startDate: z.date().optional(),
endDate: z.date().optional(),
page: z.coerce.number().optional(),