feat(auth): migra login/sessão/roles para o auth partilhado (auditoria 1.3)
config/db.php liga-se com 'auth' => 'shared' em vez de 'own': login, sessão, papéis e guardas passam a correr sobre shared/platform-tools/functions_auth.php (auth_attempt/auth_login/ auth_at_least/auth_is/auth_session_start/auth_remember_*) em vez da lógica local duplicada. Os ~30 ficheiros em views/ que chamam require_login()/require_admin()/require_staff()/require_sysadmin()/ is_admin()/etc. não mudam — só o que está por baixo dessas funções. - roles: staff=40, client=10 registados para preservar a hierarquia sysadmin>admin>staff>client que os antigos in_array() garantiam. `users` já tinha exactamente as colunas por omissão do auth_schema(), não foi preciso mapear nada. - sessão: o bloco manual de ini_set()/session_start() em config/db.php foi substituído pela opção 'session' do platform_connect() — mesmo nome de cookie (FIT_SESS), mesmo SameSite=Lax, secure/httponly passam a ser geridos por auth_session_start(). - «Manter sessão iniciada»: config/functions_remember.php (a reimplementação local do selector/validator) foi eliminado a favor do módulo partilhado (auth_remember_issue/resume/clear via 'remember' => true), que já cobria o mesmo desenho. - auth_attempts: tinha (ip, scope) do throttle próprio da app (config/functions_throttle.php), não o `identifier` que o throttle partilhado exige. Migração aditiva (migration_20260925_shared_auth_attempts.sql) torna a tabela conforme sem tocar no throttle da app, que continua a ser o único usado para login/registo/recuperação — o throttle partilhado não é chamado, para não duplicar a mesma defesa com duas contagens. - last_login_ua (sem equivalente partilhado) passa a ser gravado por fit_stamp_login_ua(), chamado a seguir a auth_login(). Testado ponta-a-ponta num contentor scratch na VPS (site-fitme:8.4, cópia da BD de produção + migração aplicada): visitante anónimo é redirigido, password errada dá o mesmo erro genérico e conta para o throttle (bloqueia à 5ª tentativa, 429), login correcto regenera a sessão, grava last_login_at/ip/ua, audita "auth.login" e (com "lembrar-me") emite o cookie am_remember; esse cookie sozinho, numa sessão nova, repõe a sessão certa com rotação do validator; logout limpa sessão+cookie+token e audita "auth.logout"; platform.php (guard=require_sysadmin) e views/admin/dashboard.php respondem 302 sem sessão e 200 com sessão de sysadmin. php -l limpo em todos os ficheiros alterados. Migração já aplicada em produção (aditiva). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ccc644ac0d
commit
fa87aabb87
9 files changed
+196
-274
No files matched your search
+47
-26
@@ -66,36 +66,57 @@ try {
|
||||
_bootstrap_fail('PDO connect failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Distinct session cookie per app so installs that share a host (e.g.
|
||||
// development.andmore.pt/id.andmore.pt/ and /fitme3070.pt/) don't overwrite
|
||||
// each other's PHPSESSID. Must run before session_start().
|
||||
//
|
||||
// Cookie e modo estrito definidos aqui, no código, para valerem em qualquer
|
||||
// servidor: antes o cookie saía só com path=/ (sem Secure, HttpOnly nem
|
||||
// SameSite) e o PHP aceitava IDs de sessão inventados pelo cliente.
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
ini_set('session.use_strict_mode', '1');
|
||||
ini_set('session.use_only_cookies', '1');
|
||||
ini_set('session.cookie_httponly', '1');
|
||||
ini_set('session.cookie_samesite', 'Lax');
|
||||
if (request_is_https()) ini_set('session.cookie_secure', '1');
|
||||
session_name('FIT_SESS');
|
||||
session_start();
|
||||
}
|
||||
|
||||
// ─── Ligação ao platform-tools ───────────────────────────────────────────────
|
||||
//
|
||||
// A app mantém as suas próprias passwords e sessão (`config/auth.php`,
|
||||
// `views/auth/login.php`) — não passa a usar o functions_auth.php partilhado
|
||||
// (`'auth' => 'own'`), porque a `auth_attempts` desta app não tem a coluna
|
||||
// `identifier` que o throttle partilhado exige (tem `ip`+`scope`, do seu
|
||||
// próprio throttle em functions_throttle.php). Ainda assim liga-se ao
|
||||
// bloqueio de IP da frota, ao router das páginas de sistema (Backups,
|
||||
// Actualizações, Auditoria, IP bans, Geo, Origem das visitas, Hub) e ao
|
||||
// resto do platform-tools, tudo por cima da autenticação própria.
|
||||
// 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' => 'own',
|
||||
'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
|
||||
|
||||
Reference in new issue
Block a user