diff --git a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts index 3b858e427..52a277c31 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts @@ -6,6 +6,10 @@ import { AcmeConnectionError, AcmeDnsFailureError, AcmeIncorrectResponseError } import { AcmeAuthStatus, AcmeChallengeStatus, AcmeChallengeType } from "./pki-acme-schemas"; import { TPkiAcmeChallengeServiceFactory } from "./pki-acme-types"; +type FetchError = Error & { + code?: string; +}; + type TPkiAcmeChallengeServiceFactoryDep = { acmeChallengeDAL: Pick< TPkiAcmeChallengeDALFactory, @@ -69,27 +73,33 @@ export const pkiAcmeChallengeServiceFactory = ({ throw new AcmeIncorrectResponseError({ message: "ACME challenge response is not correct" }); } await acmeChallengeDAL.markAsValidCascadeById(challengeId, tx); - } catch (error) { + } catch (exp) { // TODO: we should retry the challenge validation a few times, but let's keep it simple for now await acmeChallengeDAL.markAsInvalidCascadeById(challengeId, tx); // Properly type and inspect the error - if (error instanceof TypeError && error.message.includes("fetch failed")) { - const cause = error.cause; - const errors = cause instanceof AggregateError ? cause.errors : cause instanceof Error ? [cause] : []; + if (exp instanceof TypeError && exp.message.includes("fetch failed")) { + const { cause } = exp; + let errors: Error[] = []; + if (cause instanceof AggregateError) { + errors = cause.errors; + } else if (cause instanceof Error) { + errors = [cause]; + } for (const err of errors) { // TODO: handle multiple errors, return a compound error instead of just the first error - if (err?.code === "ECONNREFUSED" || err?.message?.includes("ECONNREFUSED")) { + const fetchError = err as FetchError; + if (fetchError.code === "ECONNREFUSED" || fetchError.message.includes("ECONNREFUSED")) { return new AcmeConnectionError({ message: "Connection refused" }); - } else if (err?.code === "ENOTFOUND" || err?.message?.includes("ENOTFOUND")) { + } else if (fetchError.code === "ENOTFOUND" || fetchError.message.includes("ENOTFOUND")) { return new AcmeDnsFailureError({ message: "Hostname could not be resolved (DNS failure)" }); } } - } else if (error instanceof Error) { - logger.error(error, "Error validating ACME challenge response"); + } else if (exp instanceof Error) { + logger.error(exp, "Error validating ACME challenge response"); } else { - logger.error(error, "Unknown error validating ACME challenge response"); + logger.error(exp, "Unknown error validating ACME challenge response"); } - return error; + return exp; } }); if (error) { diff --git a/backend/src/ee/services/pki-acme/pki-acme-service.ts b/backend/src/ee/services/pki-acme/pki-acme-service.ts index b9a983ac8..9f848748e 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -129,7 +129,7 @@ export const pkiAcmeServiceFactory = ({ }; const validateJwsPayload = async < - TSchema extends z.ZodSchema | undefined = undefined, + TSchema extends z.ZodSchema | undefined = undefined, T = TSchema extends z.ZodSchema ? R : string >({ url, @@ -149,7 +149,8 @@ export const pkiAcmeServiceFactory = ({ throw new AcmeMalformedError({ detail: "Protected header is required" }); } const jwk = await getJWK(protectedHeader); - return await importJWK(jwk, protectedHeader.alg); + const key = await importJWK(jwk, protectedHeader.alg); + return key; }); } catch (error) { if (error instanceof AcmeError) { @@ -186,7 +187,7 @@ export const pkiAcmeServiceFactory = ({ const payload = schema ? schema.parse(JSON.parse(textPayload)) : textPayload; return { protectedHeader, - payload + payload: payload as T }; } catch (error) { if (error instanceof AcmeError) { @@ -200,14 +201,14 @@ export const pkiAcmeServiceFactory = ({ } }; - const validateNewAccountJwsPayload = async ({ + const validateNewAccountJwsPayload = ({ url, rawJwsPayload }: { url: URL; rawJwsPayload: TRawJwsPayload; }): Promise> => { - return await validateJwsPayload({ + return validateJwsPayload({ url, rawJwsPayload, getJWK: async (protectedHeader) => { @@ -355,11 +356,8 @@ export const pkiAcmeServiceFactory = ({ const eabSecret = await kmsDecryptor({ cipherTextBlob: profile.acmeConfig!.encryptedEabSecret! }); const { eabPayload, eabProtectedHeader } = await (async () => { try { - const { payload: eabPayload, protectedHeader: eabProtectedHeader } = await flattenedVerify( - externalAccountBinding, - eabSecret - ); - return { eabPayload, eabProtectedHeader }; + const result = await flattenedVerify(externalAccountBinding, eabSecret); + return { eabPayload: result.payload, eabProtectedHeader: result.protectedHeader }; } catch (error) { if (error instanceof errors.JWSSignatureVerificationFailed) { throw new AcmeMalformedError({ detail: "Invalid external account binding JWS signature" });