fix(cors): allow play from .to / staging / onion mirrors
Daemon CORS allowlist was hardcoded to torrentclaw.com + localhost. Browsers playing from any other official mirror (.to, onion, www., staging.) received 200 + body from the daemon's HLS server but no Access-Control-Allow-Origin header, so the response was dropped client-side. Probe loop treated every candidate as a failure and surfaced "No se puede conectar con tu agente — 404 todos los canales" even though the tunnel + ffmpeg were healthy. Static baseline now includes the full known mirror set (.com / www / app / staging / .to / www.to / built-in onion). At startup the daemon also fetches /api/mirrors with IPFS fallback and merges the live origins, so a future mirror addition does not require a CLI rebuild.
This commit is contained in:
parent
2e7cd7e8ed
commit
7b78d0b778
2 changed files with 69 additions and 1 deletions
|
|
@ -293,7 +293,15 @@ func runDaemonStart() error {
|
||||||
// Create persistent stream server
|
// Create persistent stream server
|
||||||
streamSrv := engine.NewStreamServer(cfg.Download.StreamPort)
|
streamSrv := engine.NewStreamServer(cfg.Download.StreamPort)
|
||||||
streamSrv.SetUPnPEnabled(cfg.Download.EnableUPnP)
|
streamSrv.SetUPnPEnabled(cfg.Download.EnableUPnP)
|
||||||
streamSrv.SetCORSAllowedOrigins(cfg.Download.CORSExtraOrigins)
|
// CORS extras = operator config + dynamic mirror list from /api/mirrors.
|
||||||
|
// Without the mirror merge, a user playing from `torrentclaw.to` (or any
|
||||||
|
// future mirror) hits the daemon, gets 200 + body, but no
|
||||||
|
// `Access-Control-Allow-Origin` → browser drops the response → player
|
||||||
|
// reports "404 todos los canales". Fetching /api/mirrors at startup
|
||||||
|
// future-proofs against mirror additions without a CLI rebuild.
|
||||||
|
corsExtras := append([]string(nil), cfg.Download.CORSExtraOrigins...)
|
||||||
|
corsExtras = append(corsExtras, mirrorCORSOrigins(ctx, cfg, userAgent)...)
|
||||||
|
streamSrv.SetCORSAllowedOrigins(corsExtras)
|
||||||
// Reap HLS tmpdirs left over from a previous daemon run before we start
|
// Reap HLS tmpdirs left over from a previous daemon run before we start
|
||||||
// accepting new sessions. The in-memory registry doesn't survive a
|
// accepting new sessions. The in-memory registry doesn't survive a
|
||||||
// restart, so without this disk usage grows unbounded across restarts.
|
// restart, so without this disk usage grows unbounded across restarts.
|
||||||
|
|
@ -862,3 +870,48 @@ func superviseFunnel(ctx context.Context, d *agent.Daemon, port int) {
|
||||||
backoff = min(backoff*2, maxBackoff)
|
backoff = min(backoff*2, maxBackoff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mirrorCORSOrigins fetches /api/mirrors from the configured primary (+ extra
|
||||||
|
// mirror candidates + static IPFS fallback) and returns the discovered URLs as
|
||||||
|
// Origin strings. Best-effort: any failure logs a warning and returns an empty
|
||||||
|
// slice; the static defaultCORSAllowedOrigins in validate.go covers the known
|
||||||
|
// mirrors (.com / .to / built-in onion) so the daemon still accepts the
|
||||||
|
// official surfaces when this call fails.
|
||||||
|
//
|
||||||
|
// Bounded to a short timeout so a slow /api/mirrors response can't delay
|
||||||
|
// daemon startup — every second here is a second the user can't play.
|
||||||
|
func mirrorCORSOrigins(parent context.Context, cfg config.Config, userAgent string) []string {
|
||||||
|
ctx, cancel := context.WithTimeout(parent, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
candidates := append([]string{cfg.Auth.APIURL}, cfg.Auth.Mirrors...)
|
||||||
|
resp, err := agent.FetchMirrorsWithFallback(ctx, candidates, userAgent)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[cors] mirror discovery failed (%v) — using static allowlist only", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
out := make([]string, 0, len(resp.Mirrors))
|
||||||
|
add := func(rawURL string) {
|
||||||
|
if rawURL == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
origin := strings.TrimRight(rawURL, "/")
|
||||||
|
if _, dup := seen[origin]; dup {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen[origin] = struct{}{}
|
||||||
|
out = append(out, origin)
|
||||||
|
}
|
||||||
|
for _, m := range resp.Mirrors {
|
||||||
|
add(m.URL)
|
||||||
|
}
|
||||||
|
if resp.Tor != nil {
|
||||||
|
add(resp.Tor.URL)
|
||||||
|
}
|
||||||
|
if len(out) > 0 {
|
||||||
|
log.Printf("[cors] merged %d mirror origins from /api/mirrors", len(out))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,27 @@ var validSessionID = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,128}$`)
|
||||||
// 127.0.0.1 is listed in addition to localhost because some browsers treat
|
// 127.0.0.1 is listed in addition to localhost because some browsers treat
|
||||||
// them as distinct origins for CORS.
|
// them as distinct origins for CORS.
|
||||||
//
|
//
|
||||||
|
// Mirrors (`.to`, `staging.torrentclaw.com`, `www.`) are listed so a user
|
||||||
|
// playing from any official mirror succeeds the HEAD probe; without these
|
||||||
|
// the browser drops the response for "missing ACAO" and the player reports
|
||||||
|
// "404 todos los canales" even though the daemon returned 200.
|
||||||
|
//
|
||||||
// Note: media tags (<video src>, <audio src>) do not send the Origin
|
// Note: media tags (<video src>, <audio src>) do not send the Origin
|
||||||
// header so they are not gated by CORS at all; this allowlist only
|
// header so they are not gated by CORS at all; this allowlist only
|
||||||
// affects fetch()/XHR.
|
// affects fetch()/XHR.
|
||||||
var defaultCORSAllowedOrigins = []string{
|
var defaultCORSAllowedOrigins = []string{
|
||||||
"https://torrentclaw.com",
|
"https://torrentclaw.com",
|
||||||
|
"https://www.torrentclaw.com",
|
||||||
"https://app.torrentclaw.com",
|
"https://app.torrentclaw.com",
|
||||||
|
"https://staging.torrentclaw.com",
|
||||||
|
"https://torrentclaw.to",
|
||||||
|
"https://www.torrentclaw.to",
|
||||||
|
// Tor mirror — Tor Browser sends `Origin: http://<addr>.onion` (plain
|
||||||
|
// http, no port). Mirror address is the BUILT_IN_ONION constant from
|
||||||
|
// torrentclaw-web/src/lib/mirrors-config.ts; rotates rarely, kept in
|
||||||
|
// sync by hand. Daemon also dynamically merges /api/mirrors at startup
|
||||||
|
// (see daemon.go) so a new key doesn't need a CLI rebuild.
|
||||||
|
"http://torrentf3aifidcsaaanmnmuhv2s53r6hqsl3zkmfidiaxainkeqk5id.onion",
|
||||||
"http://localhost:3030",
|
"http://localhost:3030",
|
||||||
"http://127.0.0.1:3030",
|
"http://127.0.0.1:3030",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue