diff options
| author | ilotterytea <iltsu@alright.party> | 2025-04-20 10:46:32 +0500 |
|---|---|---|
| committer | ilotterytea <iltsu@alright.party> | 2025-04-20 10:46:32 +0500 |
| commit | 3b6c6e5774dec41a16da03d1bb8497b448cfa564 (patch) | |
| tree | 4e9c79624c43c92dbcc288a61f5b92b1772d3c5c | |
| parent | 43e46d21c263fe8a8672e8e4b3ce38803b9cd089 (diff) | |
feat: users, account management, authentication system
| -rw-r--r-- | database.sql | 18 | ||||
| -rw-r--r-- | public/account/delete.php | 39 | ||||
| -rw-r--r-- | public/account/index.php | 59 | ||||
| -rw-r--r-- | public/account/login/index.php | 38 | ||||
| -rw-r--r-- | public/account/login/twitch.php | 146 | ||||
| -rw-r--r-- | public/account/signout.php | 25 | ||||
| -rw-r--r-- | public/static/style.css | 49 | ||||
| -rw-r--r-- | src/accounts.php | 29 |
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 |
