summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorilotterytea <iltsu@alright.party>2025-04-20 10:46:32 +0500
committerilotterytea <iltsu@alright.party>2025-04-20 10:46:32 +0500
commit3b6c6e5774dec41a16da03d1bb8497b448cfa564 (patch)
tree4e9c79624c43c92dbcc288a61f5b92b1772d3c5c
parent43e46d21c263fe8a8672e8e4b3ce38803b9cd089 (diff)
feat: users, account management, authentication system
-rw-r--r--database.sql18
-rw-r--r--public/account/delete.php39
-rw-r--r--public/account/index.php59
-rw-r--r--public/account/login/index.php38
-rw-r--r--public/account/login/twitch.php146
-rw-r--r--public/account/signout.php25
-rw-r--r--public/static/style.css49
-rw-r--r--src/accounts.php29
8 files changed, 403 insertions, 0 deletions
diff --git a/database.sql b/database.sql
index 644345e..ccd4c7e 100644
--- a/database.sql
+++ b/database.sql
@@ -1,7 +1,25 @@
+CREATE TABLE IF NOT EXISTS "users" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "username" TEXT NOT NULL UNIQUE,
+ "password" TEXT,
+ "secret_key" TEXT NOT NULL,
+ "joined_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS "connections" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "user_id" INTEGER NOT NULL,
+ "alias_id" TEXT NOT NULL,
+ "platform" TEXT NOT NULL,
+ "data" TEXT NOT NULL,
+ "connected_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
CREATE TABLE IF NOT EXISTS "emotes" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"code" TEXT NOT NULL,
"mime" TEXT NOT NULL,
"ext" TEXT NOT NULL,
+ "uploaded_by" INTEGER,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
); \ No newline at end of file
diff --git a/public/account/delete.php b/public/account/delete.php
new file mode 100644
index 0000000..af8a093
--- /dev/null
+++ b/public/account/delete.php
@@ -0,0 +1,39 @@
+<?php
+include "../../src/utils.php";
+
+session_start();
+
+if (!isset($_SESSION["user_id"])) {
+ header("Location: /account");
+ exit;
+}
+
+$id = $_SESSION["user_id"];
+
+$db = new SQLite3("../../database.db");
+
+$stmt = $db->prepare("UPDATE emotes SET uploaded_by = NULL WHERE uploaded_by = :id");
+$stmt->bindValue(":id", $id);
+$stmt->execute();
+
+$stmt = $db->prepare("DELETE FROM connections WHERE user_id = :id");
+$stmt->bindValue(":id", $id);
+$stmt->execute();
+
+$stmt = $db->prepare("DELETE FROM users WHERE id = :id");
+$stmt->bindValue(":id", $id);
+$stmt->execute();
+
+session_unset();
+session_destroy();
+
+setcookie("secret_key", "", time() - 1000);
+
+$db->close();
+
+$path = "../static/userdata/avatars/$id";
+if (is_file($path)) {
+ unlink($path);
+}
+
+header("Location: /account"); \ No newline at end of file
diff --git a/public/account/index.php b/public/account/index.php
new file mode 100644
index 0000000..8f40ec9
--- /dev/null
+++ b/public/account/index.php
@@ -0,0 +1,59 @@
+<?php
+include "../../src/accounts.php";
+authorize_user();
+
+if (!isset($_SESSION["user_id"], $_SESSION["user_name"])) {
+ header("Location: /account/login");
+ exit;
+}
+
+include "../../src/partials.php";
+
+?>
+
+<html>
+
+<head>
+ <title>Account management - alright.party</title>
+ <link rel="stylesheet" href="/static/style.css">
+</head>
+
+<body>
+ <div class="container">
+ <div class="wrapper">
+ <?php html_navigation_bar() ?>
+
+ <section class="content">
+ <section class="box accman">
+ <h1>Account management</h1>
+
+ <form action="/account.php" method="POST" enctype="multipart/form-data">
+ <h2>Profile</h2>
+ <h3>Profile picture</h3>
+ <img src="/static/userdata/avatars/<?php echo $_SESSION["user_id"] ?>" id="pfp" width="64"
+ height="64">
+ <input type="file" name="pfp" id="pfp">
+
+ <h3>Username</h3>
+ <input type="text" name="username" value="<?php echo $_SESSION["user_name"] ?>">
+
+ <button type="submit">Save</button>
+ </form>
+
+ <hr>
+
+ <form action="/account/signout.php">
+ <h2>Security</h2>
+ <button type="submit">Sign out everywhere</button>
+ </form>
+
+ <form action="/account/delete.php">
+ <button class="red" type="submit">Delete me</button>
+ </form>
+ </section>
+ </section>
+ </div>
+ </div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/public/account/login/index.php b/public/account/login/index.php
new file mode 100644
index 0000000..146fde9
--- /dev/null
+++ b/public/account/login/index.php
@@ -0,0 +1,38 @@
+<?php
+include "../../../src/accounts.php";
+// FIXME
+//authorize_user();
+
+include "../../../src/partials.php";
+?>
+
+<html>
+
+<head>
+ <title>Log in to alright.party</title>
+ <link rel="stylesheet" href="/static/style.css">
+</head>
+
+<body>
+ <div class="container">
+ <div class="wrapper">
+ <?php html_navigation_bar(); ?>
+
+ <section class="content">
+ <section class="box" style="width: 400px;">
+ <div class="box navtab">
+ <p>Log in to alright.party</p>
+ </div>
+ <div class="box content">
+ <form action="/account/login/twitch.php" method="GET">
+ <button type="submit" class="purple" style="padding:8px 24px; font-size: 18px;">Login with
+ Twitch</button>
+ </form>
+ </div>
+ </section>
+ </section>
+ </div>
+ </div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/public/account/login/twitch.php b/public/account/login/twitch.php
new file mode 100644
index 0000000..ff2fe51
--- /dev/null
+++ b/public/account/login/twitch.php
@@ -0,0 +1,146 @@
+<?php
+include "../../../src/utils.php";
+
+$client_id = "";
+$client_secret = "";
+$redirect_uri = "http://localhost:8000/account/login/twitch.php";
+
+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
+session_start();
+
+$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"])) {
+ echo "Failed to identify";
+ exit;
+}
+
+$twitch_user = $twitch_user["data"][0];
+
+// saving it
+$_SESSION["twitch_access_token"] = $response["access_token"];
+$_SESSION["twitch_refresh_token"] = $response["refresh_token"];
+$_SESSION["twitch_expires_on"] = time() + intval($response["expires_in"]);
+
+$db = new SQLite3("../../../database.db");
+
+// creating user if not exists
+$stmt = $db->prepare("SELECT id, user_id FROM connections WHERE alias_id = :alias_id AND platform = 'twitch'");
+$stmt->bindValue("alias_id", $twitch_user["id"]);
+
+$results = $stmt->execute();
+
+$user_id = "";
+$user_secret_key = "";
+$user_name = "";
+
+if ($row = $results->fetchArray()) {
+ $id = $row["id"];
+ $user_id = $row["user_id"];
+
+ $stmt = $db->prepare("SELECT * FROM users WHERE id = :id");
+ $stmt->bindValue(":id", $id);
+ $results = $stmt->execute();
+
+ if ($row = $results->fetchArray()) {
+ $user_name = $row["username"];
+ $user_secret_key = $row["secret_key"];
+ $user_id = $row["id"];
+ } else {
+ $db->close();
+ echo "Connection found, but not user?";
+ exit;
+ }
+} else {
+ $user_secret_key = generate_random_string(32);
+ $user_name = $twitch_user["login"];
+
+ $stmt = $db->prepare("INSERT INTO users(username, secret_key) VALUES (:username, :secret_key)");
+ $stmt->bindValue(":username", $user_name);
+ $stmt->bindValue(":secret_key", $user_secret_key);
+ if (!$stmt->execute()) {
+ $db->close();
+ echo "Failed to create a user";
+ exit;
+ }
+
+ $user_id = $db->lastInsertRowID();
+
+ $stmt = $db->prepare("INSERT INTO connections(user_id, alias_id, platform, data) VALUES (:user_id, :alias_id, 'twitch', :data)");
+ $stmt->bindValue(":user_id", $user_id);
+ $stmt->bindValue(":alias_id", $twitch_user["id"]);
+ $stmt->bindValue(
+ ":data",
+ $_SESSION["twitch_access_token"] . ":" . $_SESSION["twitch_refresh_token"] . ":" . $_SESSION["twitch_expires_on"]
+ );
+ $stmt->execute();
+}
+
+$_SESSION["user_id"] = $user_id;
+$_SESSION["user_name"] = $user_name;
+setcookie("secret_key", $user_secret_key, time() + 86400 * 30, "/");
+
+$db->close();
+
+// downloading profile picture
+$path = "../../static/userdata/avatars";
+
+if (!is_dir($path)) {
+ mkdir($path, 0777, true);
+}
+
+$fp = fopen("$path/$user_id", "wb");
+$request = curl_init();
+curl_setopt($request, CURLOPT_URL, $twitch_user["profile_image_url"]);
+curl_setopt($request, CURLOPT_FILE, $fp);
+curl_setopt($request, CURLOPT_HEADER, 0);
+
+curl_exec($request);
+curl_close($request);
+fclose($fp);
+
+header("Location: /account"); \ No newline at end of file
diff --git a/public/account/signout.php b/public/account/signout.php
new file mode 100644
index 0000000..dd1d0f9
--- /dev/null
+++ b/public/account/signout.php
@@ -0,0 +1,25 @@
+<?php
+include "../../src/utils.php";
+
+session_start();
+
+if (!isset($_SESSION["user_id"])) {
+ header("Location: /account");
+ exit;
+}
+
+$db = new SQLite3("../../database.db");
+
+$stmt = $db->prepare("UPDATE users SET secret_key = :secret_key WHERE id = :id");
+$stmt->bindValue(":id", $_SESSION["user_id"]);
+$stmt->bindValue(":secret_key", generate_random_string(32));
+$stmt->execute();
+
+session_unset();
+session_destroy();
+
+setcookie("secret_key", "", time() - 1000);
+
+$db->close();
+
+header("Location: /account"); \ No newline at end of file
diff --git a/public/static/style.css b/public/static/style.css
index 09e5534..1700079 100644
--- a/public/static/style.css
+++ b/public/static/style.css
@@ -175,6 +175,17 @@ button.green:hover,
background: #85dd8a;
}
+button.purple,
+.button.purple {
+ background: #9a7ad2;
+ border-color: #6d5595;
+}
+
+button.purple:hover,
+.button.purple:hover {
+ background: #ac88ea;
+}
+
/**
----------
LIST
@@ -250,6 +261,11 @@ button.green:hover,
padding: 16px;
}
+.box hr {
+ border-color: var(--border-color);
+ border-width: 1px;
+}
+
.box.emote {
width: 90px;
height: 90px;
@@ -286,4 +302,37 @@ a.box:hover {
align-items: center;
gap: 32px;
margin: 32px 0;
+}
+
+/**
+-------------
+ ACCOUNTS
+-------------
+*/
+
+.accman {
+ flex-grow: 0;
+ width: 400px;
+
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.accman form {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.accman h1 {
+ font-size: 26px;
+}
+
+.accman h2 {
+ font-size: 20px;
+}
+
+.accman h3 {
+ font-size: 16px;
} \ No newline at end of file
diff --git a/src/accounts.php b/src/accounts.php
new file mode 100644
index 0000000..4273964
--- /dev/null
+++ b/src/accounts.php
@@ -0,0 +1,29 @@
+<?php
+function authorize_user()
+{
+ session_start();
+
+ if (!isset($_COOKIE["secret_key"])) {
+ if (isset($_SESSION["user_id"])) {
+ session_unset();
+ }
+
+ return;
+ }
+
+ $db = new SQLite3("../../database.db");
+
+ $stmt = $db->prepare("SELECT id, username FROM users WHERE secret_key = :secret_key");
+ $stmt->bindValue("secret_key", $_COOKIE["secret_key"]);
+ $results = $stmt->execute();
+
+ if ($row = $results->fetchArray()) {
+ $_SESSION["user_id"] = $row["id"];
+ $_SESSION["user_name"] = $row["username"];
+ } else {
+ session_regenerate_id();
+ setcookie("secret_key", "", time() - 1000);
+ }
+
+ $db->close();
+} \ No newline at end of file