Expose directory url

This commit is contained in:
Fang-Pen Lin
2025-11-03 18:30:01 -08:00
parent e931d1936f
commit b5a555fee5
7 changed files with 143 additions and 73 deletions

View File

@@ -168,6 +168,12 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
autoRenew: z.boolean(),
renewBeforeDays: z.number().optional()
})
.optional(),
acmeConfig: z
.object({
id: z.string(),
directoryUrl: z.string()
})
.optional()
}).array(),
totalCount: z.number()

View File

@@ -274,6 +274,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
`${TableName.PkiCertificateProfile}.apiConfigId`,
`${TableName.PkiApiEnrollmentConfig}.id`
)
.leftJoin(
TableName.PkiAcmeEnrollmentConfig,
`${TableName.PkiCertificateProfile}.acmeConfigId`,
`${TableName.PkiAcmeEnrollmentConfig}.id`
)
.select(selectAllTableCols(TableName.PkiCertificateProfile))
.select(
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("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"),
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
@@ -312,6 +318,12 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
}
: undefined;
const acmeConfig = result.acmeId
? {
id: result.acmeId as string
}
: undefined;
const baseProfile = {
id: result.id,
projectId: result.projectId,
@@ -325,7 +337,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
createdAt: result.createdAt,
updatedAt: result.updatedAt,
estConfig,
apiConfig
apiConfig,
acmeConfig
};
return baseProfile as TCertificateProfileWithConfigs;

View File

@@ -627,10 +627,9 @@ export const certificateProfileServiceFactory = ({
...converted,
estConfig: decryptedEstConfig,
apiConfig: profileWithConfigs.apiConfig,
acmeConfig:
profile.enrollmentType === EnrollmentType.ACME
? { id: profile.id, directoryUrl: buildUrl(profile.id, "/directory") }
: undefined
acmeConfig: profileWithConfigs.acmeConfig
? { ...profileWithConfigs.acmeConfig, directoryUrl: buildUrl(profile.id, "/directory") }
: undefined
};
return result;

View File

@@ -37,6 +37,4 @@ export interface TApiConfigData {
renewBeforeDays?: number;
}
export interface TAcmeConfigData {
eabSecret: string;
}
export interface TAcmeConfigData {}

View File

@@ -10,7 +10,8 @@ import {
TGetProfileCertificatesDTO,
TGetProfileMetricsDTO,
TListCertificateProfilesDTO,
TProfileCertificate
TProfileCertificate,
TRevealAcmeEabSecretDTO
} from "./types";
export const certificateProfileKeys = {
@@ -41,6 +42,11 @@ export const certificateProfileKeys = {
"metrics",
profileId,
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 = ({
profileId,
offset = 0,

View File

@@ -36,6 +36,10 @@ export type TCertificateProfileWithDetails = TCertificateProfile & {
autoRenew: boolean;
renewBeforeDays?: number;
};
acmeConfig?: {
id: string;
directoryUrl: string;
};
};
export type TCreateCertificateProfileDTO = {
@@ -95,6 +99,10 @@ export type TGetCertificateProfileBySlugDTO = {
slug: string;
};
export type TRevealAcmeEabSecretDTO = {
profileId: string;
};
export type TProfileCertificate = {
id: string;
serialNumber: string;

View File

@@ -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 { TCertificateProfileWithDetails } from "@app/hooks/api/certificateProfiles";
import { useRevealAcmeEabSecret } from "@app/hooks/api/certificateProfiles/queries";
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -14,10 +24,12 @@ export const RevealAcmeEabSecretModal = ({ isOpen, onClose, profile }: Props) =>
const [isAcmeDirectoryUrlCopied, setIsAcmeDirectoryUrlCopied] = useToggle(false);
const [isEabKidCopied, setIsEabKidCopied] = 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 (
<Modal
isOpen={isOpen}
@@ -31,67 +43,81 @@ export const RevealAcmeEabSecretModal = ({ isOpen, onClose, profile }: Props) =>
title="Reveal EAB Secret"
subTitle="To issue certificates automatically, your ACME client needs the following details."
>
<FormLabel
label="ACME Directory URL"
tooltipText="The ACME directory URL for your ACME client to issue certificates."
/>
<div className="flex gap-2">
<Input value={acmeDirectoryUrl} disabled />
<IconButton
ariaLabel="copy"
variant="outline_bg"
colorSchema="secondary"
onClick={() => {
navigator.clipboard.writeText(acmeDirectoryUrl);
setIsAcmeDirectoryUrlCopied.on();
}}
className="w-10"
>
<FontAwesomeIcon icon={isAcmeDirectoryUrlCopied ? faCheck : faCopy} />
</IconButton>
</div>
{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
label="ACME Directory URL"
tooltipText="The ACME directory URL for your ACME client to issue certificates."
/>
<div className="flex gap-2">
<Input value={directoryUrl} disabled />
<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
label="EAB KID"
className="mt-4"
tooltipText="The EAB Key Identifier (KID) for your ACME client to authenticate when registering a new account."
/>
<div className="flex gap-2">
<Input value={eabKid} disabled />
<IconButton
ariaLabel="copy"
variant="outline_bg"
colorSchema="secondary"
onClick={() => {
navigator.clipboard.writeText(eabKid);
setIsEabKidCopied.on();
}}
className="w-10"
>
<FontAwesomeIcon icon={isEabKidCopied ? faCheck : faCopy} />
</IconButton>
</div>
<FormLabel
label="EAB KID"
className="mt-4"
tooltipText="The EAB Key Identifier (KID) for your ACME client to authenticate when registering a new account."
/>
<div className="flex gap-2">
<Input value={eabKid} disabled />
<IconButton
ariaLabel="copy"
variant="outline_bg"
colorSchema="secondary"
onClick={() => {
navigator.clipboard.writeText(eabKid);
setIsEabKidCopied.on();
}}
className="w-10"
>
<FontAwesomeIcon icon={isEabKidCopied ? faCheck : faCopy} />
</IconButton>
</div>
<FormLabel
label="EAB Secret"
className="mt-4"
tooltipText="The EAB Secret for your ACME client to authenticate when registering a new account."
/>
<div className="flex gap-2">
<Input value={eabSecret} isDisabled />
<IconButton
ariaLabel="copy"
variant="outline_bg"
colorSchema="secondary"
onClick={() => {
navigator.clipboard.writeText(eabSecret);
setIsEabSecretCopied.on();
}}
className="w-10"
>
<FontAwesomeIcon icon={isEabSecretCopied ? faCheck : faCopy} />
</IconButton>
</div>
<FormLabel
label="EAB Secret"
className="mt-4"
tooltipText="The EAB Secret for your ACME client to authenticate when registering a new account."
/>
<div className="flex gap-2">
<Input value={eabSecret} isDisabled />
<IconButton
ariaLabel="copy"
variant="outline_bg"
colorSchema="secondary"
onClick={() => {
navigator.clipboard.writeText(eabSecret);
setIsEabSecretCopied.on();
}}
className="w-10"
>
<FontAwesomeIcon icon={isEabSecretCopied ? faCheck : faCopy} />
</IconButton>
</div>
</>
)}
</ModalContent>
</Modal>
);