mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 19:28:16 +00:00
Address PR comments
This commit is contained in:
@@ -6,7 +6,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
if (!(await knex.schema.hasTable(TableName.PkiAlertsV2))) {
|
if (!(await knex.schema.hasTable(TableName.PkiAlertsV2))) {
|
||||||
await knex.schema.createTable(TableName.PkiAlertsV2, (t) => {
|
await knex.schema.createTable(TableName.PkiAlertsV2, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.string("slug").notNullable();
|
t.string("name").notNullable();
|
||||||
t.text("description").nullable();
|
t.text("description").nullable();
|
||||||
t.string("eventType").notNullable();
|
t.string("eventType").notNullable();
|
||||||
t.string("alertBefore").nullable();
|
t.string("alertBefore").nullable();
|
||||||
@@ -17,9 +17,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
|
|
||||||
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
t.index("projectId");
|
t.index("projectId");
|
||||||
t.index("eventType");
|
t.unique(["name", "projectId"]);
|
||||||
t.index("enabled");
|
|
||||||
t.unique(["slug", "projectId"]);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +42,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.uuid("alertId").notNullable();
|
t.uuid("alertId").notNullable();
|
||||||
t.timestamp("triggeredAt").defaultTo(knex.fn.now());
|
t.timestamp("triggeredAt").defaultTo(knex.fn.now());
|
||||||
t.boolean("notificationSent").defaultTo(false);
|
t.boolean("hasNotificationSent").defaultTo(false);
|
||||||
t.text("notificationError").nullable();
|
t.text("notificationError").nullable();
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const PkiAlertHistorySchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
alertId: z.string().uuid(),
|
alertId: z.string().uuid(),
|
||||||
triggeredAt: z.date().nullable().optional(),
|
triggeredAt: z.date().nullable().optional(),
|
||||||
notificationSent: z.boolean().default(false).nullable().optional(),
|
hasNotificationSent: z.boolean().default(false).nullable().optional(),
|
||||||
notificationError: z.string().nullable().optional(),
|
notificationError: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const PkiAlertsV2Schema = z.object({
|
export const PkiAlertsV2Schema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
eventType: z.string(),
|
eventType: z.string(),
|
||||||
alertBefore: z.string().nullable().optional(),
|
alertBefore: z.string().nullable().optional(),
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { ApiDocsTags } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import {
|
import {
|
||||||
CreatePkiAlertV2Schema,
|
CreatePkiAlertV2Schema,
|
||||||
createSecureAlertBeforeValidator,
|
createSecureAlertBeforeValidator,
|
||||||
|
PkiAlertChannelType,
|
||||||
PkiAlertEventType,
|
PkiAlertEventType,
|
||||||
PkiFilterRuleSchema,
|
PkiFilterRuleSchema,
|
||||||
UpdatePkiAlertV2Schema
|
UpdatePkiAlertV2Schema
|
||||||
@@ -22,32 +24,34 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Create a new PKI alert",
|
description: "Create a new PKI alert",
|
||||||
tags: ["PKI Alerts"],
|
tags: [ApiDocsTags.PkiAlerting],
|
||||||
body: z.object({
|
body: CreatePkiAlertV2Schema.extend({
|
||||||
projectId: z.string().uuid().describe("Project ID"),
|
projectId: z.string().uuid().describe("Project ID")
|
||||||
...CreatePkiAlertV2Schema.shape
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
id: z.string().uuid(),
|
alert: z.object({
|
||||||
slug: z.string(),
|
id: z.string().uuid(),
|
||||||
description: z.string().nullable(),
|
name: z.string(),
|
||||||
eventType: z.nativeEnum(PkiAlertEventType),
|
description: z.string().nullable(),
|
||||||
alertBefore: z.string(),
|
eventType: z.nativeEnum(PkiAlertEventType),
|
||||||
filters: z.array(z.any()),
|
alertBefore: z.string(),
|
||||||
enabled: z.boolean(),
|
filters: z.array(PkiFilterRuleSchema),
|
||||||
channels: z.array(
|
enabled: z.boolean(),
|
||||||
z.object({
|
projectId: z.string().uuid(),
|
||||||
id: z.string().uuid(),
|
channels: z.array(
|
||||||
channelType: z.string(),
|
z.object({
|
||||||
config: z.record(z.any()),
|
id: z.string().uuid(),
|
||||||
enabled: z.boolean(),
|
channelType: z.nativeEnum(PkiAlertChannelType),
|
||||||
createdAt: z.date(),
|
config: z.record(z.any()),
|
||||||
updatedAt: z.date()
|
enabled: z.boolean(),
|
||||||
})
|
createdAt: z.date(),
|
||||||
),
|
updatedAt: z.date()
|
||||||
createdAt: z.date(),
|
})
|
||||||
updatedAt: z.date()
|
),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -67,14 +71,14 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
type: EventType.CREATE_PKI_ALERT,
|
type: EventType.CREATE_PKI_ALERT,
|
||||||
metadata: {
|
metadata: {
|
||||||
pkiAlertId: alert.id,
|
pkiAlertId: alert.id,
|
||||||
name: alert.slug,
|
name: alert.name,
|
||||||
eventType: alert.eventType,
|
eventType: alert.eventType,
|
||||||
alertBefore: alert.alertBefore
|
alertBefore: alert.alertBefore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return alert;
|
return { alert };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "List PKI alerts for a project",
|
description: "List PKI alerts for a project",
|
||||||
tags: ["PKI Alerts"],
|
tags: [ApiDocsTags.PkiAlerting],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
projectId: z.string().uuid(),
|
projectId: z.string().uuid(),
|
||||||
search: z.string().optional(),
|
search: z.string().optional(),
|
||||||
@@ -101,16 +105,16 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
alerts: z.array(
|
alerts: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
description: z.string().nullable(),
|
description: z.string().nullable(),
|
||||||
eventType: z.nativeEnum(PkiAlertEventType),
|
eventType: z.nativeEnum(PkiAlertEventType),
|
||||||
alertBefore: z.string(),
|
alertBefore: z.string(),
|
||||||
filters: z.array(z.any()),
|
filters: z.array(PkiFilterRuleSchema),
|
||||||
enabled: z.boolean(),
|
enabled: z.boolean(),
|
||||||
channels: z.array(
|
channels: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
channelType: z.string(),
|
channelType: z.nativeEnum(PkiAlertChannelType),
|
||||||
config: z.record(z.any()),
|
config: z.record(z.any()),
|
||||||
enabled: z.boolean(),
|
enabled: z.boolean(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
@@ -147,31 +151,34 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Get a PKI alert by ID",
|
description: "Get a PKI alert by ID",
|
||||||
tags: ["PKI Alerts"],
|
tags: [ApiDocsTags.PkiAlerting],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
alertId: z.string().uuid().describe("Alert ID")
|
alertId: z.string().uuid().describe("Alert ID")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
id: z.string().uuid(),
|
alert: z.object({
|
||||||
slug: z.string(),
|
id: z.string().uuid(),
|
||||||
description: z.string().nullable(),
|
name: z.string(),
|
||||||
eventType: z.nativeEnum(PkiAlertEventType),
|
description: z.string().nullable(),
|
||||||
alertBefore: z.string(),
|
eventType: z.nativeEnum(PkiAlertEventType),
|
||||||
filters: z.array(z.any()),
|
alertBefore: z.string(),
|
||||||
enabled: z.boolean(),
|
filters: z.array(PkiFilterRuleSchema),
|
||||||
channels: z.array(
|
enabled: z.boolean(),
|
||||||
z.object({
|
projectId: z.string().uuid(),
|
||||||
id: z.string().uuid(),
|
channels: z.array(
|
||||||
channelType: z.string(),
|
z.object({
|
||||||
config: z.record(z.any()),
|
id: z.string().uuid(),
|
||||||
enabled: z.boolean(),
|
channelType: z.nativeEnum(PkiAlertChannelType),
|
||||||
createdAt: z.date(),
|
config: z.record(z.any()),
|
||||||
updatedAt: z.date()
|
enabled: z.boolean(),
|
||||||
})
|
createdAt: z.date(),
|
||||||
),
|
updatedAt: z.date()
|
||||||
createdAt: z.date(),
|
})
|
||||||
updatedAt: z.date()
|
),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -195,7 +202,7 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return alert;
|
return { alert };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -208,32 +215,35 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Update a PKI alert",
|
description: "Update a PKI alert",
|
||||||
tags: ["PKI Alerts"],
|
tags: [ApiDocsTags.PkiAlerting],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
alertId: z.string().uuid().describe("Alert ID")
|
alertId: z.string().uuid().describe("Alert ID")
|
||||||
}),
|
}),
|
||||||
body: UpdatePkiAlertV2Schema,
|
body: UpdatePkiAlertV2Schema,
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
id: z.string().uuid(),
|
alert: z.object({
|
||||||
slug: z.string(),
|
id: z.string().uuid(),
|
||||||
description: z.string().nullable(),
|
name: z.string(),
|
||||||
eventType: z.nativeEnum(PkiAlertEventType),
|
description: z.string().nullable(),
|
||||||
alertBefore: z.string(),
|
eventType: z.nativeEnum(PkiAlertEventType),
|
||||||
filters: z.array(z.any()),
|
alertBefore: z.string(),
|
||||||
enabled: z.boolean(),
|
filters: z.array(PkiFilterRuleSchema),
|
||||||
channels: z.array(
|
enabled: z.boolean(),
|
||||||
z.object({
|
projectId: z.string().uuid(),
|
||||||
id: z.string().uuid(),
|
channels: z.array(
|
||||||
channelType: z.string(),
|
z.object({
|
||||||
config: z.record(z.any()),
|
id: z.string().uuid(),
|
||||||
enabled: z.boolean(),
|
channelType: z.nativeEnum(PkiAlertChannelType),
|
||||||
createdAt: z.date(),
|
config: z.record(z.any()),
|
||||||
updatedAt: z.date()
|
enabled: z.boolean(),
|
||||||
})
|
createdAt: z.date(),
|
||||||
),
|
updatedAt: z.date()
|
||||||
createdAt: z.date(),
|
})
|
||||||
updatedAt: z.date()
|
),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -254,14 +264,14 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
type: EventType.UPDATE_PKI_ALERT,
|
type: EventType.UPDATE_PKI_ALERT,
|
||||||
metadata: {
|
metadata: {
|
||||||
pkiAlertId: alert.id,
|
pkiAlertId: alert.id,
|
||||||
name: alert.slug,
|
name: alert.name,
|
||||||
eventType: alert.eventType,
|
eventType: alert.eventType,
|
||||||
alertBefore: alert.alertBefore
|
alertBefore: alert.alertBefore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return alert;
|
return { alert };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -274,31 +284,34 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Delete a PKI alert",
|
description: "Delete a PKI alert",
|
||||||
tags: ["PKI Alerts"],
|
tags: [ApiDocsTags.PkiAlerting],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
alertId: z.string().uuid().describe("Alert ID")
|
alertId: z.string().uuid().describe("Alert ID")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
id: z.string().uuid(),
|
alert: z.object({
|
||||||
slug: z.string(),
|
id: z.string().uuid(),
|
||||||
description: z.string().nullable(),
|
name: z.string(),
|
||||||
eventType: z.nativeEnum(PkiAlertEventType),
|
description: z.string().nullable(),
|
||||||
alertBefore: z.string(),
|
eventType: z.nativeEnum(PkiAlertEventType),
|
||||||
filters: z.array(z.any()),
|
alertBefore: z.string(),
|
||||||
enabled: z.boolean(),
|
filters: z.array(PkiFilterRuleSchema),
|
||||||
channels: z.array(
|
enabled: z.boolean(),
|
||||||
z.object({
|
projectId: z.string().uuid(),
|
||||||
id: z.string().uuid(),
|
channels: z.array(
|
||||||
channelType: z.string(),
|
z.object({
|
||||||
config: z.record(z.any()),
|
id: z.string().uuid(),
|
||||||
enabled: z.boolean(),
|
channelType: z.nativeEnum(PkiAlertChannelType),
|
||||||
createdAt: z.date(),
|
config: z.record(z.any()),
|
||||||
updatedAt: z.date()
|
enabled: z.boolean(),
|
||||||
})
|
createdAt: z.date(),
|
||||||
),
|
updatedAt: z.date()
|
||||||
createdAt: z.date(),
|
})
|
||||||
updatedAt: z.date()
|
),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -322,7 +335,7 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return alert;
|
return { alert };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -335,7 +348,7 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "List certificates that match an alert's filter rules",
|
description: "List certificates that match an alert's filter rules",
|
||||||
tags: ["PKI Alerts"],
|
tags: [ApiDocsTags.PkiAlerting],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
alertId: z.string().uuid().describe("Alert ID")
|
alertId: z.string().uuid().describe("Alert ID")
|
||||||
}),
|
}),
|
||||||
@@ -358,9 +371,7 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
status: z.string()
|
status: z.string()
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
total: z.number(),
|
total: z.number()
|
||||||
limit: z.number(),
|
|
||||||
offset: z.number()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -382,12 +393,12 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/preview/certificates",
|
url: "/preview/certificates",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Preview certificates that would match the given filter rules",
|
description: "Preview certificates that would match the given filter rules",
|
||||||
tags: ["PKI Alerts"],
|
tags: [ApiDocsTags.PkiAlerting],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
projectId: z.string().uuid().describe("Project ID"),
|
projectId: z.string().uuid().describe("Project ID"),
|
||||||
filters: z.array(PkiFilterRuleSchema),
|
filters: z.array(PkiFilterRuleSchema),
|
||||||
@@ -413,9 +424,7 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
status: z.string()
|
status: z.string()
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
total: z.number(),
|
total: z.number()
|
||||||
limit: z.number(),
|
|
||||||
offset: z.number()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const pkiAlertHistoryDALFactory = (db: TDbClient) => {
|
|||||||
alertId: string,
|
alertId: string,
|
||||||
certificateIds: string[],
|
certificateIds: string[],
|
||||||
options?: {
|
options?: {
|
||||||
notificationSent?: boolean;
|
hasNotificationSent?: boolean;
|
||||||
notificationError?: string;
|
notificationError?: string;
|
||||||
}
|
}
|
||||||
): Promise<TPkiAlertHistory> => {
|
): Promise<TPkiAlertHistory> => {
|
||||||
@@ -21,7 +21,7 @@ export const pkiAlertHistoryDALFactory = (db: TDbClient) => {
|
|||||||
const historyRecords = await tx(TableName.PkiAlertHistory)
|
const historyRecords = await tx(TableName.PkiAlertHistory)
|
||||||
.insert({
|
.insert({
|
||||||
alertId,
|
alertId,
|
||||||
notificationSent: options?.notificationSent || false,
|
hasNotificationSent: options?.hasNotificationSent || false,
|
||||||
notificationError: options?.notificationError
|
notificationError: options?.notificationError
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
@@ -91,7 +91,7 @@ export const pkiAlertHistoryDALFactory = (db: TDbClient) => {
|
|||||||
.from(`${TableName.PkiAlertHistory} as hist`)
|
.from(`${TableName.PkiAlertHistory} as hist`)
|
||||||
.join(`${TableName.PkiAlertHistoryCertificate} as cert`, "hist.id", "cert.alertHistoryId")
|
.join(`${TableName.PkiAlertHistoryCertificate} as cert`, "hist.id", "cert.alertHistoryId")
|
||||||
.where("hist.alertId", alertId)
|
.where("hist.alertId", alertId)
|
||||||
.where("hist.notificationSent", true)
|
.where("hist.hasNotificationSent", true)
|
||||||
.where("hist.triggeredAt", ">=", cutoffDate)
|
.where("hist.triggeredAt", ">=", cutoffDate)
|
||||||
.whereIn("cert.certificateId", certificateIds)) as Array<{ certificateId: string }>;
|
.whereIn("cert.certificateId", certificateIds)) as Array<{ certificateId: string }>;
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findByProjectId = async (
|
const findByProjectIdWithCount = async (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
filters?: {
|
filters?: {
|
||||||
search?: string;
|
search?: string;
|
||||||
@@ -112,15 +112,32 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
offset?: number;
|
offset?: number;
|
||||||
},
|
},
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
): Promise<TAlertWithChannels[]> => {
|
): Promise<{ alerts: TAlertWithChannels[]; total: number }> => {
|
||||||
try {
|
try {
|
||||||
|
let countQuery = (tx || db.replicaNode())
|
||||||
|
.count("* as count")
|
||||||
|
.from(TableName.PkiAlertsV2)
|
||||||
|
.where(`${TableName.PkiAlertsV2}.projectId`, projectId);
|
||||||
|
|
||||||
|
if (filters?.search) {
|
||||||
|
countQuery = countQuery.whereILike(`${TableName.PkiAlertsV2}.name`, `%${sanitizeLikeInput(filters.search)}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.eventType) {
|
||||||
|
countQuery = countQuery.where(`${TableName.PkiAlertsV2}.eventType`, filters.eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.enabled !== undefined) {
|
||||||
|
countQuery = countQuery.where(`${TableName.PkiAlertsV2}.enabled`, filters.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
let alertQuery = (tx || db.replicaNode())
|
let alertQuery = (tx || db.replicaNode())
|
||||||
.select(selectAllTableCols(TableName.PkiAlertsV2))
|
.select(selectAllTableCols(TableName.PkiAlertsV2))
|
||||||
.from(TableName.PkiAlertsV2)
|
.from(TableName.PkiAlertsV2)
|
||||||
.where(`${TableName.PkiAlertsV2}.projectId`, projectId);
|
.where(`${TableName.PkiAlertsV2}.projectId`, projectId);
|
||||||
|
|
||||||
if (filters?.search) {
|
if (filters?.search) {
|
||||||
alertQuery = alertQuery.whereILike(`${TableName.PkiAlertsV2}.slug`, `%${sanitizeLikeInput(filters.search)}%`);
|
alertQuery = alertQuery.whereILike(`${TableName.PkiAlertsV2}.name`, `%${sanitizeLikeInput(filters.search)}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters?.eventType) {
|
if (filters?.eventType) {
|
||||||
@@ -141,13 +158,11 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
alertQuery = alertQuery.offset(filters.offset);
|
alertQuery = alertQuery.offset(filters.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
const alerts = (await alertQuery) as TPkiAlertsV2[];
|
const [countResult, alerts] = await Promise.all([countQuery, alertQuery]);
|
||||||
|
|
||||||
if (alerts.length === 0) {
|
const total = parseInt((countResult[0] as { count: string }).count, 10);
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const alertIds = alerts.map((alert) => alert.id);
|
const alertIds = (alerts as TPkiAlertsV2[]).map((alert) => alert.id);
|
||||||
const channels = (await (tx || db.replicaNode())
|
const channels = (await (tx || db.replicaNode())
|
||||||
.select(selectAllTableCols(TableName.PkiAlertChannels))
|
.select(selectAllTableCols(TableName.PkiAlertChannels))
|
||||||
.from(TableName.PkiAlertChannels)
|
.from(TableName.PkiAlertChannels)
|
||||||
@@ -164,17 +179,32 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
{} as Record<string, TChannelResult[]>
|
{} as Record<string, TChannelResult[]>
|
||||||
);
|
);
|
||||||
|
|
||||||
const result: TAlertWithChannels[] = alerts.map((alert) => ({
|
const alertsWithChannels: TAlertWithChannels[] = (alerts as TPkiAlertsV2[]).map((alert) => ({
|
||||||
...alert,
|
...alert,
|
||||||
channels: channelsByAlertId[alert.id] || []
|
channels: channelsByAlertId[alert.id] || []
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return result;
|
return { alerts: alertsWithChannels, total };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindByProjectId" });
|
throw new DatabaseError({ error, name: "FindByProjectIdWithCount" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findByProjectId = async (
|
||||||
|
projectId: string,
|
||||||
|
filters?: {
|
||||||
|
search?: string;
|
||||||
|
eventType?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
},
|
||||||
|
tx?: Knex
|
||||||
|
): Promise<TAlertWithChannels[]> => {
|
||||||
|
const result = await findByProjectIdWithCount(projectId, filters, tx);
|
||||||
|
return result.alerts;
|
||||||
|
};
|
||||||
|
|
||||||
const countByProjectId = async (
|
const countByProjectId = async (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
filters?: {
|
filters?: {
|
||||||
@@ -191,7 +221,7 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
.where(`${TableName.PkiAlertsV2}.projectId`, projectId);
|
.where(`${TableName.PkiAlertsV2}.projectId`, projectId);
|
||||||
|
|
||||||
if (filters?.search) {
|
if (filters?.search) {
|
||||||
query = query.whereILike(`${TableName.PkiAlertsV2}.slug`, `%${sanitizeLikeInput(filters.search)}%`);
|
query = query.whereILike(`${TableName.PkiAlertsV2}.name`, `%${sanitizeLikeInput(filters.search)}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters?.eventType) {
|
if (filters?.eventType) {
|
||||||
@@ -257,7 +287,7 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
let caCountQuery = (tx || db.replicaNode())
|
let caCountQuery = (tx || db.replicaNode())
|
||||||
.count("* as count")
|
.count("* as count")
|
||||||
.from(TableName.CertificateAuthority)
|
.from(TableName.CertificateAuthority)
|
||||||
.leftJoin(
|
.innerJoin(
|
||||||
`${TableName.InternalCertificateAuthority} as ica`,
|
`${TableName.InternalCertificateAuthority} as ica`,
|
||||||
`${TableName.CertificateAuthority}.id`,
|
`${TableName.CertificateAuthority}.id`,
|
||||||
`ica.caId`
|
`ica.caId`
|
||||||
@@ -268,16 +298,16 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
if (options?.alertBefore) {
|
if (options?.alertBefore) {
|
||||||
if (options.showFutureMatches) {
|
if (options.showFutureMatches) {
|
||||||
caCountQuery = caCountQuery
|
caCountQuery = caCountQuery
|
||||||
.whereRaw(`ica."notAfter" > NOW() + INTERVAL '${options.alertBefore}'`)
|
.whereRaw(`ica."notAfter" > NOW() + ?::interval`, [options.alertBefore])
|
||||||
.whereRaw(`ica."notAfter" > NOW()`);
|
.whereRaw(`ica."notAfter" > NOW()`);
|
||||||
} else if (options.showCurrentMatches) {
|
} else if (options.showCurrentMatches) {
|
||||||
caCountQuery = caCountQuery
|
caCountQuery = caCountQuery
|
||||||
.whereRaw(`ica."notAfter" > NOW()`)
|
.whereRaw(`ica."notAfter" > NOW()`)
|
||||||
.whereRaw(`ica."notAfter" <= NOW() + INTERVAL '${options.alertBefore}'`);
|
.whereRaw(`ica."notAfter" <= NOW() + ?::interval`, [options.alertBefore]);
|
||||||
} else {
|
} else {
|
||||||
caCountQuery = caCountQuery
|
caCountQuery = caCountQuery
|
||||||
.whereRaw(`ica."notAfter" > NOW()`)
|
.whereRaw(`ica."notAfter" > NOW()`)
|
||||||
.whereRaw(`ica."notAfter" <= NOW() + INTERVAL '${options.alertBefore}'`);
|
.whereRaw(`ica."notAfter" <= NOW() + ?::interval`, [options.alertBefore]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,23 +320,23 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
if (options?.showPreview) {
|
if (options?.showPreview) {
|
||||||
certCountQuery = certCountQuery
|
certCountQuery = certCountQuery
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW()`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW()`)
|
||||||
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
||||||
} else if (options?.alertBefore) {
|
} else if (options?.alertBefore) {
|
||||||
if (options.showFutureMatches) {
|
if (options.showFutureMatches) {
|
||||||
certCountQuery = certCountQuery
|
certCountQuery = certCountQuery
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW() + INTERVAL '${options.alertBefore}'`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW() + ?::interval`, [options.alertBefore])
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW()`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW()`)
|
||||||
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
||||||
} else if (options.showCurrentMatches) {
|
} else if (options.showCurrentMatches) {
|
||||||
certCountQuery = certCountQuery
|
certCountQuery = certCountQuery
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW()`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW()`)
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" <= NOW() + INTERVAL '${options.alertBefore}'`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" <= NOW() + ?::interval`, [options.alertBefore])
|
||||||
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
||||||
} else {
|
} else {
|
||||||
certCountQuery = certCountQuery
|
certCountQuery = certCountQuery
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW()`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW()`)
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" <= NOW() + INTERVAL '${options.alertBefore}'`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" <= NOW() + ?::interval`, [options.alertBefore])
|
||||||
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,7 +352,7 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.PkiAlertHistoryCertificate}.alertHistoryId`
|
`${TableName.PkiAlertHistoryCertificate}.alertHistoryId`
|
||||||
)
|
)
|
||||||
.where(`${TableName.PkiAlertHistory}.alertId`, options.alertId)
|
.where(`${TableName.PkiAlertHistory}.alertId`, options.alertId)
|
||||||
.whereRaw(`"${TableName.PkiAlertHistoryCertificate}"."certificateId" = "${TableName.Certificate}"."id"`)
|
.whereRaw(`"${TableName.PkiAlertHistoryCertificate}"."certificateId" = "${TableName.Certificate}".id`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +376,7 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (needsProfileJoin) {
|
if (needsProfileJoin) {
|
||||||
selectColumns.push("profile.slug as profileName");
|
selectColumns.push("profile.name as profileName");
|
||||||
}
|
}
|
||||||
|
|
||||||
let certificateQuery = (tx || db.replicaNode()).select(selectColumns).from(TableName.Certificate);
|
let certificateQuery = (tx || db.replicaNode()).select(selectColumns).from(TableName.Certificate);
|
||||||
@@ -355,23 +385,23 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
if (options?.showPreview) {
|
if (options?.showPreview) {
|
||||||
certificateQuery = certificateQuery
|
certificateQuery = certificateQuery
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW()`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW()`)
|
||||||
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
||||||
} else if (options?.alertBefore) {
|
} else if (options?.alertBefore) {
|
||||||
if (options.showFutureMatches) {
|
if (options.showFutureMatches) {
|
||||||
certificateQuery = certificateQuery
|
certificateQuery = certificateQuery
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW() + INTERVAL '${options.alertBefore}'`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW() + ?::interval`, [options.alertBefore])
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW()`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW()`)
|
||||||
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
||||||
} else if (options.showCurrentMatches) {
|
} else if (options.showCurrentMatches) {
|
||||||
certificateQuery = certificateQuery
|
certificateQuery = certificateQuery
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW()`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW()`)
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" <= NOW() + INTERVAL '${options.alertBefore}'`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" <= NOW() + ?::interval`, [options.alertBefore])
|
||||||
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
||||||
} else {
|
} else {
|
||||||
certificateQuery = certificateQuery
|
certificateQuery = certificateQuery
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" > NOW()`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" > NOW()`)
|
||||||
.whereRaw(`${TableName.Certificate}."notAfter" <= NOW() + INTERVAL '${options.alertBefore}'`)
|
.whereRaw(`"${TableName.Certificate}"."notAfter" <= NOW() + ?::interval`, [options.alertBefore])
|
||||||
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
.whereNot(`${TableName.Certificate}.status`, "revoked");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,7 +417,7 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.PkiAlertHistoryCertificate}.alertHistoryId`
|
`${TableName.PkiAlertHistoryCertificate}.alertHistoryId`
|
||||||
)
|
)
|
||||||
.where(`${TableName.PkiAlertHistory}.alertId`, options.alertId)
|
.where(`${TableName.PkiAlertHistory}.alertId`, options.alertId)
|
||||||
.whereRaw(`"${TableName.PkiAlertHistoryCertificate}"."certificateId" = "${TableName.Certificate}"."id"`)
|
.whereRaw(`"${TableName.PkiAlertHistoryCertificate}"."certificateId" = "${TableName.Certificate}".id`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +477,7 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
`ica.notAfter`
|
`ica.notAfter`
|
||||||
)
|
)
|
||||||
.from(TableName.CertificateAuthority)
|
.from(TableName.CertificateAuthority)
|
||||||
.leftJoin(
|
.innerJoin(
|
||||||
`${TableName.InternalCertificateAuthority} as ica`,
|
`${TableName.InternalCertificateAuthority} as ica`,
|
||||||
`${TableName.CertificateAuthority}.id`,
|
`${TableName.CertificateAuthority}.id`,
|
||||||
`ica.caId`
|
`ica.caId`
|
||||||
@@ -458,12 +488,12 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
if (options?.alertBefore) {
|
if (options?.alertBefore) {
|
||||||
if (options.showFutureMatches) {
|
if (options.showFutureMatches) {
|
||||||
caQuery = caQuery
|
caQuery = caQuery
|
||||||
.whereRaw(`ica."notAfter" > NOW() + INTERVAL '${options.alertBefore}'`)
|
.whereRaw(`ica."notAfter" > NOW() + ?::interval`, [options.alertBefore])
|
||||||
.whereRaw(`ica."notAfter" > NOW()`);
|
.whereRaw(`ica."notAfter" > NOW()`);
|
||||||
} else {
|
} else {
|
||||||
caQuery = caQuery
|
caQuery = caQuery
|
||||||
.whereRaw(`ica."notAfter" > NOW()`)
|
.whereRaw(`ica."notAfter" > NOW()`)
|
||||||
.whereRaw(`ica."notAfter" <= NOW() + INTERVAL '${options.alertBefore}'`);
|
.whereRaw(`ica."notAfter" <= NOW() + ?::interval`, [options.alertBefore]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +548,7 @@ export const pkiAlertV2DALFactory = (db: TDbClient) => {
|
|||||||
findById,
|
findById,
|
||||||
findByIdWithChannels,
|
findByIdWithChannels,
|
||||||
findByProjectId,
|
findByProjectId,
|
||||||
|
findByProjectIdWithCount,
|
||||||
countByProjectId,
|
countByProjectId,
|
||||||
getDistinctProjectIds,
|
getDistinctProjectIds,
|
||||||
findMatchingCertificates
|
findMatchingCertificates
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ import { logger } from "@app/lib/logger";
|
|||||||
import { PkiFilterField, PkiFilterOperator, TPkiFilterRule } from "./pki-alert-v2-types";
|
import { PkiFilterField, PkiFilterOperator, TPkiFilterRule } from "./pki-alert-v2-types";
|
||||||
|
|
||||||
export const sanitizeLikeInput = (input: string): string => {
|
export const sanitizeLikeInput = (input: string): string => {
|
||||||
|
const allowedCharsRegex = new RE2("^[a-zA-Z0-9\\s\\-_\\.@\\*]+$");
|
||||||
|
if (!allowedCharsRegex.test(input)) {
|
||||||
|
throw new Error(
|
||||||
|
"Invalid characters in input. Only alphanumeric characters, spaces, hyphens, underscores, dots, @ and * are allowed."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const backslashRegex = new RE2("\\\\", "g");
|
const backslashRegex = new RE2("\\\\", "g");
|
||||||
const percentRegex = new RE2("%", "g");
|
const percentRegex = new RE2("%", "g");
|
||||||
const underscoreRegex = new RE2("_", "g");
|
const underscoreRegex = new RE2("_", "g");
|
||||||
@@ -169,25 +176,32 @@ const applySanFilter = (query: Knex.QueryBuilder, filter: TPkiFilterRule): Knex.
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return query.where((builder) => {
|
return query.where((builder) => {
|
||||||
value.forEach((v, index) => {
|
value.forEach((v, index) => {
|
||||||
const condition = `${columnName}::text ILIKE ?`;
|
const sanitizedValue = `%"${String(v)}"%`;
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
void builder.whereRaw(condition, [`%"${String(v)}"%`]);
|
void builder.whereRaw(`??."altNames"::text ILIKE ?`, [TableName.Certificate, sanitizedValue]);
|
||||||
} else {
|
} else {
|
||||||
void builder.orWhereRaw(condition, [`%"${String(v)}"%`]);
|
void builder.orWhereRaw(`??."altNames"::text ILIKE ?`, [TableName.Certificate, sanitizedValue]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return query.whereRaw(`${columnName}::text ILIKE ?`, [`%"${String(value)}"%`]);
|
{
|
||||||
|
const sanitizedValue = `%"${String(value)}"%`;
|
||||||
|
return query.whereRaw(`??."altNames"::text ILIKE ?`, [TableName.Certificate, sanitizedValue]);
|
||||||
|
}
|
||||||
|
|
||||||
case PkiFilterOperator.CONTAINS:
|
case PkiFilterOperator.CONTAINS:
|
||||||
return applySanFilter(query, { ...filter, operator: PkiFilterOperator.MATCHES });
|
return applySanFilter(query, { ...filter, operator: PkiFilterOperator.MATCHES });
|
||||||
|
|
||||||
case PkiFilterOperator.STARTS_WITH:
|
case PkiFilterOperator.STARTS_WITH: {
|
||||||
return query.whereRaw(`${columnName}::text ILIKE ?`, [`%"${String(value)}%`]);
|
const startsWithValue = `%"${String(value)}%`;
|
||||||
|
return query.whereRaw(`??."altNames"::text ILIKE ?`, [TableName.Certificate, startsWithValue]);
|
||||||
|
}
|
||||||
|
|
||||||
case PkiFilterOperator.ENDS_WITH:
|
case PkiFilterOperator.ENDS_WITH: {
|
||||||
return query.whereRaw(`${columnName}::text ILIKE ?`, [`%${String(value)}"%`]);
|
const endsWithValue = `%${String(value)}"%`;
|
||||||
|
return query.whereRaw(`??."altNames"::text ILIKE ?`, [TableName.Certificate, endsWithValue]);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.warn(`Unsupported operator for SAN: ${String(filter.operator)}`);
|
logger.warn(`Unsupported operator for SAN: ${String(filter.operator)}`);
|
||||||
@@ -244,7 +258,7 @@ export const applyCaFilters = (
|
|||||||
filters: TPkiFilterRule[],
|
filters: TPkiFilterRule[],
|
||||||
projectId: string
|
projectId: string
|
||||||
): Knex.QueryBuilder => {
|
): Knex.QueryBuilder => {
|
||||||
let filteredQuery = query.where(`${TableName.CertificateAuthority}.projectId`, projectId);
|
let filteredQuery = query.where(`${TableName.CertificateAuthority}.projectId`, projectId).whereNotNull("ica.caId"); // Only include CAs that have internal CA data
|
||||||
|
|
||||||
filters.forEach((filter) => {
|
filters.forEach((filter) => {
|
||||||
switch (filter.field) {
|
switch (filter.field) {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const pkiAlertV2QueueServiceFactory = ({
|
|||||||
const evaluateAlert = async (
|
const evaluateAlert = async (
|
||||||
alert: {
|
alert: {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
name: string;
|
||||||
eventType: string;
|
eventType: string;
|
||||||
alertBefore: string;
|
alertBefore: string;
|
||||||
filters: TPkiFilterRule[];
|
filters: TPkiFilterRule[];
|
||||||
@@ -135,7 +135,7 @@ export const pkiAlertV2QueueServiceFactory = ({
|
|||||||
for (const alert of alerts) {
|
for (const alert of alerts) {
|
||||||
const typedAlert = alert as {
|
const typedAlert = alert as {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
name: string;
|
||||||
eventType: string;
|
eventType: string;
|
||||||
alertBefore: string;
|
alertBefore: string;
|
||||||
filters: TPkiFilterRule[];
|
filters: TPkiFilterRule[];
|
||||||
@@ -147,13 +147,13 @@ export const pkiAlertV2QueueServiceFactory = ({
|
|||||||
await pkiAlertV2Service.sendAlertNotifications(typedAlert.id, certificateIds);
|
await pkiAlertV2Service.sendAlertNotifications(typedAlert.id, certificateIds);
|
||||||
notificationsSent += 1;
|
notificationsSent += 1;
|
||||||
logger.info(
|
logger.info(
|
||||||
`Sent notification for alert ${typedAlert.id} (${typedAlert.slug}) with ${certificateIds.length} certificates`
|
`Sent notification for alert ${typedAlert.id} (${typedAlert.name}) with ${certificateIds.length} certificates`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
alertsProcessed += 1;
|
alertsProcessed += 1;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error, `Failed to process alert ${typedAlert.id} (${typedAlert.slug})`);
|
logger.error(error, `Failed to process alert ${typedAlert.id} (${typedAlert.name})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ type TPkiAlertV2ServiceFactoryDep = {
|
|||||||
| "updateById"
|
| "updateById"
|
||||||
| "deleteById"
|
| "deleteById"
|
||||||
| "findByProjectId"
|
| "findByProjectId"
|
||||||
|
| "findByProjectIdWithCount"
|
||||||
| "countByProjectId"
|
| "countByProjectId"
|
||||||
| "findMatchingCertificates"
|
| "findMatchingCertificates"
|
||||||
|
| "transaction"
|
||||||
>;
|
>;
|
||||||
pkiAlertChannelDAL: Pick<TPkiAlertChannelDALFactory, "create" | "findByAlertId" | "deleteByAlertId" | "insertMany">;
|
pkiAlertChannelDAL: Pick<TPkiAlertChannelDALFactory, "create" | "findByAlertId" | "deleteByAlertId" | "insertMany">;
|
||||||
pkiAlertHistoryDAL: Pick<TPkiAlertHistoryDALFactory, "createWithCertificates" | "findByAlertId">;
|
pkiAlertHistoryDAL: Pick<TPkiAlertHistoryDALFactory, "createWithCertificates" | "findByAlertId">;
|
||||||
@@ -59,7 +61,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
}: TPkiAlertV2ServiceFactoryDep) => {
|
}: TPkiAlertV2ServiceFactoryDep) => {
|
||||||
type TAlertWithChannels = {
|
type TAlertWithChannels = {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
eventType: string;
|
eventType: string;
|
||||||
alertBefore: string;
|
alertBefore: string;
|
||||||
@@ -81,7 +83,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
const formatAlertResponse = (alert: TAlertWithChannels): TAlertV2Response => {
|
const formatAlertResponse = (alert: TAlertWithChannels): TAlertV2Response => {
|
||||||
return {
|
return {
|
||||||
id: alert.id,
|
id: alert.id,
|
||||||
slug: alert.slug,
|
name: alert.name,
|
||||||
description: alert.description,
|
description: alert.description,
|
||||||
eventType: alert.eventType as PkiAlertEventType,
|
eventType: alert.eventType as PkiAlertEventType,
|
||||||
alertBefore: alert.alertBefore,
|
alertBefore: alert.alertBefore,
|
||||||
@@ -103,7 +105,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
|
|
||||||
const createAlert = async ({
|
const createAlert = async ({
|
||||||
projectId,
|
projectId,
|
||||||
slug,
|
name,
|
||||||
description,
|
description,
|
||||||
eventType,
|
eventType,
|
||||||
alertBefore,
|
alertBefore,
|
||||||
@@ -132,31 +134,36 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Invalid alertBefore format. Use format like '30d', '1w', '3m', '1y'" });
|
throw new BadRequestError({ message: "Invalid alertBefore format. Use format like '30d', '1w', '3m', '1y'" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const alert = await pkiAlertV2DAL.create({
|
return pkiAlertV2DAL.transaction(async (tx) => {
|
||||||
projectId,
|
const alert = await pkiAlertV2DAL.create(
|
||||||
slug,
|
{
|
||||||
description,
|
projectId,
|
||||||
eventType,
|
name,
|
||||||
alertBefore,
|
description,
|
||||||
filters,
|
eventType,
|
||||||
enabled
|
alertBefore,
|
||||||
|
filters,
|
||||||
|
enabled
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const channelInserts = channels.map((channel) => ({
|
||||||
|
alertId: alert.id,
|
||||||
|
channelType: channel.channelType,
|
||||||
|
config: channel.config,
|
||||||
|
enabled: channel.enabled
|
||||||
|
}));
|
||||||
|
|
||||||
|
await pkiAlertChannelDAL.insertMany(channelInserts, tx);
|
||||||
|
|
||||||
|
const completeAlert = await pkiAlertV2DAL.findByIdWithChannels(alert.id, tx);
|
||||||
|
if (!completeAlert) {
|
||||||
|
throw new NotFoundError({ message: "Failed to retrieve created alert" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatAlertResponse(completeAlert as TAlertWithChannels);
|
||||||
});
|
});
|
||||||
|
|
||||||
const channelInserts = channels.map((channel) => ({
|
|
||||||
alertId: alert.id,
|
|
||||||
channelType: channel.channelType,
|
|
||||||
config: channel.config,
|
|
||||||
enabled: channel.enabled
|
|
||||||
}));
|
|
||||||
|
|
||||||
await pkiAlertChannelDAL.insertMany(channelInserts);
|
|
||||||
|
|
||||||
const completeAlert = await pkiAlertV2DAL.findByIdWithChannels(alert.id);
|
|
||||||
if (!completeAlert) {
|
|
||||||
throw new NotFoundError({ message: "Failed to retrieve created alert" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatAlertResponse(completeAlert as TAlertWithChannels);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlertById = async ({
|
const getAlertById = async ({
|
||||||
@@ -208,10 +215,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
|
|
||||||
const filters = { search, eventType, enabled, limit, offset };
|
const filters = { search, eventType, enabled, limit, offset };
|
||||||
|
|
||||||
const [alerts, total] = await Promise.all([
|
const { alerts, total } = await pkiAlertV2DAL.findByProjectIdWithCount(projectId, filters);
|
||||||
pkiAlertV2DAL.findByProjectId(projectId, filters),
|
|
||||||
pkiAlertV2DAL.countByProjectId(projectId, { search, eventType, enabled })
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
alerts: alerts.map((alert) => formatAlertResponse(alert as TAlertWithChannels)),
|
alerts: alerts.map((alert) => formatAlertResponse(alert as TAlertWithChannels)),
|
||||||
@@ -221,7 +225,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
|
|
||||||
const updateAlert = async ({
|
const updateAlert = async ({
|
||||||
alertId,
|
alertId,
|
||||||
slug,
|
name,
|
||||||
description,
|
description,
|
||||||
eventType,
|
eventType,
|
||||||
alertBefore,
|
alertBefore,
|
||||||
@@ -256,41 +260,43 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateData: {
|
const updateData: {
|
||||||
slug?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
eventType?: PkiAlertEventType;
|
eventType?: PkiAlertEventType;
|
||||||
alertBefore?: string;
|
alertBefore?: string;
|
||||||
filters?: TPkiFilterRule[];
|
filters?: TPkiFilterRule[];
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
} = {};
|
} = {};
|
||||||
if (slug !== undefined) updateData.slug = slug;
|
if (name !== undefined) updateData.name = name;
|
||||||
if (description !== undefined) updateData.description = description;
|
if (description !== undefined) updateData.description = description;
|
||||||
if (eventType !== undefined) updateData.eventType = eventType;
|
if (eventType !== undefined) updateData.eventType = eventType;
|
||||||
if (alertBefore !== undefined) updateData.alertBefore = alertBefore;
|
if (alertBefore !== undefined) updateData.alertBefore = alertBefore;
|
||||||
if (filters !== undefined) updateData.filters = filters;
|
if (filters !== undefined) updateData.filters = filters;
|
||||||
if (enabled !== undefined) updateData.enabled = enabled;
|
if (enabled !== undefined) updateData.enabled = enabled;
|
||||||
|
|
||||||
alert = await pkiAlertV2DAL.updateById(alertId, updateData);
|
return pkiAlertV2DAL.transaction(async (tx) => {
|
||||||
|
alert = await pkiAlertV2DAL.updateById(alertId, updateData, tx);
|
||||||
|
|
||||||
if (channels) {
|
if (channels) {
|
||||||
await pkiAlertChannelDAL.deleteByAlertId(alertId);
|
await pkiAlertChannelDAL.deleteByAlertId(alertId, tx);
|
||||||
|
|
||||||
const channelInserts = channels.map((channel) => ({
|
const channelInserts = channels.map((channel) => ({
|
||||||
alertId,
|
alertId,
|
||||||
channelType: channel.channelType,
|
channelType: channel.channelType,
|
||||||
config: channel.config,
|
config: channel.config,
|
||||||
enabled: channel.enabled
|
enabled: channel.enabled
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await pkiAlertChannelDAL.insertMany(channelInserts);
|
await pkiAlertChannelDAL.insertMany(channelInserts, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
const completeAlert = await pkiAlertV2DAL.findByIdWithChannels(alertId);
|
const completeAlert = await pkiAlertV2DAL.findByIdWithChannels(alertId, tx);
|
||||||
if (!completeAlert) {
|
if (!completeAlert) {
|
||||||
throw new NotFoundError({ message: "Failed to retrieve updated alert" });
|
throw new NotFoundError({ message: "Failed to retrieve updated alert" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatAlertResponse(completeAlert as TAlertWithChannels);
|
return formatAlertResponse(completeAlert as TAlertWithChannels);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteAlert = async ({
|
const deleteAlert = async ({
|
||||||
@@ -365,9 +371,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
certificates: result.certificates,
|
certificates: result.certificates,
|
||||||
total: result.total,
|
total: result.total
|
||||||
limit,
|
|
||||||
offset
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -415,9 +419,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
certificates: result.certificates,
|
certificates: result.certificates,
|
||||||
total: result.total,
|
total: result.total
|
||||||
limit,
|
|
||||||
offset
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -445,7 +447,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
|
|
||||||
if (matchingCertificates.length === 0) return;
|
if (matchingCertificates.length === 0) return;
|
||||||
|
|
||||||
let notificationSent = false;
|
let hasNotificationSent = false;
|
||||||
let notificationError: string | undefined;
|
let notificationError: string | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -455,7 +457,7 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const alertBeforeDays = parseTimeToDays((alert as { alertBefore: string }).alertBefore);
|
const alertBeforeDays = parseTimeToDays((alert as { alertBefore: string }).alertBefore);
|
||||||
const alertName = (alert as { slug: string }).slug;
|
const alertName = (alert as { name: string }).name;
|
||||||
|
|
||||||
const emailPromises = emailChannels.map((channel) => {
|
const emailPromises = emailChannels.map((channel) => {
|
||||||
const config = channel.config as TEmailChannelConfig;
|
const config = channel.config as TEmailChannelConfig;
|
||||||
@@ -480,14 +482,14 @@ export const pkiAlertV2ServiceFactory = ({
|
|||||||
|
|
||||||
await Promise.all(emailPromises);
|
await Promise.all(emailPromises);
|
||||||
|
|
||||||
notificationSent = true;
|
hasNotificationSent = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notificationError = error instanceof Error ? error.message : "Unknown error occurred";
|
notificationError = error instanceof Error ? error.message : "Unknown error occurred";
|
||||||
logger.error(error, `Failed to send notifications for alert ${alertId}`);
|
logger.error(error, `Failed to send notifications for alert ${alertId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await pkiAlertHistoryDAL.createWithCertificates(alertId, certificateIds, {
|
await pkiAlertHistoryDAL.createWithCertificates(alertId, certificateIds, {
|
||||||
notificationSent,
|
hasNotificationSent,
|
||||||
notificationError
|
notificationError
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TGenericPermission } from "@app/lib/types";
|
import { TGenericPermission } from "@app/lib/types";
|
||||||
|
|
||||||
const createSecureSlugValidator = () => {
|
const createSecureNameValidator = () => {
|
||||||
const slugRegex = new RE2("^[a-z0-9]+(?:-[a-z0-9]+)*$");
|
// Validates name format: lowercase alphanumeric characters with optional hyphens
|
||||||
return (value: string) => slugRegex.test(value);
|
// Pattern: starts and ends with alphanumeric, allows hyphens between segments
|
||||||
|
// Examples: "my-alert", "alert1", "test-alert-2"
|
||||||
|
const nameRegex = new RE2("^[a-z0-9]+(?:-[a-z0-9]+)*$");
|
||||||
|
return (value: string) => nameRegex.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createSecureAlertBeforeValidator = () => {
|
export const createSecureAlertBeforeValidator = () => {
|
||||||
|
// Validates alertBefore duration format: number followed by time unit
|
||||||
|
// Pattern: one or more digits followed by d(days), w(weeks), m(months), or y(years)
|
||||||
|
// Examples: "30d", "2w", "6m", "1y"
|
||||||
const alertBeforeRegex = new RE2("^\\d+[dwmy]$");
|
const alertBeforeRegex = new RE2("^\\d+[dwmy]$");
|
||||||
return (value: string) => {
|
return (value: string) => {
|
||||||
if (value.length > 32) return false;
|
if (value.length > 32) return false;
|
||||||
@@ -98,11 +104,11 @@ export const CreateChannelSchema = z.object({
|
|||||||
export type TCreateChannel = z.infer<typeof CreateChannelSchema>;
|
export type TCreateChannel = z.infer<typeof CreateChannelSchema>;
|
||||||
|
|
||||||
export const CreatePkiAlertV2Schema = z.object({
|
export const CreatePkiAlertV2Schema = z.object({
|
||||||
slug: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1)
|
||||||
.max(255)
|
.max(255)
|
||||||
.refine(createSecureSlugValidator(), "Must be a valid slug (lowercase, numbers, hyphens only)"),
|
.refine(createSecureNameValidator(), "Must be a valid name (lowercase, numbers, hyphens only)"),
|
||||||
description: z.string().max(1000).optional(),
|
description: z.string().max(1000).optional(),
|
||||||
eventType: z.nativeEnum(PkiAlertEventType),
|
eventType: z.nativeEnum(PkiAlertEventType),
|
||||||
alertBefore: z.string().refine(createSecureAlertBeforeValidator(), "Must be in format like '30d', '1w', '3m', '1y'"),
|
alertBefore: z.string().refine(createSecureAlertBeforeValidator(), "Must be in format like '30d', '1w', '3m', '1y'"),
|
||||||
@@ -169,7 +175,7 @@ export type TCertificatePreview = {
|
|||||||
|
|
||||||
export type TAlertV2Response = {
|
export type TAlertV2Response = {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
name: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
eventType: PkiAlertEventType;
|
eventType: PkiAlertEventType;
|
||||||
alertBefore: string;
|
alertBefore: string;
|
||||||
@@ -196,6 +202,4 @@ export type TListAlertsV2Response = {
|
|||||||
export type TListMatchingCertificatesResponse = {
|
export type TListMatchingCertificatesResponse = {
|
||||||
certificates: TCertificatePreview[];
|
certificates: TCertificatePreview[];
|
||||||
total: number;
|
total: number;
|
||||||
limit: number;
|
|
||||||
offset: number;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ export const useCreatePkiAlertV2 = () => {
|
|||||||
|
|
||||||
return useMutation<TPkiAlertV2, unknown, TCreatePkiAlertV2>({
|
return useMutation<TPkiAlertV2, unknown, TCreatePkiAlertV2>({
|
||||||
mutationFn: async (data) => {
|
mutationFn: async (data) => {
|
||||||
const { data: response } = await apiRequest.post<TPkiAlertV2>("/api/v2/pki/alerts", data);
|
const { data: response } = await apiRequest.post<{ alert: TPkiAlertV2 }>(
|
||||||
return response;
|
"/api/v2/pki/alerts",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return response.alert;
|
||||||
},
|
},
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
@@ -26,11 +29,11 @@ export const useUpdatePkiAlertV2 = () => {
|
|||||||
|
|
||||||
return useMutation<TPkiAlertV2, unknown, TUpdatePkiAlertV2>({
|
return useMutation<TPkiAlertV2, unknown, TUpdatePkiAlertV2>({
|
||||||
mutationFn: async ({ alertId, ...data }) => {
|
mutationFn: async ({ alertId, ...data }) => {
|
||||||
const { data: response } = await apiRequest.patch<TPkiAlertV2>(
|
const { data: response } = await apiRequest.patch<{ alert: TPkiAlertV2 }>(
|
||||||
`/api/v2/pki/alerts/${alertId}`,
|
`/api/v2/pki/alerts/${alertId}`,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
return response;
|
return response.alert;
|
||||||
},
|
},
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
@@ -48,8 +51,10 @@ export const useDeletePkiAlertV2 = () => {
|
|||||||
|
|
||||||
return useMutation<TPkiAlertV2, unknown, TDeletePkiAlertV2>({
|
return useMutation<TPkiAlertV2, unknown, TDeletePkiAlertV2>({
|
||||||
mutationFn: async ({ alertId }) => {
|
mutationFn: async ({ alertId }) => {
|
||||||
const { data } = await apiRequest.delete<TPkiAlertV2>(`/api/v2/pki/alerts/${alertId}`);
|
const { data } = await apiRequest.delete<{ alert: TPkiAlertV2 }>(
|
||||||
return data;
|
`/api/v2/pki/alerts/${alertId}`
|
||||||
|
);
|
||||||
|
return data.alert;
|
||||||
},
|
},
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const fetchPkiAlertsV2 = async (params: TGetPkiAlertsV2): Promise<TGetPkiAlertsV
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchPkiAlertV2ById = async ({ alertId }: TGetPkiAlertV2ById): Promise<TPkiAlertV2> => {
|
const fetchPkiAlertV2ById = async ({ alertId }: TGetPkiAlertV2ById): Promise<TPkiAlertV2> => {
|
||||||
const { data } = await apiRequest.get<TPkiAlertV2>(`/api/v2/pki/alerts/${alertId}`);
|
const { data } = await apiRequest.get<{ alert: TPkiAlertV2 }>(`/api/v2/pki/alerts/${alertId}`);
|
||||||
return data;
|
return data.alert;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchPkiAlertV2MatchingCertificates = async (
|
const fetchPkiAlertV2MatchingCertificates = async (
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export interface TPkiAlertChannelV2 {
|
|||||||
export interface TPkiAlertV2 {
|
export interface TPkiAlertV2 {
|
||||||
id: string;
|
id: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
slug: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
eventType: PkiAlertEventTypeV2;
|
eventType: PkiAlertEventTypeV2;
|
||||||
alertBefore?: string;
|
alertBefore?: string;
|
||||||
@@ -94,7 +94,7 @@ export interface TGetPkiAlertV2ById {
|
|||||||
|
|
||||||
export interface TCreatePkiAlertV2 {
|
export interface TCreatePkiAlertV2 {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
slug: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
eventType: PkiAlertEventTypeV2;
|
eventType: PkiAlertEventTypeV2;
|
||||||
alertBefore?: string;
|
alertBefore?: string;
|
||||||
@@ -105,7 +105,7 @@ export interface TCreatePkiAlertV2 {
|
|||||||
|
|
||||||
export interface TUpdatePkiAlertV2 {
|
export interface TUpdatePkiAlertV2 {
|
||||||
alertId: string;
|
alertId: string;
|
||||||
slug?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
eventType?: PkiAlertEventTypeV2;
|
eventType?: PkiAlertEventTypeV2;
|
||||||
alertBefore?: string;
|
alertBefore?: string;
|
||||||
@@ -172,11 +172,11 @@ export const pkiAlertChannelV2Schema = z.object({
|
|||||||
|
|
||||||
export const createPkiAlertV2Schema = z.object({
|
export const createPkiAlertV2Schema = z.object({
|
||||||
projectId: z.string().uuid(),
|
projectId: z.string().uuid(),
|
||||||
slug: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1)
|
||||||
.max(255)
|
.max(255)
|
||||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Must be a valid slug (lowercase, numbers, hyphens only)"),
|
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Must be a valid name (lowercase, numbers, hyphens only)"),
|
||||||
description: z.string().max(1000).optional(),
|
description: z.string().max(1000).optional(),
|
||||||
eventType: z.nativeEnum(PkiAlertEventTypeV2),
|
eventType: z.nativeEnum(PkiAlertEventTypeV2),
|
||||||
alertBefore: z
|
alertBefore: z
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const PkiAlertsV2Page = ({ hideContainer = false }: Props) => {
|
|||||||
const [deleteModal, setDeleteModal] = useState<{
|
const [deleteModal, setDeleteModal] = useState<{
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
alertId?: string;
|
alertId?: string;
|
||||||
slug?: string;
|
name?: string;
|
||||||
}>({
|
}>({
|
||||||
isOpen: false
|
isOpen: false
|
||||||
});
|
});
|
||||||
@@ -113,7 +113,7 @@ export const PkiAlertsV2Page = ({ hideContainer = false }: Props) => {
|
|||||||
setDeleteModal({
|
setDeleteModal({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
alertId: alert.id,
|
alertId: alert.id,
|
||||||
slug: alert.slug
|
name: alert.name
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -199,8 +199,8 @@ export const PkiAlertsV2Page = ({ hideContainer = false }: Props) => {
|
|||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={deleteModal.isOpen}
|
isOpen={deleteModal.isOpen}
|
||||||
deleteKey="delete"
|
deleteKey="delete"
|
||||||
title={`Delete PKI Alert "${deleteModal.slug}"`}
|
title={`Delete PKI Alert "${deleteModal.name}"`}
|
||||||
onChange={(isOpen) => setDeleteModal({ isOpen, alertId: undefined, slug: undefined })}
|
onChange={(isOpen) => setDeleteModal({ isOpen, alertId: undefined, name: undefined })}
|
||||||
onDeleteApproved={handleDeleteAlert}
|
onDeleteApproved={handleDeleteAlert}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export const CreatePkiAlertV2FormSteps = () => {
|
|||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Alert Type" isError={Boolean(error)} errorText={error?.message}>
|
<FormControl label="Alert Type" isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={field.value}
|
value={field.value}
|
||||||
onValueChange={(value) => field.onChange(value)}
|
onValueChange={(value) => field.onChange(value)}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
@@ -185,9 +185,9 @@ export const CreatePkiAlertV2FormSteps = () => {
|
|||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="slug"
|
name="name"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Alert Slug" isError={Boolean(error)} errorText={error?.message}>
|
<FormControl label="Alert Name" isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Input {...field} placeholder="e.g., prod-cert-expiring-soon" />
|
<Input {...field} placeholder="e.g., prod-cert-expiring-soon" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -490,12 +490,12 @@ export const CreatePkiAlertV2FormSteps = () => {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FormControl label="Email Recipients">
|
<FormControl label="Email Recipients">
|
||||||
<TextArea
|
<TextArea
|
||||||
defaultValue={
|
value={
|
||||||
Array.isArray((watchedChannels?.[0]?.config as any)?.recipients)
|
Array.isArray((watchedChannels?.[0]?.config as any)?.recipients)
|
||||||
? (watchedChannels[0].config as any).recipients.join(", ")
|
? (watchedChannels[0].config as any).recipients.join(", ")
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
onBlur={(e) => {
|
onChange={(e) => {
|
||||||
const emailList = e.target.value
|
const emailList = e.target.value
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((email) => email.trim())
|
.map((email) => email.trim())
|
||||||
@@ -522,7 +522,7 @@ export const CreatePkiAlertV2FormSteps = () => {
|
|||||||
<span className="text-sm text-mineshaft-300">Basic Information</span>
|
<span className="text-sm text-mineshaft-300">Basic Information</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-x-8 gap-y-2">
|
<div className="flex flex-wrap gap-x-8 gap-y-2">
|
||||||
<GenericFieldLabel label="Slug">{watch("slug") || "Not specified"}</GenericFieldLabel>
|
<GenericFieldLabel label="Name">{watch("name") || "Not specified"}</GenericFieldLabel>
|
||||||
<GenericFieldLabel label="Event Type">
|
<GenericFieldLabel label="Event Type">
|
||||||
{formatEventType(watchedEventType)}
|
{formatEventType(watchedEventType)}
|
||||||
</GenericFieldLabel>
|
</GenericFieldLabel>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const FORM_TABS: { name: string; key: string; fields: (keyof TFormData)[] }[] =
|
|||||||
{
|
{
|
||||||
name: "Details",
|
name: "Details",
|
||||||
key: "basicInfo",
|
key: "basicInfo",
|
||||||
fields: ["slug", "description", "alertBefore"]
|
fields: ["name", "description", "alertBefore"]
|
||||||
},
|
},
|
||||||
{ name: "Filters", key: "filterRules", fields: ["filters"] },
|
{ name: "Filters", key: "filterRules", fields: ["filters"] },
|
||||||
{ name: "Preview", key: "preview", fields: [] },
|
{ name: "Preview", key: "preview", fields: [] },
|
||||||
@@ -61,7 +61,7 @@ export const CreatePkiAlertV2Modal = ({ isOpen, onOpenChange, alertToEdit, alert
|
|||||||
resolver: zodResolver(isEditing ? updatePkiAlertV2Schema : createPkiAlertV2Schema),
|
resolver: zodResolver(isEditing ? updatePkiAlertV2Schema : createPkiAlertV2Schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
projectId: currentProject?.id || "",
|
projectId: currentProject?.id || "",
|
||||||
slug: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
eventType: PkiAlertEventTypeV2.EXPIRATION,
|
eventType: PkiAlertEventTypeV2.EXPIRATION,
|
||||||
alertBefore: "30d",
|
alertBefore: "30d",
|
||||||
@@ -93,7 +93,7 @@ export const CreatePkiAlertV2Modal = ({ isOpen, onOpenChange, alertToEdit, alert
|
|||||||
if (editingAlert && isEditing) {
|
if (editingAlert && isEditing) {
|
||||||
reset({
|
reset({
|
||||||
projectId: currentProject?.id || "",
|
projectId: currentProject?.id || "",
|
||||||
slug: editingAlert.slug,
|
name: editingAlert.name,
|
||||||
description: editingAlert.description || "",
|
description: editingAlert.description || "",
|
||||||
eventType: editingAlert.eventType,
|
eventType: editingAlert.eventType,
|
||||||
alertBefore: editingAlert.alertBefore || "30d",
|
alertBefore: editingAlert.alertBefore || "30d",
|
||||||
@@ -121,7 +121,7 @@ export const CreatePkiAlertV2Modal = ({ isOpen, onOpenChange, alertToEdit, alert
|
|||||||
} else if (!isEditing) {
|
} else if (!isEditing) {
|
||||||
reset({
|
reset({
|
||||||
projectId: currentProject?.id || "",
|
projectId: currentProject?.id || "",
|
||||||
slug: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
eventType: PkiAlertEventTypeV2.EXPIRATION,
|
eventType: PkiAlertEventTypeV2.EXPIRATION,
|
||||||
alertBefore: "30d",
|
alertBefore: "30d",
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const PkiAlertV2Row = ({ alert, onView, onEdit, onDelete }: Props) => {
|
|||||||
<Tr>
|
<Tr>
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="font-medium text-gray-200">{alert.slug}</div>
|
<div className="font-medium text-gray-200">{alert.name}</div>
|
||||||
{alert.description && (
|
{alert.description && (
|
||||||
<Tooltip content={alert.description}>
|
<Tooltip content={alert.description}>
|
||||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const UpdatePkiAlertV2Modal = ({ isOpen, onOpenChange, alertId }: Props)
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (alert) {
|
if (alert) {
|
||||||
reset({
|
reset({
|
||||||
slug: alert.slug,
|
name: alert.name,
|
||||||
description: alert.description || "",
|
description: alert.description || "",
|
||||||
eventType: alert.eventType,
|
eventType: alert.eventType,
|
||||||
alertBefore: alert.alertBefore || "",
|
alertBefore: alert.alertBefore || "",
|
||||||
@@ -190,7 +190,7 @@ export const UpdatePkiAlertV2Modal = ({ isOpen, onOpenChange, alertId }: Props)
|
|||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="slug"
|
name="name"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Alert Name" isError={Boolean(error)} errorText={error?.message}>
|
<FormControl label="Alert Name" isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Input {...field} placeholder="certificate-expiration-alert" />
|
<Input {...field} placeholder="certificate-expiration-alert" />
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export const ViewPkiAlertV2Modal = ({ isOpen, onOpenChange, alertId }: Props) =>
|
|||||||
<ModalContent title="Certificate Alert Details" className="max-w-6xl">
|
<ModalContent title="Certificate Alert Details" className="max-w-6xl">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-xl font-semibold text-gray-200">{alert.slug}</h2>
|
<h2 className="text-xl font-semibold text-gray-200">{alert.name}</h2>
|
||||||
<Badge variant={alert.enabled ? "success" : "neutral"}>
|
<Badge variant={alert.enabled ? "success" : "neutral"}>
|
||||||
{alert.enabled ? "Enabled" : "Disabled"}
|
{alert.enabled ? "Enabled" : "Disabled"}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -176,11 +176,11 @@ export const ViewPkiAlertV2Modal = ({ isOpen, onOpenChange, alertId }: Props) =>
|
|||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="text-lg font-medium text-gray-200">Filter Rules</h3>
|
<h3 className="text-lg font-medium text-gray-200">Filter Rules</h3>
|
||||||
{alert.filters.length === 0 ? (
|
{(alert.filters || []).length === 0 ? (
|
||||||
<p className="text-gray-400">No filter rules configured</p>
|
<p className="text-gray-400">No filter rules configured</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{alert.filters.map((filter) => {
|
{(alert.filters || []).map((filter) => {
|
||||||
const formatFilterText = () => {
|
const formatFilterText = () => {
|
||||||
const field = filter.field.replace(/_/g, " ");
|
const field = filter.field.replace(/_/g, " ");
|
||||||
const operator = filter.operator.replace(/_/g, " ");
|
const operator = filter.operator.replace(/_/g, " ");
|
||||||
@@ -207,14 +207,14 @@ export const ViewPkiAlertV2Modal = ({ isOpen, onOpenChange, alertId }: Props) =>
|
|||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="text-lg font-medium text-gray-200">Notification Recipients</h3>
|
<h3 className="text-lg font-medium text-gray-200">Notification Recipients</h3>
|
||||||
{alert.channels.some(
|
{(alert.channels || []).some(
|
||||||
(channel) =>
|
(channel) =>
|
||||||
channel.channelType === "email" && (channel.config as any)?.recipients
|
channel.channelType === "email" && (channel.config as any)?.recipients
|
||||||
) ? (
|
) ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{alert.channels
|
{(alert.channels || [])
|
||||||
.filter(
|
.filter(
|
||||||
(channel) =>
|
(channel) =>
|
||||||
channel.channelType === "email" &&
|
channel.channelType === "email" &&
|
||||||
|
|||||||
Reference in New Issue
Block a user