fix: remove checks for portable admins in backup settings saving and normalization

This commit is contained in:
shuaiplus
2026-05-23 02:53:03 +08:00
parent 22d267f5bc
commit ea9e238aa7
2 changed files with 18 additions and 34 deletions
-19
View File
@@ -409,13 +409,6 @@ export async function loadBackupSettings(storage: StorageService, env: Env, fall
export async function saveBackupSettings(storage: StorageService, env: Env, settings: BackupSettings): Promise<void> { export async function saveBackupSettings(storage: StorageService, env: Env, settings: BackupSettings): Promise<void> {
const users = await storage.getAllUsers(); 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); const encrypted = await encryptBackupSettingsEnvelope(serializeBackupSettings(settings), env, users);
await storage.setConfigValue(BACKUP_SETTINGS_CONFIG_KEY, encrypted); await storage.setConfigValue(BACKUP_SETTINGS_CONFIG_KEY, encrypted);
} }
@@ -442,12 +435,6 @@ export async function normalizeImportedBackupSettingsValue(
try { try {
const decrypted = await decryptBackupSettingsRuntime(raw, env); const decrypted = await decryptBackupSettingsRuntime(raw, env);
const settings = parseBackupSettings(decrypted, fallbackTimezone); 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); return encryptBackupSettingsEnvelope(serializeBackupSettings(settings), env, users);
} catch { } catch {
// Keep imported portable recovery data intact until an admin signs in and repairs it. // 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 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); return encryptBackupSettingsEnvelope(serializeBackupSettings(settings), env, users);
} }
+18 -15
View File
@@ -6,6 +6,8 @@ import type { Env, User } from '../types';
// server's scheduled backup runner. // server's scheduled backup runner.
// - portable: AES-GCM encrypted with a random DEK; that DEK is RSA-wrapped for // - 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. // 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 // New admin-entered provider secrets, such as mail API keys, should use this
// pattern or a deliberately documented replacement. Do not store provider // pattern or a deliberately documented replacement. Do not store provider
@@ -186,9 +188,6 @@ export async function encryptBackupSettingsEnvelope(
): Promise<string> { ): Promise<string> {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const eligibleUsers = getEligiblePortableUsers(users); 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 runtimeKey = await deriveRuntimeKey(env.JWT_SECRET);
const runtime = await encryptAesGcm(encoder.encode(plaintext), runtimeKey); const runtime = await encryptAesGcm(encoder.encode(plaintext), runtimeKey);
@@ -205,18 +204,22 @@ export async function encryptBackupSettingsEnvelope(
const wraps: BackupSettingsPortableWrap[] = []; const wraps: BackupSettingsPortableWrap[] = [];
for (const user of eligibleUsers) { for (const user of eligibleUsers) {
const publicKey = await importPortablePublicKey(user.publicKey!); try {
const wrappedKey = new Uint8Array( const publicKey = await importPortablePublicKey(user.publicKey!);
await crypto.subtle.encrypt( const wrappedKey = new Uint8Array(
{ name: PORTABLE_ALGORITHM }, await crypto.subtle.encrypt(
publicKey, { name: PORTABLE_ALGORITHM },
portableDek publicKey,
) portableDek
); )
wraps.push({ );
userId: user.id, wraps.push({
wrappedKey: bytesToBase64(wrappedKey), userId: user.id,
}); wrappedKey: bytesToBase64(wrappedKey),
});
} catch {
// Keep runtime settings usable even if an imported admin key is malformed.
}
} }
const envelope: BackupSettingsEnvelopeV2 = { const envelope: BackupSettingsEnvelopeV2 = {