mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: add function to export portable backup settings envelope
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import { zipSync, unzipSync } from 'fflate';
|
import { zipSync, unzipSync } from 'fflate';
|
||||||
import type { Env } from '../types';
|
import type { Env } from '../types';
|
||||||
import { APP_VERSION } from '../../shared/app-version';
|
import { APP_VERSION } from '../../shared/app-version';
|
||||||
|
import { BACKUP_SETTINGS_CONFIG_KEY } from './backup-config';
|
||||||
|
import { exportPortableBackupSettingsEnvelope } from './backup-settings-crypto';
|
||||||
import {
|
import {
|
||||||
getAttachmentObjectKey,
|
getAttachmentObjectKey,
|
||||||
getBlobStorageKind,
|
getBlobStorageKind,
|
||||||
@@ -9,6 +11,7 @@ import {
|
|||||||
type SqlRow = Record<string, string | number | null>;
|
type SqlRow = Record<string, string | number | null>;
|
||||||
|
|
||||||
const BACKUP_FORMAT_VERSION = 1;
|
const BACKUP_FORMAT_VERSION = 1;
|
||||||
|
const BACKUP_RUNNER_LOCK_CONFIG_KEY = 'backup.runner.lock.v1';
|
||||||
const BACKUP_FILE_HASH_PREFIX_LENGTH = 5;
|
const BACKUP_FILE_HASH_PREFIX_LENGTH = 5;
|
||||||
// Worker-side backup export must stay well below Cloudflare CPU limits.
|
// Worker-side backup export must stay well below Cloudflare CPU limits.
|
||||||
// Prefer store-only ZIP entries over heavier compression to keep exports reliable.
|
// Prefer store-only ZIP entries over heavier compression to keep exports reliable.
|
||||||
@@ -89,6 +92,23 @@ async function queryRows(db: D1Database, sql: string, ...values: unknown[]): Pro
|
|||||||
return (result.results || []).map((row) => ({ ...row }));
|
return (result.results || []).map((row) => ({ ...row }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeConfigRowsForExport(rows: SqlRow[]): SqlRow[] {
|
||||||
|
const sanitized: SqlRow[] = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
const key = String(row.key || '').trim();
|
||||||
|
if (!key || key === BACKUP_RUNNER_LOCK_CONFIG_KEY) continue;
|
||||||
|
|
||||||
|
if (key === BACKUP_SETTINGS_CONFIG_KEY) {
|
||||||
|
const portableOnly = exportPortableBackupSettingsEnvelope(typeof row.value === 'string' ? row.value : null);
|
||||||
|
if (portableOnly) sanitized.push({ ...row, value: portableOnly });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitized.push({ ...row });
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
async function sha256Hex(bytes: Uint8Array): Promise<string> {
|
async function sha256Hex(bytes: Uint8Array): Promise<string> {
|
||||||
const digest = await crypto.subtle.digest('SHA-256', bytes);
|
const digest = await crypto.subtle.digest('SHA-256', bytes);
|
||||||
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||||
@@ -353,6 +373,7 @@ export async function buildBackupArchive(
|
|||||||
queryRows(env.DB, 'SELECT id, user_id, type, folder_id, name, notes, favorite, data, reprompt, key, created_at, updated_at, archived_at, deleted_at FROM ciphers ORDER BY created_at ASC'),
|
queryRows(env.DB, 'SELECT id, user_id, type, folder_id, name, notes, favorite, data, reprompt, key, created_at, updated_at, archived_at, deleted_at FROM ciphers ORDER BY created_at ASC'),
|
||||||
queryRows(env.DB, 'SELECT id, cipher_id, file_name, size, size_name, key FROM attachments ORDER BY cipher_id ASC, id ASC'),
|
queryRows(env.DB, 'SELECT id, cipher_id, file_name, size, size_name, key FROM attachments ORDER BY cipher_id ASC, id ASC'),
|
||||||
]);
|
]);
|
||||||
|
const exportedConfigRows = sanitizeConfigRowsForExport(configRows);
|
||||||
const exportedAttachmentRows = includeAttachments ? attachmentRows : [];
|
const exportedAttachmentRows = includeAttachments ? attachmentRows : [];
|
||||||
const attachmentBlobs: BackupManifestAttachmentBlob[] = exportedAttachmentRows.map((row) => {
|
const attachmentBlobs: BackupManifestAttachmentBlob[] = exportedAttachmentRows.map((row) => {
|
||||||
const cipherId = String(row.cipher_id || '').trim();
|
const cipherId = String(row.cipher_id || '').trim();
|
||||||
@@ -371,7 +392,7 @@ export async function buildBackupArchive(
|
|||||||
appVersion: APP_VERSION,
|
appVersion: APP_VERSION,
|
||||||
storageKind: getBlobStorageKind(env),
|
storageKind: getBlobStorageKind(env),
|
||||||
tableCounts: {
|
tableCounts: {
|
||||||
config: configRows.length,
|
config: exportedConfigRows.length,
|
||||||
users: userRows.length,
|
users: userRows.length,
|
||||||
user_revisions: revisionRows.length,
|
user_revisions: revisionRows.length,
|
||||||
folders: folderRows.length,
|
folders: folderRows.length,
|
||||||
@@ -392,7 +413,7 @@ export async function buildBackupArchive(
|
|||||||
const files: Record<string, Uint8Array> = {
|
const files: Record<string, Uint8Array> = {
|
||||||
'manifest.json': encoder.encode(JSON.stringify(manifestBase, null, BACKUP_JSON_INDENT)),
|
'manifest.json': encoder.encode(JSON.stringify(manifestBase, null, BACKUP_JSON_INDENT)),
|
||||||
'db.json': encoder.encode(JSON.stringify({
|
'db.json': encoder.encode(JSON.stringify({
|
||||||
config: configRows,
|
config: exportedConfigRows,
|
||||||
users: userRows,
|
users: userRows,
|
||||||
user_revisions: revisionRows,
|
user_revisions: revisionRows,
|
||||||
folders: folderRows,
|
folders: folderRows,
|
||||||
|
|||||||
@@ -155,6 +155,20 @@ export function parseBackupSettingsEnvelope(raw: string | null): BackupSettingsE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function exportPortableBackupSettingsEnvelope(raw: string | null): string | null {
|
||||||
|
const envelope = parseBackupSettingsEnvelope(raw);
|
||||||
|
if (!envelope) return null;
|
||||||
|
return JSON.stringify({
|
||||||
|
version: 2,
|
||||||
|
portableOnly: true,
|
||||||
|
runtime: {
|
||||||
|
iv: '',
|
||||||
|
ciphertext: '',
|
||||||
|
},
|
||||||
|
portable: envelope.portable,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function encryptBackupSettingsEnvelope(
|
export async function encryptBackupSettingsEnvelope(
|
||||||
plaintext: string,
|
plaintext: string,
|
||||||
env: Env,
|
env: Env,
|
||||||
|
|||||||
Reference in New Issue
Block a user