PKI revamp: frontend implementation

This commit is contained in:
Carlos Monastyrski
2025-10-10 01:50:10 -03:00
parent 4ddce06887
commit 15a6b8da04
57 changed files with 5271 additions and 298 deletions
@@ -127,11 +127,21 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
search?: string;
enrollmentType?: EnrollmentType;
caId?: string;
includeMetrics?: boolean;
expiringDays?: number;
} = {},
tx?: Knex
) => {
try {
const { offset = 0, limit = 20, search, enrollmentType, caId } = options;
const {
offset = 0,
limit = 20,
search,
enrollmentType,
caId,
includeMetrics = false,
expiringDays = 7
} = options;
let query = (tx || db)(TableName.CertificateProfile).where(
`${TableName.CertificateProfile}.projectId`,
@@ -155,6 +165,38 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
query = query.where(`${TableName.CertificateProfile}.caId`, caId);
}
if (includeMetrics) {
const now = new Date();
const expiringDate = new Date();
expiringDate.setDate(now.getDate() + expiringDays);
const certificateProfiles = await query
.leftJoin(TableName.Certificate, `${TableName.CertificateProfile}.id`, `${TableName.Certificate}.profileId`)
.select(
selectAllTableCols(TableName.CertificateProfile),
db.raw("COUNT(certificates.id) as total_certificates"),
db.raw(
'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" > ? THEN 1 END) as active_certificates',
[expiringDate]
),
db.raw(
'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" <= ? THEN 1 END) as expired_certificates',
[now]
),
db.raw(
'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" > ? AND certificates."notAfter" <= ? THEN 1 END) as expiring_certificates',
[now, expiringDate]
),
db.raw('COUNT(CASE WHEN certificates."revokedAt" IS NOT NULL THEN 1 END) as revoked_certificates')
)
.groupBy(`${TableName.CertificateProfile}.id`)
.orderBy(`${TableName.CertificateProfile}.createdAt`, "desc")
.offset(offset)
.limit(limit);
return certificateProfiles;
}
const certificateProfiles = await query
.select(selectAllTableCols(TableName.CertificateProfile))
.orderBy(`${TableName.CertificateProfile}.createdAt`, "desc")
@@ -239,13 +281,13 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
if (status) {
switch (status) {
case "active":
query = query.where("notAfter", ">", now).where("isRevoked", false);
query = query.where("notAfter", ">", now).whereNull("revokedAt");
break;
case "expired":
query = query.where("notAfter", "<=", now).where("isRevoked", false);
query = query.where("notAfter", "<=", now).whereNull("revokedAt");
break;
case "revoked":
query = query.where("isRevoked", true);
query = query.whereNotNull("revokedAt");
break;
default:
break;
@@ -259,7 +301,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
.select((tx || db).ref("status").withSchema(TableName.Certificate))
.select((tx || db).ref("notBefore").withSchema(TableName.Certificate))
.select((tx || db).ref("notAfter").withSchema(TableName.Certificate))
.select((tx || db).ref("isRevoked").withSchema(TableName.Certificate))
.select((tx || db).ref("revokedAt").withSchema(TableName.Certificate))
.select((tx || db).ref("createdAt").withSchema(TableName.Certificate))
.orderBy("createdAt", "desc")
.offset(offset)
@@ -273,7 +315,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
const getProfileMetrics = async (
profileId: string,
expiringDays: number = 30,
expiringDays: number = 7,
tx?: Knex
): Promise<TCertificateProfileMetrics> => {
try {
@@ -285,13 +327,15 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
.where("profileId", profileId)
.select(
db.raw("COUNT(*) as total_certificates"),
db.raw("COUNT(CASE WHEN NOT is_revoked AND not_after > ? THEN 1 END) as active_certificates", [now]),
db.raw("COUNT(CASE WHEN NOT is_revoked AND not_after <= ? THEN 1 END) as expired_certificates", [now]),
db.raw('COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" > ? THEN 1 END) as active_certificates', [
expiringDate
]),
db.raw('COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" <= ? THEN 1 END) as expired_certificates', [now]),
db.raw(
"COUNT(CASE WHEN NOT is_revoked AND not_after > ? AND not_after <= ? THEN 1 END) as expiring_certificates",
'COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" > ? AND "notAfter" <= ? THEN 1 END) as expiring_certificates',
[now, expiringDate]
),
db.raw("COUNT(CASE WHEN is_revoked THEN 1 END) as revoked_certificates")
db.raw('COUNT(CASE WHEN "revokedAt" IS NOT NULL THEN 1 END) as revoked_certificates')
)
.first();
@@ -466,7 +466,9 @@ describe("CertificateProfileService", () => {
limit: 20,
search: undefined,
enrollmentType: undefined,
caId: undefined
caId: undefined,
includeMetrics: false,
expiringDays: 30
});
});
@@ -486,7 +488,51 @@ describe("CertificateProfileService", () => {
limit: 5,
search: "test",
enrollmentType: EnrollmentType.API,
caId: "ca-123"
caId: "ca-123",
includeMetrics: false,
expiringDays: 30
});
});
it("should list profiles with metrics when includeMetrics is true", async () => {
const mockProfilesWithMetrics = [
{
...sampleProfile,
total_certificates: 10,
active_certificates: 8,
expired_certificates: 1,
expiring_certificates: 1,
revoked_certificates: 0
}
];
(mockCertificateProfileDAL.findByProjectId as any).mockResolvedValue(mockProfilesWithMetrics);
const result = await service.listProfiles({
...mockActor,
projectId: "project-123",
includeMetrics: true,
expiringDays: 15
});
expect(result.profiles).toHaveLength(1);
expect(result.profiles[0]).toHaveProperty("metrics");
expect(result.profiles[0].metrics).toEqual({
profileId: sampleProfile.id,
totalCertificates: 10,
activeCertificates: 8,
expiredCertificates: 1,
expiringCertificates: 1,
revokedCertificates: 0
});
expect(mockCertificateProfileDAL.findByProjectId).toHaveBeenCalledWith("project-123", {
offset: 0,
limit: 20,
search: undefined,
enrollmentType: undefined,
caId: undefined,
includeMetrics: true,
expiringDays: 15
});
});
});
@@ -23,7 +23,8 @@ import {
TCertificateProfileInsert,
TCertificateProfileMetrics,
TCertificateProfileUpdate,
TCertificateProfileWithConfigs
TCertificateProfileWithConfigs,
TCertificateProfileWithRawMetrics
} from "./certificate-profile-types";
export type TCertificateProfileCreateData = Omit<TCertificateProfileInsert, "estConfigId" | "apiConfigId"> & {
@@ -48,10 +49,6 @@ const convertDalToService = (dalResult: Record<string, unknown>): TCertificatePr
} as TCertificateProfile;
};
const convertDalArrayToService = (dalResults: Record<string, unknown>[]): TCertificateProfile[] => {
return dalResults.map(convertDalToService);
};
const validateEnrollmentConfig = async (data: {
enrollmentType: EnrollmentType;
estConfig?: TEstConfigData | null;
@@ -289,14 +286,18 @@ export const certificateProfileServiceFactory = ({
actorId,
actorAuthMethod,
actorOrgId,
profileId
profileId,
includeMetrics = false,
expiringDays = 30
}: {
actor: ActorType;
actorId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string;
profileId: string;
}): Promise<TCertificateProfile> => {
includeMetrics?: boolean;
expiringDays?: number;
}): Promise<TCertificateProfile & { metrics?: TCertificateProfileMetrics }> => {
const profile = await certificateProfileDAL.findById(profileId);
if (!profile) {
throw new NotFoundError({ message: "Certificate profile not found" });
@@ -315,7 +316,17 @@ export const certificateProfileServiceFactory = ({
ProjectPermissionSub.CertificateProfiles
);
return convertDalToService(profile);
const converted = convertDalToService(profile);
if (includeMetrics) {
const metrics = await certificateProfileDAL.getProfileMetrics(profileId, expiringDays);
return {
...converted,
metrics
};
}
return converted;
};
const getProfileByIdWithConfigs = async ({
@@ -401,7 +412,9 @@ export const certificateProfileServiceFactory = ({
limit = 20,
search,
enrollmentType,
caId
caId,
includeMetrics = false,
expiringDays = 30
}: {
actor: ActorType;
actorId: string;
@@ -413,8 +426,10 @@ export const certificateProfileServiceFactory = ({
search?: string;
enrollmentType?: EnrollmentType;
caId?: string;
includeMetrics?: boolean;
expiringDays?: number;
}): Promise<{
profiles: TCertificateProfile[];
profiles: (TCertificateProfile & { metrics?: TCertificateProfileMetrics })[];
totalCount: number;
}> => {
const { permission } = await permissionService.getProjectPermission({
@@ -435,7 +450,9 @@ export const certificateProfileServiceFactory = ({
limit,
search,
enrollmentType,
caId
caId,
includeMetrics,
expiringDays
});
const totalCount = await certificateProfileDAL.countByProjectId(projectId, {
@@ -444,8 +461,27 @@ export const certificateProfileServiceFactory = ({
caId
});
const convertedProfiles = profiles.map((profile) => {
const converted = convertDalToService(profile);
if (includeMetrics) {
const profileWithMetrics = profile as TCertificateProfileWithRawMetrics;
return {
...converted,
metrics: {
profileId: converted.id,
totalCertificates: parseInt(String(profileWithMetrics.total_certificates || 0), 10),
activeCertificates: parseInt(String(profileWithMetrics.active_certificates || 0), 10),
expiredCertificates: parseInt(String(profileWithMetrics.expired_certificates || 0), 10),
expiringCertificates: parseInt(String(profileWithMetrics.expiring_certificates || 0), 10),
revokedCertificates: parseInt(String(profileWithMetrics.revoked_certificates || 0), 10)
}
};
}
return converted;
});
return {
profiles: convertDalArrayToService(profiles),
profiles: convertedProfiles,
totalCount
};
};
@@ -45,6 +45,7 @@ export type TCertificateProfileWithConfigs = TCertificateProfile & {
autoRenew: boolean;
autoRenewDays?: number;
};
metrics?: TCertificateProfileMetrics;
};
export interface TCertificateProfileMetrics {
@@ -63,6 +64,14 @@ export interface TCertificateProfileCertificate {
status: string;
notBefore: Date;
notAfter: Date;
isRevoked: boolean;
revokedAt: Date | null | undefined;
createdAt: Date;
}
export type TCertificateProfileWithRawMetrics = TCertificateProfile & {
total_certificates?: string;
active_certificates?: string;
expired_certificates?: string;
expiring_certificates?: string;
revoked_certificates?: string;
};