mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 22:51:53 +00:00
PKI revamp: UI improvements
This commit is contained in:
@@ -139,7 +139,7 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
||||
|
||||
if (search) {
|
||||
query = query.where((builder) => {
|
||||
void builder.whereILike("name", `%${search}%`).orWhereILike("description", `%${search}%`);
|
||||
void builder.whereILike("slug", `%${search}%`).orWhereILike("description", `%${search}%`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
||||
|
||||
if (search) {
|
||||
query = query.where((builder) => {
|
||||
void builder.whereILike("name", `%${search}%`).orWhereILike("description", `%${search}%`);
|
||||
void builder.whereILike("slug", `%${search}%`).orWhereILike("description", `%${search}%`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -176,10 +176,10 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findByNameAndProjectId = async (name: string, projectId: string, tx?: Knex) => {
|
||||
const findBySlugAndProjectId = async (slug: string, projectId: string, tx?: Knex) => {
|
||||
try {
|
||||
const certificateTemplateV2 = await (tx || db)(TableName.CertificateTemplateV2)
|
||||
.where({ name, projectId })
|
||||
.where({ slug, projectId })
|
||||
.first();
|
||||
|
||||
if (!certificateTemplateV2) {
|
||||
@@ -188,7 +188,7 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
||||
|
||||
return parseJsonFields(certificateTemplateV2);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find certificate template v2 by name and project id" });
|
||||
throw new DatabaseError({ error, name: "Find certificate template v2 by slug and project id" });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -213,7 +213,7 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
||||
findById,
|
||||
findByProjectId,
|
||||
countByProjectId,
|
||||
findByNameAndProjectId,
|
||||
findBySlugAndProjectId,
|
||||
isTemplateInUse
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
const attributeTypeSchema = z.enum(["common_name"]);
|
||||
@@ -87,7 +88,11 @@ export const templateV2KeyAlgorithmSchema = z.object({
|
||||
|
||||
export const createCertificateTemplateV2Schema = z.object({
|
||||
projectId: z.string().min(1),
|
||||
name: z.string().min(1).max(255),
|
||||
slug: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(255)
|
||||
.regex(new RE2("^[a-z0-9-]+$"), "Slug must contain only lowercase letters, numbers, and hyphens"),
|
||||
description: z.string().max(1000).optional(),
|
||||
attributes: z.array(templateV2AttributeSchema).optional(),
|
||||
keyUsages: templateV2KeyUsagesSchema.optional(),
|
||||
@@ -99,7 +104,12 @@ export const createCertificateTemplateV2Schema = z.object({
|
||||
});
|
||||
|
||||
export const updateCertificateTemplateV2Schema = z.object({
|
||||
name: z.string().min(1).max(255).optional(),
|
||||
slug: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(255)
|
||||
.regex(new RE2("^[a-z0-9-]+$"), "Slug must contain only lowercase letters, numbers, and hyphens")
|
||||
.optional(),
|
||||
description: z.string().max(1000).optional(),
|
||||
attributes: z.array(templateV2AttributeSchema).optional(),
|
||||
keyUsages: templateV2KeyUsagesSchema.optional(),
|
||||
@@ -114,6 +124,11 @@ export const getCertificateTemplateV2ByIdSchema = z.object({
|
||||
id: z.string().uuid()
|
||||
});
|
||||
|
||||
export const getCertificateTemplateV2BySlugSchema = z.object({
|
||||
projectId: z.string().min(1),
|
||||
slug: z.string().min(1)
|
||||
});
|
||||
|
||||
export const listCertificateTemplatesV2Schema = z.object({
|
||||
projectId: z.string().min(1),
|
||||
offset: z.coerce.number().min(0).default(0),
|
||||
|
||||
+6
-4
@@ -27,6 +27,7 @@ describe("CertificateTemplateV2Service", () => {
|
||||
let service: TCertificateTemplateV2ServiceFactory;
|
||||
|
||||
const mockCertificateTemplateV2DAL = {
|
||||
findBySlugAndProjectId: vi.fn(),
|
||||
create: vi.fn(),
|
||||
findById: vi.fn(),
|
||||
updateById: vi.fn(),
|
||||
@@ -99,7 +100,7 @@ describe("CertificateTemplateV2Service", () => {
|
||||
const sampleTemplate: TCertificateTemplateV2 = {
|
||||
id: "template-123",
|
||||
projectId: "project-123",
|
||||
name: "Web Server Template",
|
||||
slug: "web-server-template",
|
||||
description: "Template for web server certificates",
|
||||
...samplePolicy,
|
||||
createdAt: new Date(),
|
||||
@@ -136,6 +137,7 @@ describe("CertificateTemplateV2Service", () => {
|
||||
});
|
||||
|
||||
mockCertificateTemplateV2DAL.findByNameAndProjectId.mockResolvedValue(null);
|
||||
mockCertificateTemplateV2DAL.findBySlugAndProjectId.mockResolvedValue(null);
|
||||
|
||||
service = certificateTemplateV2ServiceFactory({
|
||||
certificateTemplateV2DAL: mockCertificateTemplateV2DAL as TCertificateTemplateV2DALFactory,
|
||||
@@ -149,7 +151,7 @@ describe("CertificateTemplateV2Service", () => {
|
||||
|
||||
describe("createTemplateV2", () => {
|
||||
const createData: Omit<TCertificateTemplateV2Insert, "projectId"> = {
|
||||
name: "Test Template",
|
||||
slug: "test-template",
|
||||
description: "Test description",
|
||||
...samplePolicy
|
||||
};
|
||||
@@ -239,7 +241,7 @@ describe("CertificateTemplateV2Service", () => {
|
||||
|
||||
describe("updateTemplateV2", () => {
|
||||
it("should update template with valid data", async () => {
|
||||
const updateData = { name: "Updated Template Name" };
|
||||
const updateData = { slug: "updated-template-name" };
|
||||
const updatedTemplate = { ...sampleTemplate, ...updateData };
|
||||
|
||||
mockCertificateTemplateV2DAL.findById.mockResolvedValue(sampleTemplate);
|
||||
@@ -263,7 +265,7 @@ describe("CertificateTemplateV2Service", () => {
|
||||
service.updateTemplateV2({
|
||||
...mockActor,
|
||||
templateId: "nonexistent-template",
|
||||
data: { name: "Updated Name" }
|
||||
data: { slug: "updated-name" }
|
||||
})
|
||||
).rejects.toThrow(NotFoundError);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import RE2 from "re2";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||
import { TCertificateTemplateV2DALFactory } from "./certificate-template-v2-dal";
|
||||
@@ -115,6 +117,28 @@ export const certificateTemplateV2ServiceFactory = ({
|
||||
);
|
||||
};
|
||||
|
||||
const generateTemplateSlug = (baseSlug?: string): string => {
|
||||
if (baseSlug) {
|
||||
return slugify(baseSlug);
|
||||
}
|
||||
return slugify(alphaNumericNanoId(12));
|
||||
};
|
||||
|
||||
const ensureUniqueSlug = async (projectId: string, desiredSlug: string, templateId?: string): Promise<string> => {
|
||||
const existingTemplate = await certificateTemplateV2DAL.findBySlugAndProjectId(desiredSlug, projectId);
|
||||
if (!existingTemplate || (templateId && existingTemplate.id === templateId)) {
|
||||
return desiredSlug;
|
||||
}
|
||||
const alternativeSlug = `${desiredSlug}-${alphaNumericNanoId(8)}`;
|
||||
const existingAlternative = await certificateTemplateV2DAL.findBySlugAndProjectId(alternativeSlug, projectId);
|
||||
if (!existingAlternative) {
|
||||
return alternativeSlug;
|
||||
}
|
||||
|
||||
const randomSlug = slugify(alphaNumericNanoId(12));
|
||||
return randomSlug;
|
||||
};
|
||||
|
||||
const validateRequestAgainstPolicy = (
|
||||
template: TCertificateTemplateV2,
|
||||
request: TCertificateRequest
|
||||
@@ -361,8 +385,12 @@ export const certificateTemplateV2ServiceFactory = ({
|
||||
keyAlgorithm: data.keyAlgorithm
|
||||
});
|
||||
|
||||
const slug = data.slug || generateTemplateSlug();
|
||||
const uniqueSlug = await ensureUniqueSlug(projectId, slug);
|
||||
|
||||
const template = await certificateTemplateV2DAL.create({
|
||||
...data,
|
||||
slug: uniqueSlug,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -417,7 +445,13 @@ export const certificateTemplateV2ServiceFactory = ({
|
||||
validateTemplatePolicy(mergedPolicy);
|
||||
}
|
||||
|
||||
const updatedTemplate = await certificateTemplateV2DAL.updateById(templateId, data);
|
||||
const updateData = { ...data };
|
||||
if (data.slug && typeof data.slug === "string" && data.slug !== existingTemplate.slug) {
|
||||
const uniqueSlug = await ensureUniqueSlug(existingTemplate.projectId, data.slug, templateId);
|
||||
updateData.slug = uniqueSlug;
|
||||
}
|
||||
|
||||
const updatedTemplate = await certificateTemplateV2DAL.updateById(templateId, updateData);
|
||||
if (!updatedTemplate) {
|
||||
throw new NotFoundError({ message: "Failed to update certificate template" });
|
||||
}
|
||||
@@ -459,6 +493,43 @@ export const certificateTemplateV2ServiceFactory = ({
|
||||
return template;
|
||||
};
|
||||
|
||||
const getTemplateV2BySlug = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
projectId,
|
||||
slug
|
||||
}: {
|
||||
actor: ActorType;
|
||||
actorId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId: string;
|
||||
projectId: string;
|
||||
slug: string;
|
||||
}): Promise<TCertificateTemplateV2> => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.CertificateManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionPkiTemplateActions.Read,
|
||||
ProjectPermissionSub.CertificateTemplates
|
||||
);
|
||||
|
||||
const template = await certificateTemplateV2DAL.findBySlugAndProjectId(slug, projectId);
|
||||
if (!template) {
|
||||
throw new NotFoundError({ message: "Certificate template not found" });
|
||||
}
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
const listTemplatesV2 = async ({
|
||||
actor,
|
||||
actorId,
|
||||
@@ -571,6 +642,7 @@ export const certificateTemplateV2ServiceFactory = ({
|
||||
createTemplateV2,
|
||||
updateTemplateV2,
|
||||
getTemplateV2ById,
|
||||
getTemplateV2BySlug,
|
||||
listTemplatesV2,
|
||||
deleteTemplateV2,
|
||||
validateCertificateRequest
|
||||
|
||||
@@ -74,7 +74,7 @@ export type TCertificateTemplateV2Insert = Omit<
|
||||
export type TCertificateTemplateV2Update = Partial<
|
||||
Pick<
|
||||
TCertificateTemplateV2,
|
||||
| "name"
|
||||
| "slug"
|
||||
| "description"
|
||||
| "attributes"
|
||||
| "keyUsages"
|
||||
|
||||
Reference in New Issue
Block a user