diff options
author | RaindropsSys <contact@minteck.org> | 2023-05-13 19:25:44 +0200 |
---|---|---|
committer | RaindropsSys <contact@minteck.org> | 2023-05-13 19:25:44 +0200 |
commit | 21ed7d0e837d74c1ebd8ada4396f96ce42c14fb1 (patch) | |
tree | 8bdad11e806ad4ac6c68902eaf72913a4554e484 /pages | |
parent | f80190dddaa72d9f8863b0b922e557668b6cba27 (diff) | |
download | pluralconnect-21ed7d0e837d74c1ebd8ada4396f96ce42c14fb1.tar.gz pluralconnect-21ed7d0e837d74c1ebd8ada4396f96ce42c14fb1.tar.bz2 pluralconnect-21ed7d0e837d74c1ebd8ada4396f96ce42c14fb1.zip |
Updated 14 files and added 6 files (automated)
Diffstat (limited to 'pages')
-rw-r--r-- | pages/api.inc | 1 | ||||
-rw-r--r-- | pages/api/browser.php | 4 | ||||
-rw-r--r-- | pages/api/computer.php | 6 | ||||
-rw-r--r-- | pages/api/disconnect.php | 26 | ||||
-rw-r--r-- | pages/api/reauthenticate.php | 8 | ||||
-rw-r--r-- | pages/api/rename.php | 17 | ||||
-rw-r--r-- | pages/api/session.php | 24 | ||||
-rw-r--r-- | pages/home.inc | 7 | ||||
-rw-r--r-- | pages/logout.inc | 2 | ||||
-rw-r--r-- | pages/pair.inc | 32 | ||||
-rw-r--r-- | pages/sessions.inc | 182 |
11 files changed, 304 insertions, 5 deletions
diff --git a/pages/api.inc b/pages/api.inc index f71ac10..5c13bac 100644 --- a/pages/api.inc +++ b/pages/api.inc @@ -12,6 +12,7 @@ if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pages/api/" . $toplevel . ".php")) require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/api/" . $toplevel . ".php"; } else { header("HTTP/1.1 500 Internal Server Error"); + header("Content-Type: text/plain"); echo("Endpoint not found"); die(); }
\ No newline at end of file diff --git a/pages/api/browser.php b/pages/api/browser.php new file mode 100644 index 0000000..657b2a7 --- /dev/null +++ b/pages/api/browser.php @@ -0,0 +1,4 @@ +<?php + +header("Content-Type: application/json"); +die(json_encode(get_browser(), JSON_PRETTY_PRINT));
\ No newline at end of file diff --git a/pages/api/computer.php b/pages/api/computer.php index a9b87ae..f5117f9 100644 --- a/pages/api/computer.php +++ b/pages/api/computer.php @@ -6,7 +6,11 @@ if (isset($_GET["chrome"])) { } require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $_PROFILE; -if (!$isLoggedIn) header("Location: /-/login") and die(); + +if (!$isLoggedIn || !isset($_PROFILE) || !isset($_PROFILE["login"])) { + header("Location: /-/login"); + die(); +} $request_raw = file_get_contents('php://input'); $json_object = json_decode($request_raw, true); diff --git a/pages/api/disconnect.php b/pages/api/disconnect.php new file mode 100644 index 0000000..13363e6 --- /dev/null +++ b/pages/api/disconnect.php @@ -0,0 +1,26 @@ +<?php + +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc"; +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $isLowerLoggedIn; global $_PROFILE; +if (!$isLoggedIn && !$isLowerLoggedIn) { + header("Location: /-/login"); + die(); +} + +$list = array_filter([...scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens"), ...scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens")], function ($token) use ($_PROFILE) { + $session = file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token) ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token), true) : json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token), true); + + return $token !== "." && $token !== ".." && isset($session["last"]) && isset($session["profile"]) && $session["profile"]["id"] === $_PROFILE["id"]; +}); + +foreach ($list as $token) { + $session = file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token) ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token), true) : json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token), true); + + if (isset($_GET["id"]) && sha1($token) . md5($token) === $_GET["id"]) { + if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token)) { + unlink($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token); + } else { + unlink($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token); + } + } +}
\ No newline at end of file diff --git a/pages/api/reauthenticate.php b/pages/api/reauthenticate.php index 50657cc..e726e8e 100644 --- a/pages/api/reauthenticate.php +++ b/pages/api/reauthenticate.php @@ -13,7 +13,13 @@ if (!$isLoggedIn || $isLowerLoggedIn) { $newToken = generateToken(); if (isset($_COOKIE['PEH2_SESSION_TOKEN'])) { - file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $newToken, file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $_COOKIE['PEH2_SESSION_TOKEN'])); + $old = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $_COOKIE['PEH2_SESSION_TOKEN']), true); + $old["name"] = base64_decode($_GET["name"] ?? "LQo="); + $old["created"] = time(); + $old["addresses"] = []; + $old["last"] = time(); + + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $newToken, json_encode($old)); } die($newToken);
\ No newline at end of file diff --git a/pages/api/rename.php b/pages/api/rename.php new file mode 100644 index 0000000..d450557 --- /dev/null +++ b/pages/api/rename.php @@ -0,0 +1,17 @@ +<?php + +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc"; +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $isLowerLoggedIn; + +header("Content-Type: application/json"); + +if (!$isLoggedIn || $isLowerLoggedIn) { + header("Location: /-/login"); + die(); +} + +$data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])), true); + +$data["name"] = $_GET["name"] ?? $data["name"]; + +file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']), json_encode($data));
\ No newline at end of file diff --git a/pages/api/session.php b/pages/api/session.php new file mode 100644 index 0000000..f91288e --- /dev/null +++ b/pages/api/session.php @@ -0,0 +1,24 @@ +<?php + +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc"; +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $isLowerLoggedIn; + +header("Content-Type: application/json"); + +if (!$isLoggedIn || $isLowerLoggedIn) { + die(json_encode([ + "name" => null, + "created" => null, + "last_seen" => null, + "seen_at" => null + ], JSON_PRETTY_PRINT)); +} + +$data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])), true); + +die(json_encode([ + "name" => $data["name"], + "created" => date('c', $data["created"]), + "last_seen" => date('c', $data["last"]), + "seen_at" => array_keys($data["addresses"]) +], JSON_PRETTY_PRINT));
\ No newline at end of file diff --git a/pages/home.inc b/pages/home.inc index 9686f7f..48c26da 100644 --- a/pages/home.inc +++ b/pages/home.inc @@ -77,6 +77,7 @@ function members() { global $isLoggedIn; global $isLowerLoggedIn; global $app; ? </div> <?php endif; ?> + <?php $cache = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/home.json"), true); @@ -155,6 +156,12 @@ function members() { global $isLoggedIn; global $isLowerLoggedIn; global $app; ? <b>Notice:</b> The administrators are currently trying a new optimisation technique based on a virtual file system (chvfs). Data loss, corruption or inconsistency may happen and should be reported on <a href="https://bugs.equestria.dev/issues/CH" target="_blank">bugs.equestria.dev</a>. </div>--> + <?php if (isset($_COOKIE["PEH2_SESSION_TOKEN"]) && $_COOKIE["PEH2_SESSION_TOKEN"] !== "" && !$isLoggedIn && !$isLowerLoggedIn): ?> + <div class="alert alert-warning" style="margin-top:20px;"> + <b>You were previously logged in to Cold Haze, </b>however you have been logged out due to inactivity, due to your device being removed, or due to switching to a new authentication system. Please log in again. <a href="https://bugs.equestria.dev/issue/CH-56/Better-session-security" target="_blank">Learn more.</a> + </div> + <?php endif; ?> + <?php if ($isLowerLoggedIn || $isLoggedIn) { diff --git a/pages/logout.inc b/pages/logout.inc index 37d89ed..3d3f39b 100644 --- a/pages/logout.inc +++ b/pages/logout.inc @@ -6,6 +6,8 @@ if (isset($_COOKIE['PEH2_SESSION_TOKEN'])) { } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])))) { unlink($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']))); } + + header("Set-Cookie: PEH2_SESSION_TOKEN=; SameSite=None; Path=/; Secure; HttpOnly; Expires=0"); } header("Location: /") and die();
\ No newline at end of file diff --git a/pages/pair.inc b/pages/pair.inc index 7f57420..fa301ad 100644 --- a/pages/pair.inc +++ b/pages/pair.inc @@ -21,9 +21,15 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; glob <p>Pairing allows a connected device that cannot normally use Cold Haze to gather data. This device will get full access to your account as if they were acting on your behalf, so make sure you trust the device and/or application you are using.</p> + <?php $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])), true); if (isset($data["profile"])): ?> <p>Equestria.dev may not have verified the application you use, always check for a signature. Enter the pairing code displayed on your device below:</p> <input autofocus type="text" placeholder="Pairing code" class="form-control" style="margin-bottom:15px;color:white;background:#111;border-color:#222;" id="code"> + <?php else: ?> + <div class="alert alert-danger"> + <b>Error:</b> You cannot use the pairing feature because your current session is using the old authentication system. Please log out and log in again to continue. + </div> + <?php endif; ?> </div> <div class="modal fade" id="confirm"> @@ -39,7 +45,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; glob <p>You are about to pair the following device with your Cold Haze account:</p> <blockquote style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 10px;"> <div style="display: flex; align-items: center; justify-content: center;"> - <img src="/assets/logo/newlogo3.png" style="width: 32px; height: 32px;"> + <img src="/assets/logo/newlogo3.png" id="device-icon" style="width: 32px; height: 32px;"> </div> <div> <b>Name:</b> <span id="device-name">-</span><br> @@ -82,12 +88,30 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; glob <script> const modal = new bootstrap.Modal(document.getElementById("confirm")); + function getIconForName(name) { + if (name.startsWith("Luna Desktop ")) { + return "https://git.equestria.dev/equestria.dev/luna/raw/branch/mane/icons/logo.png"; + } + + if (name.startsWith("Cold Haze for Wear OS ")) { + return "https://static.equestria.horse/ch-wear.png"; + } + + return "/assets/logo/newlogo3.png"; + } + (async () => { const token = await (await fetch("/api/token")).text(); let ws = window.ws = new WebSocket("wss://ponies.equestria.horse/_PairingServices-WebSocket-EntryPoint/socket"); ws.onopen = (event) => { console.log(event); + + if (location.hash && location.hash.startsWith("#/")) { + document.getElementById("code").value = location.hash.substring(2); + document.getElementById("code").onkeydown(); + location.hash = ""; + } } ws.onclose = (event) => { @@ -109,8 +133,9 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; glob modal.hide(); document.getElementById("code").focus(); } else if (data.type === "device") { - document.getElementById("device-name").innerText = document.getElementById("paired-name-1").innerText = document.getElementById("paired-name-2").innerText = data.identity.name; + document.getElementById("device-name").innerText = document.getElementById("paired-name-1").innerText = document.getElementById("paired-name-2").innerText = window.currentName = data.identity.name; document.getElementById("device-address").innerText = data.identity.address; + document.getElementById("device-icon").src = getIconForName(data.identity.name ?? ""); document.getElementById("modal-button").classList.add("disabled"); modal.show(); @@ -134,6 +159,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; glob } window.currentCode = null; + window.currentName = ""; function pair(code) { const ua = new UAParser(navigator.userAgent).getResult(); @@ -162,7 +188,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; glob ws.send(JSON.stringify({ type: "confirm", code: window.currentCode, - token: (await (await fetch("/api/reauthenticate")).text()).trim() + token: (await (await fetch("/api/reauthenticate/?name=" + encodeURIComponent(btoa(window.currentName)))).text()).trim() })); } diff --git a/pages/sessions.inc b/pages/sessions.inc new file mode 100644 index 0000000..6b52a6d --- /dev/null +++ b/pages/sessions.inc @@ -0,0 +1,182 @@ +<?php + +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/init.inc"; global $title; global $isLoggedIn; global $isLowerLoggedIn; global $lang; global $pages; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; global $_PROFILE; + +?> + +<br> +<div class="container"> + <h1>Sessions</h1> + <script>window.devices = {};</script> + + <p>Here are all the currently open sessions for your account. Clicking on a session will delete it, meaning the device using this session will be logged out.</p> + + <?php $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])), true); if (isset($data["profile"])): ?> + <div class="list-group"> + <?php + + $list = array_filter([...scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens"), ...scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens")], function ($token) { + $session = file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token) ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token), true) : json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token), true); + + return $token !== "." && $token !== ".." && isset($session["last"]) && isset($session["profile"]); + }); + usort($list, function ($token1, $token2) { + $session1 = file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token1) ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token1), true) : json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token1), true); + $session2 = file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token2) ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token2), true) : json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token2), true); + + if (isset($session1["last"]) && isset($session2["last"])) { + return $session2["last"] - $session1["last"]; + } else { + return INF; + } + }); + + foreach ($list as $token): $session = file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token) ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token), true) : json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token), true); if (isset($session["profile"]) && isset($session["name"]) && $session["profile"]["id"] === $_PROFILE["id"]): uasort($session["addresses"], function ($a, $b) { + return $b - $a; + }); ?> + <a class="list-group-item list-group-item-action" onclick="logOut("<?= sha1($token) . md5($token) ?>");"> + <b><?= $session["name"] ?></b><?php if ($token === $_COOKIE["PEH2_SESSION_TOKEN"]): ?><span style="margin-left: 10px;" class="badge bg-primary">This device</span><?php endif; ?><script>window.devices["<?= sha1($token) . md5($token) ?>"]=JSON.parse(`<?= json_encode([ + "name" => $session["name"], + "lastIP" => array_keys($session["addresses"])[count(array_keys($session["addresses"])) - 1], + "lastSeen" => timeAgo($session["last"]), + "currentDevice" => $token === $_COOKIE["PEH2_SESSION_TOKEN"] + ]) ?>`);</script><br> + Logged in <?= timeAgo($session["created"]) ?>, last activity <?= timeAgo($session["last"]) ?> + <blockquote class="session-bq"> + <?php foreach ($session["addresses"] as $address => $last): ?> + <?= $address ?> ยท <?= timeAgo($last) ?><br> + <?php endforeach; ?> + </blockquote> + </a> + <?php endif; endforeach; ?> + </div> + <?php else: ?> + <div class="alert alert-danger"> + <b>Error:</b> You cannot use the session manager because your current session is using the old authentication system. Please log out and log in again to continue. + </div> + <?php endif; ?> +</div> + +<div class="modal fade" id="confirm"> + <div class="modal-dialog"> + <div class="modal-content"> + + <div class="modal-header"> + <h4 class="modal-title">Log out this device?</h4> + <button type="button" class="btn-close" data-bs-toggle="modal"></button> + </div> + + <div class="modal-body"> + <p>You are about to log out the following device from your Cold Haze account:</p> + <blockquote id="device-bq"> + <div> + <b>Name:</b> <span id="device-name">-</span><br> + <b>Last address:</b> <span id="device-address">-</span><br> + <b>Last activity:</b> <span id="device-activity">-</span> + </div> + </blockquote> + <p class="text-danger" id="device-current" style="display: none;">This is the device you are currently using, which means you will get logged out as soon as you click on confirm.</p> + <p id="device-normal"></p> + <span class="btn btn-success" id="modal-button" style="margin-right: 5px;" onclick="confirm();">Confirm</span><span class="btn btn-outline-secondary" data-bs-toggle="modal">Cancel</span> + </div> + </div> + </div> +</div> + +<script> + window.currentSession = null; + window.currentDevice = null; + window.modal = new bootstrap.Modal(document.getElementById("confirm")); + + async function confirm() { + await fetch("/api/disconnect?id=" + window.currentSession); + + if (currentDevice.currentDevice) { + location.href = "/-/logout"; + } else { + location.reload(); + } + } + + function logOut(id) { + window.currentSession = id; + window.currentDevice = devices[id]; + + if (currentDevice.currentDevice) { + document.getElementById("device-current").style.display = ""; + document.getElementById("device-normal").style.display = "none"; + } else { + document.getElementById("device-current").style.display = "none"; + document.getElementById("device-normal").style.display = ""; + } + + document.getElementById("device-name").innerText = currentDevice.name; + document.getElementById("device-address").innerText = currentDevice.lastIP; + document.getElementById("device-activity").innerText = currentDevice.lastSeen; + + modal.show(); + } +</script> + +<style> + .modal-header { + border-bottom: 1px solid #353738; + } + + .modal-content { + border: 1px solid rgba(255, 255, 255, .2); + background-color: #111; + } + + .btn-close { + filter: invert(1); + } + + .list-group-item { + color: #fff; + background-color: #222; + border: 1px solid rgba(255, 255, 255, .125); + } + + .list-group-item.disabled { + color: #fff; + background-color: #222; + border-color: rgba(255, 255, 255, .125); + opacity: .75; + } + + .list-group-item:hover { + background-color: #252525; + color: #ddd; + } + + .list-group-item:active, .list-group-item:focus { + background-color: #272727; + color: #bbb; + } + + .member-link, .list-group-item-action { + cursor: pointer !important; + } + + .alert-dismissible .btn-close { + filter: none !important; + } + + .session-bq { + margin-bottom: 5px; + margin-top: 10px; + margin-left: 5px; + padding-left: 10px; + border-left: 3px solid rgba(255, 255, 255, .25); + } + + #device-bq { + margin-left: 5px; + padding-left: 10px; + border-left: 3px solid rgba(255, 255, 255, .25); + } +</style> + +<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> |