diff --git a/packages/admin-portal/src/components/keys-ceremony/DownloadStep.tsx b/packages/admin-portal/src/components/keys-ceremony/DownloadStep.tsx index 4017d297406..5cc9aa53139 100644 --- a/packages/admin-portal/src/components/keys-ceremony/DownloadStep.tsx +++ b/packages/admin-portal/src/components/keys-ceremony/DownloadStep.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import {useMutation} from "@apollo/client" import React, {useContext, useState} from "react" -import {FormControlLabel, FormGroup, Typography, Checkbox} from "@mui/material" +import {Alert, FormControlLabel, FormGroup, Typography, Checkbox} from "@mui/material" import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos" import DownloadIcon from "@mui/icons-material/Download" import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos" @@ -18,8 +18,14 @@ import {AuthContext} from "@/providers/AuthContextProvider" import {WizardStyles} from "@/components/styles/WizardStyles" import {GET_PRIVATE_KEY} from "@/queries/GetPrivateKey" import {Dialog} from "@sequentech/ui-essentials" -import {useNotify} from "react-admin" +import {useGetOne, useNotify} from "react-admin" import {useAliasRenderer} from "@/hooks/useAliasRenderer" +import {SettingsContext} from "@/providers/SettingsContextProvider" +import { + IKeysCeremonyExecutionStatus as EStatus, + IKeysCeremonyTrusteeStatus as TStatus, +} from "@/services/KeyCeremony" +import {isPrivateKeyDownloadUnavailableError} from "@/services/privateKeyDownloadError" export interface DownloadStepProps { electionEvent: Sequent_Backend_Election_Event @@ -36,10 +42,12 @@ export const DownloadStep: React.FC = ({ }) => { const {t} = useTranslation() const authContext = useContext(AuthContext) + const {globalSettings} = useContext(SettingsContext) const [downloaded, setDownloaded] = useState(false) const [downloading, setDownloading] = useState(false) const [openConfirmationModal, setOpenConfirmationModal] = useState(false) const [errors, setErrors] = useState(null) + const [downloadUnavailable, setDownloadUnavailable] = useState(false) const notify = useNotify() const aliasRenderer = useAliasRenderer() @@ -57,10 +65,30 @@ export const DownloadStep: React.FC = ({ } const {firstCheckbox, secondCheckbox} = checkboxState + const {data: latestCeremony} = useGetOne( + "sequent_backend_keys_ceremony", + {id: currentCeremony.id}, + {refetchInterval: globalSettings.QUERY_FAST_POLL_INTERVAL_MS} + ) + const isDownloadUnavailable = + downloadUnavailable || + (latestCeremony?.execution_status ?? currentCeremony.execution_status) !== + EStatus.IN_PROGRESS + const trusteeStatus = (latestCeremony?.status ?? currentCeremony.status)?.trustees?.find( + (trustee: {name: string}) => trustee.name === authContext.trustee + )?.status + const downloadUnavailableMessage = + trusteeStatus === TStatus.KEY_CHECKED + ? "keysGeneration.downloadStep.alreadyVerified" + : "keysGeneration.downloadStep.unavailable" + const [getPrivateKeysMutation] = useMutation(GET_PRIVATE_KEY) const download = async () => { setErrors(null) setDownloaded(false) + if (isDownloadUnavailable) { + return + } setDownloading(true) try { const {data, errors} = await getPrivateKeysMutation({ @@ -71,9 +99,11 @@ export const DownloadStep: React.FC = ({ }) setDownloading(false) if (errors) { - setErrors( - t("keysGeneration.downloadStep.errorDownloading", {error: errors.toString()}) - ) + if (isPrivateKeyDownloadUnavailableError({graphQLErrors: errors})) { + setDownloadUnavailable(true) + } else { + setErrors(t("keysGeneration.downloadStep.unexpectedError")) + } return null } else { const privateKey = data?.get_private_key?.private_key_base64 @@ -92,11 +122,13 @@ export const DownloadStep: React.FC = ({ tempLink.click() setDownloaded(true) } - } catch (exception: any) { + } catch (exception: unknown) { setDownloading(false) - setErrors( - t("keysGeneration.downloadStep.errorDownloading", {error: exception.toString()}) - ) + if (isPrivateKeyDownloadUnavailableError(exception)) { + setDownloadUnavailable(true) + } else { + setErrors(t("keysGeneration.downloadStep.unexpectedError")) + } return null } } @@ -116,6 +148,7 @@ export const DownloadStep: React.FC = ({ @@ -123,12 +156,15 @@ export const DownloadStep: React.FC = ({ {downloading ? : null} + {isDownloadUnavailable ? ( + {t(downloadUnavailableMessage)} + ) : null} {downloaded ? ( - {t("keysGeneration.checkStep.downloaded")} + {t("keysGeneration.downloadStep.downloaded")} ) : null} {errors ? ( diff --git a/packages/admin-portal/src/services/privateKeyDownloadError.test.ts b/packages/admin-portal/src/services/privateKeyDownloadError.test.ts new file mode 100644 index 00000000000..65484234003 --- /dev/null +++ b/packages/admin-portal/src/services/privateKeyDownloadError.test.ts @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2026 Sequent Tech Inc +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + isPrivateKeyDownloadUnavailableError, + PRIVATE_KEY_DOWNLOAD_UNAVAILABLE_ERROR_CODE, +} from "./privateKeyDownloadError" + +describe("isPrivateKeyDownloadUnavailableError", () => { + it("recognizes a direct Hasura error code", () => { + expect( + isPrivateKeyDownloadUnavailableError({ + graphQLErrors: [{extensions: {code: PRIVATE_KEY_DOWNLOAD_UNAVAILABLE_ERROR_CODE}}], + }) + ).toBe(true) + }) + + it("recognizes a code in the action response body", () => { + expect( + isPrivateKeyDownloadUnavailableError({ + graphQLErrors: [ + { + extensions: { + internal: { + response: { + body: JSON.stringify({ + extensions: { + code: PRIVATE_KEY_DOWNLOAD_UNAVAILABLE_ERROR_CODE, + }, + }), + }, + }, + }, + }, + ], + }) + ).toBe(true) + }) + + it("rejects unrelated and malformed errors", () => { + expect( + isPrivateKeyDownloadUnavailableError({ + graphQLErrors: [ + {extensions: {code: "InternalServerError"}}, + {extensions: {internal: {response: {body: "not json"}}}}, + ], + }) + ).toBe(false) + expect(isPrivateKeyDownloadUnavailableError(new Error("Network error"))).toBe(false) + }) +}) diff --git a/packages/admin-portal/src/services/privateKeyDownloadError.ts b/packages/admin-portal/src/services/privateKeyDownloadError.ts new file mode 100644 index 00000000000..c12af097827 --- /dev/null +++ b/packages/admin-portal/src/services/privateKeyDownloadError.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2026 Sequent Tech Inc +// +// SPDX-License-Identifier: AGPL-3.0-only + +import {IGraphQLActionError} from "@sequentech/ui-core" +import {parseActionResponseBody} from "./graphqlActionError" + +export const PRIVATE_KEY_DOWNLOAD_UNAVAILABLE_ERROR_CODE = "PrivateKeyDownloadUnavailable" + +export const isPrivateKeyDownloadUnavailableError = (error: unknown): boolean => { + const actionError = error as IGraphQLActionError | undefined + + return ( + actionError?.graphQLErrors?.some((graphQLError) => { + if (graphQLError.extensions?.code === PRIVATE_KEY_DOWNLOAD_UNAVAILABLE_ERROR_CODE) { + return true + } + + const responseBody = parseActionResponseBody( + graphQLError.extensions?.internal?.response?.body + ) as {extensions?: {code?: unknown}} | undefined + + return responseBody?.extensions?.code === PRIVATE_KEY_DOWNLOAD_UNAVAILABLE_ERROR_CODE + }) === true + ) +} diff --git a/packages/admin-portal/src/translations/cat.ts b/packages/admin-portal/src/translations/cat.ts index fc485057502..5e357971bb5 100644 --- a/packages/admin-portal/src/translations/cat.ts +++ b/packages/admin-portal/src/translations/cat.ts @@ -2026,8 +2026,12 @@ const catalanTranslation: TranslationType = { subtitle: "Per continuar, si us plau descarrega i guarda la teva Clau Privada Encriptada en almenys dos dispositius diferents:", downloadButton: "Descarregar la teva Clau Privada Encriptada", - errorDownloading: "Error de descàrrega: {{error}}", + downloaded: "Clau Privada Encriptada descarregada correctament.", errorEmptyKey: "Error de descàrrega, fitxer buit", + unexpectedError: "No s'ha pogut descarregar la clau privada. Torna-ho a provar.", + alreadyVerified: "La teva clau privada ja s'havia descarregat i verificat.", + unavailable: + "La descàrrega de la clau privada ja no està disponible perquè la cerimònia ha avançat.", confirmdDialog: { ok: "Confirmar còpies de seguretat i Continuar", cancel: "Tornar", @@ -2048,7 +2052,6 @@ const catalanTranslation: TranslationType = { "Còpia de Seguretat de la Clau Encriptada Privada invàlida, si us plau intenta-ho de nou", errorEmptyFile: "Fitxer buit o no trobat", verified: "Còpia de seguretat verificada correctament.", - downloaded: "Clau Encriptada Privada generada amb èxit.", }, }, miruExport: { diff --git a/packages/admin-portal/src/translations/en.ts b/packages/admin-portal/src/translations/en.ts index a9b07165a64..fcef875d445 100644 --- a/packages/admin-portal/src/translations/en.ts +++ b/packages/admin-portal/src/translations/en.ts @@ -1993,8 +1993,12 @@ const englishTranslation = { subtitle: "To continue, please download and store your Encrypted Private Key at least into two different devices:", downloadButton: "Download your Encrypted Private Key", - errorDownloading: "Download error: {{error}}", + downloaded: "Encrypted Private Key downloaded successfully.", errorEmptyKey: "Download error, empty file", + unexpectedError: "The private key could not be downloaded. Please try again.", + alreadyVerified: "Your private key was already downloaded and verified.", + unavailable: + "Private key download is no longer available because the ceremony has moved on.", confirmdDialog: { ok: "Confirm Backups and Continue", cancel: "Go Back", @@ -2014,7 +2018,6 @@ const englishTranslation = { errorUploading: "Invalid Encrypted Private Key Backup, please try again", errorEmptyFile: "File empty or not found", verified: "Backup verified successfully.", - downloaded: "Encrypted Private Key generated successfully.", }, }, miruExport: { diff --git a/packages/admin-portal/src/translations/es.ts b/packages/admin-portal/src/translations/es.ts index 0ded4af8a8d..197d8372c8a 100644 --- a/packages/admin-portal/src/translations/es.ts +++ b/packages/admin-portal/src/translations/es.ts @@ -2016,8 +2016,12 @@ const spanishTranslation: TranslationType = { subtitle: "Para continuar, por favor descarga y guarda tu Clave Privada Encriptada en al menos dos dispositivos diferentes:", downloadButton: "Descargar tu Clave Privada Encriptada", - errorDownloading: "Error de descarga: {{error}}", + downloaded: "Clave Privada Encriptada descargada correctamente.", errorEmptyKey: "Error de descarga, fichero vacío", + unexpectedError: "No se pudo descargar la clave privada. Inténtalo de nuevo.", + alreadyVerified: "Tu clave privada ya se había descargado y verificado.", + unavailable: + "La descarga de la clave privada ya no está disponible porque la ceremonia ha avanzado.", confirmdDialog: { ok: "Confirmar copias de seguridad y Continuar", cancel: "Volver", @@ -2038,7 +2042,6 @@ const spanishTranslation: TranslationType = { "Copa de Seguridad de la Clave Encriptada Privada inválida, por favor inténtalo de nuevo", errorEmptyFile: "Fichero vacío o no encontrado", verified: "Copia de seguridad verificada correctamente.", - downloaded: "Clave Encriptada Privada generada exitosamente.", }, }, miruExport: { diff --git a/packages/admin-portal/src/translations/eu.ts b/packages/admin-portal/src/translations/eu.ts index 90d406851e8..83f8c34191e 100644 --- a/packages/admin-portal/src/translations/eu.ts +++ b/packages/admin-portal/src/translations/eu.ts @@ -2009,8 +2009,12 @@ const basqueTranslation: TranslationType = { subtitle: "Jarraitzeko, mesedez deskargatu eta gorde zure Zifratutako Giltza Pribatua gutxienez bi gailu desberdinetan:", downloadButton: "Deskargatu zure Zifratutako Giltza Pribatua", - errorDownloading: "Deskarga errorea: {{error}}", + downloaded: "Zifratutako Giltza Pribatua behar bezala deskargatu da.", errorEmptyKey: "Deskarga errorea, fitxategi hutsa", + unexpectedError: "Ezin izan da gako pribatua deskargatu. Saiatu berriro.", + alreadyVerified: "Zure gako pribatua deskargatuta eta egiaztatuta zegoen.", + unavailable: + "Gako pribatuaren deskarga jada ez dago erabilgarri, zeremoniak aurrera egin duelako.", confirmdDialog: { ok: "Berretsi Babeskopiak eta Jarraitu", cancel: "Itzuli", @@ -2031,7 +2035,6 @@ const basqueTranslation: TranslationType = { "Zifratutako Giltza Pribatu Babeskopia baliogabea, mesedez saiatu berriro", errorEmptyFile: "Fitxategia hutsa edo ez da aurkitu", verified: "Babeskopia arrakastaz egiaztatua.", - downloaded: "Zifratutako Giltza Pribatua arrakastaz sortua.", }, }, miruExport: { diff --git a/packages/admin-portal/src/translations/fr.ts b/packages/admin-portal/src/translations/fr.ts index 0ba7f6b9e3a..0be5008ef41 100644 --- a/packages/admin-portal/src/translations/fr.ts +++ b/packages/admin-portal/src/translations/fr.ts @@ -2029,8 +2029,12 @@ const frenchTranslation: TranslationType = { subtitle: "Pour continuer, veuillez télécharger et sauvegarder votre Clé Privée Cryptée sur au moins deux appareils différents :", downloadButton: "Télécharger votre Clé Privée Cryptée", - errorDownloading: "Erreur de téléchargement : {{error}}", + downloaded: "Clé Privée Cryptée téléchargée avec succès.", errorEmptyKey: "Erreur de téléchargement, fichier vide", + unexpectedError: "La clé privée n'a pas pu être téléchargée. Veuillez réessayer.", + alreadyVerified: "Votre clé privée a déjà été téléchargée et vérifiée.", + unavailable: + "Le téléchargement de la clé privée n'est plus disponible car la cérémonie a progressé.", confirmdDialog: { ok: "Confirmer les copies de sauvegarde et Continuer", cancel: "Revenir", @@ -2051,7 +2055,6 @@ const frenchTranslation: TranslationType = { "Copa de Sauvegarde de la Clé Privée Cryptée invalide, veuillez réessayer", errorEmptyFile: "Fichier vide ou non trouvé", verified: "Copie de sauvegarde vérifiée avec succès.", - downloaded: "Clé Privée Cryptée générée avec succès.", }, }, miruExport: { diff --git a/packages/admin-portal/src/translations/gl.ts b/packages/admin-portal/src/translations/gl.ts index faa93b1f741..86115b4bb69 100644 --- a/packages/admin-portal/src/translations/gl.ts +++ b/packages/admin-portal/src/translations/gl.ts @@ -2015,8 +2015,12 @@ const galegoTranslation: TranslationType = { subtitle: "Para continuar, por favor descarga e almacena a túa Chave Privada Cifrada en polo menos dous dispositivos diferentes:", downloadButton: "Descargar a túa Chave Privada Cifrada", - errorDownloading: "Erro de descarga: {{error}}", + downloaded: "Chave Privada Cifrada descargada correctamente.", errorEmptyKey: "Erro de descarga, ficheiro baleiro", + unexpectedError: "Non se puido descargar a clave privada. Téntao de novo.", + alreadyVerified: "A túa chave privada xa se descargara e verificara.", + unavailable: + "A descarga da clave privada xa non está dispoñible porque a cerimonia avanzou.", confirmdDialog: { ok: "Confirmar Copias de Seguridade e Continuar", cancel: "Volver Atrás", @@ -2037,7 +2041,6 @@ const galegoTranslation: TranslationType = { "Copia de Seguridade da Chave Privada Cifrada inválida, por favor intenta de novo", errorEmptyFile: "Ficheiro baleiro ou non atopado", verified: "Copia de seguridade verificada correctamente.", - downloaded: "Chave Privada Cifrada xerada correctamente.", }, }, miruExport: { diff --git a/packages/admin-portal/src/translations/nl.ts b/packages/admin-portal/src/translations/nl.ts index cfe461ab9fb..3247192047e 100644 --- a/packages/admin-portal/src/translations/nl.ts +++ b/packages/admin-portal/src/translations/nl.ts @@ -2014,8 +2014,12 @@ const dutchTranslation: TranslationType = { subtitle: "Om door te gaan, download en bewaar uw Versleutelde Privésleutel op minstens twee verschillende apparaten:", downloadButton: "Download uw Versleutelde Privésleutel", - errorDownloading: "Downloadfout: {{error}}", + downloaded: "Versleutelde privésleutel succesvol gedownload.", errorEmptyKey: "Downloadfout, leeg bestand", + unexpectedError: "De privésleutel kon niet worden gedownload. Probeer het opnieuw.", + alreadyVerified: "Uw privésleutel was al gedownload en geverifieerd.", + unavailable: + "De privésleutel kan niet meer worden gedownload omdat de ceremonie is gevorderd.", confirmdDialog: { ok: "Back-ups Bevestigen en Doorgaan", cancel: "Terug", @@ -2035,7 +2039,6 @@ const dutchTranslation: TranslationType = { errorUploading: "Ongeldige Back-up van Versleutelde Privésleutel, probeer opnieuw", errorEmptyFile: "Bestand leeg of niet gevonden", verified: "Back-up succesvol geverifieerd.", - downloaded: "Versleutelde Privésleutel succesvol gegenereerd.", }, }, miruExport: { diff --git a/packages/admin-portal/src/translations/tl.ts b/packages/admin-portal/src/translations/tl.ts index cfeedbbf211..d8c07acd827 100644 --- a/packages/admin-portal/src/translations/tl.ts +++ b/packages/admin-portal/src/translations/tl.ts @@ -2020,8 +2020,12 @@ const tagalogTranslation: TranslationType = { subtitle: "Upang magpatuloy, mangyaring i-download at itago ang iyong Encrypted Private Key sa hindi bababa sa dalawang magkaibang device:", downloadButton: "I-download ang iyong Encrypted Private Key", - errorDownloading: "Error sa pag-download: {{error}}", + downloaded: "Matagumpay na na-download ang Encrypted Private Key.", errorEmptyKey: "Error sa pag-download, walang laman na file", + unexpectedError: "Hindi ma-download ang pribadong key. Pakisubukang muli.", + alreadyVerified: "Na-download at na-verify na ang iyong pribadong key.", + unavailable: + "Hindi na maaaring i-download ang pribadong key dahil nagpatuloy na ang seremonya.", confirmdDialog: { ok: "Kumpirmahin ang mga Backup at Magpatuloy", cancel: "Bumalik", @@ -2041,7 +2045,6 @@ const tagalogTranslation: TranslationType = { errorUploading: "Di-wastong Encrypted Private Key Backup, mangyaring subukan muli", errorEmptyFile: "Walang laman na file o hindi natagpuan", verified: "Backup ay matagumpay na nasuri.", - downloaded: "Encrypted Private Key ay matagumpay na nabuo.", }, }, miruExport: { diff --git a/packages/harvest/src/routes/keys_ceremony.rs b/packages/harvest/src/routes/keys_ceremony.rs index 7060a1e2134..d8a328cdec7 100644 --- a/packages/harvest/src/routes/keys_ceremony.rs +++ b/packages/harvest/src/routes/keys_ceremony.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only use crate::services::authorization::authorize; +use crate::types::error_response::{ErrorCode, ErrorResponse, JsonError}; use crate::types::resources::{Aggregate, DataList, TotalAggregate}; use anyhow::anyhow; use anyhow::{Context, Result}; @@ -19,7 +20,7 @@ use tracing::{error, event, instrument, Level}; use windmill::postgres; use windmill::postgres::election::get_elections; use windmill::services::ceremonies::keys_ceremony::{ - self, validate_permission_labels, + self, validate_permission_labels, PrivateKeyDownloadUnavailable, }; use windmill::services::database::get_hasura_pool; @@ -40,7 +41,7 @@ pub struct CheckPrivateKeyOutput { } // The main function to get the private key -#[instrument(skip(claims))] +#[instrument(skip(body, claims))] #[post("/check-private-key", format = "json", data = "")] pub async fn check_private_key( body: Json, @@ -109,19 +110,44 @@ pub struct GetPrivateKeyOutput { private_key_base64: String, } +fn private_key_download_unavailable() -> JsonError { + ErrorResponse::new( + Status::Conflict, + "Private key download is no longer available", + ErrorCode::PrivateKeyDownloadUnavailable, + ) +} + +fn private_key_download_internal_error() -> JsonError { + ErrorResponse::new( + Status::InternalServerError, + "Failed to download private key", + ErrorCode::InternalServerError, + ) +} + // The main function to get the private key #[instrument(skip(claims))] #[post("/get-private-key", format = "json", data = "")] pub async fn get_private_key( body: Json, claims: JwtClaims, -) -> Result, (Status, String)> { +) -> Result, JsonError> { authorize( &claims, true, Some(claims.hasura_claims.tenant_id.clone()), vec![Permissions::TRUSTEE_CEREMONY], - )?; + ) + .map_err(|(status, message)| { + let code = + if status == Status::Unauthorized || status == Status::Forbidden { + ErrorCode::Unauthorized + } else { + ErrorCode::UnknownError + }; + ErrorResponse::new(status, &message, code) + })?; let input = body.into_inner(); let tenant_id = claims.hasura_claims.tenant_id.clone(); @@ -129,12 +155,18 @@ pub async fn get_private_key( .await .get() .await - .map_err(|e| (Status::InternalServerError, format!("{:?}", e)))?; - - let hasura_transaction = hasura_db_client - .transaction() - .await - .map_err(|e| (Status::InternalServerError, format!("{:?}", e)))?; + .map_err(|error| { + error!("Failed to get database client for private key download: {error:#}"); + private_key_download_internal_error() + })?; + + let hasura_transaction = + hasura_db_client.transaction().await.map_err(|error| { + error!( + "Failed to start private key download transaction: {error:#}" + ); + private_key_download_internal_error() + })?; let encrypted_private_key = keys_ceremony::get_private_key( &hasura_transaction, @@ -144,7 +176,21 @@ pub async fn get_private_key( input.keys_ceremony_id.clone(), ) .await - .map_err(|e| (Status::InternalServerError, format!("{:?}", e)))?; + .map_err(|error| { + if error + .downcast_ref::() + .is_some() + { + private_key_download_unavailable() + } else { + error!( + election_event_id = %input.election_event_id, + keys_ceremony_id = %input.keys_ceremony_id, + "Failed to download private key: {error:#}" + ); + private_key_download_internal_error() + } + })?; event!( Level::INFO, @@ -153,11 +199,10 @@ pub async fn get_private_key( input.keys_ceremony_id.clone(), ); - hasura_transaction - .commit() - .await - .with_context(|| "error comitting transaction") - .map_err(|e| (Status::InternalServerError, format!("{:?}", e)))?; + hasura_transaction.commit().await.map_err(|error| { + error!("Failed to commit private key download transaction: {error:#}"); + private_key_download_internal_error() + })?; Ok(Json(GetPrivateKeyOutput { private_key_base64: encrypted_private_key, diff --git a/packages/harvest/src/types/error_response.rs b/packages/harvest/src/types/error_response.rs index 0fdd56f463d..081480d2018 100644 --- a/packages/harvest/src/types/error_response.rs +++ b/packages/harvest/src/types/error_response.rs @@ -43,6 +43,7 @@ pub enum ErrorCode { UserProfileValidation, DocumentPasswordUnavailable, VoterInformationLetterUnavailable, + PrivateKeyDownloadUnavailable, ConfirmPolicyShowCastVoteLogsFailed, BallotIdMismatch, BallotPublicationValidation, diff --git a/packages/windmill/src/services/ceremonies/keys_ceremony.rs b/packages/windmill/src/services/ceremonies/keys_ceremony.rs index d8d62e97ade..f46f8c5d9ab 100644 --- a/packages/windmill/src/services/ceremonies/keys_ceremony.rs +++ b/packages/windmill/src/services/ceremonies/keys_ceremony.rs @@ -30,6 +30,10 @@ use tracing::instrument; use tracing::{event, info, Level}; use uuid::Uuid; +#[derive(Debug, thiserror::Error)] +#[error("Private key download is no longer available")] +pub struct PrivateKeyDownloadUnavailable; + // returns (board_name, election_id), where the election_id might be None for an event Board #[instrument(skip(transaction), err)] pub async fn get_keys_ceremony_board( @@ -90,9 +94,7 @@ pub async fn get_private_key( .await?; // check keys_ceremony has correct execution status if keys_ceremony.execution_status()? != KeysCeremonyExecutionStatus::IN_PROGRESS { - return Err(anyhow!( - "Keys ceremony status should be in ExecutionStatus::IN_PROGRESS which is set when config message has been added to the board and trustees are working." - )); + return Err(PrivateKeyDownloadUnavailable.into()); } // get ceremony status