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>