From 3edfd6c1649a3a8451de99d80fe5392ccb9169c8 Mon Sep 17 00:00:00 2001 From: Victor Westerlund Date: Tue, 23 Sep 2025 19:29:57 +0200 Subject: [PATCH] wip: 2025-09-23T19:29:57+0200 (1758648597) --- .env.example.ini | 1 + .env.ini | 1 + assets/css/pages/dashboard.css | 5 ++ assets/css/pages/index.css | 5 ++ assets/css/shell.css | 6 ++- assets/js/modules/Logger.js | 99 ++++++++++++++++++++++++++++++++++ assets/js/pages/dashboard.js | 5 ++ assets/js/pages/login.js | 74 ++++++++++++++++++------- assets/js/shell.js | 89 ++++++++---------------------- logs/.gitkeep | 0 public/dashboard.php | 3 ++ public/error.php | 1 + public/index.php | 3 +- public/log.php | 2 +- public/login.php | 10 ++-- public/shell.php | 4 +- src/Log.php | 29 +++++++++- 17 files changed, 240 insertions(+), 97 deletions(-) create mode 100644 .env.example.ini create mode 100644 .env.ini create mode 100644 assets/css/pages/dashboard.css create mode 100644 assets/css/pages/index.css create mode 100644 assets/js/modules/Logger.js create mode 100644 assets/js/pages/dashboard.js delete mode 100644 logs/.gitkeep create mode 100644 public/dashboard.php create mode 100644 public/error.php diff --git a/.env.example.ini b/.env.example.ini new file mode 100644 index 0000000..2a85ab7 --- /dev/null +++ b/.env.example.ini @@ -0,0 +1 @@ +LOG_ENABLED = true \ No newline at end of file diff --git a/.env.ini b/.env.ini new file mode 100644 index 0000000..ac1b58f --- /dev/null +++ b/.env.ini @@ -0,0 +1 @@ +LOG_ENABLED = false \ No newline at end of file diff --git a/assets/css/pages/dashboard.css b/assets/css/pages/dashboard.css new file mode 100644 index 0000000..c08c9c4 --- /dev/null +++ b/assets/css/pages/dashboard.css @@ -0,0 +1,5 @@ +vv-shell { + display: grid; + justify-items: center; + align-items: center; +} \ No newline at end of file diff --git a/assets/css/pages/index.css b/assets/css/pages/index.css new file mode 100644 index 0000000..c08c9c4 --- /dev/null +++ b/assets/css/pages/index.css @@ -0,0 +1,5 @@ +vv-shell { + display: grid; + justify-items: center; + align-items: center; +} \ No newline at end of file diff --git a/assets/css/shell.css b/assets/css/shell.css index 4a9ef43..0703b47 100644 --- a/assets/css/shell.css +++ b/assets/css/shell.css @@ -59,6 +59,10 @@ button { } } +dialog { + margin: auto; +} + /* Sections */ vv-shell { @@ -71,7 +75,7 @@ vv-shell { border-radius: 9px; background-color: white; - &[vv-loading="true"] { + &[vv-loading="true"] ::not(dialog) { pointer-events: none; } diff --git a/assets/js/modules/Logger.js b/assets/js/modules/Logger.js new file mode 100644 index 0000000..97ad174 --- /dev/null +++ b/assets/js/modules/Logger.js @@ -0,0 +1,99 @@ +const MOUSE_MOVE_TIMEOUT_MS = 100; + +globalThis.Logger = class Logger { + #abort; + #mouseMoveTimeout; + + static get url() { + const url = new URL(window.location); + url.pathname = "/log"; + + return url; + } + + /** + * Return a best-effort unique string that identifies this client + * @returns {String} + */ + static async #fingerprint() { + // Create a hash from various identifying browser data + const buffer = await window.crypto.subtle.digest("SHA-1", new TextEncoder().encode(JSON.stringify([ + navigator.userAgent, + navigator.buildId, + navigator.languages + ]))); + + return new DataView(buffer).getBigUint64().toString(); + } + + /** + * Extract desired MouseEvent data into an object literal + * @param {MouseEvent} event + * @returns {Object} + */ + static #mouseEvent(event) { + return { + e: event.type, + w: window.innerWidth, + h: window.innerHeight, + x: event.x, + y: event.y + } + } + + /** + * Extract desired KeyboardEvent data into an object literal + * @param {KeyboardEvent} event + * @returns {Object} + */ + static #keyEvent(event) { + return { + e: event.type, + c: event.key, + s: event.shiftKey + } + } + + constructor() {} + + /** + * Start logging user activities + */ + start() { + this.#abort = new AbortController(); + + document.addEventListener("keyup", (event) => this.log(Logger.#keyEvent(event)), { signal: this.#abort.signal }); + document.addEventListener("keydown", (event) => this.log(Logger.#keyEvent(event)), { signal: this.#abort.signal }); + document.addEventListener("click", (event) => this.log(Logger.#mouseEvent(event)), { signal: this.#abort.signal }); + document.addEventListener("mousemove", (event) => { + // Throttle mousemove events + clearTimeout(this.#mouseMoveTimeout); + //this.#mouseMoveTimeout = setTimeout(() => this.#log(mouseEvent(event)), MOUSE_MOVE_TIMEOUT_MS); + }, { signal: this.#abort.signal }); + } + + /** + * Stop logging user activitiers + */ + stop() { + this.#abort.abort(); + } + + /** + * Log user data + * @param {Object} data + * @returns {Response} + */ + async log(data) { + return await fetch(Logger.url, { + body: JSON.stringify({ + client: data, + fingerprint: (await Logger.#fingerprint()) + }), + method: "POST", + headers: Object.assign({ + "Content-Type": "application/json" + }, VV.header) + }); + }; +} \ No newline at end of file diff --git a/assets/js/pages/dashboard.js b/assets/js/pages/dashboard.js new file mode 100644 index 0000000..06f766e --- /dev/null +++ b/assets/js/pages/dashboard.js @@ -0,0 +1,5 @@ +// Clear all content and display the loading spinner for now. I want to add more stuff here later! +setTimeout(() => { + VV.shell.innerHTML = ""; + VV.shell.setAttribute("vv-loading", true); +}, VV.delay); \ No newline at end of file diff --git a/assets/js/pages/login.js b/assets/js/pages/login.js index bd6b5fd..d0d3588 100644 --- a/assets/js/pages/login.js +++ b/assets/js/pages/login.js @@ -1,23 +1,57 @@ -const WHITELIST_USERNAMES = [ - "user", - "root", - "admin", - "mydlink" -]; -const WHITELIST_PASSWORDS = [ - "root", - "admin", - "12345", - "mydlink", - "password", - "123456789" -] +// Simulate a fake login page +{ + const WHITELIST_USERNAMES = [ + "user", + "root", + "admin", + "mydlink" + ]; + const WHITELIST_PASSWORDS = [ + "root", + "admin", + "12345", + "mydlink", + "password", + "123456789" + ]; + const INPUT_NAME_USERNAME = "username"; + const INPUT_NAME_PASSWORD = "password"; + + document.querySelector("form button").addEventListener("click", (event) => { + event.preventDefault(); + + VV.shell.setAttribute("vv-loading", true); + const form = new FormData(event.target.closest("form")); -document.querySelector("form button").addEventListener("click", (event) => { - event.preventDefault(); + // Invalid fake username or password derp + if ( + !WHITELIST_USERNAMES.includes(form.get(INPUT_NAME_USERNAME)) + || !WHITELIST_PASSWORDS.includes(form.get(INPUT_NAME_PASSWORD)) + ) { + // Show "incorrect credentials" dialog after global Vegvisir delay + setTimeout(() => { + VV.shell.setAttribute("vv-loading", false); + document.querySelector("dialog").showModal(); + }, VV.delay); - VV.shell.setAttribute("vv-loading", true); - const form = new FormData(event.target.closest("form")); + return; + } - console.log("Hello"); -}); \ No newline at end of file + new VV().navigate("/dashboard"); + }); +} + +// Only start logging if the user does something with the input fields +{ + const abortInitialInputChange = new AbortController(); + + const startLogging = () =>{ + abortInitialInputChange.abort(); + new globalThis.Logger().start(); + }; + + document.querySelector("button").addEventListener("click", () => startLogging(), { signal: abortInitialInputChange.signal }); + document.querySelectorAll("input").forEach(element => element.addEventListener("click", () => startLogging(), { signal: abortInitialInputChange.signal })); + document.querySelectorAll("input").forEach(element => element.addEventListener("keydown", () => startLogging(), { signal: abortInitialInputChange.signal })); + document.querySelectorAll("input").forEach(element => element.addEventListener("change", () => startLogging(), { signal: abortInitialInputChange.signal })); +} \ No newline at end of file diff --git a/assets/js/shell.js b/assets/js/shell.js index bb067c5..c3df959 100644 --- a/assets/js/shell.js +++ b/assets/js/shell.js @@ -1,73 +1,30 @@ -// Set a global delay to simulate crappy web software -VV.delay = 200; +const LOGIN_PAGE = "/login"; +const STORAGE_KEY_LOGGEDIN = "mydlink_dashboard_login"; -// Log user activities -{ - const MOUSE_MOVE_TIMEOUT_MS = 100; +// Set a generous global navigation delay to simulate crappy web software +VV.delay = 3500; - const logUrl = new URL(window.location); - logUrl.pathname = "/log"; +// Redirect the user to the login page if session storage key is not set +if (!sessionStorage.getItem(STORAGE_KEY_LOGGEDIN) && window.location.pathname !== LOGIN_PAGE) { + const getRandomString = (length = 16) => { + const CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let string = ""; - let mouseMoveTimeout; - - // Return a fingerprint for this browser - const fingerprint = async () => { - const buffer = await window.crypto.subtle.digest("SHA-1", new TextEncoder().encode(JSON.stringify([ - navigator.userAgent, - navigator.buildId, - navigator.languages - ]))); - - let fingerprint; - - for (let i = 0; i < buffer.byteLength; i++) { - fingerprint += buffer[i]; - } - - return fingerprint; - }; - - // Log data - const log = async (data) => { - console.log(JSON.stringify({ - data: data, - fingerprint: await fingerprint() - })); - - return await fetch(logUrl, { - body: JSON.stringify({ - data: data, - fingerprint: await fingerprint() - }), - method: "POST", - headers: VV.header - }); + for (let i = 0; i < length; i++) string += CHARSET[Math.floor(Math.random() * CHARSET.length)]; + + return string; }; - const mouseEvent = (event) => { - return { - e: event.type, - w: window.innerWidth, - h: window.innerHeight, - x: event.x, - y: event.y - } - } + const url = new URL(window.location); - const keyEvent = (event) => { - return { - e: event.type, - c: event.key, - s: event.shiftKey - } - } + // Set some legit looking overcomplicated search parameters + url.searchParams.set("mydl_sid", getRandomString()); + // This is our fake "user is logged in" Storage API key + url.searchParams.set("action", STORAGE_KEY_LOGGEDIN); + url.searchParams.set(`mydl_${getRandomString(3)}`, "dashboard"); + url.searchParams.set(`mydl_asas_${getRandomString(4)}_${getRandomString(8)}`, "login_cgi"); + + url.pathname = LOGIN_PAGE; - document.addEventListener("keyup", (event) => log(keyEvent(event))); - document.addEventListener("keydown", (event) => log(keyEvent(event))); - document.addEventListener("click", (event) => log(mouseEvent(event))); - document.addEventListener("mousemove", (event) => { - // Throttle mousemove events - clearTimeout(mouseMoveTimeout); - //mouseMoveTimeout = setTimeout(() => log(mouseEvent(event)), MOUSE_MOVE_TIMEOUT_MS); - }); -} + new VV().navigate(url); +} \ No newline at end of file diff --git a/logs/.gitkeep b/logs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/public/dashboard.php b/public/dashboard.php new file mode 100644 index 0000000..29e0ad3 --- /dev/null +++ b/public/dashboard.php @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/error.php b/public/error.php new file mode 100644 index 0000000..96d8eaa --- /dev/null +++ b/public/error.php @@ -0,0 +1 @@ +

404 Not Found

\ No newline at end of file diff --git a/public/index.php b/public/index.php index daa8dcd..cbe3be2 100644 --- a/public/index.php +++ b/public/index.php @@ -1 +1,2 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/public/log.php b/public/log.php index 9296dc1..90cecb0 100644 --- a/public/log.php +++ b/public/log.php @@ -5,7 +5,7 @@ require_once VV::root("src/Log.php"); if ($_SERVER["REQUEST_METHOD"] === "POST" && !empty($_POST)) { - save_log($_POST); + save_log((object) $_POST); } ?> \ No newline at end of file diff --git a/public/login.php b/public/login.php index 09c90e9..1c4741c 100644 --- a/public/login.php +++ b/public/login.php @@ -2,11 +2,11 @@
@@ -16,9 +16,9 @@

Please follow these steps in order to register your mdlink-enabled product and get access to both mydlink.com and our mobile apps. Learn more details here.

-

Incorrect username or password

- +

Incorrect username or password

+
- \ No newline at end of file + \ No newline at end of file diff --git a/public/shell.php b/public/shell.php index 76f1240..0d59f9d 100644 --- a/public/shell.php +++ b/public/shell.php @@ -2,14 +2,15 @@ - mydlink +
+

DIR-880L