From 57472eab3c7b035392c6a5aa240593ecaa7d1ccf Mon Sep 17 00:00:00 2001 From: ilotterytea Date: Mon, 8 Dec 2025 21:53:36 +0500 Subject: upd: moved all /public/ files to the root folder --- account/change_emoteset.php | 36 ++++++ account/delete.php | 50 ++++++++ account/index.php | 306 ++++++++++++++++++++++++++++++++++++++++++++ account/login/index.php | 99 ++++++++++++++ account/login/twitch.php | 175 +++++++++++++++++++++++++ account/register.php | 111 ++++++++++++++++ account/security.php | 52 ++++++++ account/signout.php | 16 +++ 8 files changed, 845 insertions(+) create mode 100644 account/change_emoteset.php create mode 100644 account/delete.php create mode 100644 account/index.php create mode 100644 account/login/index.php create mode 100644 account/login/twitch.php create mode 100644 account/register.php create mode 100644 account/security.php create mode 100644 account/signout.php (limited to 'account') diff --git a/account/change_emoteset.php b/account/change_emoteset.php new file mode 100644 index 0000000..c2fc209 --- /dev/null +++ b/account/change_emoteset.php @@ -0,0 +1,36 @@ +prepare("SELECT id FROM acquired_emote_sets WHERE emote_set_id = ? AND user_id = ?"); +$stmt->execute([$emote_set_id, $user_id]); + +if ($stmt->rowCount() == 0) { + generate_alert("/404.php", "You don't own emote set ID $emote_set_id", 403); + exit; +} + +$_SESSION["user_active_emote_set_id"] = $emote_set_id; + +header("Location: " . $_POST["redirect"] ?? "/"); \ No newline at end of file diff --git a/account/delete.php b/account/delete.php new file mode 100644 index 0000000..ec8c040 --- /dev/null +++ b/account/delete.php @@ -0,0 +1,50 @@ +prepare("DELETE FROM user_badges WHERE user_id = ?")->execute([$id]); +} + +if ($profile) { + $db->prepare("DELETE FROM users WHERE id = ?")->execute([$id]); + + session_unset(); + session_destroy(); + + setcookie("secret_key", "", time() - 1000); +} + +header("Location: /account"); \ No newline at end of file diff --git a/account/index.php b/account/index.php new file mode 100644 index 0000000..2b9e790 --- /dev/null +++ b/account/index.php @@ -0,0 +1,306 @@ +prepare("SELECT id FROM users WHERE username = ?"); + $stmt->execute([$username]); + + if ($stmt->rowCount() == 0) { + $stmt = $db->prepare("UPDATE users SET username = ? WHERE id = ?"); + $stmt->execute([$username, $_SESSION["user_id"]]); + } else { + generate_alert("/account", "The username has already taken"); + exit; + } + } + + if (isset($_FILES["pfp"]) && !empty($_FILES["pfp"]["tmp_name"])) { + $pfp = $_FILES["pfp"]; + + if ( + $err = create_image_bundle( + $pfp["tmp_name"], + $_SERVER["DOCUMENT_ROOT"] . "/static/userdata/avatars/" . $_SESSION["user_id"], + ACCOUNT_PFP_MAX_SIZE[0], + ACCOUNT_PFP_MAX_SIZE[1], + true, + true + ) + ) { + generate_alert("/account", sprintf("Error occurred while processing the profile picture (%d)", $err)); + exit; + } + } + + if (isset($_FILES["banner"]) && !empty($_FILES["banner"]["tmp_name"])) { + $banner = $_FILES["banner"]; + + if ( + $err = create_image_bundle( + $banner["tmp_name"], + $_SERVER["DOCUMENT_ROOT"] . "/static/userdata/banners/" . $_SESSION["user_id"], + ACCOUNT_BANNER_MAX_SIZE[0], + ACCOUNT_BANNER_MAX_SIZE[1], + true, + true + ) + ) { + generate_alert("/account", sprintf("Error occurred while processing the profile banner (%d)", $err)); + exit; + } + } + + if (isset($_FILES["badge"]) && !empty($_FILES["badge"]["tmp_name"])) { + $badge = $_FILES["badge"]; + $badge_id = bin2hex(random_bytes(16)); + if ( + $err = create_image_bundle( + $badge["tmp_name"], + $_SERVER["DOCUMENT_ROOT"] . "/static/userdata/badges/" . $badge_id, + ACCOUNT_BADGE_MAX_SIZE[0], + ACCOUNT_BADGE_MAX_SIZE[1], + true, + true + ) + ) { + generate_alert("/account", sprintf("Error occurred while processing the personal badge (%d)", $err)); + exit; + } + + $db->prepare("DELETE FROM user_badges WHERE badge_id != ? AND user_id = ?")->execute([$badge_id, $_SESSION["user_id"]]); + $db->prepare("INSERT INTO badges(id, uploaded_by) VALUES (?, ?)")->execute([$badge_id, $_SESSION["user_id"]]); + $db->prepare("INSERT INTO user_badges(badge_id, user_id) VALUES (?, ?)")->execute([$badge_id, $_SESSION["user_id"]]); + } + + $db = null; + generate_alert("/account", "Your changes have been applied!", 200); + exit; +} + +?> + + + + + Account management - <?php echo INSTANCE_NAME ?> + + + + + +
+
+ + +
+ +
+

Account management

+ +
+

Profile

+

Profile picture

+ '; + } else { + echo "

You don't have profile picture

"; + } + ?> +
+ + + + Remove profile picture + + +
+ +

Profile banner

+ '; + } else { + echo "

You don't have profile banner

"; + } + ?> +
+ + + + Remove banner + + +
+ +

Personal badge

+ prepare("SELECT badge_id FROM user_badges WHERE user_id = ?"); + $stmt->execute([$_SESSION["user_id"]]); + + $has_badge = false; + + if ($row = $stmt->fetch()) { + echo '
'; + echo ''; + echo ''; + echo ''; + echo '
'; + $has_badge = true; + } else { + echo "

You don't have personal badge

"; + } + ?> +
+ + + + Remove badge + + +
+ +

Username

+ "> + + +
+ +
+ +
+

Connections

+
+ prepare("SELECT * FROM connections WHERE user_id = ?"); + $stmt->execute([$_SESSION["user_id"]]); + $connections = $stmt->fetchAll(); + $platforms = ["twitch"]; + + foreach ($platforms as $platform) { + $connection = null; + $key = array_search($platform, array_column($connections, "platform")); + + if (!is_bool($key)) { + $connection = $connections[$key]; + } + + echo "
"; + echo "
"; + + echo "
"; + echo "" . ucfirst($platform) . ""; + + // TODO: check if connection is still alive + if ($connection == null) { + echo "Not connected"; + } else { + echo "" . $connection["alias_id"] . ""; + } + + echo "
"; + + echo "
"; + + if ($connection == null) { + echo ""; + echo 'Connect'; + echo ""; + } else { + echo ""; + echo 'Disconnect'; + echo ""; + } + + echo "
"; + } + ?> +
+
+ +
+ +
+

Security & Privacy

+
+ prepare("SELECT CASE WHEN password IS NOT NULL THEN 1 ELSE 0 END as set_password FROM users WHERE id = ?"); + $stmt->execute([$_SESSION["user_id"]]); + $set_password = $stmt->fetch()[0]; + if ($set_password): ?> + + + + + +
+
+ prepare("SELECT private_profile FROM user_preferences WHERE id = ?"); + $stmt->execute([$_SESSION["user_id"]]); + if (intval($stmt->fetch()[0]) == 1) { + echo 'checked'; + } + ?>> + +

Enabling this feature will hide your authorship of uploaded emotes and + actions.

+ +
+
+ + +
+ + +
+ + Delete + me +
+
+
+
+ + + + + \ No newline at end of file diff --git a/account/login/index.php b/account/login/index.php new file mode 100644 index 0000000..ace116d --- /dev/null +++ b/account/login/index.php @@ -0,0 +1,99 @@ +prepare("SELECT secret_key, password FROM users WHERE username = ? AND password IS NOT NULL"); + $stmt->execute([$username]); + + if ($row = $stmt->fetch()) { + if (password_verify($password, $row["password"])) { + setcookie("secret_key", $row["secret_key"], $remember ? (time() + ACCOUNT_COOKIE_MAX_LIFETIME) : 0, "/"); + header("Location: /account"); + exit; + } else { + generate_alert("/account/login", "Passwords do not match!", 403); + exit; + } + } else { + generate_alert("/account/login", "User not found or is not accessable", 404); + exit; + } +} +?> + + + + + Login - <?php echo INSTANCE_NAME ?> + + + + + +
+
+ +
+ +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + + Register + +
+
+
+
+ + +
+ Login with Twitch +

Logging in via Twitch gives you the ability to use + emotes in your Twitch chat. +

+
+ +
+
+
+ + + \ No newline at end of file diff --git a/account/login/twitch.php b/account/login/twitch.php new file mode 100644 index 0000000..38fd6cc --- /dev/null +++ b/account/login/twitch.php @@ -0,0 +1,175 @@ +prepare("SELECT c.id, + CASE WHEN ( + SELECT u.password FROM users u WHERE u.id = c.user_id + ) IS NOT NULL + THEN 1 ELSE 0 + END AS set_password + FROM connections c + WHERE c.user_id = ? + "); + $stmt->execute([$_SESSION["user_id"]]); + + if ($row = $stmt->fetch()) { + if ($row["set_password"]) { + $db->prepare("DELETE FROM connections WHERE user_id = ? AND platform = 'twitch'")->execute([$_SESSION["user_id"]]); + generate_alert("/account", "Successfully disconnected from Twitch!", 200); + } else { + generate_alert("/account", "You must set a password before deleting any connections", 403); + } + } else { + generate_alert("/account", "No Twitch connection found", 404); + } + exit; +} + +$client_id = TWITCH_CLIENT_ID; +$client_secret = TWITCH_SECRET_KEY; +$redirect_uri = TWITCH_REDIRECT_URI; + +if (isset($_GET["error"])) { + header("Location: /account/login"); + exit; +} + +if (!isset($_GET["code"])) { + header("Location: https://id.twitch.tv/oauth2/authorize?client_id=$client_id&redirect_uri=$redirect_uri&response_type=code"); + exit; +} + +$code = $_GET["code"]; + +// obtaining twitch token +$request = curl_init(); +curl_setopt($request, CURLOPT_URL, "https://id.twitch.tv/oauth2/token"); +curl_setopt($request, CURLOPT_POST, 1); +curl_setopt( + $request, + CURLOPT_POSTFIELDS, + "client_id=$client_id&client_secret=$client_secret&code=$code&grant_type=authorization_code&redirect_uri=$redirect_uri" +); +curl_setopt($request, CURLOPT_RETURNTRANSFER, true); + +$response = curl_exec($request); +curl_close($request); + +$response = json_decode($response, true); + +if (array_key_exists("status", $response)) { + header("Location: /account/login"); + exit; +} + +// identifying user +$request = curl_init(); +curl_setopt($request, CURLOPT_URL, "https://api.twitch.tv/helix/users"); +curl_setopt($request, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $response["access_token"], + "Client-Id: $client_id" +]); +curl_setopt($request, CURLOPT_RETURNTRANSFER, true); + +$twitch_user = curl_exec($request); +curl_close($request); + +$twitch_user = json_decode($twitch_user, true); + +if (empty($twitch_user["data"])) { + generate_alert("/account", "Failed to identify Twitch user", 500); + exit; +} + +$twitch_user = $twitch_user["data"][0]; + +// saving it +$twitch_access_token = $response["access_token"]; +$twitch_refresh_token = $response["refresh_token"]; +$twitch_expires_on = time() + intval($response["expires_in"]); + +// creating user if not exists +$stmt = $db->prepare("SELECT * FROM users u + INNER JOIN connections c ON c.alias_id = ? + WHERE c.user_id = u.id AND c.platform = 'twitch' +"); +$stmt->execute([$twitch_user["id"]]); + +$user_id = ""; +$user_secret_key = ""; +$user_name = ""; + +if ($row = $stmt->fetch()) { + if (isset($_SESSION["user_id"]) && $_SESSION["user_id"] != $row["id"]) { + generate_alert("/account", "There is another " . INSTANCE_NAME . " account associated with that Twitch account", 409); + exit; + } + + $user_name = $row["username"]; + $user_secret_key = $row["secret_key"]; + $user_id = $row["id"]; +} else { + $user_secret_key = generate_random_string(32); + $user_name = $twitch_user["login"]; + $user_id = bin2hex(random_bytes(16)); + + list($user_secret_key, $user_name, $user_id) = match (isset($_SESSION["user_id"])) { + true => [$_COOKIE["secret_key"], $_SESSION["user_name"], $_SESSION["user_id"]], + default => [generate_random_string(32), $twitch_user["login"], bin2hex(random_bytes(16))] + }; + + if (!isset($_SESSION["user_id"])) { + // checking for duplicates + $stmt = $db->prepare("SELECT COUNT(*) FROM users WHERE username = ?"); + $stmt->execute([$user_name]); + $duplicates = intval($stmt->fetch()[0]); + if ($duplicates > 0) { + $i = 1; + while (true) { + $stmt = $db->prepare("SELECT COUNT(*) FROM users WHERE username = ?"); + $stmt->execute(["$user_name$i"]); + + if ($stmt->fetch()[0] == 0) { + break; + } + + $i++; + } + $user_name .= $i; + } + + $stmt = $db->prepare("INSERT INTO users(id, username, secret_key) VALUES (?, ?, ?)"); + if (!$stmt->execute([$user_id, $user_name, $user_secret_key])) { + $db = null; + echo "Failed to create a user"; + exit; + } + } + + $stmt = $db->prepare("INSERT INTO connections(user_id, alias_id, platform, data) VALUES (?, ?, 'twitch', ?)"); + $stmt->execute([ + $user_id, + $twitch_user["id"], + sprintf("%s:%s:%s", $twitch_access_token, $twitch_refresh_token, $twitch_expires_on) + ]); +} + +$_SESSION["user_id"] = $user_id; +$_SESSION["user_name"] = $user_name; +setcookie("secret_key", $user_secret_key, time() + ACCOUNT_COOKIE_MAX_LIFETIME, "/"); + +$db = null; + +header("Location: /account"); \ No newline at end of file diff --git a/account/register.php b/account/register.php new file mode 100644 index 0000000..1da89a0 --- /dev/null +++ b/account/register.php @@ -0,0 +1,111 @@ + $username_length || $username_length > ACCOUNT_USERNAME_LENGTH[1]) { + generate_alert("/account/register.php", sprintf("Username must be between %d-%d characters long", ACCOUNT_USERNAME_LENGTH[0], ACCOUNT_USERNAME_LENGTH[1])); + exit; + } + + if (!preg_match(ACCOUNT_USERNAME_REGEX, $username)) { + generate_alert("/account/register.php", "Bad username"); + exit; + } + + $password = $_POST["password"]; + if (ACCOUNT_PASSWORD_MIN_LENGTH > strlen($password)) { + generate_alert("/account/register.php", "Password must be at least " . ACCOUNT_PASSWORD_MIN_LENGTH . " characters"); + exit; + } + + $db = new PDO(DB_URL, DB_USER, DB_PASS); + + $stmt = $db->prepare("SELECT id FROM users WHERE username = ?"); + $stmt->execute([$username]); + + if ($stmt->rowCount() != 0) { + generate_alert("/account/register.php", "The username has already been taken"); + exit; + } + + $secret_key = generate_random_string(ACCOUNT_SECRET_KEY_LENGTH); + $password = password_hash($password, PASSWORD_DEFAULT); + + $id = bin2hex(random_bytes(16)); + + $stmt = $db->prepare("INSERT INTO users(id, username, password, secret_key) VALUES (?, ?, ?, ?)"); + $stmt->execute([$id, $username, $password, $secret_key]); + + setcookie("secret_key", $secret_key, time() + ACCOUNT_COOKIE_MAX_LIFETIME, "/"); + header("Location: /account"); + exit; +} +?> + + + + + Register an account - <?php echo INSTANCE_NAME ?> + + + + + +
+
+ + +
+ +
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+

+ Since doesn't require email and password reset via email is + not supported, please remember your passwords! +

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/account/security.php b/account/security.php new file mode 100644 index 0000000..5545b60 --- /dev/null +++ b/account/security.php @@ -0,0 +1,52 @@ +prepare("SELECT * FROM users WHERE id = ?"); +$stmt->execute([$_SESSION["user_id"]]); + +$user = $stmt->fetch(); +$current_password = $_POST["password-current"] ?? ""; + +if ($user["password"] != null && !password_verify($current_password, $user["password"])) { + generate_alert("/account", "Password is required to apply changes in 'Security' section"); + exit; +} + +if (!empty($_POST["password-new"])) { + $password = $_POST["password-new"]; + if (ACCOUNT_PASSWORD_MIN_LENGTH > strlen($password)) { + generate_alert("/account", "Your password must be at least " . ACCOUNT_PASSWORD_MIN_LENGTH . " characters"); + exit; + } + + $db->prepare("UPDATE users SET password = ? WHERE id = ?") + ->execute([password_hash($password, PASSWORD_DEFAULT), $user["id"]]); +} + +$private_profile = (int) (intval($_POST["make-private"] ?? "0") == 1); + +$db->prepare("UPDATE user_preferences SET private_profile = ? WHERE id = ?") + ->execute([$private_profile, $user["id"]]); + +if (intval($_POST["signout-everywhere"] ?? "0") == 1) { + $db->prepare("UPDATE users SET secret_key = ? WHERE id = ?") + ->execute([generate_random_string(ACCOUNT_SECRET_KEY_LENGTH), $_SESSION["user_id"]]); + + session_unset(); + session_destroy(); + + setcookie("secret_key", "", time() - 1000); +} + +generate_alert("/account", "Your changes have been applied!", 200); \ No newline at end of file diff --git a/account/signout.php b/account/signout.php new file mode 100644 index 0000000..f971d4a --- /dev/null +++ b/account/signout.php @@ -0,0 +1,16 @@ +