summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/icons/sessions.svg1
-rw-r--r--auth/callback/index.php16
-rw-r--r--includes/components/explicit.php2
-rw-r--r--includes/components/footer.inc5
-rw-r--r--includes/components/navigation.inc8
-rw-r--r--includes/external/pair/reference.js13
-rw-r--r--includes/pages.json7
-rw-r--r--includes/util/functions.inc2
-rw-r--r--includes/util/session.inc42
-rw-r--r--pages/api.inc1
-rw-r--r--pages/api/browser.php4
-rw-r--r--pages/api/computer.php6
-rw-r--r--pages/api/disconnect.php26
-rw-r--r--pages/api/reauthenticate.php8
-rw-r--r--pages/api/rename.php17
-rw-r--r--pages/api/session.php24
-rw-r--r--pages/home.inc7
-rw-r--r--pages/logout.inc2
-rw-r--r--pages/pair.inc32
-rw-r--r--pages/sessions.inc182
20 files changed, 389 insertions, 16 deletions
diff --git a/assets/icons/sessions.svg b/assets/icons/sessions.svg
new file mode 100644
index 0000000..6cf4935
--- /dev/null
+++ b/assets/icons/sessions.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.995 11a2 2 0 0 1-1.245 1.852v2.398a.75.75 0 0 1-1.5 0v-2.394A2 2 0 1 1 13.995 11Z" fill="#000000"/><path d="M3.75 5a.75.75 0 0 0-.75.75V11c0 5.001 2.958 8.676 8.725 10.948a.75.75 0 0 0 .55 0C18.042 19.676 21 16 21 11V5.75a.75.75 0 0 0-.75-.75c-2.663 0-5.258-.943-7.8-2.85a.75.75 0 0 0-.9 0C9.008 4.057 6.413 5 3.75 5Zm.75 6V6.478c2.577-.152 5.08-1.09 7.5-2.8 2.42 1.71 4.923 2.648 7.5 2.8V11c0 4.256-2.453 7.379-7.5 9.442C6.953 18.379 4.5 15.256 4.5 11Z" fill="#000000"/></svg> \ No newline at end of file
diff --git a/auth/callback/index.php b/auth/callback/index.php
index 4fa1eba..75d73ea 100644
--- a/auth/callback/index.php
+++ b/auth/callback/index.php
@@ -51,9 +51,21 @@ if (isset($result["access_token"])) {
$token = generateToken();
if (in_array($result["id"], $appdata["oauth"]["allowed"]["admin"])) {
- file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token, json_encode($result));
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . $token, json_encode([
+ "created" => time(),
+ "last" => time(),
+ "profile" => $result,
+ "addresses" => [],
+ "name" => "Cold Haze Web (" . get_browser(null, true)["browser"] . " on " . str_replace("Windows dows", "Windows", str_replace("Win", "Windows ", str_replace("MacOSX", "macOS", get_browser(null, true)["platform"]))) . ")"
+ ]));
} else {
- file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token, json_encode($result));
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . $token, json_encode([
+ "created" => time(),
+ "last" => time(),
+ "profile" => $result,
+ "addresses" => [],
+ "name" => "Cold Haze Web (" . get_browser(null, true)["browser"] . " on " . get_browser(null, true)["platform"] . ")"
+ ]));
}
header("Set-Cookie: PEH2_SESSION_TOKEN=" . $token . "; SameSite=None; Path=/; Secure; HttpOnly; Expires=" . date("r", time() + (86400 * 730)));
diff --git a/includes/components/explicit.php b/includes/components/explicit.php
index 4c55896..f7b4533 100644
--- a/includes/components/explicit.php
+++ b/includes/components/explicit.php
@@ -1,4 +1,4 @@
-<?php global $isLoggedIn; global $isLowerLoggedIn; global $_PROFILE; global $app; if ($isLoggedIn || $isLowerLoggedIn): ?>
+<?php global $isLoggedIn; global $isLowerLoggedIn; global $_PROFILE; global $app; if (isset($_PROFILE["login"]) && $isLoggedIn || $isLowerLoggedIn): ?>
<div class="modal" id="explicit-modal" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
diff --git a/includes/components/footer.inc b/includes/components/footer.inc
index 059d956..78c3148 100644
--- a/includes/components/footer.inc
+++ b/includes/components/footer.inc
@@ -6,6 +6,7 @@ global $pageFile;
?>
+<script src="/assets/editor/ua-parser.js"></script>
<div id="footer-pre"></div>
<div id="footer">
<hr>
@@ -35,7 +36,9 @@ global $pageFile;
if (!item.classList.contains("tooltip-nohelp")) {
item.style.cursor = "help";
}
- })
+ });
+
+ window.fetch("/api/rename?name=" + encodeURIComponent("Cold Haze Web (" + UAParser().browser.name + " on " + UAParser().os.name + ")"));
</script>
<?php if (isset($_GET["performance"])): ?>
diff --git a/includes/components/navigation.inc b/includes/components/navigation.inc
index a5aa734..9a777ce 100644
--- a/includes/components/navigation.inc
+++ b/includes/components/navigation.inc
@@ -214,6 +214,14 @@ $navigation_admin = [
"private" => true
],
[
+ "name" => $pages["sessions"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/sessions.svg",
+ "invert" => true,
+ "link" => "/-/sessions",
+ "stepped" => null,
+ "private" => true
+ ],
+ [
"name" => $pages["logout"]["name"][$lang["_name"]],
"icon" => "/assets/icons/logout.svg",
"invert" => true,
diff --git a/includes/external/pair/reference.js b/includes/external/pair/reference.js
index 1d9fd46..16d1653 100644
--- a/includes/external/pair/reference.js
+++ b/includes/external/pair/reference.js
@@ -43,7 +43,18 @@ ws.on('message', (raw) => {
// server as the 'PEH2_SESSION_TOKEN' cookie when making an authenticated request.
console.log(`Token: ${data.token.substring(0, 10)}${"*".repeat(data.token.length - 10)}`);
// The token is stored in 'data.token', the code above censors all but the first 10 characters.
- process.exit();
+ // Once you have the token, you can make authenticated requests:
+ fetch("https://ponies.equestria.horse/api/session", {
+ // The 'session' endpoint returns information about the current session (name, IPs, dates, ...)
+ headers: {
+ Cookie: "PEH2_SESSION_TOKEN=" + data.token // Passing the token as a cookie
+ }
+ }).then((res) => {
+ res.json().then((data) => { // Most (if not all) endpoints return JSON data
+ console.log(data);
+ process.exit();
+ });
+ });
break;
case "reject":
diff --git a/includes/pages.json b/includes/pages.json
index c1d7b20..4753214 100644
--- a/includes/pages.json
+++ b/includes/pages.json
@@ -174,6 +174,13 @@
"admin": true,
"limited": true
},
+ "sessions": {
+ "name": {
+ "en": "Sessions"
+ },
+ "admin": true,
+ "limited": true
+ },
"splitting": {
"name": {
"en": "By splitting date"
diff --git a/includes/util/functions.inc b/includes/util/functions.inc
index 9727b51..3d84bb7 100644
--- a/includes/util/functions.inc
+++ b/includes/util/functions.inc
@@ -25,7 +25,7 @@ if (!function_exists("formatPonypush")) {
if (!function_exists("generateToken")) {
function generateToken(): string {
- return bin2hex(random_bytes(32));
+ return str_replace("/", ".", base64_encode(random_bytes(96)));
}
}
diff --git a/includes/util/session.inc b/includes/util/session.inc
index 0a5999f..81192b9 100644
--- a/includes/util/session.inc
+++ b/includes/util/session.inc
@@ -16,9 +16,25 @@ if (!function_exists("formatPonypush")) {
}
if (isset($_COOKIE['PEH2_SESSION_TOKEN'])) {
- if (!(str_contains($_COOKIE['PEH2_SESSION_TOKEN'], ".") || str_contains($_COOKIE['PEH2_SESSION_TOKEN'], "/") || trim($_COOKIE["PEH2_SESSION_TOKEN"]) === "")) {
- if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])))) {
- $_PROFILE = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']))), true);
+ if (!(str_contains($_COOKIE['PEH2_SESSION_TOKEN'], "/") || trim($_COOKIE["PEH2_SESSION_TOKEN"]) === "" || trim($_COOKIE["PEH2_SESSION_TOKEN"]) === "." || trim($_COOKIE["PEH2_SESSION_TOKEN"]) === "..")) {
+ if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']))) {
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])), true);
+
+ if (isset($data["profile"])) {
+ $_PROFILE = $data["profile"];
+ $data["last"] = time();
+ $data["addresses"][$_SERVER["HTTP_X_FORWARDED_FOR"] ?? $_SERVER["REMOTE_ADDR"]] = time();
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']), json_encode($data));
+
+ if (time() - $data["last"] > 86400 * 30) {
+ unlink($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']));
+ unset($_PROFILE);
+ $isLoggedIn = false;
+ $isLowerLoggedIn = false;
+ }
+ } else {
+ $_PROFILE = $data;
+ }
if (isset($_GET['invert'])) {
$_PROFILE["login"] = $_PROFILE["login"] === "raindrops" ? "cloudburst" : "raindrops";
@@ -26,8 +42,24 @@ if (isset($_COOKIE['PEH2_SESSION_TOKEN'])) {
}
$isLoggedIn = true;
- } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])))) {
- $_PROFILE = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']))), true);
+ } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']))) {
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/lowertokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])), true);
+
+ if (isset($data["profile"])) {
+ $_PROFILE = $data["profile"];
+ $data["last"] = time();
+ $data["addresses"][$_SERVER["HTTP_X_FORWARDED_FOR"] ?? $_SERVER["REMOTE_ADDR"]] = time();
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']), json_encode($data));
+
+ if (time() - $data["last"] > 86400 * 30) {
+ unlink($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']));
+ unset($_PROFILE);
+ $isLoggedIn = false;
+ $isLowerLoggedIn = false;
+ }
+ } else {
+ $_PROFILE = $data;
+ }
$isLowerLoggedIn = true;
}
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(&quot;<?= sha1($token) . md5($token) ?>&quot;);">
+ <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'; ?>