clusev/app/Providers/AppServiceProvider.php

103 lines
4.4 KiB
PHP

<?php
namespace App\Providers;
use App\Http\Middleware\EnsureSecurityOnboarded;
use App\Models\Setting;
use App\Services\DeploymentService;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\ServiceProvider;
use Livewire\Livewire;
use Throwable;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
/**
* Apply the effective panel domain (dashboard override in DB, else install-time
* APP_DOMAIN) to runtime config at boot. boot() runs every request, so a domain
* change takes effect on the next request after the operator restarts the stack —
* nothing is switched mid-session, which is what makes it safe.
*
* - Server -> Reverb publishing is pinned to the INTERNAL reverb service (both dev
* and prod compose name it `reverb`), so realtime keeps working right after a
* domain change and never depends on the new public cert. The browser endpoint is
* injected by the layout from DeploymentService::reverbClient().
* - app.url -> https://<domain> so absolute URL generation matches how the panel is
* served. With no domain (bare IP / dev) the env-derived app.url is left as is.
*/
public function boot(DeploymentService $deployment): void
{
// Re-apply the onboarding gate to EVERY Livewire component update, not just the
// initial page GET. Livewire only re-runs route middleware that is registered as
// persistent; without this, a component mounted while onboarded could keep running
// destructive actions (hardening, SSH-key provisioning, credential changes) over
// /livewire/update after the account is reverted to must_change_password mid-session.
Livewire::addPersistentMiddleware([
EnsureSecurityOnboarded::class,
]);
config([
'broadcasting.connections.reverb.options.host' => 'reverb',
'broadcasting.connections.reverb.options.port' => 8080,
'broadcasting.connections.reverb.options.scheme' => 'http',
'broadcasting.connections.reverb.options.useTLS' => false,
]);
$domain = $deployment->domain();
if ($domain !== null) {
config(['app.url' => 'https://'.$domain]);
}
$this->applyMailSettings();
}
/**
* Apply the operator's SMTP settings (Settings\Email) to runtime config so
* password-reset mails and notifications actually send. boot() runs every request,
* so a saved change takes effect on the next request.
*
* Only switches to the `smtp` mailer when host + from-address are configured;
* otherwise the safe install default (`log`) is left untouched, so nothing breaks
* while unconfigured. The stored SMTP password is encrypted at rest (APP_KEY) and
* decrypted here. Wrapped in try/catch like DeploymentService — the settings table
* may not exist yet during install/migrate, and that must never break boot.
*/
private function applyMailSettings(): void
{
try {
$host = Setting::get('mail_host');
$from = Setting::get('mail_from_address');
if ($host === null || $host === '' || $from === null || $from === '') {
return; // unconfigured -> keep the safe default mailer
}
$encryption = Setting::get('mail_encryption', 'tls');
$storedPassword = Setting::get('mail_password');
$password = null;
if ($storedPassword !== null && $storedPassword !== '') {
$password = Crypt::decryptString($storedPassword);
}
config([
'mail.default' => 'smtp',
'mail.mailers.smtp.host' => $host,
'mail.mailers.smtp.port' => (int) (Setting::get('mail_port', '587') ?? 587),
'mail.mailers.smtp.username' => Setting::get('mail_username') ?: null,
'mail.mailers.smtp.password' => $password ?: null,
'mail.mailers.smtp.encryption' => $encryption === 'none' ? null : $encryption,
'mail.from.address' => $from,
'mail.from.name' => Setting::get('mail_from_name') ?: $from,
]);
} catch (Throwable) {
// Settings table not migrated yet (install/migrate) or an undecryptable value —
// leave the default mailer untouched. Never break boot.
}
}
}