harden(update): drop inline spinner style (R4), server-side return-path sanitise
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/v1-foundation
parent
d2f70900ff
commit
df5d9e2112
|
|
@ -32,14 +32,13 @@
|
||||||
{{-- Spinner + heading --}}
|
{{-- Spinner + heading --}}
|
||||||
<div class="flex flex-col items-center gap-5 text-center">
|
<div class="flex flex-col items-center gap-5 text-center">
|
||||||
<div id="js-spinner" class="relative h-14 w-14" aria-hidden="true">
|
<div id="js-spinner" class="relative h-14 w-14" aria-hidden="true">
|
||||||
<svg class="h-14 w-14 -rotate-90" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="h-14 w-14 -rotate-90 animate-spin origin-center" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<circle cx="28" cy="28" r="24" stroke-width="3" class="stroke-line"/>
|
<circle cx="28" cy="28" r="24" stroke-width="3" class="stroke-line"/>
|
||||||
<circle id="js-spinner-arc" cx="28" cy="28" r="24" stroke-width="3"
|
<circle id="js-spinner-arc" cx="28" cy="28" r="24" stroke-width="3"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-dasharray="150.796"
|
stroke-dasharray="150.796"
|
||||||
stroke-dashoffset="37.699"
|
stroke-dashoffset="37.699"
|
||||||
class="stroke-accent origin-center"
|
class="stroke-accent origin-center" />
|
||||||
style="animation: clusev-spin 1.4s linear infinite; transform-origin: 50% 50%;" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -100,11 +99,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Inline CSS for the spinner animation (no Alpine/external deps) --}}
|
|
||||||
<style>
|
|
||||||
@keyframes clusev-spin { to { transform: rotate(360deg); } }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{{-- Inline JS — no external dependencies. Reads ?return= for the redirect target.
|
{{-- Inline JS — no external dependencies. Reads ?return= for the redirect target.
|
||||||
Polls /up every 2500 ms; requires 2 consecutive 200s before redirecting (flap guard).
|
Polls /up every 2500 ms; requires 2 consecutive 200s before redirecting (flap guard).
|
||||||
Times out after 10 minutes and shows a manual-reload prompt. --}}
|
Times out after 10 minutes and shows a manual-reload prompt. --}}
|
||||||
|
|
@ -113,13 +107,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ── Return URL ─────────────────────────────────────────────────────────
|
// ── Return URL ─────────────────────────────────────────────────────────
|
||||||
// Sanitise to a same-origin relative path: must start with a single /,
|
// Server-validated path from the route handler (defence-in-depth guard).
|
||||||
// must NOT start with //, http, or contain backslashes.
|
var returnUrl = {{ \Illuminate\Support\Js::from($returnPath) }};
|
||||||
var raw = new URLSearchParams(window.location.search).get('return') || '';
|
|
||||||
var returnUrl = '/';
|
|
||||||
if (raw && /^\/[^\/\\]/.test(raw) && !/^\/\//g.test(raw) && !/^https?:/i.test(raw)) {
|
|
||||||
returnUrl = raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Phase timing heuristic (purely visual) ─────────────────────────────
|
// ── Phase timing heuristic (purely visual) ─────────────────────────────
|
||||||
// Approx durations (seconds) per phase before advancing the highlight.
|
// Approx durations (seconds) per phase before advancing the highlight.
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,11 @@ Route::middleware('auth')->group(function () {
|
||||||
|
|
||||||
// Plain Blade view — deliberately NOT a Livewire component so it survives the
|
// Plain Blade view — deliberately NOT a Livewire component so it survives the
|
||||||
// container teardown that follows an update request (R1/R2 exception, by design).
|
// container teardown that follows an update request (R1/R2 exception, by design).
|
||||||
Route::get('/update-progress', fn () => view('update-progress'))->name('update.progress');
|
Route::get('/update-progress', function (Request $request) {
|
||||||
|
$parsed = parse_url((string) $request->query('return', ''), PHP_URL_PATH);
|
||||||
|
$return = (is_string($parsed) && str_starts_with($parsed, '/') && ! str_starts_with($parsed, '//')) ? $parsed : '/';
|
||||||
|
|
||||||
|
return view('update-progress', ['returnPath' => $return]);
|
||||||
|
})->name('update.progress');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue