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,10 +627,9 @@ 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
}; };
return result; return result;
@@ -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,67 +43,81 @@ 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."
> >
<FormLabel {isLoading && (
label="ACME Directory URL" <div className="flex items-center justify-center py-4">
tooltipText="The ACME directory URL for your ACME client to issue certificates." <Spinner size="sm" />
/> </div>
<div className="flex gap-2"> )}
<Input value={acmeDirectoryUrl} disabled /> {isError && (
<IconButton <Alert variant="danger">
ariaLabel="copy" <AlertDescription>Failed to reveal EAB secret: {error.message}</AlertDescription>
variant="outline_bg" </Alert>
colorSchema="secondary" )}
onClick={() => { {data && (
navigator.clipboard.writeText(acmeDirectoryUrl); <>
setIsAcmeDirectoryUrlCopied.on(); <FormLabel
}} label="ACME Directory URL"
className="w-10" tooltipText="The ACME directory URL for your ACME client to issue certificates."
> />
<FontAwesomeIcon icon={isAcmeDirectoryUrlCopied ? faCheck : faCopy} /> <div className="flex gap-2">
</IconButton> <Input value={directoryUrl} disabled />
</div> <IconButton
ariaLabel="copy"
variant="outline_bg"
colorSchema="secondary"
onClick={() => {
navigator.clipboard.writeText(directoryUrl);
setIsAcmeDirectoryUrlCopied.on();
}}
className="w-10"
>
<FontAwesomeIcon icon={isAcmeDirectoryUrlCopied ? faCheck : faCopy} />
</IconButton>
</div>
<FormLabel <FormLabel
label="EAB KID" label="EAB KID"
className="mt-4" className="mt-4"
tooltipText="The EAB Key Identifier (KID) for your ACME client to authenticate when registering a new account." tooltipText="The EAB Key Identifier (KID) for your ACME client to authenticate when registering a new account."
/> />
<div className="flex gap-2"> <div className="flex gap-2">
<Input value={eabKid} disabled /> <Input value={eabKid} disabled />
<IconButton <IconButton
ariaLabel="copy" ariaLabel="copy"
variant="outline_bg" variant="outline_bg"
colorSchema="secondary" colorSchema="secondary"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(eabKid); navigator.clipboard.writeText(eabKid);
setIsEabKidCopied.on(); setIsEabKidCopied.on();
}} }}
className="w-10" className="w-10"
> >
<FontAwesomeIcon icon={isEabKidCopied ? faCheck : faCopy} /> <FontAwesomeIcon icon={isEabKidCopied ? faCheck : faCopy} />
</IconButton> </IconButton>
</div> </div>
<FormLabel <FormLabel
label="EAB Secret" label="EAB Secret"
className="mt-4" className="mt-4"
tooltipText="The EAB Secret for your ACME client to authenticate when registering a new account." tooltipText="The EAB Secret for your ACME client to authenticate when registering a new account."
/> />
<div className="flex gap-2"> <div className="flex gap-2">
<Input value={eabSecret} isDisabled /> <Input value={eabSecret} isDisabled />
<IconButton <IconButton
ariaLabel="copy" ariaLabel="copy"
variant="outline_bg" variant="outline_bg"
colorSchema="secondary" colorSchema="secondary"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(eabSecret); navigator.clipboard.writeText(eabSecret);
setIsEabSecretCopied.on(); setIsEabSecretCopied.on();
}} }}
className="w-10" className="w-10"
> >
<FontAwesomeIcon icon={isEabSecretCopied ? faCheck : faCopy} /> <FontAwesomeIcon icon={isEabSecretCopied ? faCheck : faCopy} />
</IconButton> </IconButton>
</div> </div>
</>
)}
</ModalContent> </ModalContent>
</Modal> </Modal>
); );