This commit is contained in:
+2
-2
@@ -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-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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const {
|
const {
|
||||||
|
DEFAULT_ONLINE_BACKUP_RETENTION,
|
||||||
createOnlineBackup,
|
createOnlineBackup,
|
||||||
deleteOnlineBackup,
|
deleteOnlineBackup,
|
||||||
uploadOnlineBackup
|
uploadOnlineBackup
|
||||||
@@ -121,7 +122,7 @@ function createOnlineBackupManager({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createManaged(retention = '7d') {
|
async function createManaged(retention = DEFAULT_ONLINE_BACKUP_RETENTION) {
|
||||||
try {
|
try {
|
||||||
return await serialize(() => createTransaction(retention));
|
return await serialize(() => createTransaction(retention));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const ONLINE_BACKUP_RETENTION_SECONDS = Object.freeze({
|
|||||||
'31d': 31 * 24 * 60 * 60,
|
'31d': 31 * 24 * 60 * 60,
|
||||||
forever: null
|
forever: null
|
||||||
});
|
});
|
||||||
const DEFAULT_ONLINE_BACKUP_RETENTION = '7d';
|
const DEFAULT_ONLINE_BACKUP_RETENTION = 'forever';
|
||||||
|
|
||||||
function checksum(idBytes, masterKey) {
|
function checksum(idBytes, masterKey) {
|
||||||
return crypto.createHash('sha256').update(KEY_CONTEXT).update(idBytes).update(masterKey).digest().subarray(0, CHECKSUM_LENGTH);
|
return crypto.createHash('sha256').update(KEY_CONTEXT).update(idBytes).update(masterKey).digest().subarray(0, CHECKSUM_LENGTH);
|
||||||
|
|||||||
+3
-3
@@ -3988,7 +3988,7 @@ async function doOnlineBackupCreate() {
|
|||||||
const authority = beginManagedOnlineBackupMutation();
|
const authority = beginManagedOnlineBackupMutation();
|
||||||
const createButton = document.getElementById('createOnlineBackupBtn');
|
const createButton = document.getElementById('createOnlineBackupBtn');
|
||||||
const retentionSelect = document.getElementById('onlineBackupRetentionSelect');
|
const retentionSelect = document.getElementById('onlineBackupRetentionSelect');
|
||||||
const retention = retentionSelect?.value || '7d';
|
const retention = retentionSelect?.value || 'forever';
|
||||||
if (createButton) createButton.disabled = true;
|
if (createButton) createButton.disabled = true;
|
||||||
if (retentionSelect) retentionSelect.disabled = true;
|
if (retentionSelect) retentionSelect.disabled = true;
|
||||||
setOnlineBackupStatus('Verschlüssele und speichere Einstellungen…', 'busy', authority.statusContext);
|
setOnlineBackupStatus('Verschlüssele und speichere Einstellungen…', 'busy', authority.statusContext);
|
||||||
@@ -6615,9 +6615,9 @@ function renderSettings() {
|
|||||||
<select id="onlineBackupRetentionSelect">
|
<select id="onlineBackupRetentionSelect">
|
||||||
<option value="1d">24 Stunden</option>
|
<option value="1d">24 Stunden</option>
|
||||||
<option value="3d">3 Tage</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="31d">31 Tage</option>
|
||||||
<option value="forever">Unbegrenzt</option>
|
<option value="forever" selected>Unbegrenzt (Standard)</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-1
@@ -381,7 +381,8 @@
|
|||||||
['Gültigkeitsdauer', 'Validity period'],
|
['Gültigkeitsdauer', 'Validity period'],
|
||||||
['24 Stunden', '24 hours'],
|
['24 Stunden', '24 hours'],
|
||||||
['3 Tage', '3 days'],
|
['3 Tage', '3 days'],
|
||||||
['7 Tage (Standard)', '7 days (default)'],
|
['7 Tage', '7 days'],
|
||||||
|
['Unbegrenzt (Standard)', 'Unlimited (default)'],
|
||||||
['31 Tage', '31 days'],
|
['31 Tage', '31 days'],
|
||||||
['Erstellt', 'Created'],
|
['Erstellt', 'Created'],
|
||||||
['Gültig bis', 'Valid until'],
|
['Gültig bis', 'Valid until'],
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ const espree = require('espree');
|
|||||||
|
|
||||||
const { normalizeLanguage, translateText } = require('../renderer/i18n');
|
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', () => {
|
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('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');
|
assert.equal(translateText('Dauerhaftes Löschen aktivieren', 'en'), 'Enable permanent deletion');
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ describe('transactional online backup manager', () => {
|
|||||||
const result = await fixture.manager.createManaged();
|
const result = await fixture.manager.createManaged();
|
||||||
|
|
||||||
assert.deepEqual(fixture.events, ['prepare', 'upload', 'commit']);
|
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.match(fixture.createdAt, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
|
||||||
assert.deepEqual(result, {
|
assert.deepEqual(result, {
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ describe('online backup key', () => {
|
|||||||
assert.equal(serialized.includes('private-webhook'), false);
|
assert.equal(serialized.includes('private-webhook'), false);
|
||||||
assert.equal(serialized.includes(parsed.masterKey.toString('base64url')), false);
|
assert.equal(serialized.includes(parsed.masterKey.toString('base64url')), false);
|
||||||
assert.deepEqual(Object.keys(created.record).sort(), ['blob', 'deleteVerifier', 'expiresInSeconds', 'id']);
|
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 { createOnlineBackup, normalizeOnlineBackupRetention } = require('../lib/online-backup');
|
||||||
const createdAt = '2026-08-09T00:00:00.000Z';
|
const createdAt = '2026-08-09T00:00:00.000Z';
|
||||||
const expected = new Map([
|
const expected = new Map([
|
||||||
@@ -64,7 +64,10 @@ describe('online backup key', () => {
|
|||||||
['forever', [null, null]]
|
['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) {
|
for (const [retention, [seconds, expiresAt]] of expected) {
|
||||||
const created = createOnlineBackup(settings(), '2.1.41', createdAt, retention);
|
const created = createOnlineBackup(settings(), '2.1.41', createdAt, retention);
|
||||||
assert.equal(created.record.expiresInSeconds, seconds);
|
assert.equal(created.record.expiresInSeconds, seconds);
|
||||||
|
|||||||
+1
-1
@@ -1475,7 +1475,7 @@ setTimeout(async () => {
|
|||||||
check('Online backup controls exist', onlineBackupControls);
|
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 }; })()');
|
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")');
|
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}');
|
check('Online backup input enforces the 75-character MHU key format', onlineBackupKeyContract === '75|MHU2-[A-Za-z0-9_-]{70}');
|
||||||
|
|||||||
Reference in New Issue
Block a user