Il download del backup non può essere ripreso dopo un'interruzione perché il token email è già stato consumato

Ricordo un problema simile che sembra essere stato risolto in 2026.01.0-latest, quindi credo che si tratti di un problema attuale e distinto, legato al ripristino di un download di backup interrotto, piuttosto che al vecchio errore di download iniziale.

È plausibile pensare che il link di backup a tempo limitato possa impedire al browser di riprendere un download interrotto, ma non è possibile scaricare un backup due volte con un singolo link. Quindi, quando fallisce una volta, la ripresa del download viene probabilmente annullata da Discourse stesso.

Sono ora riuscito a riprodurre esattamente quel comportamento sull’attuale versione di Discourse.

Riproduzione

Stavo scaricando un backup locale di Discourse di:

4.231.639.143 byte

utilizzando Safari su iOS.

Il download stava procedendo normalmente. Ho una registrazione dello schermo che mostra l'aumento da circa:

https://youtube.com/shorts/_Pjc-kxJ1OE?feature=shared

313,3 MB a 317,8 MB

Ho poi lasciato Safari per circa 15 secondi.

Quando sono tornato su Safari, il download si era fermato a circa:

319,8 MB

e Safari mostrava il controllo per il nuovo tentativo.


Il log di accesso di Caddy per la richiesta originale mostra:

GET /admin/backups/...

HTTP/3

Range: none

status: 200

Content-Length: 4231639143

size: 319864024

Ho poi utilizzato il controllo di nuovo tentativo di Safari stesso.

Safari ha effettuato una vera richiesta HTTP byte-range:

GET /admin/backups/...

HTTP/3

Range: bytes=319799904-

status: 422

Quindi Safari non stava semplicemente tentando un altro download completo. Stava correttamente cercando di riprendere il file esistente all’incirca nel punto in cui la precedente trasmissione si era interrotta.

Discourse ha rifiutato quella richiesta Range con 422.

Codice attuale di Discourse

Guardando l’attuale Admin::BackupsController#show, la sequenza è efficacemente:

EmailBackupToken.compare(current_user.id, token)

        ↓

find backup

        ↓

EmailBackupToken.del(current_user.id)

        ↓

send_file

Quindi la richiesta autenticata iniziale consuma il token email prima che la trasmissione di diversi gigabyte sia effettivamente completata.

Se quella trasmissione viene successivamente interrotta, Safari effettua un’altra richiesta come:

Range: bytes=319799904-

ma quella richiesta passa di nuovo attraverso /admin/backups/… e il token email è già stato eliminato.

Sembra che questo spieghi il 422 che ho catturato.

La sessione autenticata è ancora richiesta oltre al token di backup, quindi non sto suggerendo di rendere semplicemente riutilizzabile indefinitamente il token esistente.

Piuttosto, mi sto chiedendo se ci dovrebbe essere un modo limitato per lo stesso amministratore autenticato di riprendere lo stesso download di backup già autorizzato.

Ad esempio, potrebbe essere un’autorizzazione di download a breve durata associata allo stesso utente e backup, anche se i manutentori potrebbero avere un modo migliore per gestire questo.

Perché questo sta diventando più facile da riprodurre su iOS

L’interruzione che ha esposto questo problema sembra anche simile ai resoconti degli utenti di versioni recenti di iOS.

Ci sono almeno tre resoconti ospitati su Apple Community:

resoconti più vecchi

“Safari Background Downloads Failing After IOS 26 update” — Ottobre 2025

Safari Background Downloads Failing After… - Apple Community

L’utente riporta che i download più grandi di Safari falliscono quando vengono eseguiti in background dopo l’aggiornamento a iOS 26. Hanno anche aggiornato un iPhone 15 Pro a iOS 26 e riportato lo stesso comportamento.

“Safari iOS Background Download Issue - Cancels on Minimize” — Dicembre 2025

Safari iOS Background Download Issue - Ca… - Apple Community

Riportato su un iPhone 15 con iOS 26.1. I download iniziano normalmente ma vengono annullati dopo il passaggio a un’altra app o la minimizzazione di Safari.

“Safari can’t complete a large download” — Aprile 2026

Riportato su un iPhone 17 Pro Max con iOS 26.4. I download di grandi dimensioni si fermano brevemente dopo aver bloccato il dispositivo, lasciando Safari, o passando tra cellulare e Wi-Fi. L’autore riporta specificamente un buon segnale e dice che gli stessi download si completano su PC, Mac e Android.

Non credo che quei resoconti della comunità siano sufficienti per dire che Apple ha confermato una regressione di iOS.

Tuttavia, sono coerenti con la mia registrazione dello schermo: il backup stava scaricando normalmente, sono passato brevemente da Safari e quando sono tornato la trasmissione era diventata interrotta.

Il punto importante sul lato Discourse è indipendente dalla causa dell’interruzione:

la trasmissione del backup di grandi dimensioni è interrotta

        ↓

il browser tenta una ripresa Range basata sugli standard

        ↓

il token di backup è già stato consumato

        ↓

la ripresa riceve 422

Osservazioni lato server

Questo non sembra essere un problema generale con l’attuale percorso nginx/sendfile.

Lo stesso percorso di backup multi-GB ha completato con successo:

  • da Firefox/Linux tramite HTTP/3; e
  • dallo stesso iPhone tramite Wi-Fi usando HTTP/3.

L’attuale nginx pubblicizza anche il supporto per byte-range.

Quindi non sto suggerendo che HTTP/3, Caddy o nginx siano intrinsecamente incapaci di fornire il backup.

In questa particolare riproduzione, la trasmissione originale è stata interrotta dopo circa 320 MB, Safari ha successivamente effettuato una richiesta Range apparentemente valida, e quella seconda richiesta è stata rifiutata da Discourse.

Avrebbe senso che un download di backup già autorizzato avesse un meccanismo a breve durata che consenta allo stesso amministratore autenticato di riprendere quello stesso file dopo una trasmissione interrotta?

Ho dato un’occhiata allo storico e alla copertura dei test attuale per avere un’idea migliore di ciò che una possibile correzione dovrebbe preservare.

Il comportamento a uso singolo risale alla modifica di rafforzamento della sicurezza di marzo 2017:

Quel commit ha introdotto i token di download di backup inviati via e-mail e ha anche consumato immediatamente il token una volta che la richiesta di download era stata autorizzata.

La stessa sequenza esiste ancora nell’attuale Admin::BackupsController#show:

L’implementazione sottostante del token è ancora molto semplice:

EmailBackupToken memorizza un token Redis per utente con una scadenza di un giorno. È associato all’utente piuttosto che a un backup specifico.

Guardando lo storico di quel file, le modifiche dal 2017 sembrano essere state meccaniche/stilistiche piuttosto che modifiche al modello di autorizzazione.

Ciò rende indesiderabile il semplice rendere riutilizzabile il token via e-mail esistente: indebolirebbe la restrizione originale a uso singolo e il token stesso non è associato al backup specifico nell’URL.

I test delle richieste attuali si trovano qui:

Coprono un download iniziale valido, token non validi, ID di backup non validi/assenti e permessi, ma non sono riuscito a trovare copertura per un trasferimento interrotto seguito da una richiesta Range:.

Lo spec delle richieste API documenta anch’esso il percorso ordinario di download di nome file + token:

Una possibile direzione per la patch sembra quindi essere quella di preservare il token e-mail a uso singolo esistente per l’avvio del download del backup, fornendo al contempo un’autorizzazione di ripresa a breve scadenza per il backup locale già autorizzato.

Mi aspetto che tale autorizzazione di ripresa sia strettamente associata all’utente autenticato e al backup specifico e che consenta una successiva richiesta di intervallo di byte senza rendere generalmente riutilizzabile il token e-mail a uso singolo di un giorno per l’utente.

I comportamenti che mirerei a preservare/aggiungere sono:

  • la GET iniziale con un token e-mail valido rimane consentita;

  • un’altra GET completa ordinaria che utilizza il token e-mail consumato rimane rifiutata;

  • un nuovo tentativo di intervallo di byte per lo stesso backup da parte dello stesso amministratore autenticato può essere ripreso per un breve periodo limitato;

  • un tentativo di ripresa contro un altro backup rimane rifiutato;

  • un’autorizzazione di ripresa scaduta rimane rifiutata.

Non ho dato per scontato che questa sia necessariamente l’implementazione che i maintainer preferiranno, ma la superficie del codice/test sembra piuttosto contenuta.

Sono lieto di preparare una bozza di patch e test di regressione per la riproduzione di Range: di Safari se questo modello di sicurezza generale sembra ragionevole.

Ho appena preparato una PR per questo:

L’implementazione preserva il token email a uso singolo esistente per iniziare il download, consentendo al contempo ai backup locali di creare un’autorizzazione di ripresa separata e limitata, vincolata a:

  • lo stesso amministratore autenticato;

  • lo stesso backup;

  • lo stesso valore del token originale; e

  • una richiesta successiva che includa un header Range.

Una seconda richiesta GET completa ordinaria utilizzando il token già consumato viene ancora rifiutata, così come una richiesta Range per un altro backup. I backup remoti/S3 mantengono il loro comportamento esistente.

Ho reso il periodo di ripresa un enum anziché una durata illimitata:

  • Disabled

  • 1 hour

  • 6 hours (recommended)

  • 12 hours

  • Until the original email token expires

Il periodo di ripresa effettivo è inoltre limitato alla durata residua del token email originale al momento dell’inizio del download iniziale.

Il comportamento attuale di Discourse rimane l’impostazione predefinita (Disabled) per motivi di compatibilità, mentre 6 hours viene presentata come opzione consigliata quando i download riprendibili sono abilitati.

La PR chiede se i maintainer preferiscano mantenere l’impostazione predefinita che preserva la compatibilità, oppure rendere il valore riprendibile consigliato l’impostazione predefinita come parte della risoluzione di questa issue.

Ho anche aggiunto una copertura di regressione per il percorso di ripresa riuscito e per i principali confini di autorizzazione, inclusi:

  • il rifiuto persistente del riutilizzo ordinario del token;

  • il rifiuto persistente della ripresa tra backup diversi;

  • la disattivazione immediata della configurazione che impedisce ulteriori riprese;

  • il fatto che S3 non riceva un’autorizzazione di ripresa locale; e

  • il fatto che il download iniziale e la sua ripresa generino un’unica voce di audit per il download del backup.

La suite CI completa è superata e la PR è pronta per la revisione.

Sarebbe gradito un feedback sul modello di sicurezza, in particolare sulla raccomandazione di 6 ore e sull’impostazione predefinita.