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
70 lines
2.7 KiB
PHP
70 lines
2.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/../../src/Auth.php';
|
|
require_once __DIR__ . '/../../src/Oauth.php';
|
|
|
|
function deny(string $msg, int $code = 401): void {
|
|
http_response_code($code);
|
|
header('Content-Type: text/html; charset=utf-8');
|
|
$safe = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');
|
|
echo <<<HTML
|
|
<!DOCTYPE html>
|
|
<html lang="es"><head><meta charset="UTF-8"><title>Acceso denegado — Donna</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
|
|
<style>
|
|
body{font-family:'DM Sans',sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#F7F6F3;}
|
|
.box{background:#fff;border:1px solid #eee;border-radius:14px;padding:44px 52px;text-align:center;max-width:440px;box-shadow:0 8px 32px rgba(0,0,0,0.06);}
|
|
.logo{font-family:'DM Serif Display',serif;font-size:24px;color:#1B3A6B;margin-bottom:6px;}
|
|
.tag{font-size:11px;color:#C9A84C;letter-spacing:.18em;text-transform:uppercase;margin-bottom:24px;font-weight:600;}
|
|
h2{color:#1B3A6B;margin:0 0 12px;font-family:'DM Serif Display',serif;font-weight:400;}
|
|
p{color:#666;font-size:14px;margin:0 0 24px;line-height:1.55;}
|
|
a{display:inline-block;padding:10px 24px;background:#1B3A6B;color:#fff;border-radius:8px;text-decoration:none;font-size:13px;font-weight:500;}
|
|
a:hover{background:#112446;}
|
|
</style></head><body>
|
|
<div class="box">
|
|
<div class="logo">Donna</div>
|
|
<div class="tag">AALN · Portal interno</div>
|
|
<h2>Acceso denegado</h2>
|
|
<p>{$safe}</p>
|
|
<a href="/">Volver al inicio</a>
|
|
</div></body></html>
|
|
HTML;
|
|
exit;
|
|
}
|
|
|
|
$state = $_GET['state'] ?? '';
|
|
if (!is_string($state) || !Auth::checkState($state)) {
|
|
deny('Token de seguridad inválido o expirado. Intenta iniciar sesión de nuevo.');
|
|
}
|
|
|
|
$code = $_GET['code'] ?? '';
|
|
if (!is_string($code) || $code === '') {
|
|
$err = $_GET['error_description'] ?? $_GET['error'] ?? 'Autorización cancelada.';
|
|
deny((string)$err);
|
|
}
|
|
|
|
$tokenResp = Oauth::exchangeCodeForToken($code);
|
|
if (empty($tokenResp['access_token'])) {
|
|
deny('No se pudo obtener el token de acceso. Reintenta más tarde.');
|
|
}
|
|
|
|
$profile = Oauth::fetchProfile($tokenResp['access_token']);
|
|
$email = $profile['email'] ?? $profile['username'] ?? '';
|
|
$name = $profile['full_name'] ?? $profile['name'] ?? $email;
|
|
|
|
if ($email === '') {
|
|
deny('No se pudo leer el perfil del usuario desde el servidor de correo.');
|
|
}
|
|
|
|
if (!Auth::emailDomainAllowed($email)) {
|
|
deny('Esta cuenta no está autorizada para Donna. Solo dominios corporativos de AALN.', 403);
|
|
}
|
|
|
|
Auth::completeLogin($email, $name);
|
|
|
|
$returnTo = Auth::popReturnTo();
|
|
if (!str_starts_with($returnTo, '/')) $returnTo = '/';
|
|
|
|
header('Location: ' . $returnTo);
|
|
exit;
|