diff --git a/public.php b/public.php index 0ca72bb7..f3d4920e 100755 --- a/public.php +++ b/public.php @@ -6,6 +6,7 @@ chdir(__DIR__); use Ubnt\UcrmPluginSdk\Service\PluginConfigManager; use Ubnt\UcrmPluginSdk\Service\UcrmApi; +use Ubnt\UcrmPluginSdk\Service\UcrmSecurity; use SmsNotifier\Service\PaymentIntentService; // Carga manual del servicio @@ -72,6 +73,119 @@ try { } + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// 0. LOGIN SCREEN SUPPORT & AUTHENTICATION +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +// Determinar si el usuario necesita login (si no tiene sesión UCRM activa) +$security = UcrmSecurity::create(); +$currentUser = $security->getUser(); +$needsLogin = !$currentUser; + +$nmsBaseUrl = "https://{$ipServer}/nms/api/v2.1"; + +// 0a. NMS Login (POST username + password) +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'nms_login') { + header('Content-Type: application/json'); + $input = json_decode(file_get_contents('php://input'), true); + $username = $input['username'] ?? ''; + $password = $input['password'] ?? ''; + + if (!$username || !$password) { + http_response_code(400); + echo json_encode(['error' => 'Se requieren usuario y contraseña.']); + exit; + } + + try { + $nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]); + $resp = $nmsClient->post("{$nmsBaseUrl}/user/login", [ + 'json' => ['username' => $username, 'password' => $password], + 'headers' => ['Content-Type' => 'application/json'], + ]); + + $statusCode = $resp->getStatusCode(); + $body = json_decode($resp->getBody()->getContents(), true); + $authToken = $resp->getHeaderLine('x-auth-token'); + + if ($statusCode === 200 && $authToken) { + $logger->info("NMS Login OK for user: {$username}"); + echo json_encode(['success' => true, 'token' => $authToken, 'user' => $body]); + } elseif ($statusCode === 201) { + $logger->info("NMS Login requires 2FA for user: {$username}"); + http_response_code(201); + echo json_encode(['requires2FA' => true, 'twoFactorToken' => $body]); + } else { + $logger->warning("NMS Login failed for user: {$username} (HTTP {$statusCode})"); + http_response_code($statusCode ?: 401); + echo json_encode(['error' => $body['message'] ?? 'Credenciales inválidas.', 'statusCode' => $statusCode]); + } + } catch (\Exception $e) { + $logger->error("NMS Login exception: " . $e->getMessage()); + http_response_code(500); + echo json_encode(['error' => 'Error de conexión con el servidor UISP.']); + } + exit; +} + +// 0b. NMS TOTP Login +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'nms_login_totp') { + header('Content-Type: application/json'); + $input = json_decode(file_get_contents('php://input'), true); + + try { + $nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]); + $resp = $nmsClient->post("{$nmsBaseUrl}/user/login/totpauth", [ + 'json' => $input, + 'headers' => ['Content-Type' => 'application/json'], + ]); + + $statusCode = $resp->getStatusCode(); + $body = json_decode($resp->getBody()->getContents(), true); + $authToken = $resp->getHeaderLine('x-auth-token'); + + if ($statusCode === 200 && $authToken) { + $logger->info("NMS 2FA Login OK"); + echo json_encode(['success' => true, 'token' => $authToken, 'user' => $body]); + } else { + http_response_code($statusCode ?: 401); + echo json_encode(['error' => $body['message'] ?? 'Código TOTP inválido.']); + } + } catch (\Exception $e) { + http_response_code(500); + echo json_encode(['error' => 'Error de conexión con el servidor UISP.']); + } + exit; +} + +// 0c. NMS Verify Session +if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'nms_verify_session') { + header('Content-Type: application/json'); + $token = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? ''; + if (!$token) { + http_response_code(401); + echo json_encode(['error' => 'No token provided.']); + exit; + } + try { + $nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]); + $resp = $nmsClient->get("{$nmsBaseUrl}/user", [ + 'headers' => ['x-auth-token' => $token], + ]); + if ($resp->getStatusCode() === 200) { + echo json_encode(['success' => true, 'user' => json_decode($resp->getBody()->getContents(), true)]); + } else { + http_response_code(401); + echo json_encode(['error' => 'Sesión inválida o expirada.']); + } + } catch (\Exception $e) { + http_response_code(500); + echo json_encode(['error' => 'Error verificando sesión.']); + } + exit; +} + // API HANDLERS if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { if ($_POST['action'] === 'save_installers') { @@ -988,10 +1102,272 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador border-color: var(--primary); color: var(--primary); } + + /* ── Login Overlay ─────────────────────────────────────── */ + #loginOverlay { + position: fixed; + inset: 0; + z-index: 99999; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%); + font-family: 'Outfit', sans-serif; + transition: opacity 0.5s ease, visibility 0.5s ease; + } + + #loginOverlay.hidden { + opacity: 0; + visibility: hidden; + pointer-events: none; + } + + .login-card { + width: 400px; + max-width: 92vw; + background: rgba(30, 41, 59, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 24px; + padding: 48px 36px 36px; + box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.04); + text-align: center; + animation: loginSlideIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; + } + + @keyframes loginSlideIn { + from { + opacity: 0; + transform: translateY(30px) scale(0.96); + } + + to { + opacity: 1; + transform: translateY(0) scale(1); + } + } + + .login-logo { + width: 90px; + height: 90px; + object-fit: contain; + margin-bottom: 12px; + filter: drop-shadow(0 4px 20px rgba(99, 102, 241, 0.3)); + } + + .login-title { + color: #f8fafc; + font-size: 1.5rem; + font-weight: 700; + margin: 0 0 4px; + } + + .login-subtitle { + color: #94a3b8; + font-size: 0.85rem; + margin: 0 0 28px; + } + + .login-field { + position: relative; + margin-bottom: 16px; + text-align: left; + } + + .login-field label { + display: block; + color: #94a3b8; + font-size: 0.78rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 6px; + } + + .login-field input { + width: 100%; + padding: 12px 14px; + background: rgba(15, 23, 42, 0.7); + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: 12px; + color: #f1f5f9; + font-size: 0.95rem; + font-family: 'Outfit', sans-serif; + outline: none; + transition: border-color 0.25s, box-shadow 0.25s; + box-sizing: border-box; + } + + .login-field input:focus { + border-color: #6366f1; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15); + } + + .login-field input::placeholder { + color: #475569; + } + + .login-btn { + width: 100%; + padding: 13px; + background: linear-gradient(135deg, #6366f1, #8b5cf6); + color: white; + border: none; + border-radius: 12px; + font-size: 1rem; + font-weight: 600; + font-family: 'Outfit', sans-serif; + cursor: pointer; + transition: transform 0.15s, box-shadow 0.25s; + margin-top: 8px; + } + + .login-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 8px 30px rgba(99, 102, 241, 0.35); + } + + .login-btn:active:not(:disabled) { + transform: translateY(0); + } + + .login-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + } + + .login-error { + background: rgba(239, 68, 68, 0.12); + border: 1px solid rgba(239, 68, 68, 0.25); + color: #fca5a5; + padding: 10px 14px; + border-radius: 10px; + font-size: 0.85rem; + margin-bottom: 16px; + display: none; + } + + .login-error.visible { + display: block; + } + + .login-spinner { + display: inline-block; + width: 18px; + height: 18px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: white; + border-radius: 50%; + animation: loginSpin 0.6s linear infinite; + vertical-align: middle; + margin-right: 6px; + } + + @keyframes loginSpin { + to { + transform: rotate(360deg); + } + } + + #loginTotpSection { + display: none; + } + + #loginTotpSection.visible { + display: block; + } + + .login-footer { + margin-top: 24px; + color: #475569; + font-size: 0.72rem; + } + + .login-footer a { + color: #6366f1; + text-decoration: none; + } + + /* Particles background */ + .login-particles { + position: absolute; + inset: 0; + overflow: hidden; + pointer-events: none; + } + + .login-particles span { + position: absolute; + width: 4px; + height: 4px; + background: rgba(99, 102, 241, 0.3); + border-radius: 50%; + animation: loginFloat 12s infinite ease-in-out; + } + + @keyframes loginFloat { + + 0%, + 100% { + transform: translateY(0) translateX(0); + opacity: 0; + } + + 10% { + opacity: 1; + } + + 90% { + opacity: 1; + } + + 50% { + transform: translateY(-400px) translateX(100px); + } + }
+ +
+ Inicia sesión con tu cuenta de UISP
+ + + + +