"Atualizar submódulo" failed on the server with:
fatal: Unable to find refs/remotes/origin/HEAD revision in submodule
path 'shared/platform-tools'
Two causes, both in .gitmodules.
1. No `branch` key. platform_submodules() falls back to the submodule's
refs/remotes/origin/HEAD symbolic ref when .gitmodules does not name a
branch. That ref is written by `git remote set-head`, which a plain clone
on the server never ran, so the lookup had nothing to resolve. Declaring
`branch = main` removes the fallback entirely. platform-tools' default
branch is `main`, not `master`.
2. url used http://, which returns 523 from the origin. Switched to the
relative form fotografia.andmore.pt already uses: it resolves against this
repo's own origin, so it follows whatever host and scheme the parent was
cloned with and carries no credentials of its own.
.gitmodules now matches the fotografia sibling exactly. The pull that brings
this commit in also runs `git submodule sync --recursive` (git_sync_submodules),
which is what rewrites the stored URL — no manual step on the server.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fitme3070.pt delegated all authentication to hub.andmore.pt: views/auth/*
were bounce stubs, and because this domain is outside the .andmore.pt cookie
scope the real handoff was a one-time HTTPS ticket redeem. Bring identity back
in-house — passwords live in users.password_hash, which the SSO migration
never dropped.
Removed: config/sso.php, IDENTITY_MODE, every SSO_* constant and env key,
identity_is_federated() and the cookie/ticket paths in the auth views and the
public-site bootstrap. ANDMORE_ID_URL stays — it is still the hub /notify
endpoint for outbound email/SMS, which the reset mails ride on, and has
nothing to do with authentication now.
Added, ported from the fotografia.andmore.pt sibling rather than invented:
- auth_login() with transparent password_needs_rehash upgrades and the
last_login trail; auth_register_client(); auth_set_password(); the
single-use 2h reset tokens.
- Real login / register / forgot_password / reset_password pages on a new
anonymous shell (includes/auth_header.php). includes/header.php renders
the backoffice sidebar and a logout button, which is wrong for a visitor
who is not signed in yet.
- safe_return_path(), so a crafted ?return_to= cannot bounce a freshly
authenticated user off-site.
- Per-IP throttling on login, register and reset-request. Successes count
too, so a guessed password cannot be parlayed into stuffing the rest of
the user list from the same IP.
- views/user/set_password.php: admin override for anyone whose email is
undeliverable. Prefers mailing an invite link so the backoffice never
handles the password. A plain admin cannot act on a sysadmin, and nobody
can use it on themselves — self-service goes through profile.php, which
demands the current password so a hijacked session cannot lock the owner
out.
Accounts provisioned during the SSO era have password_hash NULL. They get in
via the reset email, or by registering again with the same address:
auth_register_client() claims a passwordless row, but only a plain `client`
one, so knowing a staff address does not let anyone self-assign the role.
users.andmore_id is deliberately left in place. No code reads it any more, but
it records which central identity each row was linked to.
Deployment: apply database/migration_20260913_local_auth.sql (password_resets
+ auth_attempts) in the SAME sysadmin session that pulls this commit — the
forgot-password flow needs that table, and after the pull a login requires a
local password.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rename api/hub-content.php -> api/delete_hub-content.php so the old
content-pull path returns 404, disabling the hub social-post endpoint
without removing the implementation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The save AJAX endpoint swallowed DB exceptions behind a generic
"Erro ao guardar" with no logging, making failures undiagnosable.
Now log the real exception (same sink as the global handler) and
echo the detail back in dev.
Also validate `local` against the allowed sala set instead of only
its length, so an out-of-enum value returns a clean 422 instead of
a 500 (horario.local is a strict ENUM).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
One-line require of the platform-tools submodule agent. Needs HUB_OPS_SECRET
in .env and "Operações remotas" enabled on id.andmore.pt to respond.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This install is on external hosting and can't reach the central DB's private
LAN address (192.168.1.3), so the direct-DB burn always failed with
ECONNREFUSED. Redeem via the hub's api/sso_redeem.php over HTTPS (HMAC with
HUB_CONTENT_SECRET, ticket folded into the signature) and provision the
returned user into the local DB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fitme3070.pt is off the .andmore.pt cookie scope, so am_sso never arrives
and login never provisioned the user. Redeem the ?sso_ticket=… the hub now
appends to the return URL instead.
- sso_redeem_ticket(): atomic single-use burn of sso_tickets in the central
DB, bound to PLATFORM_SLUG, then provision the local row + FIT_SESS session.
- Extract _sso_provision_local() shared with the cookie path.
- views/auth/login.php redeems the ticket before the cookie fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plain-PHP fleet platform with id.andmore.pt SSO. Wires shared/platform-tools
as a submodule (platform_update, backups, git/migration tools) like the other
platforms.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>