Initial import of fitme3070.pt platform
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>
This commit is contained in:
commit
e68d357177
2330 files changed
+215968
No files matched your search
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
// Password recovery lives on id.andmore.pt — every local form/handler was
|
||||
// removed when this install became SSO-only. This file is a bounce-only stub.
|
||||
|
||||
require __DIR__ . '/../../config/db.php';
|
||||
require_once __DIR__ . '/../../config/functions.php';
|
||||
|
||||
$return_to = base_url('views/auth/login.php');
|
||||
header('Location: ' . ANDMORE_ID_URL . 'auth/forgot_password.php?return_to=' . urlencode($return_to));
|
||||
exit;
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// SSO-only platform: there is no local login form. All credentials live on
|
||||
// id.andmore.pt. This file exists solely so old links / bookmarks pointing
|
||||
// at /views/auth/login.php still land the visitor on the central form.
|
||||
//
|
||||
// Also acts as the post-login landing for the id.andmore.pt return_to: the
|
||||
// am_sso cookie is now in the browser, sso_resolve_session() picks it up,
|
||||
// and we forward to the dashboard. The `?from=sso` flag prevents an infinite
|
||||
// loop if resolution fails here — we render a clean error instead of
|
||||
// bouncing back to id.andmore.pt (which would just send us back here again).
|
||||
//
|
||||
// Carries through the optional claim intent (?next=session&session_id=X)
|
||||
// from the homepage "vagas disponíveis" cards so that after authenticating
|
||||
// the user lands on their dashboard scrolled to the open session.
|
||||
|
||||
require __DIR__ . '/../../config/db.php';
|
||||
require_once __DIR__ . '/../../config/functions.php';
|
||||
|
||||
// Visitor may already have an am_sso cookie — resolve it instead of bouncing
|
||||
// (saves a round-trip).
|
||||
if (empty($_SESSION['user_id']) && identity_is_federated()) {
|
||||
sso_resolve_session($pdo);
|
||||
}
|
||||
|
||||
$next = $_GET['next'] ?? '';
|
||||
$session_id = (int) ($_GET['session_id'] ?? 0);
|
||||
$claim_intent = ($next === 'session' && $session_id > 0);
|
||||
$from_sso = isset($_GET['from']) && $_GET['from'] === 'sso';
|
||||
|
||||
$dashboard_for = function (string $role) use ($claim_intent, $session_id): string {
|
||||
if ($claim_intent && $role === 'client') {
|
||||
return 'views/user/dashboard.php?focus_session=' . $session_id;
|
||||
}
|
||||
return post_login_destination($role);
|
||||
};
|
||||
|
||||
if (!empty($_SESSION['user_id'])) {
|
||||
header('Location: ' . base_url($dashboard_for($_SESSION['user']['role'] ?? '')));
|
||||
exit;
|
||||
}
|
||||
|
||||
// We came back from id.andmore.pt but sso_resolve_session() couldn't pick up
|
||||
// a valid session. Bouncing to id.andmore.pt again would loop forever — show
|
||||
// a clean error instead so the user can see what's wrong.
|
||||
if ($from_sso) {
|
||||
http_response_code(500);
|
||||
$am_cookie_present = !empty($_COOKIE[SSO_COOKIE_NAME] ?? '');
|
||||
?><!doctype html><html lang="pt"><head><meta charset="utf-8"><title>Erro de autenticação</title>
|
||||
<style>body{font-family:system-ui,-apple-system,sans-serif;max-width:560px;margin:80px auto;padding:0 1.5rem;color:#1c1c1a;background:#f7f5f1;line-height:1.6}h1{font-weight:300;font-size:1.6rem;margin-bottom:.5rem}code{background:rgba(0,0,0,.05);padding:2px 6px;border-radius:3px;font-size:.9em}.muted{color:#6b6863;font-size:.9rem}a{color:#5a7a62}</style>
|
||||
</head><body>
|
||||
<h1>Não foi possível concluir o login</h1>
|
||||
<p>O servidor Andmore ID autenticou-o, mas esta plataforma não conseguiu carregar a sua sessão.</p>
|
||||
<p class="muted">
|
||||
Cookie <code>am_sso</code>: <?= $am_cookie_present ? 'presente' : '<strong>em falta</strong>' ?>.<br>
|
||||
Plataforma: <code><?= htmlspecialchars(PLATFORM_SLUG) ?></code> · Modo: <code><?= htmlspecialchars(IDENTITY_MODE) ?></code>.
|
||||
</p>
|
||||
<p><a href="<?= htmlspecialchars(base_url('index.php')) ?>">Voltar ao início</a> · <a href="<?= htmlspecialchars(base_url('views/auth/logout.php')) ?>">Tentar novamente</a></p>
|
||||
</body></html><?php
|
||||
exit;
|
||||
}
|
||||
|
||||
// First arrival — bounce to id.andmore.pt for credentials, tagging the
|
||||
// return URL with ?from=sso so the loop-guard above fires on the way back.
|
||||
$return_to = base_url('views/auth/login.php?from=sso'
|
||||
. ($claim_intent ? '&next=session&session_id=' . $session_id : ''));
|
||||
|
||||
$_login_qs = '?return_to=' . urlencode($return_to) . '&platform=' . urlencode(PLATFORM_SLUG);
|
||||
header('Location: ' . ANDMORE_ID_URL . 'auth/login.php' . $_login_qs);
|
||||
exit;
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../config/db.php';
|
||||
require __DIR__ . '/../../config/functions.php';
|
||||
|
||||
// Always clear the local session first.
|
||||
$_SESSION = [];
|
||||
session_destroy();
|
||||
|
||||
// Federated mode: always bounce through Andmore ID's logout endpoint so both
|
||||
// the central sso_sessions row and the am_sso cookie are destroyed AND
|
||||
// id.andmore.pt's own PHP session is cleared. The old check for the am_sso
|
||||
// cookie was a bug: if the cookie happened to be absent from $_COOKIE (e.g.
|
||||
// due to a Secure-flag or SameSite edge case), the central logout was skipped,
|
||||
// leaving id.andmore.pt's session alive. That session then caused a redirect
|
||||
// loop — id.andmore.pt auto-logged the user in and bounced them back to the
|
||||
// login stub, which immediately bounced them back to id.andmore.pt, ad inf.
|
||||
//
|
||||
// Return to the public landing page (not the login stub) so the post-logout
|
||||
// redirect from id.andmore.pt lands on a real page and the chain stops.
|
||||
if (identity_is_federated()) {
|
||||
header('Location: ' . ANDMORE_ID_URL . 'auth/logout.php?return_to=' . urlencode(APP_BASE_URL));
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Location: ' . base_url('views/auth/login.php'));
|
||||
exit;
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
// SSO-only platform: there is no local registration form. Accounts live on
|
||||
// id.andmore.pt and are mirrored locally by sso_resolve_session() the first
|
||||
// time the user lands on this platform. This file exists only so old
|
||||
// /views/auth/register.php links bounce through to the central registration.
|
||||
|
||||
require __DIR__ . '/../../config/db.php';
|
||||
require_once __DIR__ . '/../../config/functions.php';
|
||||
|
||||
// Already authenticated? Skip the bounce and land on the right dashboard.
|
||||
if (empty($_SESSION['user_id']) && identity_is_federated()) {
|
||||
sso_resolve_session($pdo);
|
||||
}
|
||||
|
||||
$next = $_GET['next'] ?? '';
|
||||
$session_id = (int) ($_GET['session_id'] ?? 0);
|
||||
$claim_intent = ($next === 'session' && $session_id > 0);
|
||||
|
||||
$dashboard_for = function (string $role) use ($claim_intent, $session_id): string {
|
||||
if ($claim_intent && $role === 'client') {
|
||||
return 'views/user/dashboard.php?focus_session=' . $session_id;
|
||||
}
|
||||
return post_login_destination($role);
|
||||
};
|
||||
|
||||
if (!empty($_SESSION['user_id'])) {
|
||||
header('Location: ' . base_url($dashboard_for($_SESSION['user']['role'] ?? '')));
|
||||
exit;
|
||||
}
|
||||
|
||||
$return_to = base_url('views/auth/login.php'
|
||||
. ($claim_intent ? '?next=session&session_id=' . $session_id : ''));
|
||||
|
||||
header('Location: ' . ANDMORE_ID_URL . 'auth/register.php?return_to=' . urlencode($return_to));
|
||||
exit;
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
// Password reset happens on id.andmore.pt — bounce anyone landing here with
|
||||
// an old reset link so they don't get a "page not working" experience.
|
||||
|
||||
require __DIR__ . '/../../config/db.php';
|
||||
require_once __DIR__ . '/../../config/functions.php';
|
||||
|
||||
header('Location: ' . ANDMORE_ID_URL . 'auth/forgot_password.php?return_to='
|
||||
. urlencode(base_url('views/auth/login.php')));
|
||||
exit;
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
require __DIR__ . '/../../config/db.php';
|
||||
require __DIR__ . '/../../config/functions.php';
|
||||
|
||||
$error = '';
|
||||
$success = '';
|
||||
|
||||
// Self-locks after success by writing system_settings.setup_completed_at —
|
||||
// relying only on "is there a sysadmin?" left the door open if anyone ever
|
||||
// truncated the users table.
|
||||
$setupDoneAt = (string) setting('setup_completed_at', '');
|
||||
$alreadyDone = ($setupDoneAt !== '' || admin_exists($pdo));
|
||||
|
||||
if ($alreadyDone) {
|
||||
$success = 'Já existe uma conta de administrador. Esta página já não é necessária — elimine views/auth/setup.php.';
|
||||
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
csrf_check();
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$confirm = $_POST['confirm'] ?? '';
|
||||
|
||||
if (!$name || !$email || !$password) {
|
||||
$error = 'Todos os campos são obrigatórios.';
|
||||
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$error = 'Endereço de email inválido.';
|
||||
} elseif (strlen($password) < 8) {
|
||||
$error = 'A palavra-passe deve ter pelo menos 8 caracteres.';
|
||||
} elseif ($password !== $confirm) {
|
||||
$error = 'As palavras-passe não coincidem.';
|
||||
} else {
|
||||
$newId = auth_create_sysadmin($pdo, $name, $email, $password);
|
||||
// Latch the gate so a future `users` truncate can't re-open setup.
|
||||
db_update_setting($pdo, 'setup_completed_at', date('Y-m-d H:i:s'), $newId);
|
||||
// Best-effort self-destruct.
|
||||
@unlink(__FILE__);
|
||||
$alreadyDone = true;
|
||||
$success = 'Conta de sysadmin criada. <strong>Elimine este ficheiro (views/auth/setup.php) agora.</strong> <a href="' . base_url('views/auth/login.php') . '">Ir para o login →</a>';
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../includes/header.php';
|
||||
?>
|
||||
<div class="container" style="max-width:480px; margin-top:80px;">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="mb-1">Configuração inicial</h3>
|
||||
<p class="text-muted small mb-4">Crie a conta de administrador. Elimine este ficheiro depois.</p>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success"><?= $success ?></div>
|
||||
<?php elseif (!$alreadyDone): ?>
|
||||
<form method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="setup-name">Nome</label>
|
||||
<input type="text" id="setup-name" name="name" class="form-control" required
|
||||
autocomplete="name"
|
||||
value="<?= htmlspecialchars($_POST['name'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="setup-email">Email</label>
|
||||
<input type="email" id="setup-email" name="email" class="form-control" required
|
||||
autocomplete="email" inputmode="email"
|
||||
value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="setup-password">Palavra-passe <span class="text-muted small">(mín. 8 caracteres)</span></label>
|
||||
<input type="password" id="setup-password" name="password" class="form-control" required minlength="8"
|
||||
autocomplete="new-password">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label" for="setup-confirm">Confirmar palavra-passe</label>
|
||||
<input type="password" id="setup-confirm" name="confirm" class="form-control" required
|
||||
autocomplete="new-password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Criar conta de administrador</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php include __DIR__ . '/../../includes/footer.php'; ?>
|
||||
Reference in new issue
Block a user