From ea9e238aa7bd132668f70a469ca6598006fea753 Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Sat, 23 May 2026 02:53:03 +0800 Subject: [PATCH] fix: remove checks for portable admins in backup settings saving and normalization --- src/services/backup-config.ts | 19 --------------- src/services/backup-settings-crypto.ts | 33 ++++++++++++++------------ 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/services/backup-config.ts b/src/services/backup-config.ts index a67849d..e614a8e 100644 --- a/src/services/backup-config.ts +++ b/src/services/backup-config.ts @@ -409,13 +409,6 @@ export async function loadBackupSettings(storage: StorageService, env: Env, fall export async function saveBackupSettings(storage: StorageService, env: Env, settings: BackupSettings): Promise { const users = await storage.getAllUsers(); - const hasPortableAdmins = users.some( - (user) => user.role === 'admin' && user.status === 'active' && typeof user.publicKey === 'string' && user.publicKey.trim().length > 0 - ); - if (!hasPortableAdmins) { - await storage.setConfigValue(BACKUP_SETTINGS_CONFIG_KEY, serializeBackupSettings(settings)); - return; - } const encrypted = await encryptBackupSettingsEnvelope(serializeBackupSettings(settings), env, users); await storage.setConfigValue(BACKUP_SETTINGS_CONFIG_KEY, encrypted); } @@ -442,12 +435,6 @@ export async function normalizeImportedBackupSettingsValue( try { const decrypted = await decryptBackupSettingsRuntime(raw, env); const settings = parseBackupSettings(decrypted, fallbackTimezone); - const hasPortableAdmins = users.some( - (user) => user.role === 'admin' && user.status === 'active' && typeof user.publicKey === 'string' && user.publicKey.trim().length > 0 - ); - if (!hasPortableAdmins) { - return serializeBackupSettings(settings); - } return encryptBackupSettingsEnvelope(serializeBackupSettings(settings), env, users); } catch { // Keep imported portable recovery data intact until an admin signs in and repairs it. @@ -455,12 +442,6 @@ export async function normalizeImportedBackupSettingsValue( } } const settings = parseBackupSettings(raw, fallbackTimezone); - const hasPortableAdmins = users.some( - (user) => user.role === 'admin' && user.status === 'active' && typeof user.publicKey === 'string' && user.publicKey.trim().length > 0 - ); - if (!hasPortableAdmins) { - return serializeBackupSettings(settings); - } return encryptBackupSettingsEnvelope(serializeBackupSettings(settings), env, users); } diff --git a/src/services/backup-settings-crypto.ts b/src/services/backup-settings-crypto.ts index 2d11e08..33015b4 100644 --- a/src/services/backup-settings-crypto.ts +++ b/src/services/backup-settings-crypto.ts @@ -6,6 +6,8 @@ import type { Env, User } from '../types'; // server's scheduled backup runner. // - portable: AES-GCM encrypted with a random DEK; that DEK is RSA-wrapped for // active admin public keys so settings can be repaired after restore/migration. +// Historical/imported databases may not have usable admin public keys; in that +// case portable.wraps is empty but the runtime ciphertext is still encrypted. // // New admin-entered provider secrets, such as mail API keys, should use this // pattern or a deliberately documented replacement. Do not store provider @@ -186,9 +188,6 @@ export async function encryptBackupSettingsEnvelope( ): Promise { const encoder = new TextEncoder(); const eligibleUsers = getEligiblePortableUsers(users); - if (!eligibleUsers.length) { - throw new Error('No active administrator public keys are available for backup settings recovery'); - } const runtimeKey = await deriveRuntimeKey(env.JWT_SECRET); const runtime = await encryptAesGcm(encoder.encode(plaintext), runtimeKey); @@ -205,18 +204,22 @@ export async function encryptBackupSettingsEnvelope( const wraps: BackupSettingsPortableWrap[] = []; for (const user of eligibleUsers) { - const publicKey = await importPortablePublicKey(user.publicKey!); - const wrappedKey = new Uint8Array( - await crypto.subtle.encrypt( - { name: PORTABLE_ALGORITHM }, - publicKey, - portableDek - ) - ); - wraps.push({ - userId: user.id, - wrappedKey: bytesToBase64(wrappedKey), - }); + try { + const publicKey = await importPortablePublicKey(user.publicKey!); + const wrappedKey = new Uint8Array( + await crypto.subtle.encrypt( + { name: PORTABLE_ALGORITHM }, + publicKey, + portableDek + ) + ); + wraps.push({ + userId: user.id, + wrappedKey: bytesToBase64(wrappedKey), + }); + } catch { + // Keep runtime settings usable even if an imported admin key is malformed. + } } const envelope: BackupSettingsEnvelopeV2 = {