Default new online backup keys to unlimited validity
CI / verify (push) Waiting to run

This commit is contained in:
Sucukdeluxe
2026-09-22 04:52:45 +02:00
parent 15e920fbde
commit 4d17dbd06f
9 changed files with 29 additions and 13 deletions
+2 -2
View File
@@ -27,7 +27,7 @@ Multi-Hoster-Upload ist eine Electron-Desktopanwendung für Windows, die große
- DoodStream-OTP-Prüfungen verwenden dieselbe Cookie-Sitzung weiter, fassen identische oder parallele Checks zusammen und fordern einen neuen Code nur nach einer ausdrücklichen Aktion mit mindestens 60 Sekunden Abstand an.
- DoodStream respektiert die ausdrückliche Auswahl `authType=login`: Weblogin und Webupload werden nicht durch einen zusätzlich gespeicherten API-Key übersteuert. API-Accounts und ältere Accounts ohne ausdrückliche Login-Auswahl behalten den API-Weg.
- Sammelchecks melden jedes Account-Ergebnis einzeln an den Renderer, sodass fertige Karten sofort grün, rot oder als OTP-pflichtig erscheinen, während die übrigen Accounts weiter geprüft werden.
- Neue Online-Backups unterstützen `24 Stunden`, `3 Tage`, `7 Tage` (Standard), `31 Tage` und `Unbegrenzt`. Endliche Schlüssel werden lokal aus dem verschlüsselten Schlüsselbund entfernt und serverseitig ab Ablauf nicht mehr wiederhergestellt; der Dienst räumt abgelaufene Datensätze bei Zugriff oder der nächsten Speicherung auf.
- Neue Online-Backups unterstützen `24 Stunden`, `3 Tage`, `7 Tage`, `31 Tage` und `Unbegrenzt` (neuer Standard in Oberfläche und Erstellungslogik; nach `2.1.46` geändert, noch nicht veröffentlicht). Bestehende Schlüssel behalten ihre Laufzeit. Endliche Schlüssel werden lokal aus dem verschlüsselten Schlüsselbund entfernt und serverseitig ab Ablauf nicht mehr wiederhergestellt; der Dienst räumt abgelaufene Datensätze bei Zugriff oder der nächsten Speicherung auf.
- Vorhandene Online-Backups und alte Upload-Payloads ohne Ablaufangabe bleiben zur Abwärtskompatibilität unbegrenzt gültig.
- Backup-Importe wenden Sprache und automatischen Account-Check sofort an. Alle Konfigurationsfelder werden übertragen; Warteschlange, Upload-Wiederherstellungsstatus, letzter Dateiauswahlordner, Automatik-Telemetrie und Pausenzustand bleiben bewusst geräte- beziehungsweise laufzeitgebunden.
- Ein nicht vorhandener Ordnerüberwachungspfad bleibt nach dem Import sichtbar gespeichert, die Überwachung wird aber deaktiviert und der Nutzer erhält eine Warnung. Ein nicht vorhandener Log-Ordner wird ebenfalls gemeldet, ohne den konfigurierten Pfad still zu löschen.
@@ -77,7 +77,7 @@ Stand: 22.09.2026 (Produktivaktivierung und Release `2.1.46`; ältere Versionspr
- Eingebauter Updater mit isoliertem Testprofil und installierter Version `2.1.45` erkennt `2.1.46`, übernimmt den englischen GitHub-Changelog und akzeptiert das Forgejo-Update-Manifest. Alle acht veröffentlichten Dateien wurden erneut heruntergeladen und anhand Größe und SHA-512 gegen die lokalen Dateien geprüft. Titel, Tag, Sprache, Veröffentlichungsstatus und beide anbieterspezifischen Manifeste stimmen. Vollständiger öffentlicher Quellcheck: 163 erlaubte Dateien, Screenshots gültig. Anleitung auf dem Desktop mit abgeschlossenem Live- und Release-Stand aktualisiert.
- Lint: erfolgreich, 0 Warnungen und 0 Fehler.
- Haupttests: 822 erfolgreich, 0 fehlgeschlagen; einschließlich vier neuer Wiederherstellungstests und öffentlicher Quellmanifest-Prüfung (163 Dateien).
- Haupttests: 823 erfolgreich, 0 fehlgeschlagen; einschließlich Standardgültigkeit `Unbegrenzt`, deutscher/englischer Auswahl, aller expliziten Laufzeiten, vier Wiederherstellungstests und öffentlicher Quellmanifest-Prüfung (163 Dateien). Backup-API weiterhin 17 Tests erfolgreich; Lint ohne Fehler. Die Änderung der Standardgültigkeit ist nach `2.1.46` committed, aber noch nicht veröffentlicht.
- Wiederherstellung lokal Ende-zu-Ende geprüft: öffentliche Schlüsselabfrage, verschlüsselter Serverdatensatz, normaler Import, private Offline-Wiederherstellung und Löschen. Falsche Schlüssel, beschädigte Daten, Kennungstausch, Ablauf, fehlende Serverkonfiguration und Überschreiben bestehender Schlüssel/Ausgabedateien werden geprüft. Anleitung auf dem lokalen Desktop mit eingerichteten Schlüsselpfaden und Sicherungsnachweis aktualisiert.
- Backup-API-Tests: 17 erfolgreich, 0 fehlgeschlagen.
- Der Regressionstest für die VOE-Fallback-Kette bestätigt bei deaktivierter normaler Rotation genau einen Versuch auf jedem vollen Account und anschließend den erfolgreichen Wechsel auf den vierten Account.
+2 -1
View File
@@ -1,4 +1,5 @@
const {
DEFAULT_ONLINE_BACKUP_RETENTION,
createOnlineBackup,
deleteOnlineBackup,
uploadOnlineBackup
@@ -121,7 +122,7 @@ function createOnlineBackupManager({
}
}
async function createManaged(retention = '7d') {
async function createManaged(retention = DEFAULT_ONLINE_BACKUP_RETENTION) {
try {
return await serialize(() => createTransaction(retention));
} catch (error) {
+1 -1
View File
@@ -23,7 +23,7 @@ const ONLINE_BACKUP_RETENTION_SECONDS = Object.freeze({
'31d': 31 * 24 * 60 * 60,
forever: null
});
const DEFAULT_ONLINE_BACKUP_RETENTION = '7d';
const DEFAULT_ONLINE_BACKUP_RETENTION = 'forever';
function checksum(idBytes, masterKey) {
return crypto.createHash('sha256').update(KEY_CONTEXT).update(idBytes).update(masterKey).digest().subarray(0, CHECKSUM_LENGTH);
+3 -3
View File
@@ -3988,7 +3988,7 @@ async function doOnlineBackupCreate() {
const authority = beginManagedOnlineBackupMutation();
const createButton = document.getElementById('createOnlineBackupBtn');
const retentionSelect = document.getElementById('onlineBackupRetentionSelect');
const retention = retentionSelect?.value || '7d';
const retention = retentionSelect?.value || 'forever';
if (createButton) createButton.disabled = true;
if (retentionSelect) retentionSelect.disabled = true;
setOnlineBackupStatus('Verschlüssele und speichere Einstellungen…', 'busy', authority.statusContext);
@@ -6615,9 +6615,9 @@ function renderSettings() {
<select id="onlineBackupRetentionSelect">
<option value="1d">24 Stunden</option>
<option value="3d">3 Tage</option>
<option value="7d" selected>7 Tage (Standard)</option>
<option value="7d">7 Tage</option>
<option value="31d">31 Tage</option>
<option value="forever">Unbegrenzt</option>
<option value="forever" selected>Unbegrenzt (Standard)</option>
</select>
</span>
</div>
+2 -1
View File
@@ -381,7 +381,8 @@
['Gültigkeitsdauer', 'Validity period'],
['24 Stunden', '24 hours'],
['3 Tage', '3 days'],
['7 Tage (Standard)', '7 days (default)'],
['7 Tage', '7 days'],
['Unbegrenzt (Standard)', 'Unlimited (default)'],
['31 Tage', '31 days'],
['Erstellt', 'Created'],
['Gültig bis', 'Valid until'],
+11
View File
@@ -6,6 +6,17 @@ const espree = require('espree');
const { normalizeLanguage, translateText } = require('../renderer/i18n');
test('online backup selection defaults to unlimited in both languages', () => {
assert.equal(translateText('Unbegrenzt (Standard)', 'en'), 'Unlimited (default)');
assert.equal(translateText('Unlimited (default)', 'de'), 'Unbegrenzt (Standard)');
assert.equal(translateText('7 Tage', 'en'), '7 days');
const source = fs.readFileSync(path.join(__dirname, '../renderer/app.js'), 'utf8');
const select = source.match(/<select id="onlineBackupRetentionSelect">([\s\S]*?)<\/select>/)[1];
assert.match(select, /<option value="forever" selected>Unbegrenzt \(Standard\)<\/option>/);
assert.equal((select.match(/ selected/g) || []).length, 1);
assert.match(source, /const retention = retentionSelect\?\.value \|\| 'forever';/);
});
test('translates permanent source deletion controls to English', () => {
assert.equal(translateText('Quelldatei nach vollständigem Upload dauerhaft löschen', 'en'), 'Permanently delete source file after complete upload');
assert.equal(translateText('Dauerhaftes Löschen aktivieren', 'en'), 'Enable permanent deletion');
+1 -1
View File
@@ -182,7 +182,7 @@ describe('transactional online backup manager', () => {
const result = await fixture.manager.createManaged();
assert.deepEqual(fixture.events, ['prepare', 'upload', 'commit']);
assert.deepEqual(fixture.createArguments, [settings, '2.1.31', fixture.createdAt, '7d', null]);
assert.deepEqual(fixture.createArguments, [settings, '2.1.31', fixture.createdAt, 'forever', null]);
assert.match(fixture.createdAt, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
assert.deepEqual(result, {
ok: true,
+6 -3
View File
@@ -50,10 +50,10 @@ describe('online backup key', () => {
assert.equal(serialized.includes('private-webhook'), false);
assert.equal(serialized.includes(parsed.masterKey.toString('base64url')), false);
assert.deepEqual(Object.keys(created.record).sort(), ['blob', 'deleteVerifier', 'expiresInSeconds', 'id']);
assert.equal(created.record.expiresInSeconds, 604_800);
assert.equal(created.record.expiresInSeconds, null);
});
it('supports the allowed validity periods with seven days as the default', () => {
it('supports the allowed validity periods with unlimited validity as the default', () => {
const { createOnlineBackup, normalizeOnlineBackupRetention } = require('../lib/online-backup');
const createdAt = '2026-08-09T00:00:00.000Z';
const expected = new Map([
@@ -64,7 +64,10 @@ describe('online backup key', () => {
['forever', [null, null]]
]);
assert.equal(createOnlineBackup(settings(), '2.1.41', createdAt).record.expiresInSeconds, 604_800);
assert.equal(normalizeOnlineBackupRetention(), 'forever');
const defaultBackup = createOnlineBackup(settings(), '2.1.41', createdAt);
assert.equal(defaultBackup.record.expiresInSeconds, null);
assert.equal(defaultBackup.expiresAt, null);
for (const [retention, [seconds, expiresAt]] of expected) {
const created = createOnlineBackup(settings(), '2.1.41', createdAt, retention);
assert.equal(created.record.expiresInSeconds, seconds);
+1 -1
View File
@@ -1475,7 +1475,7 @@ setTimeout(async () => {
check('Online backup controls exist', onlineBackupControls);
const onlineBackupRetentionState = await wc.executeJavaScript('(() => { const select = document.getElementById("onlineBackupRetentionSelect"); const wrapper = select.closest(".online-backup-retention-select"); return { value: select.value, options: [...select.options].map(option => option.value + ":" + option.textContent), arrowRight: getComputedStyle(wrapper, "::after").right, fontSize: getComputedStyle(select).fontSize }; })()');
check('Online backup validity defaults to seven days and offers every requested duration', onlineBackupRetentionState.value === '7d' && onlineBackupRetentionState.options.join('|') === '1d:24 Stunden|3d:3 Tage|7d:7 Tage (Standard)|31d:31 Tage|forever:Unbegrenzt' && onlineBackupRetentionState.arrowRight === '14px' && parseFloat(onlineBackupRetentionState.fontSize) >= 10);
check('Online backup validity defaults to unlimited and offers every requested duration', onlineBackupRetentionState.value === 'forever' && onlineBackupRetentionState.options.join('|') === '1d:24 Stunden|3d:3 Tage|7d:7 Tage|31d:31 Tage|forever:Unbegrenzt (Standard)' && onlineBackupRetentionState.arrowRight === '14px' && parseFloat(onlineBackupRetentionState.fontSize) >= 10);
const onlineBackupKeyContract = await wc.executeJavaScript('document.getElementById("onlineBackupKeyInput")?.maxLength + "|" + document.getElementById("onlineBackupKeyInput")?.getAttribute("pattern")');
check('Online backup input enforces the 75-character MHU key format', onlineBackupKeyContract === '75|MHU2-[A-Za-z0-9_-]{70}');