diff --git a/backend/api/index.php b/backend/api/index.php index b2f4099..d4d0386 100644 --- a/backend/api/index.php +++ b/backend/api/index.php @@ -40,6 +40,12 @@ try { case 'settings': handleSettings($method, $db); break; + case 'login': + handleLogin($method, $db); + break; + case 'logout': + handleLogout(); + break; case 'teams': handleTeams($method, $id, $db); break; @@ -69,6 +75,10 @@ try { function handleSession($method, $db) { $loggedin = isset($_SESSION['neptune_loggedin']) && $_SESSION['neptune_loggedin'] === true; + if (!$loggedin && $method === 'GET') { + echo json_encode(['loggedin' => false]); + return; + } if ($loggedin) { $role = $_SESSION['neptune_role'] ?? 'user'; $stmt = $db->prepare("SELECT COUNT(*) as c FROM neptune_users WHERE role='admin'"); @@ -85,6 +95,80 @@ function handleSession($method, $db) { } } +function handleLogin($method, $db) { + if ($method !== 'POST') { + http_response_code(405); + echo json_encode(['error' => 'POST required']); + return; + } + $data = json_decode(file_get_contents('php://input'), true); + $auth_token = $data['auth_token'] ?? ''; + if (!$auth_token) { + http_response_code(400); + echo json_encode(['error' => 'auth_token required']); + return; + } + + $check_url = "https://auth.jakach.ch/api/auth/check_auth_key.php?auth_token=" . urlencode($auth_token); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $check_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + $response = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($http_code !== 200 || !$response) { + http_response_code(502); + echo json_encode(['error' => 'Failed to contact auth server']); + return; + } + + $info = json_decode($response, true); + if (!isset($info['status']) || $info['status'] !== 'success') { + http_response_code(401); + echo json_encode(['error' => $info['msg'] ?? 'Auth failed']); + return; + } + + $user_token = $info['user_token']; + $username = $info['username']; + $email = $info['email'] ?? ''; + + $stmt = $db->prepare("SELECT * FROM neptune_users WHERE user_token = ?"); + $stmt->execute([$user_token]); + $user = $stmt->fetch(); + + if ($user) { + $_SESSION['neptune_loggedin'] = true; + $_SESSION['neptune_user_token'] = $user['user_token']; + $_SESSION['neptune_username'] = $user['username']; + $_SESSION['neptune_role'] = $user['role']; + } else { + $count = $db->query("SELECT COUNT(*) as c FROM neptune_users")->fetch()['c']; + $role = ($count == 0) ? 'admin' : 'user'; + $stmt = $db->prepare("INSERT INTO neptune_users (user_token, username, email, role) VALUES (?, ?, ?, ?)"); + $stmt->execute([$user_token, $username, $email, $role]); + $_SESSION['neptune_loggedin'] = true; + $_SESSION['neptune_user_token'] = $user_token; + $_SESSION['neptune_username'] = $username; + $_SESSION['neptune_role'] = $role; + } + + echo json_encode([ + 'status' => 'success', + 'username' => $_SESSION['neptune_username'], + 'role' => $_SESSION['neptune_role'] + ]); +} + +function handleLogout() { + $_SESSION = array(); + session_destroy(); + echo json_encode(['status' => 'success']); +} + function handleSettings($method, $db) { $role = $_SESSION['neptune_role'] ?? 'user'; if ($method === 'GET') { diff --git a/backend/login.php b/backend/login.php deleted file mode 100644 index 0b37970..0000000 --- a/backend/login.php +++ /dev/null @@ -1,114 +0,0 @@ -prepare("SELECT * FROM neptune_users WHERE user_token = ?"); - $stmt->execute([$user_token]); - $user = $stmt->fetch(); - - if ($user) { - $_SESSION['neptune_loggedin'] = true; - $_SESSION['neptune_user_token'] = $user['user_token']; - $_SESSION['neptune_username'] = $user['username']; - $_SESSION['neptune_role'] = $user['role']; - header('Location: /'); - exit; - } else { - // First user becomes admin - $count = $db->query("SELECT COUNT(*) as c FROM neptune_users")->fetch()['c']; - $role = ($count == 0) ? 'admin' : 'user'; - - $stmt = $db->prepare("INSERT INTO neptune_users (user_token, username, email, role) VALUES (?, ?, ?, ?)"); - $stmt->execute([$user_token, $username, $email, $role]); - - $_SESSION['neptune_loggedin'] = true; - $_SESSION['neptune_user_token'] = $user_token; - $_SESSION['neptune_username'] = $username; - $_SESSION['neptune_role'] = $role; - header('Location: /'); - exit; - } - } else { - $error = 'Authentication failed: ' . ($data['msg'] ?? 'unknown error'); - } - } -} -?> - - - - - - Neptune - Login - - - - - -
- -

Neptune

-

Cybersecurity Incident Journal

- - -
- - -
- - - - Log in with Jakach Auth - - First user automatically becomes admin -
- - \ No newline at end of file diff --git a/backend/logout.php b/backend/logout.php deleted file mode 100644 index 0b7054a..0000000 --- a/backend/logout.php +++ /dev/null @@ -1,6 +0,0 @@ - { document.getElementById('saveLink').addEventListener('click', saveLink); document.getElementById('saveShape').addEventListener('click', saveShape); document.getElementById('addUserBtn').addEventListener('click', addUser); + document.getElementById('logoutBtn').addEventListener('click', logout); document.getElementById('teamFilter').addEventListener('change', renderTimeline); document.getElementById('searchEvents').addEventListener('input', renderTimeline); document.getElementById('shapeOpacity').addEventListener('input', (e) => { @@ -892,43 +893,89 @@ let currentRole = null; async function checkSession() { try { const res = await fetch('/api/session'); - if (res.redirected || !res.ok) { - window.location.replace('/login.php'); - return; - } const data = await res.json(); if (data.loggedin) { currentUser = data.username; currentRole = data.role; document.getElementById('userDisplay').textContent = data.username; - if (data.role === 'admin' || data.admin_count === 0) { + if (data.role === 'admin') { document.getElementById('settingsBtn').classList.remove('d-none'); } + document.getElementById('loginOverlay').style.display = 'none'; + return; + } + } catch (_) {} + // Show login overlay + document.getElementById('loginOverlay').style.display = 'flex'; +} + +async function performLogin(authToken) { + const errEl = document.getElementById('loginError'); + const sucEl = document.getElementById('loginSuccess'); + errEl.style.display = 'none'; + sucEl.style.display = 'none'; + try { + const res = await fetch('/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ auth_token: authToken }) + }); + const data = await res.json(); + if (data.status === 'success') { + currentUser = data.username; + currentRole = data.role; + document.getElementById('userDisplay').textContent = data.username; + if (data.role === 'admin') document.getElementById('settingsBtn').classList.remove('d-none'); + document.getElementById('loginOverlay').style.display = 'none'; + // Clean URL + window.history.replaceState({}, '', '/'); + // Reload data + loadTeams().then(() => loadEvents()); + loadNetworkData(); } else { - window.location.replace('/login.php'); + errEl.textContent = data.error || 'Login failed'; + errEl.style.display = 'block'; } } catch (e) { - // Retry once after a brief delay in case of transient network issue - setTimeout(async () => { - try { - const res = await fetch('/api/session'); - if (!res.ok || res.redirected) throw new Error(); - const data = await res.json(); - if (data.loggedin) { - currentUser = data.username; - currentRole = data.role; - document.getElementById('userDisplay').textContent = data.username; - if (data.role === 'admin' || data.admin_count === 0) { - document.getElementById('settingsBtn').classList.remove('d-none'); - } - return; - } - } catch (_) {} - window.location.replace('/login.php'); - }, 500); + errEl.textContent = 'Connection error'; + errEl.style.display = 'block'; } } +async function logout() { + await apiFetch('logout', { method: 'POST' }); + currentUser = null; + currentRole = null; + document.getElementById('settingsBtn').classList.add('d-none'); + document.getElementById('userDisplay').textContent = ''; + document.getElementById('loginOverlay').style.display = 'flex'; +} + +// Check for auth token in URL on page load +document.addEventListener('DOMContentLoaded', () => { + const params = new URLSearchParams(window.location.search); + const authToken = params.get('auth'); + if (authToken) { + // Show a loading state on overlay + document.getElementById('loginOverlay').style.display = 'flex'; + document.querySelector('#loginOverlay .btn').textContent = 'Authenticating...'; + performLogin(authToken); + } + + checkSession().then(() => { + // Init canvas and load data + canvas = document.getElementById('networkCanvas'); + ctx = canvas.getContext('2d'); + resizeCanvas(); + + loadTeams().then(() => loadEvents()); + loadNetworkData(); + + document.getElementById('loginBtn').addEventListener('click', () => { + const callbackUrl = window.location.origin + '/?auth_callback=1'; + window.location.href = 'https://auth.jakach.ch/?send_to=' + encodeURIComponent(callbackUrl); + }); + async function loadUsers() { const list = document.getElementById('userList'); try { diff --git a/frontend/index.html b/frontend/index.html index a9cb29b..2dd365d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -36,7 +36,7 @@
- +
@@ -375,6 +375,21 @@ + +
+
+ +

Neptune

+

Cybersecurity Incident Journal

+ + + + First user automatically becomes admin +
+
+