feat: add expiring backups and live account checks
CI / verify (push) Canceled after 0s

This commit is contained in:
Sucukdeluxe
2026-09-01 15:43:20 +02:00
parent 96037f2e1c
commit 9e5c5126dd
19 changed files with 710 additions and 128 deletions
+37 -3
View File
@@ -16,6 +16,14 @@ const MAX_PLAINTEXT_BYTES = 512 * 1024;
const REQUEST_TIMEOUT_MS = 12_000;
const KEY_CONTEXT = Buffer.from('MHU2-ONLINE-KEY-V1', 'utf8');
const AAD_CONTEXT = Buffer.from('MHU-ONLINE-BACKUP-V1', 'utf8');
const ONLINE_BACKUP_RETENTION_SECONDS = Object.freeze({
'1d': 24 * 60 * 60,
'3d': 3 * 24 * 60 * 60,
'7d': 7 * 24 * 60 * 60,
'31d': 31 * 24 * 60 * 60,
forever: null
});
const DEFAULT_ONLINE_BACKUP_RETENTION = '7d';
function checksum(idBytes, masterKey) {
return crypto.createHash('sha256').update(KEY_CONTEXT).update(idBytes).update(masterKey).digest().subarray(0, CHECKSUM_LENGTH);
@@ -131,7 +139,27 @@ function parseOnlineBackupKey(key) {
};
}
function createOnlineBackup(settings, appVersion, exportedAt = new Date().toISOString()) {
function normalizeOnlineBackupRetention(value = DEFAULT_ONLINE_BACKUP_RETENTION) {
const normalized = String(value || '').trim();
if (!Object.prototype.hasOwnProperty.call(ONLINE_BACKUP_RETENTION_SECONDS, normalized)) {
throw new Error('Gültigkeitsdauer der Online-Sicherung ist ungültig');
}
return normalized;
}
function onlineBackupExpiration(exportedAt, retention) {
const normalized = normalizeOnlineBackupRetention(retention);
const seconds = ONLINE_BACKUP_RETENTION_SECONDS[normalized];
if (seconds === null) return null;
const created = new Date(exportedAt);
if (!Number.isFinite(created.getTime())) throw new Error('Erstellungszeit der Online-Sicherung ist ungültig');
return new Date(created.getTime() + seconds * 1000).toISOString();
}
function createOnlineBackup(settings, appVersion, exportedAt = new Date().toISOString(), retention = DEFAULT_ONLINE_BACKUP_RETENTION) {
const normalizedRetention = normalizeOnlineBackupRetention(retention);
const expiresInSeconds = ONLINE_BACKUP_RETENTION_SECONDS[normalizedRetention];
const expiresAt = onlineBackupExpiration(exportedAt, normalizedRetention);
const idBytes = crypto.randomBytes(RECORD_ID_LENGTH);
const masterKey = crypto.randomBytes(MASTER_KEY_LENGTH);
const key = encodeKey(idBytes, masterKey);
@@ -163,8 +191,10 @@ function createOnlineBackup(settings, appVersion, exportedAt = new Date().toISOS
record: {
id: parsed.id,
blob: blobBytes.toString('base64url'),
deleteVerifier
}
deleteVerifier,
expiresInSeconds
},
expiresAt
};
}
@@ -241,10 +271,14 @@ async function deleteOnlineBackup(key, baseUrl = ONLINE_BACKUP_API_URL, options)
}
module.exports = {
DEFAULT_ONLINE_BACKUP_RETENTION,
ONLINE_BACKUP_API_URL,
ONLINE_BACKUP_RETENTION_SECONDS,
createOnlineBackup,
deleteOnlineBackup,
downloadOnlineBackup,
normalizeOnlineBackupRetention,
onlineBackupExpiration,
parseOnlineBackupKey,
restoreOnlineBackup,
uploadOnlineBackup