Files
donna/src/Auth.php
T
Soporte ZH Consultores 4ee32aec6d Initial commit — Donna portal v1
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
2026-05-11 19:10:36 +00:00

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);
}
}