mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 16:27:39 +00:00
Addressed PR comments
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { CertificateProfilesSchema } from "@app/db/schemas";
|
||||
@@ -6,12 +7,7 @@ import { ApiDocsTags } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import {
|
||||
createCertificateProfileSchema,
|
||||
deleteCertificateProfileSchema,
|
||||
listCertificateProfilesSchema,
|
||||
updateCertificateProfileSchema
|
||||
} from "@app/services/certificate-profile/certificate-profile-schemas";
|
||||
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
|
||||
|
||||
export const registerCertificateProfilesRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
@@ -23,7 +19,57 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateProfiles],
|
||||
body: createCertificateProfileSchema,
|
||||
body: z
|
||||
.object({
|
||||
projectId: z.string().min(1),
|
||||
caId: z.string().uuid(),
|
||||
certificateTemplateId: z.string().uuid(),
|
||||
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(),
|
||||
enrollmentType: z.nativeEnum(EnrollmentType),
|
||||
estConfig: z
|
||||
.object({
|
||||
disableBootstrapCaValidation: z.boolean().default(false),
|
||||
passphrase: z.string().min(1),
|
||||
encryptedCaChain: z.string()
|
||||
})
|
||||
.optional(),
|
||||
apiConfig: z
|
||||
.object({
|
||||
autoRenew: z.boolean().default(false),
|
||||
autoRenewDays: z.number().min(1).max(365).optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.enrollmentType === EnrollmentType.EST) {
|
||||
if (!data.estConfig) {
|
||||
return false;
|
||||
}
|
||||
if (data.apiConfig) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (data.enrollmentType === EnrollmentType.API) {
|
||||
if (!data.apiConfig) {
|
||||
return false;
|
||||
}
|
||||
if (data.estConfig) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"EST enrollment type requires EST configuration and cannot have API configuration. API enrollment type requires API configuration and cannot have EST configuration."
|
||||
}
|
||||
),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificateProfile: CertificateProfilesSchema
|
||||
@@ -68,7 +114,13 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateProfiles],
|
||||
querystring: listCertificateProfilesSchema.extend({
|
||||
querystring: z.object({
|
||||
projectId: z.string().min(1),
|
||||
offset: z.coerce.number().min(0).default(0),
|
||||
limit: z.coerce.number().min(1).max(100).default(20),
|
||||
search: z.string().optional(),
|
||||
enrollmentType: z.nativeEnum(EnrollmentType).optional(),
|
||||
caId: z.string().uuid().optional(),
|
||||
includeMetrics: z.coerce.boolean().optional().default(false),
|
||||
expiringDays: z.coerce.number().min(1).max(365).optional().default(7)
|
||||
}),
|
||||
@@ -209,7 +261,8 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
||||
event: {
|
||||
type: EventType.GET_CERTIFICATE_PROFILE,
|
||||
metadata: {
|
||||
certificateProfileId: certificateProfile.id
|
||||
certificateProfileId: certificateProfile.id,
|
||||
name: certificateProfile.slug
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -266,7 +319,48 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
||||
params: z.object({
|
||||
id: z.string().min(1)
|
||||
}),
|
||||
body: updateCertificateProfileSchema,
|
||||
body: z
|
||||
.object({
|
||||
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(),
|
||||
enrollmentType: z.nativeEnum(EnrollmentType).optional(),
|
||||
estConfig: z
|
||||
.object({
|
||||
disableBootstrapCaValidation: z.boolean().default(false),
|
||||
passphrase: z.string().min(1),
|
||||
encryptedCaChain: z.string()
|
||||
})
|
||||
.optional(),
|
||||
apiConfig: z
|
||||
.object({
|
||||
autoRenew: z.boolean().default(false),
|
||||
autoRenewDays: z.number().min(1).max(365).optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.enrollmentType === EnrollmentType.EST) {
|
||||
if (data.apiConfig) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (data.enrollmentType === EnrollmentType.API) {
|
||||
if (data.estConfig) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Cannot have EST config with API enrollment type or API config with EST enrollment type."
|
||||
}
|
||||
),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificateProfile: CertificateProfilesSchema
|
||||
@@ -309,7 +403,9 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateProfiles],
|
||||
params: deleteCertificateProfileSchema,
|
||||
params: z.object({
|
||||
id: z.string().uuid()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificateProfile: CertificateProfilesSchema
|
||||
@@ -332,7 +428,8 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
||||
event: {
|
||||
type: EventType.DELETE_CERTIFICATE_PROFILE,
|
||||
metadata: {
|
||||
certificateProfileId: certificateProfile.id
|
||||
certificateProfileId: certificateProfile.id,
|
||||
name: certificateProfile.slug
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -117,7 +117,12 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
||||
type: EventType.CREATE_CERTIFICATE_TEMPLATE,
|
||||
metadata: {
|
||||
certificateTemplateId: certificateTemplate.id,
|
||||
caId: certificateTemplate.caId,
|
||||
pkiCollectionId: certificateTemplate.pkiCollectionId as string,
|
||||
name: certificateTemplate.name,
|
||||
commonName: certificateTemplate.commonName,
|
||||
subjectAlternativeName: certificateTemplate.subjectAlternativeName,
|
||||
ttl: certificateTemplate.ttl,
|
||||
projectId: certificateTemplate.projectId
|
||||
}
|
||||
}
|
||||
@@ -181,7 +186,12 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
||||
type: EventType.UPDATE_CERTIFICATE_TEMPLATE,
|
||||
metadata: {
|
||||
certificateTemplateId: certificateTemplate.id,
|
||||
name: certificateTemplate.name
|
||||
name: certificateTemplate.name,
|
||||
caId: certificateTemplate.caId,
|
||||
pkiCollectionId: certificateTemplate.pkiCollectionId as string,
|
||||
commonName: certificateTemplate.commonName,
|
||||
subjectAlternativeName: certificateTemplate.subjectAlternativeName,
|
||||
ttl: certificateTemplate.ttl
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,18 +4,107 @@ import { CertificateTemplatesV2Schema } from "@app/db/schemas";
|
||||
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 { slugSchema } from "@app/server/lib/schemas";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import {
|
||||
certificateRequestSchema,
|
||||
createCertificateTemplateV2Schema,
|
||||
deleteCertificateTemplateV2Schema,
|
||||
getCertificateTemplateV2ByIdSchema,
|
||||
listCertificateTemplatesV2Schema,
|
||||
updateCertificateTemplateV2Schema
|
||||
} from "@app/services/certificate-template-v2/certificate-template-v2-schemas";
|
||||
CertDurationUnit,
|
||||
CertExtendedKeyUsageType,
|
||||
CertIncludeType,
|
||||
CertKeyUsageType,
|
||||
CertSubjectAlternativeNameType,
|
||||
CertSubjectAttributeType
|
||||
} from "@app/services/certificate-common/certificate-constants";
|
||||
|
||||
export const registerCertificateTemplatesV2Router = async (server: FastifyZodProvider) => {
|
||||
const templateV2AttributeSchema = z
|
||||
.object({
|
||||
type: z.nativeEnum(CertSubjectAttributeType),
|
||||
include: z.nativeEnum(CertIncludeType),
|
||||
value: z.array(z.string()).optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.type === CertSubjectAttributeType.COMMON_NAME && data.value && data.value.length > 1) {
|
||||
return false;
|
||||
}
|
||||
if (data.include === CertIncludeType.MANDATORY && (!data.value || data.value.length > 1)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Common name can only have one value. Mandatory attributes can only have one value or no value (empty)"
|
||||
}
|
||||
);
|
||||
|
||||
const templateV2KeyUsagesSchema = z.object({
|
||||
requiredUsages: z
|
||||
.object({
|
||||
all: z.array(z.nativeEnum(CertKeyUsageType))
|
||||
})
|
||||
.optional(),
|
||||
optionalUsages: z
|
||||
.object({
|
||||
all: z.array(z.nativeEnum(CertKeyUsageType))
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
const templateV2ExtendedKeyUsagesSchema = z.object({
|
||||
requiredUsages: z
|
||||
.object({
|
||||
all: z.array(z.nativeEnum(CertExtendedKeyUsageType))
|
||||
})
|
||||
.optional(),
|
||||
optionalUsages: z
|
||||
.object({
|
||||
all: z.array(z.nativeEnum(CertExtendedKeyUsageType))
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
const templateV2SanSchema = z
|
||||
.object({
|
||||
type: z.nativeEnum(CertSubjectAlternativeNameType),
|
||||
include: z.nativeEnum(CertIncludeType),
|
||||
value: z.array(z.string()).optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.include === CertIncludeType.MANDATORY && (!data.value || data.value.length > 1)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Mandatory SANs can only have one value or no value (empty)"
|
||||
}
|
||||
);
|
||||
|
||||
const templateV2ValiditySchema = z.object({
|
||||
maxDuration: z.object({
|
||||
value: z.number().positive(),
|
||||
unit: z.nativeEnum(CertDurationUnit)
|
||||
}),
|
||||
minDuration: z
|
||||
.object({
|
||||
value: z.number().positive(),
|
||||
unit: z.nativeEnum(CertDurationUnit)
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
const templateV2SignatureAlgorithmSchema = z.object({
|
||||
allowedAlgorithms: z.array(z.string()).min(1),
|
||||
defaultAlgorithm: z.string()
|
||||
});
|
||||
|
||||
const templateV2KeyAlgorithmSchema = z.object({
|
||||
allowedKeyTypes: z.array(z.string()).min(1),
|
||||
defaultKeyType: z.string()
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/",
|
||||
@@ -25,7 +114,36 @@ export const registerCertificateTemplatesV2Router = async (server: FastifyZodPro
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||
body: createCertificateTemplateV2Schema,
|
||||
body: z
|
||||
.object({
|
||||
projectId: z.string().min(1),
|
||||
slug: slugSchema({ min: 1, max: 255 }),
|
||||
description: z.string().max(1000).optional(),
|
||||
attributes: z.array(templateV2AttributeSchema).optional(),
|
||||
keyUsages: templateV2KeyUsagesSchema.optional(),
|
||||
extendedKeyUsages: templateV2ExtendedKeyUsagesSchema.optional(),
|
||||
subjectAlternativeNames: z.array(templateV2SanSchema).optional(),
|
||||
validity: templateV2ValiditySchema.optional(),
|
||||
signatureAlgorithm: templateV2SignatureAlgorithmSchema.optional(),
|
||||
keyAlgorithm: templateV2KeyAlgorithmSchema.optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const hasConstraints =
|
||||
(data.attributes && data.attributes.length > 0) ||
|
||||
(data.subjectAlternativeNames && data.subjectAlternativeNames.length > 0) ||
|
||||
data.keyUsages ||
|
||||
data.extendedKeyUsages ||
|
||||
data.validity ||
|
||||
data.signatureAlgorithm ||
|
||||
data.keyAlgorithm;
|
||||
return hasConstraints;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Certificate template must define at least one constraint (attributes, SANs, key usages, validity, or algorithms)"
|
||||
}
|
||||
),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificateTemplate: CertificateTemplatesV2Schema
|
||||
@@ -70,7 +188,12 @@ export const registerCertificateTemplatesV2Router = async (server: FastifyZodPro
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||
querystring: listCertificateTemplatesV2Schema,
|
||||
querystring: z.object({
|
||||
projectId: z.string().min(1),
|
||||
offset: z.coerce.number().min(0).default(0),
|
||||
limit: z.coerce.number().min(1).max(100).default(20),
|
||||
search: z.string().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificateTemplates: CertificateTemplatesV2Schema.array(),
|
||||
@@ -112,7 +235,9 @@ export const registerCertificateTemplatesV2Router = async (server: FastifyZodPro
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||
params: getCertificateTemplateV2ByIdSchema,
|
||||
params: z.object({
|
||||
id: z.string().uuid()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificateTemplate: CertificateTemplatesV2Schema
|
||||
@@ -154,8 +279,20 @@ export const registerCertificateTemplatesV2Router = async (server: FastifyZodPro
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||
params: getCertificateTemplateV2ByIdSchema,
|
||||
body: updateCertificateTemplateV2Schema,
|
||||
params: z.object({
|
||||
id: z.string().uuid()
|
||||
}),
|
||||
body: z.object({
|
||||
slug: slugSchema({ min: 1, max: 255 }).optional(),
|
||||
description: z.string().max(1000).optional(),
|
||||
attributes: z.array(templateV2AttributeSchema).optional(),
|
||||
keyUsages: templateV2KeyUsagesSchema.optional(),
|
||||
extendedKeyUsages: templateV2ExtendedKeyUsagesSchema.optional(),
|
||||
subjectAlternativeNames: z.array(templateV2SanSchema).optional(),
|
||||
validity: templateV2ValiditySchema.optional(),
|
||||
signatureAlgorithm: templateV2SignatureAlgorithmSchema.optional(),
|
||||
keyAlgorithm: templateV2KeyAlgorithmSchema.optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificateTemplate: CertificateTemplatesV2Schema
|
||||
@@ -198,7 +335,9 @@ export const registerCertificateTemplatesV2Router = async (server: FastifyZodPro
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||
params: deleteCertificateTemplateV2Schema,
|
||||
params: z.object({
|
||||
id: z.string().uuid()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificateTemplate: CertificateTemplatesV2Schema
|
||||
@@ -230,38 +369,4 @@ export const registerCertificateTemplatesV2Router = async (server: FastifyZodPro
|
||||
return { certificateTemplate };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:id/validate",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||
params: getCertificateTemplateV2ByIdSchema,
|
||||
body: z.object({
|
||||
request: certificateRequestSchema
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
valid: z.boolean(),
|
||||
errors: z.array(z.string()).optional()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const result = await server.services.certificateTemplateV2.validateCertificateRequest(
|
||||
req.params.id,
|
||||
req.body.request
|
||||
);
|
||||
|
||||
return {
|
||||
valid: result.isValid,
|
||||
errors: result.errors.length > 0 ? result.errors : undefined
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -6,12 +6,22 @@ import { ms } from "@app/lib/ms";
|
||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||
import {
|
||||
ACMESANType,
|
||||
CertificateOrderStatus,
|
||||
CertKeyAlgorithm,
|
||||
CertSignatureAlgorithm
|
||||
} from "@app/services/certificate/certificate-types";
|
||||
import {
|
||||
validateAltNamesField,
|
||||
validateAndMapAltNameType,
|
||||
validateCaDateField
|
||||
} from "@app/services/certificate-authority/certificate-authority-validators";
|
||||
import {
|
||||
CertExtendedKeyUsageType,
|
||||
CertKeyUsageType,
|
||||
CertSubjectAlternativeNameType
|
||||
} from "@app/services/certificate-common/certificate-constants";
|
||||
import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils";
|
||||
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||
|
||||
@@ -25,18 +35,43 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificates],
|
||||
body: z.object({
|
||||
profileId: z.string().uuid(),
|
||||
commonName: validateTemplateRegexField,
|
||||
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
||||
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional(),
|
||||
notBefore: validateCaDateField.optional(),
|
||||
notAfter: validateCaDateField.optional(),
|
||||
altNames: validateAltNamesField.optional(),
|
||||
signatureAlgorithm: z.string().optional(),
|
||||
keyAlgorithm: z.string().optional()
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
profileId: z.string().uuid(),
|
||||
commonName: validateTemplateRegexField,
|
||||
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||
keyUsages: z.nativeEnum(CertKeyUsageType).array().optional(),
|
||||
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsageType).array().optional(),
|
||||
notBefore: validateCaDateField.optional(),
|
||||
notAfter: validateCaDateField.optional(),
|
||||
subjectAltNames: validateAltNamesField.optional(),
|
||||
signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(),
|
||||
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const hasDateFields = data.notBefore || data.notAfter;
|
||||
const hasTtl = data.ttl;
|
||||
return !(hasDateFields && hasTtl);
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Cannot specify both TTL and notBefore/notAfter. Use either TTL for duration-based validity or notBefore/notAfter for explicit date range."
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.notBefore && data.notAfter) {
|
||||
const notBefore = new Date(data.notBefore);
|
||||
const notAfter = new Date(data.notAfter);
|
||||
return notBefore < notAfter;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "notBefore must be earlier than notAfter"
|
||||
}
|
||||
),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificate: z.string().trim(),
|
||||
@@ -54,8 +89,8 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
commonName: req.body.commonName,
|
||||
keyUsages: req.body.keyUsages,
|
||||
extendedKeyUsages: req.body.extendedKeyUsages,
|
||||
subjectAlternativeNames: req.body.altNames
|
||||
? req.body.altNames
|
||||
altNames: req.body.subjectAltNames
|
||||
? req.body.subjectAltNames
|
||||
.split(", ")
|
||||
.map((name) => name.trim())
|
||||
.map((name) => {
|
||||
@@ -68,7 +103,7 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
url: "uri"
|
||||
} as const;
|
||||
return {
|
||||
type: typeMapping[mappedType.type] as "dns_name" | "ip_address" | "email" | "uri",
|
||||
type: typeMapping[mappedType.type] as CertSubjectAlternativeNameType,
|
||||
value: mappedType.value
|
||||
};
|
||||
})
|
||||
@@ -94,23 +129,16 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
certificateRequest: mappedCertificateRequest
|
||||
});
|
||||
|
||||
const profile = await server.services.certificateProfile.getProfileById({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
profileId: req.body.profileId
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: profile.projectId,
|
||||
projectId: data.projectId,
|
||||
event: {
|
||||
type: EventType.ISSUE_CERTIFICATE_FROM_PROFILE,
|
||||
metadata: {
|
||||
certificateProfileId: req.body.profileId,
|
||||
certificateId: data.certificateId,
|
||||
commonName: req.body.commonName || ""
|
||||
commonName: req.body.commonName || "",
|
||||
profileName: data.profileName
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -128,13 +156,38 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificates],
|
||||
body: z.object({
|
||||
profileId: z.string().uuid(),
|
||||
csr: z.string().trim().min(1).max(4096),
|
||||
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||
notBefore: validateCaDateField.optional(),
|
||||
notAfter: validateCaDateField.optional()
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
profileId: z.string().uuid(),
|
||||
csr: z.string().trim().min(1).max(4096),
|
||||
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||
notBefore: validateCaDateField.optional(),
|
||||
notAfter: validateCaDateField.optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const hasDateFields = data.notBefore || data.notAfter;
|
||||
const hasTtl = data.ttl;
|
||||
return !(hasDateFields && hasTtl);
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Cannot specify both TTL and notBefore/notAfter. Use either TTL for duration-based validity or notBefore/notAfter for explicit date range."
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.notBefore && data.notAfter) {
|
||||
const notBefore = new Date(data.notBefore);
|
||||
const notAfter = new Date(data.notAfter);
|
||||
return notBefore < notAfter;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "notBefore must be earlier than notAfter"
|
||||
}
|
||||
),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificate: z.string().trim(),
|
||||
@@ -161,22 +214,16 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined
|
||||
});
|
||||
|
||||
const profile = await server.services.certificateProfile.getProfileById({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
profileId: req.body.profileId
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: profile.projectId,
|
||||
projectId: data.projectId,
|
||||
event: {
|
||||
type: EventType.SIGN_CERTIFICATE_FROM_PROFILE,
|
||||
metadata: {
|
||||
certificateProfileId: req.body.profileId,
|
||||
certificateId: data.certificateId
|
||||
certificateId: data.certificateId,
|
||||
profileName: data.profileName,
|
||||
commonName: req.body.csr || ""
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -194,48 +241,73 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificates],
|
||||
body: z.object({
|
||||
profileId: z.string().uuid(),
|
||||
subjectAlternativeNames: z
|
||||
.array(
|
||||
z.object({
|
||||
type: z.enum(["dns", "ip"]),
|
||||
value: z.string()
|
||||
})
|
||||
)
|
||||
.min(1),
|
||||
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
||||
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional(),
|
||||
notBefore: validateCaDateField.optional(),
|
||||
notAfter: validateCaDateField.optional(),
|
||||
commonName: validateTemplateRegexField.optional(),
|
||||
signatureAlgorithm: z.string().optional(),
|
||||
keyAlgorithm: z.string().optional()
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
profileId: z.string().uuid(),
|
||||
subjectAlternativeNames: z
|
||||
.array(
|
||||
z.object({
|
||||
type: z.nativeEnum(ACMESANType),
|
||||
value: z.string()
|
||||
})
|
||||
)
|
||||
.min(1),
|
||||
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||
keyUsages: z.nativeEnum(CertKeyUsageType).array().optional(),
|
||||
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsageType).array().optional(),
|
||||
notBefore: validateCaDateField.optional(),
|
||||
notAfter: validateCaDateField.optional(),
|
||||
commonName: validateTemplateRegexField.optional(),
|
||||
signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(),
|
||||
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const hasDateFields = data.notBefore || data.notAfter;
|
||||
const hasTtl = data.ttl;
|
||||
return !(hasDateFields && hasTtl);
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Cannot specify both TTL and notBefore/notAfter. Use either TTL for duration-based validity or notBefore/notAfter for explicit date range."
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.notBefore && data.notAfter) {
|
||||
const notBefore = new Date(data.notBefore);
|
||||
const notAfter = new Date(data.notAfter);
|
||||
return notBefore < notAfter;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "notBefore must be earlier than notAfter"
|
||||
}
|
||||
),
|
||||
response: {
|
||||
200: z.object({
|
||||
orderId: z.string(),
|
||||
status: z.enum(["pending", "processing", "valid", "invalid"]),
|
||||
status: z.nativeEnum(CertificateOrderStatus),
|
||||
subjectAlternativeNames: z.array(
|
||||
z.object({
|
||||
type: z.enum(["dns", "ip"]),
|
||||
type: z.nativeEnum(ACMESANType),
|
||||
value: z.string(),
|
||||
status: z.enum(["pending", "processing", "valid", "invalid"])
|
||||
status: z.nativeEnum(CertificateOrderStatus)
|
||||
})
|
||||
),
|
||||
authorizations: z.array(
|
||||
z.object({
|
||||
identifier: z.object({
|
||||
type: z.enum(["dns", "ip"]),
|
||||
type: z.nativeEnum(ACMESANType),
|
||||
value: z.string()
|
||||
}),
|
||||
status: z.enum(["pending", "processing", "valid", "invalid"]),
|
||||
status: z.nativeEnum(CertificateOrderStatus),
|
||||
expires: z.string().optional(),
|
||||
challenges: z.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
status: z.enum(["pending", "processing", "valid", "invalid"]),
|
||||
status: z.nativeEnum(CertificateOrderStatus),
|
||||
url: z.string(),
|
||||
token: z.string()
|
||||
})
|
||||
@@ -256,7 +328,7 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
actorOrgId: req.permission.orgId,
|
||||
profileId: req.body.profileId,
|
||||
certificateOrder: {
|
||||
subjectAlternativeNames: req.body.subjectAlternativeNames,
|
||||
altNames: req.body.subjectAlternativeNames,
|
||||
validity: {
|
||||
ttl: req.body.ttl
|
||||
},
|
||||
@@ -270,23 +342,15 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
});
|
||||
|
||||
const profile = await server.services.certificateProfile.getProfileById({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
profileId: req.body.profileId
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: profile.projectId,
|
||||
projectId: data.projectId,
|
||||
event: {
|
||||
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
|
||||
metadata: {
|
||||
certificateProfileId: req.body.profileId,
|
||||
orderId: data.orderId,
|
||||
subjectAlternativeNames: req.body.subjectAlternativeNames.map((san) => `${san.type}:${san.value}`)
|
||||
profileName: data.profileName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user