session()->put('locale', $locale); if ($user = $request->user()) { $user->forceFill(['locale' => $locale])->save(); } } return redirect()->back(); })->name('locale.set'); // Caddy on-demand TLS gate: Caddy calls this before issuing a certificate for a host. // Approve ONLY the configured panel domain, so Caddy can never be tricked into // requesting certs for arbitrary domains pointed at this IP (rate-limit abuse). Route::get('/_caddy/ask', function (Request $request, DeploymentService $deployment) { $asked = strtolower(trim((string) $request->query('domain'))); $domain = $deployment->domain(); abort_unless($domain !== null && $asked !== '' && $asked === $domain && ! $deployment->externalTls(), 403); return response('ok', 200); })->name('caddy.ask'); Route::middleware('guest')->group(function () { Route::get('/login', Auth\Login::class)->name('login'); Route::get('/forgot-password', Auth\ForgotPassword::class)->name('password.request'); Route::get('/reset-password/{token}', Auth\ResetPassword::class)->name('password.reset'); Route::get('/two-factor-challenge', Auth\TwoFactorChallenge::class)->name('two-factor.challenge'); }); Route::middleware('auth')->group(function () { // Security onboarding — reachable before the panel is unlocked. Route::get('/password', Auth\PasswordChange::class)->name('password.change'); Route::get('/two-factor-setup', Auth\TwoFactorSetup::class)->name('two-factor.setup'); // 2FA backup-codes download — the codes themselves are shown in a modal (Modals\RecoveryCodes). Route::get('/two-factor/recovery-codes/download', function () { $codes = AuthFacade::user()->recoveryCodes(); return response()->streamDownload( fn () => print (implode("\n", $codes)."\n"), 'clusev-recovery-codes.txt', ['Content-Type' => 'text/plain; charset=UTF-8'], ); })->name('two-factor.recovery.download'); Route::post('/logout', function () { AuthFacade::logout(); session()->invalidate(); session()->regenerateToken(); return redirect()->route('login'); })->name('logout'); // The panel — requires completed onboarding (seeded password rotated). Route::middleware(EnsureSecurityOnboarded::class)->group(function () { Route::get('/', Dashboard::class)->name('dashboard'); Route::get('/servers', Servers\Index::class)->name('servers.index'); Route::get('/servers/{server}', Servers\Show::class)->name('servers.show'); Route::get('/services', Services\Index::class)->name('services.index'); Route::get('/files', Files\Index::class)->name('files.index'); Route::get('/audit', Audit\Index::class)->name('audit.index'); Route::get('/settings', Settings\Index::class)->name('settings'); Route::get('/versions', Versions\Index::class)->name('versions'); Route::get('/system', System\Index::class)->name('system'); }); });