From 74bfe26febbd40415c6c551986dd52794ab6f28e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=B0=8F=E9=B8=AD?= Date: Wed, 11 Nov 2026 23:57:13 +0800 Subject: [PATCH] fix(sse): send immediate key deletion as a confirmed two-step request The server only accepts an immediate KMS key deletion as a JSON body that repeats the key ID, and only for a key that is already pending deletion. The console sent `force_immediate=true` in the query string with no confirmation, so "Delete Immediately" was always refused, and it offered the action on keys that had never been scheduled. Send the request as a JSON body with `confirm_key_id`, offer "Delete Immediately" only on keys in PendingDeletion, and require the operator to type the key ID before the action is enabled. Update the explanatory copy in every locale to describe the two steps and the server-side switch. --- app/(dashboard)/sse/page.tsx | 118 +++++++++++++++++++---------- hooks/use-sse.ts | 12 +-- i18n/locales/ar-MA.json | 5 +- i18n/locales/de-DE.json | 5 +- i18n/locales/en-US.json | 5 +- i18n/locales/es-ES.json | 5 +- i18n/locales/fr-FR.json | 5 +- i18n/locales/id-ID.json | 5 +- i18n/locales/it-IT.json | 5 +- i18n/locales/ja-JP.json | 5 +- i18n/locales/ko-KR.json | 5 +- i18n/locales/pt-BR.json | 5 +- i18n/locales/ru-RU.json | 5 +- i18n/locales/tr-TR.json | 5 +- i18n/locales/vi-VN.json | 5 +- i18n/locales/zh-CN.json | 5 +- i18n/locales/zh-TW.json | 5 +- lib/sse/key-deletion.ts | 19 +++++ tests/lib/sse-key-deletion.test.ts | 57 ++++++++++++++ types/kms.ts | 1 - 20 files changed, 204 insertions(+), 78 deletions(-) create mode 100644 lib/sse/key-deletion.ts create mode 100644 tests/lib/sse-key-deletion.test.ts diff --git a/app/(dashboard)/sse/page.tsx b/app/(dashboard)/sse/page.tsx index 5f6b6010..95c61b8f 100644 --- a/app/(dashboard)/sse/page.tsx +++ b/app/(dashboard)/sse/page.tsx @@ -43,6 +43,7 @@ import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, Tabl import { useSSE } from "@/hooks/use-sse" import { useMessage } from "@/lib/feedback/message" import { formatDateTime } from "@/lib/functions" +import { isImmediateKeyDeletionConfirmed, isKeyPendingDeletion } from "@/lib/sse/key-deletion" import { INITIAL_FORM_STATE, buildFormStateFromStatus, @@ -187,7 +188,12 @@ export default function SSEPage() { const [keyDetailsReloadVersion, setKeyDetailsReloadVersion] = React.useState(0) const keyDetailsRequestRef = React.useRef(0) - const [pendingKeyAction, setPendingKeyAction] = React.useState(null) + const [pendingKeyAction, setPendingKeyActionState] = React.useState(null) + const [confirmKeyId, setConfirmKeyId] = React.useState("") + const setPendingKeyAction = React.useCallback((action: KeyActionState) => { + setConfirmKeyId("") + setPendingKeyActionState(action) + }, []) const [processingKeyAction, setProcessingKeyAction] = React.useState(false) const [pendingServiceAction, setPendingServiceAction] = React.useState<"start" | "restart" | "stop" | null>(null) const [pendingNavigation, setPendingNavigation] = React.useState(null) @@ -938,7 +944,7 @@ export default function SSEPage() { } if (pendingKeyAction.type === "forceDelete") { - const response = await forceDeleteKey(pendingKeyAction.key.key_id) + const response = await forceDeleteKey(pendingKeyAction.key.key_id, confirmKeyId) if (response.success === false) { message.error(response.message || t("Failed to force delete key")) setPendingKeyAction(null) @@ -981,7 +987,9 @@ export default function SSEPage() { const keyActionDescription = pendingKeyAction?.type === "forceDelete" - ? t("This permanently deletes the key immediately and cannot be undone.") + ? t( + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.", + ) : pendingKeyAction?.type === "cancelDeletion" ? t("This restores the key from the pending deletion state.") : t("This schedules the key for deletion using the default pending window.") @@ -1223,7 +1231,9 @@ export default function SSEPage() { {t("Two-stage key deletion")} - {t("Keys in PendingDeletion can still be restored or deleted immediately.")} + {t( + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.", + )} @@ -1257,7 +1267,7 @@ export default function SSEPage() {
{keys.map((key) => { const isDefaultKey = key.key_id === status?.config_summary?.default_key_id - const isPendingDeletion = (key.status ?? "").toLowerCase().includes("pendingdeletion") + const isPendingDeletion = isKeyPendingDeletion(key.status) return (
@@ -1316,21 +1326,23 @@ export default function SSEPage() { {t("Schedule Deletion")} )} - + {isPendingDeletion ? ( + + ) : null}
) @@ -1354,7 +1366,7 @@ export default function SSEPage() { {keys.map((key) => { - const isPendingDeletion = (key.status ?? "").toLowerCase().includes("pendingdeletion") + const isPendingDeletion = isKeyPendingDeletion(key.status) const isDefaultKey = key.key_id === status?.config_summary?.default_key_id return ( @@ -1401,27 +1413,31 @@ export default function SSEPage() { )} {isPendingDeletion && ( - setPendingKeyAction({ type: "cancelDeletion", key })} - disabled={Boolean(activeMutation) || loadingStatus || Boolean(keysError)} - > - {t("Cancel Deletion")} - + <> + setPendingKeyAction({ type: "cancelDeletion", key })} + disabled={ + Boolean(activeMutation) || loadingStatus || Boolean(keysError) + } + > + {t("Cancel Deletion")} + + + setPendingKeyAction({ type: "forceDelete", key })} + disabled={ + isDefaultKey || + Boolean(activeMutation) || + loadingStatus || + Boolean(keysError) || + staticKmsReadOnly + } + className="text-destructive focus:text-destructive" + > + {t("Delete Immediately")} + + )} - - setPendingKeyAction({ type: "forceDelete", key })} - disabled={ - isDefaultKey || - Boolean(activeMutation) || - loadingStatus || - Boolean(keysError) || - staticKmsReadOnly - } - className="text-destructive focus:text-destructive" - > - {t("Delete Immediately")} -
@@ -2349,6 +2365,22 @@ export default function SSEPage() { ) : null} + {pendingKeyAction?.type === "forceDelete" ? ( + + {t("Type the KMS key ID to confirm")} + + setConfirmKeyId(event.target.value)} + autoComplete="off" + spellCheck={false} + disabled={processingKeyAction} + /> + + + ) : null} {t("Cancel")} => { - return deleteKey(keyId, { force_immediate: true }) + async (keyId: string, confirmKeyId: string): Promise => { + return (await api.delete("/kms/keys/delete", { + body: buildImmediateKeyDeletionRequest(keyId, confirmKeyId), + })) as KmsMutationResponse }, - [deleteKey], + [api], ) const cancelKeyDeletion = useCallback( diff --git a/i18n/locales/ar-MA.json b/i18n/locales/ar-MA.json index 5ea15860..54447e02 100644 --- a/i18n/locales/ar-MA.json +++ b/i18n/locales/ar-MA.json @@ -587,7 +587,7 @@ "Key list refreshed": "تم تحديث قائمة المفاتيح", "Key services and configuration values reported by the cluster.": "خدمات المفاتيح وقيم الإعداد التي أبلغ عنها المجموعة.", "Key storage path prefix in KV store": "بادئة مسار تخزين المفتاح في مخزن KV", - "Keys in PendingDeletion can still be restored or deleted immediately.": "لا يزال بإمكان استعادة المفاتيح في حالة PendingDeletion أو حذفها فورًا.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "قم بجدولة الحذف أولاً؛ يمكن إلغاؤه خلال فترة الانتظار. لا يمكن الحذف الفوري إلا لمفتاح في حالة PendingDeletion، ويجب أن يسمح الخادم بالحذف الفوري.", "Language": "اللغة", "Large File Count Warning": "تحذير من كثرة الملفات الكبيرة", "Last": "الأخير", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "يُستخدم هذا المفتاح كمفتاح SSE الافتراضي للمنصة لكل من SSE-KMS وSSE-S3.", "This key is used as the platform default for SSE-KMS.": "يُستخدم هذا المفتاح كافتراضي للمنصة لـ SSE-KMS.", "This link will expire when your session ends or at the specified time.": "ستنتهي صلاحية هذا الرابط عند انتهاء جلستك أو في الوقت المحدد.", - "This permanently deletes the key immediately and cannot be undone.": "يحذف المفتاح نهائيًا فورًا ولا يمكن التراجع.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "سيُحذف المفتاح نهائياً الآن دون انتظار تاريخ الحذف المجدول. لن يمكن أبداً فك تشفير الكائنات المشفّرة به.", + "Type the KMS key ID to confirm": "اكتب معرّف مفتاح KMS للتأكيد", "This restores the key from the pending deletion state.": "يستعيد المفتاح من حالة الحذف المعلّق.", "This schedules the key for deletion using the default pending window.": "يجدول حذف المفتاح باستخدام نافذة الانتظار الافتراضية.", "This server does not provide the detailed site replication status endpoint yet.": "هذا الخادم لا يوفّر بعد نقطة نهاية تفصيلية لحالة نسخ المواقع.", diff --git a/i18n/locales/de-DE.json b/i18n/locales/de-DE.json index 98d7c5aa..deda9913 100644 --- a/i18n/locales/de-DE.json +++ b/i18n/locales/de-DE.json @@ -587,7 +587,7 @@ "Key list refreshed": "Schlüssel-Liste aktualisiert", "Key services and configuration values reported by the cluster.": "Schlüsseldienste und Konfigurationswerte, die vom Cluster gemeldet werden.", "Key storage path prefix in KV store": "Schlüsselspeicherpfad-Präfix im KV-Speicher", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Schlüssel im Status PendingDeletion können weiterhin wiederhergestellt oder sofort gelöscht werden.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Planen Sie zuerst die Löschung; sie kann während der Wartezeit abgebrochen werden. Nur ein Schlüssel im Status PendingDeletion kann sofort gelöscht werden, und der Server muss die sofortige Löschung erlauben.", "Language": "Sprache", "Large File Count Warning": "Warnung bei großer Dateianzahl", "Last": "Letzte", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Dieser Schlüssel wird als plattformweiter Standard-SSE-Schlüssel für SSE-KMS und SSE-S3 verwendet.", "This key is used as the platform default for SSE-KMS.": "Dieser Schlüssel ist die Plattform-Standardvorgabe für SSE-KMS.", "This link will expire when your session ends or at the specified time.": "Dieser Link läuft ab, wenn Ihre Sitzung endet oder zum angegebenen Zeitpunkt.", - "This permanently deletes the key immediately and cannot be undone.": "Der Schlüssel wird sofort dauerhaft gelöscht und kann nicht rückgängig gemacht werden.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "Der Schlüssel wird jetzt endgültig gelöscht, ohne das geplante Löschdatum abzuwarten. Damit verschlüsselte Objekte können nie wieder entschlüsselt werden.", + "Type the KMS key ID to confirm": "KMS-Schlüssel-ID zur Bestätigung eingeben", "This restores the key from the pending deletion state.": "Stellt den Schlüssel aus dem ausstehenden Löschzustand wieder her.", "This schedules the key for deletion using the default pending window.": "Plant die Schlüssellöschung mit dem Standard-Wartefenster.", "This server does not provide the detailed site replication status endpoint yet.": "Dieser Server stellt noch keinen detaillierten Endpunkt für den Status der Site-Replikation bereit.", diff --git a/i18n/locales/en-US.json b/i18n/locales/en-US.json index 0d27b827..69286783 100644 --- a/i18n/locales/en-US.json +++ b/i18n/locales/en-US.json @@ -585,7 +585,7 @@ "Key list refreshed": "Key list refreshed", "Key services and configuration values reported by the cluster.": "Key services and configuration values reported by the cluster.", "Key storage path prefix in KV store": "Key storage path prefix in KV store", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Keys in PendingDeletion can still be restored or deleted immediately.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.", "Language": "Language", "Large File Count Warning": "Large File Count Warning", "Last": "Last", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.", "This key is used as the platform default for SSE-KMS.": "This key is used as the platform default for SSE-KMS.", "This link will expire when your session ends or at the specified time.": "This link will expire when your session ends or at the specified time.", - "This permanently deletes the key immediately and cannot be undone.": "This permanently deletes the key immediately and cannot be undone.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.", + "Type the KMS key ID to confirm": "Type the KMS key ID to confirm", "This restores the key from the pending deletion state.": "This restores the key from the pending deletion state.", "This schedules the key for deletion using the default pending window.": "This schedules the key for deletion using the default pending window.", "This server does not provide the detailed site replication status endpoint yet.": "This server does not provide the detailed site replication status endpoint yet.", diff --git a/i18n/locales/es-ES.json b/i18n/locales/es-ES.json index ef312542..ce4b25eb 100644 --- a/i18n/locales/es-ES.json +++ b/i18n/locales/es-ES.json @@ -587,7 +587,7 @@ "Key list refreshed": "Lista de claves actualizada", "Key services and configuration values reported by the cluster.": "Servicios de claves y valores de configuración reportados por el clúster.", "Key storage path prefix in KV store": "Prefijo de ruta de almacenamiento de claves en el almacén KV", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Las claves en PendingDeletion aún pueden restaurarse o eliminarse de inmediato.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Programe primero la eliminación; puede cancelarse durante el periodo de espera. Solo una clave en PendingDeletion puede eliminarse de inmediato, y el servidor debe permitir la eliminación inmediata.", "Language": "Idioma", "Large File Count Warning": "Advertencia de Gran Recuento de Archivos", "Last": "Último", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Esta clave se usa como la clave SSE predeterminada de la plataforma para SSE-KMS y SSE-S3.", "This key is used as the platform default for SSE-KMS.": "Esta clave se usa como predeterminada de la plataforma para SSE-KMS.", "This link will expire when your session ends or at the specified time.": "Este enlace caducará cuando finalice su sesión o en la hora especificada.", - "This permanently deletes the key immediately and cannot be undone.": "Esto elimina la clave de forma permanente e inmediata y no se puede deshacer.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "La clave se elimina de forma permanente ahora, sin esperar a la fecha de eliminación programada. Los objetos cifrados con ella no podrán descifrarse nunca más.", + "Type the KMS key ID to confirm": "Escriba el ID de la clave KMS para confirmar", "This restores the key from the pending deletion state.": "Restaura la clave desde el estado de eliminación pendiente.", "This schedules the key for deletion using the default pending window.": "Programa la eliminación de la clave con la ventana de espera predeterminada.", "This server does not provide the detailed site replication status endpoint yet.": "Este servidor aún no proporciona el endpoint detallado del estado de replicación de sitios.", diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 47e0239b..983c37f7 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -587,7 +587,7 @@ "Key list refreshed": "Liste des clés actualisée", "Key services and configuration values reported by the cluster.": "Services de clés et valeurs de configuration signalés par le cluster.", "Key storage path prefix in KV store": "Préfixe du chemin de stockage de clé dans le magasin KV", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Les clés en PendingDeletion peuvent encore être restaurées ou supprimées immédiatement.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Planifiez d’abord la suppression ; elle peut être annulée pendant la période d’attente. Seule une clé en PendingDeletion peut être supprimée immédiatement, et le serveur doit autoriser la suppression immédiate.", "Language": "Langue", "Large File Count Warning": "Avertissement de nombre de fichiers élevé", "Last": "Dernier", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Cette clé est utilisée comme clé SSE par défaut de la plateforme pour SSE-KMS et SSE-S3.", "This key is used as the platform default for SSE-KMS.": "Cette clé est utilisée par défaut sur la plateforme pour SSE-KMS.", "This link will expire when your session ends or at the specified time.": "Ce lien expirera lorsque votre session prendra fin ou à l'heure indiquée.", - "This permanently deletes the key immediately and cannot be undone.": "Supprime définitivement la clé immédiatement, sans retour en arrière.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "La clé est supprimée définitivement maintenant, sans attendre la date de suppression planifiée. Les objets chiffrés avec elle ne pourront plus jamais être déchiffrés.", + "Type the KMS key ID to confirm": "Saisissez l’ID de la clé KMS pour confirmer", "This restores the key from the pending deletion state.": "Restaure la clé depuis l’état de suppression en attente.", "This schedules the key for deletion using the default pending window.": "Planifie la suppression de la clé avec la fenêtre d’attente par défaut.", "This server does not provide the detailed site replication status endpoint yet.": "Ce serveur ne fournit pas encore le point de terminaison détaillé de l'état de réplication des sites.", diff --git a/i18n/locales/id-ID.json b/i18n/locales/id-ID.json index b35a68d5..b77e9af2 100644 --- a/i18n/locales/id-ID.json +++ b/i18n/locales/id-ID.json @@ -587,7 +587,7 @@ "Key list refreshed": "Daftar kunci diperbarui", "Key services and configuration values reported by the cluster.": "Layanan utama dan nilai konfigurasi dilaporkan oleh klaster.", "Key storage path prefix in KV store": "Key storage path prefix in KV store", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Kunci dalam PendingDeletion masih dapat dipulihkan atau dihapus segera.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Jadwalkan penghapusan terlebih dahulu; penghapusan dapat dibatalkan selama masa tunggu. Hanya kunci dalam PendingDeletion yang dapat dihapus segera, dan server harus mengizinkan penghapusan segera.", "Language": "Bahasa", "Large File Count Warning": "Peringatan Jumlah File Besar", "Last": "Terakhir", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Kunci ini digunakan sebagai kunci SSE default platform untuk SSE-KMS dan SSE-S3.", "This key is used as the platform default for SSE-KMS.": "Kunci ini digunakan sebagai default platform untuk SSE-KMS.", "This link will expire when your session ends or at the specified time.": "Tautan ini akan kedaluwarsa saat sesi Anda berakhir atau pada waktu yang ditentukan.", - "This permanently deletes the key immediately and cannot be undone.": "Menghapus kunci secara permanen segera dan tidak dapat dibatalkan.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "Kunci dihapus secara permanen sekarang tanpa menunggu tanggal penghapusan yang dijadwalkan. Objek yang dienkripsi dengan kunci ini tidak akan pernah bisa didekripsi lagi.", + "Type the KMS key ID to confirm": "Ketik ID kunci KMS untuk mengonfirmasi", "This restores the key from the pending deletion state.": "Memulihkan kunci dari status penghapusan tertunda.", "This schedules the key for deletion using the default pending window.": "Menjadwalkan penghapusan kunci dengan jendela tunggu default.", "This server does not provide the detailed site replication status endpoint yet.": "Server ini belum menyediakan endpoint status replikasi situs yang detail.", diff --git a/i18n/locales/it-IT.json b/i18n/locales/it-IT.json index ef8b6d95..99fc8360 100644 --- a/i18n/locales/it-IT.json +++ b/i18n/locales/it-IT.json @@ -587,7 +587,7 @@ "Key list refreshed": "Elenco chiavi aggiornato", "Key services and configuration values reported by the cluster.": "Servizi chiave e valori di configurazione segnalati dal cluster.", "Key storage path prefix in KV store": "Prefisso percorso archiviazione chiave nel negozio KV", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Le chiavi in PendingDeletion possono ancora essere ripristinate o eliminate immediatamente.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Pianifica prima l’eliminazione; può essere annullata durante il periodo di attesa. Solo una chiave in PendingDeletion può essere eliminata immediatamente, e il server deve consentire l’eliminazione immediata.", "Language": "Lingua", "Large File Count Warning": "Avviso conteggio file elevato", "Last": "Ultimo", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Questa chiave viene usata come chiave SSE predefinita della piattaforma per SSE-KMS e SSE-S3.", "This key is used as the platform default for SSE-KMS.": "Questa chiave è il predefinito della piattaforma per SSE-KMS.", "This link will expire when your session ends or at the specified time.": "Questo link scadrà quando la sessione terminerà o all'ora specificata.", - "This permanently deletes the key immediately and cannot be undone.": "Elimina definitivamente la chiave immediatamente e non può essere annullato.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "La chiave viene eliminata definitivamente ora, senza attendere la data di eliminazione pianificata. Gli oggetti crittografati con essa non potranno mai più essere decrittografati.", + "Type the KMS key ID to confirm": "Digita l’ID della chiave KMS per confermare", "This restores the key from the pending deletion state.": "Ripristina la chiave dallo stato di eliminazione in sospeso.", "This schedules the key for deletion using the default pending window.": "Pianifica l’eliminazione della chiave con la finestra di attesa predefinita.", "This server does not provide the detailed site replication status endpoint yet.": "Questo server non fornisce ancora l'endpoint dettagliato dello stato di replica dei siti.", diff --git a/i18n/locales/ja-JP.json b/i18n/locales/ja-JP.json index dfacbb74..633a4081 100644 --- a/i18n/locales/ja-JP.json +++ b/i18n/locales/ja-JP.json @@ -587,7 +587,7 @@ "Key list refreshed": "キーリストが更新されました", "Key services and configuration values reported by the cluster.": "クラスターによって報告されるキーサービスと設定値。", "Key storage path prefix in KV store": "KVストア内のキーストレージパスプレフィックス", - "Keys in PendingDeletion can still be restored or deleted immediately.": "PendingDeletion のキーは復元または即時削除が可能です。", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "まず削除をスケジュールしてください。待機期間中はキャンセルできます。即時削除できるのは PendingDeletion 状態のキーのみで、サーバー側で即時削除が許可されている必要があります。", "Language": "言語", "Large File Count Warning": "大きなファイル数の警告", "Last": "最後", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "このキーは、SSE-KMS および SSE-S3 用のプラットフォーム既定の SSE キーとして使用されます。", "This key is used as the platform default for SSE-KMS.": "このキーはプラットフォームの SSE-KMS デフォルトとして使用されます。", "This link will expire when your session ends or at the specified time.": "このリンクはセッション終了時、または指定した時刻に期限切れになります。", - "This permanently deletes the key immediately and cannot be undone.": "キーをすぐに完全削除します。元に戻せません。", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "予定された削除日を待たずに、キーを今すぐ完全に削除します。このキーで暗号化されたオブジェクトは二度と復号できません。", + "Type the KMS key ID to confirm": "確認のため KMS キー ID を入力してください", "This restores the key from the pending deletion state.": "削除待ち状態からキーを復元します。", "This schedules the key for deletion using the default pending window.": "デフォルトの待機期間でキー削除をスケジュールします。", "This server does not provide the detailed site replication status endpoint yet.": "このサーバーはまだ詳細なサイトレプリケーション状態エンドポイントを提供していません。", diff --git a/i18n/locales/ko-KR.json b/i18n/locales/ko-KR.json index fd5f6cf9..b6b6fb05 100644 --- a/i18n/locales/ko-KR.json +++ b/i18n/locales/ko-KR.json @@ -587,7 +587,7 @@ "Key list refreshed": "키 목록이 새로고침됨", "Key services and configuration values reported by the cluster.": "클러스터에서 보고한 키 서비스 및 구성 값.", "Key storage path prefix in KV store": "KV 저장소의 키 저장 경로 접두사", - "Keys in PendingDeletion can still be restored or deleted immediately.": "PendingDeletion 상태의 키는 복원하거나 즉시 삭제할 수 있습니다.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "먼저 삭제를 예약하세요. 대기 기간 동안 취소할 수 있습니다. PendingDeletion 상태의 키만 즉시 삭제할 수 있으며, 서버에서 즉시 삭제를 허용해야 합니다.", "Language": "언어", "Large File Count Warning": "큰 파일 수 경고", "Last": "마지막", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "이 키는 SSE-KMS 및 SSE-S3에 대한 플랫폼 기본 SSE 키로 사용됩니다.", "This key is used as the platform default for SSE-KMS.": "이 키는 플랫폼의 SSE-KMS 기본값으로 사용됩니다.", "This link will expire when your session ends or at the specified time.": "이 링크는 세션이 종료되거나 지정한 시각에 만료됩니다.", - "This permanently deletes the key immediately and cannot be undone.": "키를 즉시 영구 삭제하며 되돌릴 수 없습니다.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "예약된 삭제 날짜를 기다리지 않고 키를 지금 영구적으로 삭제합니다. 이 키로 암호화된 객체는 다시는 복호화할 수 없습니다.", + "Type the KMS key ID to confirm": "확인하려면 KMS 키 ID를 입력하세요", "This restores the key from the pending deletion state.": "삭제 대기 상태에서 키를 복원합니다.", "This schedules the key for deletion using the default pending window.": "기본 대기 기간으로 키 삭제를 예약합니다.", "This server does not provide the detailed site replication status endpoint yet.": "이 서버는 아직 상세한 사이트 복제 상태 엔드포인트를 제공하지 않습니다.", diff --git a/i18n/locales/pt-BR.json b/i18n/locales/pt-BR.json index c6fbca29..c67e633d 100644 --- a/i18n/locales/pt-BR.json +++ b/i18n/locales/pt-BR.json @@ -587,7 +587,7 @@ "Key list refreshed": "Lista de chaves atualizada", "Key services and configuration values reported by the cluster.": "Serviços de chave e valores de configuração relatados pelo cluster.", "Key storage path prefix in KV store": "Prefixo do caminho de armazenamento de chave no armazenamento KV", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Chaves em PendingDeletion ainda podem ser restauradas ou excluídas imediatamente.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Agende a exclusão primeiro; ela pode ser cancelada durante o período de espera. Somente uma chave em PendingDeletion pode ser excluída imediatamente, e o servidor precisa permitir a exclusão imediata.", "Language": "Idioma", "Large File Count Warning": "Aviso de Grande Contagem de Arquivos", "Last": "Último", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Esta chave é usada como a chave SSE padrão da plataforma para SSE-KMS e SSE-S3.", "This key is used as the platform default for SSE-KMS.": "Esta chave é usada como padrão da plataforma para SSE-KMS.", "This link will expire when your session ends or at the specified time.": "Este link expirará quando sua sessão terminar ou no horário especificado.", - "This permanently deletes the key immediately and cannot be undone.": "Exclui a chave permanentemente de imediato e não pode ser desfeito.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "A chave é excluída permanentemente agora, sem aguardar a data de exclusão agendada. Os objetos criptografados com ela nunca mais poderão ser descriptografados.", + "Type the KMS key ID to confirm": "Digite o ID da chave KMS para confirmar", "This restores the key from the pending deletion state.": "Restaura a chave do estado de exclusão pendente.", "This schedules the key for deletion using the default pending window.": "Agenda a exclusão da chave com a janela de espera padrão.", "This server does not provide the detailed site replication status endpoint yet.": "Este servidor ainda não fornece o endpoint detalhado de status da replicação de sites.", diff --git a/i18n/locales/ru-RU.json b/i18n/locales/ru-RU.json index b4cc2901..0dca3a8f 100644 --- a/i18n/locales/ru-RU.json +++ b/i18n/locales/ru-RU.json @@ -587,7 +587,7 @@ "Key list refreshed": "Список ключей обновлен", "Key services and configuration values reported by the cluster.": "Службы ключей и значения конфигурации, о которых сообщает кластер.", "Key storage path prefix in KV store": "Префикс пути хранения ключа в хранилище KV", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Ключи в состоянии PendingDeletion всё ещё можно восстановить или удалить немедленно.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Сначала запланируйте удаление; его можно отменить в течение периода ожидания. Немедленно удалить можно только ключ в состоянии PendingDeletion, и сервер должен разрешать немедленное удаление.", "Language": "Язык", "Large File Count Warning": "Предупреждение о большом количестве файлов", "Last": "Последний", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Этот ключ используется как ключ SSE по умолчанию платформы для SSE-KMS и SSE-S3.", "This key is used as the platform default for SSE-KMS.": "Этот ключ используется по умолчанию платформы для SSE-KMS.", "This link will expire when your session ends or at the specified time.": "Эта ссылка истечет после завершения вашей сессии или в указанное время.", - "This permanently deletes the key immediately and cannot be undone.": "Немедленно и безвозвратно удаляет ключ.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "Ключ будет безвозвратно удалён сейчас, без ожидания запланированной даты удаления. Объекты, зашифрованные этим ключом, больше никогда нельзя будет расшифровать.", + "Type the KMS key ID to confirm": "Введите идентификатор ключа KMS для подтверждения", "This restores the key from the pending deletion state.": "Восстанавливает ключ из состояния ожидающего удаления.", "This schedules the key for deletion using the default pending window.": "Планирует удаление ключа с окном ожидания по умолчанию.", "This server does not provide the detailed site replication status endpoint yet.": "Этот сервер пока не предоставляет эндпоинт с подробным статусом репликации сайтов.", diff --git a/i18n/locales/tr-TR.json b/i18n/locales/tr-TR.json index 785632d7..a20de306 100644 --- a/i18n/locales/tr-TR.json +++ b/i18n/locales/tr-TR.json @@ -587,7 +587,7 @@ "Key list refreshed": "Anahtar listesi yenilendi", "Key services and configuration values reported by the cluster.": "Küme tarafından bildirilen temel hizmetler ve yapılandırma değerleri.", "Key storage path prefix in KV store": "KV deposundaki anahtar depolama yolu öneki", - "Keys in PendingDeletion can still be restored or deleted immediately.": "PendingDeletion durumundaki anahtarlar hâlâ geri yüklenebilir veya hemen silinebilir.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Önce silmeyi zamanlayın; bekleme süresi içinde iptal edilebilir. Yalnızca PendingDeletion durumundaki bir anahtar hemen silinebilir ve sunucunun hemen silmeye izin vermesi gerekir.", "Language": "Dil", "Large File Count Warning": "Büyük Dosya Sayısı Uyarısı", "Last": "Son", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Bu anahtar, SSE-KMS ve SSE-S3 için platformun varsayılan SSE anahtarı olarak kullanılır.", "This key is used as the platform default for SSE-KMS.": "Bu anahtar platformun SSE-KMS varsayılanıdır.", "This link will expire when your session ends or at the specified time.": "Bu bağlantı oturumunuz sona erdiğinde veya belirtilen saatte süresi dolacaktır.", - "This permanently deletes the key immediately and cannot be undone.": "Anahtarı hemen kalıcı olarak siler ve geri alınamaz.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "Anahtar, zamanlanan silme tarihi beklenmeden şimdi kalıcı olarak silinir. Bu anahtarla şifrelenen nesnelerin şifresi bir daha asla çözülemez.", + "Type the KMS key ID to confirm": "Onaylamak için KMS anahtar kimliğini yazın", "This restores the key from the pending deletion state.": "Anahtarı bekleyen silme durumundan geri yükler.", "This schedules the key for deletion using the default pending window.": "Varsayılan bekleme süresiyle silmeyi planlar.", "This server does not provide the detailed site replication status endpoint yet.": "Bu sunucu henüz ayrıntılı site replikasyonu durum uç noktasını sağlamıyor.", diff --git a/i18n/locales/vi-VN.json b/i18n/locales/vi-VN.json index eeac6f7d..74c0bc69 100644 --- a/i18n/locales/vi-VN.json +++ b/i18n/locales/vi-VN.json @@ -587,7 +587,7 @@ "Key list refreshed": "Đã làm mới danh sách khóa", "Key services and configuration values reported by the cluster.": "Các dịch vụ chính và giá trị cấu hình được báo cáo bởi cụm máy chủ.", "Key storage path prefix in KV store": "Tiền tố đường dẫn lưu trữ khóa trong kho KV", - "Keys in PendingDeletion can still be restored or deleted immediately.": "Khóa ở trạng thái PendingDeletion vẫn có thể khôi phục hoặc xóa ngay.", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Hãy lên lịch xóa trước; có thể hủy trong thời gian chờ. Chỉ khóa ở trạng thái PendingDeletion mới có thể bị xóa ngay, và máy chủ phải cho phép xóa ngay.", "Language": "Ngôn ngữ", "Large File Count Warning": "Cảnh báo số lượng tệp lớn", "Last": "Cuối cùng", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Khóa này được dùng làm khóa SSE mặc định của nền tảng cho SSE-KMS và SSE-S3.", "This key is used as the platform default for SSE-KMS.": "Khóa này là mặc định nền tảng cho SSE-KMS.", "This link will expire when your session ends or at the specified time.": "Liên kết này sẽ hết hạn khi phiên làm việc của bạn kết thúc hoặc vào thời gian đã định.", - "This permanently deletes the key immediately and cannot be undone.": "Xóa khóa vĩnh viễn ngay lập tức và không thể hoàn tác.", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "Khóa sẽ bị xóa vĩnh viễn ngay bây giờ thay vì chờ đến ngày xóa đã lên lịch. Các đối tượng được mã hóa bằng khóa này sẽ không bao giờ giải mã được nữa.", + "Type the KMS key ID to confirm": "Nhập ID khóa KMS để xác nhận", "This restores the key from the pending deletion state.": "Khôi phục khóa khỏi trạng thái chờ xóa.", "This schedules the key for deletion using the default pending window.": "Lên lịch xóa khóa với cửa sổ chờ mặc định.", "This server does not provide the detailed site replication status endpoint yet.": "Máy chủ này chưa cung cấp endpoint trạng thái sao chép site chi tiết.", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 802b3b0e..875bd154 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -585,7 +585,7 @@ "Key list refreshed": "密钥列表已刷新", "Key services and configuration values reported by the cluster.": "集群报告的密钥服务和配置值。", "Key storage path prefix in KV store": "KV 存储中的密钥存储路径前缀", - "Keys in PendingDeletion can still be restored or deleted immediately.": "处于 PendingDeletion 状态的密钥仍可恢复或立即删除。", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "请先计划删除,等待期内可以取消。只有处于 PendingDeletion 状态的密钥才能立即删除,并且服务端必须允许立即删除。", "Language": "语言", "Large File Count Warning": "大文件数量警告", "Last": "最后一页", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "此密钥将作为平台默认 SSE 密钥,用于 SSE-KMS 和 SSE-S3。", "This key is used as the platform default for SSE-KMS.": "该密钥将作为平台默认的 SSE-KMS 密钥。", "This link will expire when your session ends or at the specified time.": "此链接将会在您的登录会话结束或达到指定时间后失效。", - "This permanently deletes the key immediately and cannot be undone.": "这会立即永久删除该密钥,且无法撤销。", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "这会立即永久删除该密钥,不再等待计划的删除日期。使用它加密的对象将永远无法解密。", + "Type the KMS key ID to confirm": "输入 KMS 密钥 ID 以确认", "This restores the key from the pending deletion state.": "这会把密钥从待删除状态恢复。", "This schedules the key for deletion using the default pending window.": "这会按默认等待窗口将密钥标记为计划删除。", "This server does not provide the detailed site replication status endpoint yet.": "当前服务器暂未提供详细的站点复制状态接口。", diff --git a/i18n/locales/zh-TW.json b/i18n/locales/zh-TW.json index 1429e165..ef4dfe7c 100644 --- a/i18n/locales/zh-TW.json +++ b/i18n/locales/zh-TW.json @@ -585,7 +585,7 @@ "Key list refreshed": "金鑰清單已重新整理", "Key services and configuration values reported by the cluster.": "叢集回報的關鍵服務與設定值。", "Key storage path prefix in KV store": "KV 儲存中的金鑰儲存路徑 Prefix", - "Keys in PendingDeletion can still be restored or deleted immediately.": "處於 PendingDeletion 狀態的金鑰仍可還原或立即刪除。", + "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "請先排程刪除,等待期內可以取消。只有處於 PendingDeletion 狀態的金鑰才能立即刪除,且伺服器必須允許立即刪除。", "Language": "語言", "Large File Count Warning": "大檔案數量警告", "Last": "最後一頁", @@ -1195,7 +1195,8 @@ "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "此金鑰將作為平台預設 SSE 金鑰,用於 SSE-KMS 和 SSE-S3。", "This key is used as the platform default for SSE-KMS.": "該金鑰將作為平台預設的 SSE-KMS 金鑰。", "This link will expire when your session ends or at the specified time.": "此連結將會在您的登入工作階段結束或達到指定時間後失效。", - "This permanently deletes the key immediately and cannot be undone.": "這會立即永久刪除該金鑰,且無法復原。", + "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "這會立即永久刪除該金鑰,不再等待排定的刪除日期。使用它加密的物件將永遠無法解密。", + "Type the KMS key ID to confirm": "輸入 KMS 金鑰 ID 以確認", "This restores the key from the pending deletion state.": "這會把金鑰從待刪除狀態還原。", "This schedules the key for deletion using the default pending window.": "這會按預設等待視窗將金鑰標記為排程刪除。", "This server does not provide the detailed site replication status endpoint yet.": "目前伺服器暫未提供詳細的站點複製狀態介面。", diff --git a/lib/sse/key-deletion.ts b/lib/sse/key-deletion.ts new file mode 100644 index 00000000..02a84ae7 --- /dev/null +++ b/lib/sse/key-deletion.ts @@ -0,0 +1,19 @@ +export interface KmsImmediateKeyDeletionRequest { + key_id: string + force_immediate: true + confirm_key_id: string +} + +// A key is destroyed in two steps: only one already scheduled for deletion can be deleted immediately. +export function isKeyPendingDeletion(status?: string | null): boolean { + return (status ?? "").toLowerCase().includes("pendingdeletion") +} + +export function isImmediateKeyDeletionConfirmed(keyId: string, typedKeyId: string): boolean { + return keyId.length > 0 && typedKeyId === keyId +} + +// The server only accepts immediate deletion as a JSON body that repeats the key ID. +export function buildImmediateKeyDeletionRequest(keyId: string, confirmKeyId: string): KmsImmediateKeyDeletionRequest { + return { key_id: keyId, force_immediate: true, confirm_key_id: confirmKeyId } +} diff --git a/tests/lib/sse-key-deletion.test.ts b/tests/lib/sse-key-deletion.test.ts new file mode 100644 index 00000000..8d80b274 --- /dev/null +++ b/tests/lib/sse-key-deletion.test.ts @@ -0,0 +1,57 @@ +import test from "node:test" +import assert from "node:assert/strict" +import fs from "node:fs" +import { + buildImmediateKeyDeletionRequest, + isImmediateKeyDeletionConfirmed, + isKeyPendingDeletion, +} from "../../lib/sse/key-deletion" + +const page = fs.readFileSync("app/(dashboard)/sse/page.tsx", "utf8") +const hook = fs.readFileSync("hooks/use-sse.ts", "utf8") + +test("only a key scheduled for deletion counts as pending deletion", () => { + assert.equal(isKeyPendingDeletion("PendingDeletion"), true) + assert.equal(isKeyPendingDeletion("pendingdeletion"), true) + for (const status of ["Active", "Enabled", "Disabled", "Deleted", "", null, undefined]) { + assert.equal(isKeyPendingDeletion(status), false, String(status)) + } +}) + +test("immediate deletion is confirmed only by the exact key ID", () => { + assert.equal(isImmediateKeyDeletionConfirmed("key-a", "key-a"), true) + for (const typed of ["", "key-A", "key-a ", " key-a", "key-ab"]) { + assert.equal(isImmediateKeyDeletionConfirmed("key-a", typed), false, JSON.stringify(typed)) + } + assert.equal(isImmediateKeyDeletionConfirmed("", ""), false) +}) + +test("immediate deletion request carries the typed confirmation in the body", () => { + assert.deepEqual(buildImmediateKeyDeletionRequest("key-a", "key-a"), { + key_id: "key-a", + force_immediate: true, + confirm_key_id: "key-a", + }) +}) + +test("immediate deletion is sent as a JSON body and never as a query string", () => { + assert.match( + hook, + /api\.delete\("\/kms\/keys\/delete", \{\s*body: buildImmediateKeyDeletionRequest\(keyId, confirmKeyId\),/, + ) + assert.doesNotMatch(hook, /queryParams\.append\("force_immediate"/) +}) + +test("Delete Immediately is offered only for keys pending deletion and needs the typed key ID", () => { + const offers = [...page.matchAll(/setPendingKeyAction\(\{ type: "forceDelete", key \}\)/g)] + assert.equal(offers.length, 2) + assert.match(page, /\{isPendingDeletion \? \(\s*[\s\S]*?type: "cancelDeletion"[\s\S]*?[\s\S]*?type: "forceDelete"[\s\S]*?<\/>\s*\)\}/, + ) + assert.match(page, /id="confirmKeyId"/) + assert.match(page, /!isImmediateKeyDeletionConfirmed\(pendingKeyAction\.key\.key_id, confirmKeyId\)/) + assert.match(page, /forceDeleteKey\(pendingKeyAction\.key\.key_id, confirmKeyId\)/) + assert.match(page, /setConfirmKeyId\(""\)\s+setPendingKeyActionState\(action\)/) +}) diff --git a/types/kms.ts b/types/kms.ts index a2d764e6..cb3f7496 100644 --- a/types/kms.ts +++ b/types/kms.ts @@ -231,7 +231,6 @@ export interface KmsKeyDetailResponse extends KmsMutationResponse { export interface KmsDeleteKeyOptions { pending_window_in_days?: number - force_immediate?: boolean } export interface KmsCancelDeletionRequest {