misc: addressed type issue and ux improvements

This commit is contained in:
Sheen Capadngan
2025-05-17 13:00:04 +08:00
parent edefa7698c
commit 56b7328231
12 changed files with 161 additions and 136 deletions
@@ -67,6 +67,8 @@ 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.string("type").notNullable(); t.string("type").notNullable();
t.string("name").notNullable(); t.string("name").notNullable();
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("appConnectionId").nullable(); t.uuid("appConnectionId").nullable();
t.foreign("appConnectionId").references("id").inTable(TableName.AppConnection); t.foreign("appConnectionId").references("id").inTable(TableName.AppConnection);
t.uuid("dnsAppConnectionId").nullable(); t.uuid("dnsAppConnectionId").nullable();
@@ -79,6 +81,13 @@ export async function up(knex: Knex): Promise<void> {
t.binary("credentials"); t.binary("credentials");
t.json("configuration"); t.json("configuration");
t.string("status").notNullable(); t.string("status").notNullable();
t.unique(["projectId", "name"]);
});
}
if (await knex.schema.hasTable(TableName.PkiSubscriber)) {
await knex.schema.alterTable(TableName.PkiSubscriber, (t) => {
t.string("ttl").nullable().alter();
}); });
} }
} }
+2 -2
View File
@@ -25,8 +25,8 @@ export const CertificatesSchema = z.object({
certificateTemplateId: z.string().uuid().nullable().optional(), certificateTemplateId: z.string().uuid().nullable().optional(),
keyUsages: z.string().array().nullable().optional(), keyUsages: z.string().array().nullable().optional(),
extendedKeyUsages: z.string().array().nullable().optional(), extendedKeyUsages: z.string().array().nullable().optional(),
projectId: z.string(), pkiSubscriberId: z.string().uuid().nullable().optional(),
pkiSubscriberId: z.string().uuid().nullable().optional() projectId: z.string()
}); });
export type TCertificates = z.infer<typeof CertificatesSchema>; export type TCertificates = z.infer<typeof CertificatesSchema>;
@@ -13,6 +13,7 @@ export const ExternalCertificateAuthoritiesSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
type: z.string(), type: z.string(),
name: z.string(), name: z.string(),
projectId: z.string(),
appConnectionId: z.string().uuid().nullable().optional(), appConnectionId: z.string().uuid().nullable().optional(),
dnsAppConnectionId: z.string().uuid().nullable().optional(), dnsAppConnectionId: z.string().uuid().nullable().optional(),
certificateAuthorityId: z.string().uuid(), certificateAuthorityId: z.string().uuid(),
+1 -1
View File
@@ -16,7 +16,7 @@ export const PkiSubscribersSchema = z.object({
name: z.string(), name: z.string(),
commonName: z.string(), commonName: z.string(),
subjectAlternativeNames: z.string().array(), subjectAlternativeNames: z.string().array(),
ttl: z.string(), ttl: z.string().nullable().optional(),
keyUsages: z.string().array(), keyUsages: z.string().array(),
extendedKeyUsages: z.string().array(), extendedKeyUsages: z.string().array(),
status: z.string() status: z.string()
@@ -1997,7 +1997,7 @@ interface CreatePkiSubscriber {
caId?: string; caId?: string;
name: string; name: string;
commonName: string; commonName: string;
ttl: string; ttl?: string;
subjectAlternativeNames: string[]; subjectAlternativeNames: string[];
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[]; extendedKeyUsages: CertExtendedKeyUsage[];
@@ -6,15 +6,9 @@ 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 { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-enums"; import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import { import { TCertificateAuthority } from "@app/services/certificate-authority/certificate-authority-types";
TCertificateAuthority,
TCertificateAuthorityInput
} from "@app/services/certificate-authority/certificate-authority-types";
export const registerCertificateAuthorityEndpoints = < export const registerCertificateAuthorityEndpoints = <T extends TCertificateAuthority>({
T extends TCertificateAuthority,
I extends TCertificateAuthorityInput
>({
server, server,
caType, caType,
createSchema, createSchema,
@@ -27,13 +21,13 @@ export const registerCertificateAuthorityEndpoints = <
name: string; name: string;
projectId: string; projectId: string;
status: CaStatus; status: CaStatus;
configuration: I["configuration"]; configuration: T["configuration"];
disableDirectIssuance: boolean; disableDirectIssuance: boolean;
}>; }>;
updateSchema: z.ZodType<{ updateSchema: z.ZodType<{
name?: string; name?: string;
status?: CaStatus; status?: CaStatus;
configuration?: I["configuration"]; configuration?: T["configuration"];
disableDirectIssuance?: boolean; disableDirectIssuance?: boolean;
}>; }>;
responseSchema: z.ZodTypeAny; responseSchema: z.ZodTypeAny;
@@ -90,7 +90,8 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
ttl: z ttl: z
.string() .string()
.trim() .trim()
.refine((val) => ms(val) > 0, "TTL must be a positive number") .refine((val) => !val || ms(val) > 0, "TTL must be a positive number")
.optional()
.describe(PKI_SUBSCRIBERS.CREATE.ttl), .describe(PKI_SUBSCRIBERS.CREATE.ttl),
subjectAlternativeNames: validateAltNameField subjectAlternativeNames: validateAltNameField
.array() .array()
@@ -134,7 +135,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
caId: subscriber.caId ?? undefined, caId: subscriber.caId ?? undefined,
name: subscriber.name, name: subscriber.name,
commonName: subscriber.commonName, commonName: subscriber.commonName,
ttl: subscriber.ttl, ttl: subscriber.ttl ?? undefined,
subjectAlternativeNames: subscriber.subjectAlternativeNames, subjectAlternativeNames: subscriber.subjectAlternativeNames,
keyUsages: subscriber.keyUsages as CertKeyUsage[], keyUsages: subscriber.keyUsages as CertKeyUsage[],
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[] extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
@@ -219,7 +220,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
caId: subscriber.caId ?? undefined, caId: subscriber.caId ?? undefined,
name: subscriber.name, name: subscriber.name,
commonName: subscriber.commonName, commonName: subscriber.commonName,
ttl: subscriber.ttl, ttl: subscriber.ttl ?? undefined,
subjectAlternativeNames: subscriber.subjectAlternativeNames, subjectAlternativeNames: subscriber.subjectAlternativeNames,
keyUsages: subscriber.keyUsages as CertKeyUsage[], keyUsages: subscriber.keyUsages as CertKeyUsage[],
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[] extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
@@ -203,6 +203,7 @@ export const AcmeCertificateAuthorityFns = ({
await appConnectionService.connectAppConnectionById(appConnection.app as AppConnection, dnsAppConnectionId, actor); await appConnectionService.connectAppConnectionById(appConnection.app as AppConnection, dnsAppConnectionId, actor);
const caEntity = await certificateAuthorityDAL.transaction(async (tx) => { const caEntity = await certificateAuthorityDAL.transaction(async (tx) => {
try {
const ca = await certificateAuthorityDAL.create( const ca = await certificateAuthorityDAL.create(
{ {
projectId, projectId,
@@ -217,6 +218,7 @@ export const AcmeCertificateAuthorityFns = ({
dnsAppConnectionId, dnsAppConnectionId,
type: CaType.ACME, type: CaType.ACME,
name, name,
projectId,
configuration: { configuration: {
directoryUrl, directoryUrl,
accountEmail, accountEmail,
@@ -228,7 +230,17 @@ export const AcmeCertificateAuthorityFns = ({
tx tx
); );
return certificateAuthorityDAL.findByIdWithAssociatedCa(ca.id, tx); return await certificateAuthorityDAL.findByIdWithAssociatedCa(ca.id, tx);
} catch (error) {
// @ts-expect-error We're expecting a database error
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (error?.error?.code === "23505") {
throw new BadRequestError({
message: "Certificate authority with the same name already exists in your project"
});
}
throw error;
}
}); });
if (!caEntity.externalCa?.id) { if (!caEntity.externalCa?.id) {
@@ -440,7 +440,7 @@ export const pkiSubscriberServiceFactory = ({
const caCertObj = new x509.X509Certificate(decryptedCaCert); const caCertObj = new x509.X509Certificate(decryptedCaCert);
const notBeforeDate = new Date(); const notBeforeDate = new Date();
const notAfterDate = new Date(new Date().getTime() + ms(subscriber.ttl)); const notAfterDate = new Date(new Date().getTime() + ms(subscriber.ttl ?? "0"));
const caCertNotBeforeDate = new Date(caCertObj.notBefore); const caCertNotBeforeDate = new Date(caCertObj.notBefore);
const caCertNotAfterDate = new Date(caCertObj.notAfter); const caCertNotAfterDate = new Date(caCertObj.notAfter);
@@ -12,7 +12,7 @@ export type TCreatePkiSubscriberDTO = {
name: string; name: string;
commonName: string; commonName: string;
status: PkiSubscriberStatus; status: PkiSubscriberStatus;
ttl: string; ttl?: string;
subjectAlternativeNames: string[]; subjectAlternativeNames: string[];
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[]; extendedKeyUsages: CertExtendedKeyUsage[];
@@ -24,7 +24,7 @@ export type TCreatePkiSubscriberDTO = {
caId: string; caId: string;
name: string; name: string;
commonName: string; commonName: string;
ttl: string; ttl?: string;
subjectAlternativeNames: string[]; subjectAlternativeNames: string[];
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[]; extendedKeyUsages: CertExtendedKeyUsage[];
@@ -45,7 +45,7 @@ const schema = z
caId: z.string().min(1, "Issuing CA is required"), caId: z.string().min(1, "Issuing CA is required"),
commonName: z.string().trim().min(1, "Common Name is required"), commonName: z.string().trim().min(1, "Common Name is required"),
subjectAlternativeNames: z.string(), subjectAlternativeNames: z.string(),
ttl: z.string().trim(), ttl: z.string().trim().optional(),
keyUsages: z.object({ keyUsages: z.object({
[CertKeyUsage.DIGITAL_SIGNATURE]: z.boolean().optional(), [CertKeyUsage.DIGITAL_SIGNATURE]: z.boolean().optional(),
[CertKeyUsage.KEY_ENCIPHERMENT]: z.boolean().optional(), [CertKeyUsage.KEY_ENCIPHERMENT]: z.boolean().optional(),
@@ -90,6 +90,7 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => {
handleSubmit, handleSubmit,
reset, reset,
setValue, setValue,
watch,
formState: { isSubmitting } formState: { isSubmitting }
} = useForm<FormData>({ } = useForm<FormData>({
resolver: zodResolver(schema), resolver: zodResolver(schema),
@@ -107,6 +108,9 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => {
} }
}); });
const selectedCaId = watch("caId");
const selectedCa = cas?.find((ca) => ca.id === selectedCaId);
useEffect(() => { useEffect(() => {
if (pkiSubscriber) { if (pkiSubscriber) {
reset({ reset({
@@ -314,6 +318,7 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
{selectedCa?.type !== CaType.ACME && (
<Controller <Controller
control={control} control={control}
name="ttl" name="ttl"
@@ -328,6 +333,8 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
)}
{selectedCa?.type !== CaType.ACME && (
<Accordion type="single" collapsible className="w-full"> <Accordion type="single" collapsible className="w-full">
<AccordionItem value="key-usages" className="data-[state=open]:border-none"> <AccordionItem value="key-usages" className="data-[state=open]:border-none">
<AccordionTrigger className="h-fit flex-none pl-1 text-sm"> <AccordionTrigger className="h-fit flex-none pl-1 text-sm">
@@ -405,6 +412,7 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => {
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
)}
<div className="mt-4 flex items-center"> <div className="mt-4 flex items-center">
<Button <Button
className="mr-4" className="mr-4"