' . '
Algo correu mal. Tente novamente em instantes.
'; } exit(1); } // Required env vars — fail loud if any is missing. Hard-coded fallback creds // (especially `root` / `admin`) were a footgun: a missing .env would let the // app boot with the wrong identity instead of refusing to start. foreach (['DB_NAME', 'DB_USER'] as $req) { if (getenv($req) === false || getenv($req) === '') { _bootstrap_fail("required env var {$req} is not set"); } } if (getenv('DB_PASS') === false) { _bootstrap_fail('required env var DB_PASS is not set (empty string is allowed)'); } $host = env('DB_HOST', '127.0.0.1'); $port = (int) env('DB_PORT', 3306); $db = env('DB_NAME'); // Not $user: that name leaks into the router's flat include scope (router.php // requires this file at top level, no function wrapper) and would shadow the // real logged-in user array that includes/header.php expects. $dbUser = env('DB_USER'); $pass = env('DB_PASS'); $dsn = "mysql:host=$host;port=$port;dbname=$db;charset=utf8mb4"; try { $pdo = new PDO($dsn, $dbUser, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Real server-side prepares (not driver emulation): a tighter defence // against injection than emulated string-interpolation. Safe here — the // codebase uses positional ? params (no reused :named, no bound LIMIT). PDO::ATTR_EMULATE_PREPARES => false, // Keep values coming back as strings, as the emulated driver did, so the // type change from native prepares doesn't ripple through existing code. PDO::ATTR_STRINGIFY_FETCHES => true, ]); } catch (PDOException $e) { _bootstrap_fail('PDO connect failed: ' . $e->getMessage()); } // ─── Ligação ao platform-tools ─────────────────────────────────────────────── // // Auditoria 2026-09-24, ponto 1.3: a app passou a usar a autenticação // partilhada (`shared/platform-tools/functions_auth.php`) em vez da sua // própria — login, sessão, roles e guardas (config/auth.php, // config/functions_user.php) agora chamam auth_attempt()/auth_login()/ // auth_at_least()/etc. por baixo, com os mesmos nomes de função de sempre // para os ~30 ficheiros que os chamam não mudarem. // // O cookie de sessão (nome, Secure/HttpOnly/SameSite, modo estrito) passa a // ser inteiramente gerido por auth_session_start() através da opção // 'session' abaixo — o bloco que estava aqui antes (ini_set + session_start // manuais) foi removido para não haver dois sítios a configurar a mesma // sessão. // // `users` já tem exactamente as colunas por omissão do auth_schema() // (id, email, name, password_hash, role, is_active, last_login_at, // last_login_ip — confirmado via information_schema em produção), por isso // não é preciso mapear nada em 'schema'. // // auth_attempts (o throttle da APP, config/functions_throttle.php — ip+scope, // cobre login/registo/recuperação) continua a ser o único throttle de login // usado; o throttle do módulo partilhado (auth_throttle_check/hit, que exige // `identifier`) não é chamado, para não duplicar a defesa do mesmo // formulário com duas contagens diferentes. A tabela foi tornada conforme na // mesma (database/migration_20260925_shared_auth_attempts.sql) para o // relatório de saúde abaixo ('conformance' => 'report') não acusar erro e // para deixar a porta aberta a usar o throttle partilhado no futuro. Ver // views/auth/login.php para a nota completa. require_once dirname(__DIR__) . '/shared/platform-tools/functions_connector.php'; platform_connect($pdo, [ 'auth' => 'shared', 'roles' => [ // Preserva a hierarquia que config/auth.php já tinha (sysadmin > // admin > staff > client, por in_array(), não por posto numérico): // staff acima de client, e os dois abaixo de admin(80), para // require_staff()/is_staff() continuarem a excluir clientes e // require_admin()/is_admin() continuarem a excluir staff e clientes. // sysadmin(100)/admin(80)/user(10) já vêm registados por omissão. 'staff' => 40, 'client' => 10, ], 'session' => [ 'name' => 'FIT_SESS', 'samesite' => 'Lax', // secure/httponly ficam a cargo de auth_session_start(): secure // segue o mesmo critério de HTTPS que o bloco antigo usava, // httponly está sempre ligado. ], 'remember' => true, // «Manter sessão iniciada» — agora auth_remember_* // do platform-tools; ver views/auth/login.php. 'guard' => 'require_sysadmin', 'router' => true, 'conformance' => 'report', // nunca recusa — só reporta no health endpoint // A Comunicações desta app já cobre SMTP (config + testes); o Estado SMTP // partilhado seria um segundo editor da mesma configuração. 'replaced_tools' => ['smtp_status' => 'views/sysadmin/comms.php'], 'hide_tools' => ['smtp_status'], 'sysadmin_tools' => [[ 'title' => 'Comunicações', 'description' => 'Configuração SMTP, fila de mensagens e testes de envio.', 'icon' => 'bi-broadcast', 'url' => 'views/sysadmin/comms.php', 'color' => 'info', ], [ 'title' => 'Fila de mensagens', 'description' => 'O que saiu, o que falhou e porquê.', 'icon' => 'bi-envelope-paper', 'url' => 'views/sysadmin/messages.php', 'color' => 'secondary', ]], ]);