Expose directory url

This commit is contained in:
Fang-Pen Lin
2025-11-07 09:20:23 -08:00
parent e931d1936f
commit b5a555fee5
7 changed files with 143 additions and 73 deletions
@@ -168,6 +168,12 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
autoRenew: z.boolean(), autoRenew: z.boolean(),
renewBeforeDays: z.number().optional() renewBeforeDays: z.number().optional()
}) })
.optional(),
acmeConfig: z
.object({
id: z.string(),
directoryUrl: z.string()
})
.optional() .optional()
}).array(), }).array(),
totalCount: z.number() totalCount: z.number()
@@ -274,6 +274,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
`${TableName.PkiCertificateProfile}.apiConfigId`, `${TableName.PkiCertificateProfile}.apiConfigId`,
`${TableName.PkiApiEnrollmentConfig}.id` `${TableName.PkiApiEnrollmentConfig}.id`
) )
.leftJoin(
TableName.PkiAcmeEnrollmentConfig,
`${TableName.PkiCertificateProfile}.acmeConfigId`,
`${TableName.PkiAcmeEnrollmentConfig}.id`
)
.select(selectAllTableCols(TableName.PkiCertificateProfile)) .select(selectAllTableCols(TableName.PkiCertificateProfile))
.select( .select(
db.ref("id").withSchema(TableName.PkiEstEnrollmentConfig).as("estId"), db.ref("id").withSchema(TableName.PkiEstEnrollmentConfig).as("estId"),
@@ -285,7 +290,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estEncryptedCaChain"), db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estEncryptedCaChain"),
db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"), db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"),
db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"), db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"),
db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays") db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays"),
db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeId")
); );
const results = (await query const results = (await query
@@ -312,6 +318,12 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
} }
: undefined; : undefined;
const acmeConfig = result.acmeId
? {
id: result.acmeId as string
}
: undefined;
const baseProfile = { const baseProfile = {
id: result.id, id: result.id,
projectId: result.projectId, projectId: result.projectId,
@@ -325,7 +337,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
createdAt: result.createdAt, createdAt: result.createdAt,
updatedAt: result.updatedAt, updatedAt: result.updatedAt,
estConfig, estConfig,
apiConfig apiConfig,
acmeConfig
}; };
return baseProfile as TCertificateProfileWithConfigs; return baseProfile as TCertificateProfileWithConfigs;
@@ -627,9 +627,8 @@ export const certificateProfileServiceFactory = ({
...converted, ...converted,
estConfig: decryptedEstConfig, estConfig: decryptedEstConfig,
apiConfig: profileWithConfigs.apiConfig, apiConfig: profileWithConfigs.apiConfig,
acmeConfig: acmeConfig: profileWithConfigs.acmeConfig
profile.enrollmentType === EnrollmentType.ACME ? { ...profileWithConfigs.acmeConfig, directoryUrl: buildUrl(profile.id, "/directory") }
? { id: profile.id, directoryUrl: buildUrl(profile.id, "/directory") }
: undefined : undefined
}; };
@@ -37,6 +37,4 @@ export interface TApiConfigData {
renewBeforeDays?: number; renewBeforeDays?: number;
} }
export interface TAcmeConfigData { export interface TAcmeConfigData {}
eabSecret: string;
}
@@ -10,7 +10,8 @@ import {
TGetProfileCertificatesDTO, TGetProfileCertificatesDTO,
TGetProfileMetricsDTO, TGetProfileMetricsDTO,
TListCertificateProfilesDTO, TListCertificateProfilesDTO,
TProfileCertificate TProfileCertificate,
TRevealAcmeEabSecretDTO
} from "./types"; } from "./types";
export const certificateProfileKeys = { export const certificateProfileKeys = {
@@ -41,6 +42,11 @@ export const certificateProfileKeys = {
"metrics", "metrics",
profileId, profileId,
params params
],
revealAcmeEabSecret: (profileId: string) => [
"certificate-profiles",
"reveal-acme-eab-secret",
profileId
] ]
}; };
@@ -112,6 +118,20 @@ export const useGetCertificateProfileBySlug = ({
}); });
}; };
export const useRevealAcmeEabSecret = ({ profileId }: TRevealAcmeEabSecretDTO) => {
return useQuery({
queryKey: certificateProfileKeys.revealAcmeEabSecret(profileId),
queryFn: async () => {
const { data } = await apiRequest.get<{
eabKid: string;
eabSecret: string;
}>(`/api/v1/pki/certificate-profiles/${profileId}/acme/eab-secret/reveal`);
return data;
},
enabled: Boolean(profileId)
});
};
export const useGetProfileCertificates = ({ export const useGetProfileCertificates = ({
profileId, profileId,
offset = 0, offset = 0,
@@ -36,6 +36,10 @@ export type TCertificateProfileWithDetails = TCertificateProfile & {
autoRenew: boolean; autoRenew: boolean;
renewBeforeDays?: number; renewBeforeDays?: number;
}; };
acmeConfig?: {
id: string;
directoryUrl: string;
};
}; };
export type TCreateCertificateProfileDTO = { export type TCreateCertificateProfileDTO = {
@@ -95,6 +99,10 @@ export type TGetCertificateProfileBySlugDTO = {
slug: string; slug: string;
}; };
export type TRevealAcmeEabSecretDTO = {
profileId: string;
};
export type TProfileCertificate = { export type TProfileCertificate = {
id: string; id: string;
serialNumber: string; serialNumber: string;
@@ -1,6 +1,16 @@
import { FormLabel, IconButton, Input, Modal, ModalContent } from "@app/components/v2"; import {
Alert,
AlertDescription,
FormLabel,
IconButton,
Input,
Modal,
ModalContent,
Spinner
} from "@app/components/v2";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { TCertificateProfileWithDetails } from "@app/hooks/api/certificateProfiles"; import { TCertificateProfileWithDetails } from "@app/hooks/api/certificateProfiles";
import { useRevealAcmeEabSecret } from "@app/hooks/api/certificateProfiles/queries";
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -14,10 +24,12 @@ export const RevealAcmeEabSecretModal = ({ isOpen, onClose, profile }: Props) =>
const [isAcmeDirectoryUrlCopied, setIsAcmeDirectoryUrlCopied] = useToggle(false); const [isAcmeDirectoryUrlCopied, setIsAcmeDirectoryUrlCopied] = useToggle(false);
const [isEabKidCopied, setIsEabKidCopied] = useToggle(false); const [isEabKidCopied, setIsEabKidCopied] = useToggle(false);
const [isEabSecretCopied, setIsEabSecretCopied] = useToggle(false); const [isEabSecretCopied, setIsEabSecretCopied] = useToggle(false);
const revealAcmeEabSecret = useRevealAcmeEabSecret({ profileId: profile.id });
const { data, isLoading, isError, error } = revealAcmeEabSecret;
const { directoryUrl } = profile.acmeConfig!;
const { eabKid, eabSecret } = data ?? { eabKid: "", eabSecret: "" };
const acmeDirectoryUrl = "http://FIXME.com/directory";
const eabKid = profile.id;
const eabSecret = "FIXME";
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
@@ -31,18 +43,30 @@ export const RevealAcmeEabSecretModal = ({ isOpen, onClose, profile }: Props) =>
title="Reveal EAB Secret" title="Reveal EAB Secret"
subTitle="To issue certificates automatically, your ACME client needs the following details." subTitle="To issue certificates automatically, your ACME client needs the following details."
> >
{isLoading && (
<div className="flex items-center justify-center py-4">
<Spinner size="sm" />
</div>
)}
{isError && (
<Alert variant="danger">
<AlertDescription>Failed to reveal EAB secret: {error.message}</AlertDescription>
</Alert>
)}
{data && (
<>
<FormLabel <FormLabel
label="ACME Directory URL" label="ACME Directory URL"
tooltipText="The ACME directory URL for your ACME client to issue certificates." tooltipText="The ACME directory URL for your ACME client to issue certificates."
/> />
<div className="flex gap-2"> <div className="flex gap-2">
<Input value={acmeDirectoryUrl} disabled /> <Input value={directoryUrl} disabled />
<IconButton <IconButton
ariaLabel="copy" ariaLabel="copy"
variant="outline_bg" variant="outline_bg"
colorSchema="secondary" colorSchema="secondary"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(acmeDirectoryUrl); navigator.clipboard.writeText(directoryUrl);
setIsAcmeDirectoryUrlCopied.on(); setIsAcmeDirectoryUrlCopied.on();
}} }}
className="w-10" className="w-10"
@@ -92,6 +116,8 @@ export const RevealAcmeEabSecretModal = ({ isOpen, onClose, profile }: Props) =>
<FontAwesomeIcon icon={isEabSecretCopied ? faCheck : faCopy} /> <FontAwesomeIcon icon={isEabSecretCopied ? faCheck : faCopy} />
</IconButton> </IconButton>
</div> </div>
</>
)}
</ModalContent> </ModalContent>
</Modal> </Modal>
); );