Setup completo del portal interno donna.aal-law.com: * Landing público con OAuth login + grid de apps autenticado * Flujo OAuth2 contra Mailcow (mail.aal-law.com) * Sesiones SSO compartidas en el dominio (cookie donna_session, 8h) * Whitelist de dominios aal-law.com / aalnlaw.com * App "Control de Escrituras" migrada desde ZH-Consultores/control-escrituras (mismo schema, mismo frontend, ahora con shared Auth/Db/Config libs) * Migraciones SQL para users, escrituras_tramites, escrituras_historial * Caddy on :80 con rewrite extless → .php * Backup diario MariaDB (systemd timer + script) con retención 14d * Documentación de arquitectura en README + infra files en docs/infra
125 lines
4.4 KiB
PHP
125 lines
4.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/Config.php';
|
|
require_once __DIR__ . '/Db.php';
|
|
|
|
final class Auth {
|
|
public static function startSession(): void {
|
|
if (session_status() === PHP_SESSION_ACTIVE) return;
|
|
|
|
$secure = ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'
|
|
|| (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
|
|
|
session_name('donna_session');
|
|
session_set_cookie_params([
|
|
'lifetime' => Config::int('SESSION_LIFETIME', 28800),
|
|
'path' => '/',
|
|
'secure' => $secure,
|
|
'httponly' => true,
|
|
'samesite' => 'Lax',
|
|
]);
|
|
session_start();
|
|
}
|
|
|
|
public static function isAuthenticated(): bool {
|
|
self::startSession();
|
|
return !empty($_SESSION['authenticated'])
|
|
&& !empty($_SESSION['auth_time'])
|
|
&& (time() - (int)$_SESSION['auth_time']) < Config::int('SESSION_LIFETIME', 28800);
|
|
}
|
|
|
|
public static function user(): array {
|
|
self::startSession();
|
|
return [
|
|
'email' => $_SESSION['user_email'] ?? '',
|
|
'name' => $_SESSION['user_name'] ?? '',
|
|
];
|
|
}
|
|
|
|
public static function buildLoginUrl(?string $returnTo = null): string {
|
|
self::startSession();
|
|
$state = bin2hex(random_bytes(16));
|
|
$_SESSION['oauth_state'] = $state;
|
|
$_SESSION['oauth_return_to'] = $returnTo ?: ($_SERVER['REQUEST_URI'] ?? '/');
|
|
|
|
$params = http_build_query([
|
|
'response_type' => 'code',
|
|
'client_id' => Config::get('OAUTH_CLIENT_ID'),
|
|
'redirect_uri' => Config::get('OAUTH_REDIRECT_URI'),
|
|
'scope' => Config::get('OAUTH_SCOPE', 'profile'),
|
|
'state' => $state,
|
|
]);
|
|
return Config::get('OAUTH_AUTH_URL') . '?' . $params;
|
|
}
|
|
|
|
public static function requireAuth(): void {
|
|
if (self::isAuthenticated()) return;
|
|
$loginUrl = self::buildLoginUrl();
|
|
$accepts = $_SERVER['HTTP_ACCEPT'] ?? '';
|
|
$isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH'])
|
|
|| str_contains($accepts, 'application/json');
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
http_response_code(401);
|
|
echo json_encode(['ok' => false, 'auth_required' => true, 'login_url' => $loginUrl]);
|
|
exit;
|
|
}
|
|
header('Location: ' . $loginUrl);
|
|
exit;
|
|
}
|
|
|
|
public static function emailDomainAllowed(string $email): bool {
|
|
$allowed = Config::list('ALLOWED_EMAIL_DOMAINS');
|
|
if (!$allowed) return true;
|
|
$parts = explode('@', strtolower(trim($email)));
|
|
if (count($parts) !== 2) return false;
|
|
return in_array($parts[1], array_map('strtolower', $allowed), true);
|
|
}
|
|
|
|
public static function completeLogin(string $email, string $name): void {
|
|
self::startSession();
|
|
session_regenerate_id(true);
|
|
$_SESSION['authenticated'] = true;
|
|
$_SESSION['user_email'] = $email;
|
|
$_SESSION['user_name'] = $name ?: $email;
|
|
$_SESSION['auth_time'] = time();
|
|
|
|
try {
|
|
$pdo = Db::pdo();
|
|
$stmt = $pdo->prepare(
|
|
'INSERT INTO users (email, name, first_seen_at, last_login_at)
|
|
VALUES (:email, :name, NOW(), NOW())
|
|
ON DUPLICATE KEY UPDATE name = VALUES(name), last_login_at = NOW()'
|
|
);
|
|
$stmt->execute([':email' => $email, ':name' => $name]);
|
|
} catch (Throwable $e) {
|
|
error_log('users upsert failed: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public static function logout(): void {
|
|
self::startSession();
|
|
$_SESSION = [];
|
|
if (ini_get('session.use_cookies')) {
|
|
$p = session_get_cookie_params();
|
|
setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'] ?? '', $p['secure'], $p['httponly']);
|
|
}
|
|
session_destroy();
|
|
}
|
|
|
|
public static function popReturnTo(): string {
|
|
self::startSession();
|
|
$to = $_SESSION['oauth_return_to'] ?? '/';
|
|
unset($_SESSION['oauth_return_to']);
|
|
return $to;
|
|
}
|
|
|
|
public static function checkState(string $state): bool {
|
|
self::startSession();
|
|
$expected = $_SESSION['oauth_state'] ?? '';
|
|
unset($_SESSION['oauth_state']);
|
|
return $state !== '' && hash_equals((string)$expected, $state);
|
|
}
|
|
}
|