added login requirement to view articles to protect against potential data scrapping, with redirect functionality to the article the user attempts to view prior to login.

This commit is contained in:
2025-04-12 13:40:51 -06:00
parent f4dfaba1c6
commit 8d78a7d182
11 changed files with 102 additions and 195 deletions
+7 -2
View File
@@ -36,7 +36,6 @@ if (!isset($_GET['article_id']) && !isset($_GET['tag']) && !isset($_GET['author_
</form>
';
echo '
<div class="article_cards_grid">
<div class="article_filters">
@@ -50,7 +49,7 @@ if (!isset($_GET['article_id']) && !isset($_GET['tag']) && !isset($_GET['author_
</div>
</div>
';
} else if (isset($_GET['article_id'])) {
} else if (isset($_COOKIE['user_id']) && isset($_GET['article_id'])) {
increment_read_counter($_GET['article_id']);
echo article_page_from_markdown($_GET['article_id']);
} else if (isset($_GET['author_id'])) {
@@ -63,6 +62,12 @@ if (!isset($_GET['article_id']) && !isset($_GET['tag']) && !isset($_GET['author_
foreach ($ids as $id)
echo article_card($id);
} else {
$redirect = (isset($_GET['article_id']) ? 'articles.php?article_id=' . $_GET['article_id'] : '');
if ($redirect)
header('Location: user.php?action=login&redirect=' . urlencode($redirect));
else
header('Location: articles.php');
}
include_once 'components/foot.php';
+6 -11
View File
@@ -4,18 +4,13 @@
<head>
<link href="css/base.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="https://nerdfonts.com/assets/css/webfont.css">
<link rel="icon" type="image/x-icon" href="data/vintagecoding-favicon.png">
<link rel="icon" type="image/x-icon" href="data/vintagecoding-favicon.png" </link>
<?php
if ($_SESSION['theme'] == 'dark') {
echo '
<link href="css/dark.css" rel="stylesheet" type="text/css">
';
} elseif ($_SESSION['theme'] == 'light') {
echo '
<link href="css/light.css" rel="stylesheet" type="text/css">
';
}
?>
if (!isset($_SESSION['theme']) || $_SESSION['theme'] == 'dark')
echo '<link href="css/dark.css" rel="stylesheet" type="text/css">';
else
echo '<link href="css/light.css" rel="stylesheet" type="text/css">';
?>
</head>
+1 -1
View File
@@ -41,7 +41,7 @@ else
$theme_toggle = 'dark';
$nav .= '
<button id="theme-toggle" value="' . $theme_toggle . '" name="theme" onchange="this.form.submit()"><i class="nf ' . ($theme_toggle == 'dark' ? 'nf-oct-moon' : 'nf-oct-sun') . '"></i></button>
<button id="theme_toggle" value="' . $theme_toggle . '" name="theme"><i class="nf ' . ($theme_toggle == 'dark' ? 'nf-oct-moon' : 'nf-oct-sun') . '"></i></button>
</form>
';
+1 -1
View File
@@ -305,7 +305,7 @@ i {
transition-duration: 500ms;
}
#theme-toggle {
#theme_toggle {
background: none;
border: none;
}
+1 -1
View File
@@ -63,7 +63,7 @@ table tr {
color: white;
}
#theme-toggle {
#theme_toggle {
color: white;
}
+1 -1
View File
@@ -63,7 +63,7 @@ table tr {
color: black;
}
#theme-toggle {
#theme_toggle {
color: black;
}
+28 -82
View File
@@ -6,17 +6,15 @@ use Postmark\PostmarkClient;
function user_id_exists($user_id)
{
$conn = get_connection();
$stmt = $conn->prepare('SELECT user_id FROM user WHERE user_id = ?');
$stmt->bind_param('i', $user_id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
$result = exec_stmt('SELECT user_id FROM user WHERE user_id = ?', 'i', $user_id)->fetch_assoc();
if ($result == null)
return false;
return true;
}
function username_exists($username) {}
function cookies($user, $time)
{
setcookie('user_id', $user['user_id'], $time);
@@ -72,11 +70,7 @@ function login($username, $password, $stay_logged_in)
{
$password_hash = hash('sha256', $password);
$conn = get_connection();
$stmt = $conn->prepare('SELECT user_id, username, email, role_id, profile_picture FROM user WHERE username = ? OR email = ? AND password_hash = ?');
$stmt->bind_param('sss', $username, $username, $password_hash);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
$user = exec_stmt('SELECT user_id, username, email, role_id, profile_picture FROM user WHERE username = ? OR email = ? AND password_hash = ?', 'sss', $username, $username, $password_hash)->fetch_assoc();
if ($user == null) {
$error_msg = '
@@ -97,9 +91,7 @@ function login($username, $password, $stay_logged_in)
// echo strlen($token);
setcookie('remember_user', $token, time() + 60 * 60 * 24 * 30, '/', '', true, true);
$stmt = $conn->prepare('INSERT INTO remember_user (token, user_id, remote_addr, http_forward) VALUES (?, ?, ?, ?)');
$stmt->bind_param('siss', $token, $user['user_id'], hash('sha256', $_SERVER['REMOTE_ADDR']), (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? hash('sha256', $_SERVER['HTTP_X_FORWARDED_FOR']) : null));
$stmt->execute();
exec_stmt('INSERT INTO remember_user (token, user_id, remote_addr, http_forward) VALUES (?, ?, ?, ?)', 'siss', $token, $user['user_id'], hash('sha256', $_SERVER['REMOTE_ADDR']), (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? hash('sha256', $_SERVER['HTTP_X_FORWARDED_FOR']) : null));
}
return true;
@@ -177,6 +169,14 @@ function create_user($username, $email, $password, $verify_password, $role_id, $
return $error_msg;
}
if (username_exists($username)) {
$error_msg = '
<div class="error">
Username is taken!
</div>
';
}
$password_hash = hash('sha256', $password);
$id = rand(1000, 999999999);
@@ -188,7 +188,7 @@ function create_user($username, $email, $password, $verify_password, $role_id, $
$orig_width = $orig_size[0];
$orig_height = $orig_size[1];
$crop_size = min($orig_width, $orig_height) * .56;
$crop_size = min($orig_width, $orig_height) * 0.56;
crop_image($upload['profile_picture']['tmp_name'], $x, $y, $crop_size, $crop_size);
$target = upload($upload['profile_picture'], $_ENV['PROFILE_IMAGES_FQ_PATH'], $id);
@@ -196,11 +196,7 @@ function create_user($username, $email, $password, $verify_password, $role_id, $
$target = 'default-profile.png';
}
$conn = get_connection();
$stmt = $conn->prepare('INSERT INTO user (user_id, username, email, password_hash, role_id, profile_picture) VALUES (?, ?, ?, ?, ?, ?)');
$stmt->bind_param('isssis', $id, $username, $email, $password_hash, $role_id, $target);
$stmt->execute();
exec_stmt('INSERT INTO user (user_id, username, email, password_hash, role_id, profile_picture) VALUES (?, ?, ?, ?, ?, ?)', 'isssis', $id, $username, $email, $password_hash, $role_id, $target);
return true;
}
@@ -263,10 +259,7 @@ function reset_password($user_id, $new_password, $verify_new_password)
}
$password_hash = hash('sha256', $new_password);
$conn = get_connection();
$stmt = $conn->prepare('UPDATE user SET password_hash = ? WHERE user_id = ?');
$stmt->bind_param('si', $password_hash, $user_id);
$stmt->execute();
exec_stmt('UPDATE user SET password_hash = ? WHERE user_id = ?', 'si', $password_hash, $user_id);
}
function update_email_form()
@@ -305,10 +298,7 @@ function update_email($user_id, $new_email, $verify_new_email)
return $error_msg;
}
$conn = get_connection();
$stmt = $conn->prepare('UPDATE user SET email = ? WHERE user_id = ?');
$stmt->bind_param('si', $new_email, $user_id);
$stmt->execute();
exec_stmt('UPDATE user SET email = ? WHERE user_id = ?', 'si', $new_email, $user_id);
}
function update_username_form()
@@ -347,25 +337,10 @@ function update_username($user_id, $new_username, $verify_new_username)
return $error_msg;
}
$conn = get_connection();
$stmt = $conn->prepare('SELECT username FROM user WHERE username = ?');
$stmt->bind_param('s', $new_username);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
if ($result = username_exists($new_username))
return $result;
if ($result != null) {
$error_msg = '
<div class="error">
Usernames is taken!
</div>
';
return $error_msg;
}
$stmt = $conn->prepare('UPDATE user SET username = ? WHERE user_id = ?');
$stmt->bind_param('si', $new_username, $user_id);
$stmt->execute();
exec_stmt('UPDATE user SET username = ? WHERE user_id = ?', 'si', $new_username, $user_id);
}
function update_profile_picture_form()
@@ -438,16 +413,8 @@ function request_role_change_form()
function request_role_change($user_id, $new_role_id)
{
$conn = get_connection();
$stmt = $conn->prepare('SELECT user_id, username, email, roles.role, created_at FROM user INNER JOIN roles WHERE user.role_id = roles.role_id AND user_id = ?');
$stmt->bind_param('i', $user_id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
$stmt = $conn->prepare('SELECT role FROM roles WHERE role_id = ?');
$stmt->bind_param('i', $new_role_id);
$stmt->execute();
$new_role = $stmt->get_result()->fetch_array();
$user = exec_stmt('SELECT user_id, username, email, roles.role, created_at FROM user INNER JOIN roles WHERE user.role_id = roles.role_id AND user_id = ?', 'i', $user_id)->fetch_assoc();
$new_role = exec_stmt('SELECT role FROM roles WHERE role_id = ?', 'i', $new_role_id)->fetch_array();
$email_html = '
<h1>User Role Change Request</h1>
@@ -461,7 +428,7 @@ function request_role_change($user_id, $new_role_id)
$client = new PostmarkClient($_ENV['POSTMARK_API_TOKEN']);
$send_result = $client->sendEmail(
$client->sendEmail(
'mailer@joshashton.dev',
'me@joshashton.dev',
'User Role Change Request - ' . $user['user_id'],
@@ -515,15 +482,9 @@ function delete_account($user_id, $password = null, $verify_password = null)
return $error_msg;
}
$conn = get_connection();
if ($password && $verify_password) {
$password_hash = hash('sha256', $password);
$stmt = $conn->prepare('SELECT user_id, profile_picture FROM user WHERE user_id = ? AND password_hash = ?');
$stmt->bind_param('is', $user_id, $password_hash);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
$user = exec_stmt('SELECT user_id, profile_picture FROM user WHERE user_id = ? AND password_hash = ?', 'is', $user_id, $password_hash)->fetch_assoc();
if ($user == null) {
$error_msg = '
@@ -539,16 +500,10 @@ function delete_account($user_id, $password = null, $verify_password = null)
if (!empty($user['profile_picture']) && $user['profile_picture'] != '2025.jpg')
delete_file($_ENV['PROFILE_IMAGES_FQ_PATH'] . $user['profile_picture']);
$stmt = 'DELETE FROM user WHERE user_id = ' . $user_id . ';';
$stmt = $conn->prepare('DELETE FROM user WHERE user_id = ?');
$stmt->bind_param('i', $user_id);
$stmt->execute();
exec_stmt('DELETE FROM user WHERE user_id = ?', 'i', $user_id);
logout();
} else if ($_COOKIE['role_id'] <= 2) {
$stmt = $conn->prepare('SELECT user_id, profile_picture FROM user WHERE user_id = ?');
$stmt->bind_param('i', $user_id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
$user = exec_stmt('SELECT user_id, profile_picture FROM user WHERE user_id = ?', 'i', $user_id)->fetch_assoc();
if ($user['user_id'] == 2025) {
$error_msg = '
@@ -564,21 +519,13 @@ function delete_account($user_id, $password = null, $verify_password = null)
if (!empty($user['profile_picture']) && $user['profile_picture'] != '2025.jpg')
delete_file($_ENV['PROFILE_IMAGES_FQ_PATH'] . $user['profile_picture']);
$stmt = 'DELETE FROM user WHERE user_id = ' . $user_id . ';';
$stmt = $conn->prepare('DELETE FROM user WHERE user_id = ?');
$stmt->bind_param('i', $user_id);
$stmt->execute();
exec_stmt('DELETE FROM user WHERE user_id = ?', 'i', $user_id);
}
}
function user_view($user_id)
{
$conn = get_connection();
$stmt = $conn->prepare('SELECT user_id, username, email, role_id, created_at, last_active, is_active, profile_picture FROM user WHERE user_id = ?');
$stmt->bind_param('i', $user_id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
$user = exec_stmt('SELECT user_id, username, email, role_id, created_at, last_active, is_active, profile_picture FROM user WHERE user_id = ?', 'i', $user_id)->fetch_assoc();
if ($user == null)
header('Location: articles.php');
@@ -593,7 +540,6 @@ function user_view($user_id)
<div class="line"></div>
';
// TODO: Allow user to update profile picture.
// <button id="update_profile_picture_form_button" class="modal_button underline">Update Profile Picture</button>
// <div id="update_profile_picture_form" class="modal_form">' . update_profile_picture_form() . '</div>
+21 -74
View File
@@ -5,7 +5,6 @@ function article_ids_by_newest()
{
$conn = get_connection();
$results = $conn->query('SELECT article_id FROM article ORDER BY published_at DESC');
$ids = [];
while ($row = $results->fetch_assoc())
@@ -18,7 +17,6 @@ function article_ids_by_oldest()
{
$conn = get_connection();
$results = $conn->query('SELECT article_id FROM article ORDER BY published_at ASC');
$ids = [];
while ($row = $results->fetch_assoc())
@@ -29,12 +27,7 @@ function article_ids_by_oldest()
function articles_ids_by_author($author_id)
{
$conn = get_connection();
$stmt = $conn->prepare('SELECT article_id FROM article WHERE author_id = ? ORDER BY published_at DESC');
$stmt->bind_param('i', $author_id);
$stmt->execute();
$results = $stmt->get_result();
$results = exec_stmt('SELECT article_id FROM article WHERE author_id = ? ORDER BY published_at DESC', 'i', $author_id);
$ids = [];
while ($row = $results->fetch_assoc())
@@ -57,12 +50,7 @@ function article_tags()
function article_ids_by_tag($tag)
{
$conn = get_connection();
$stmt = $conn->prepare('SELECT article_id FROM article WHERE article.article_id IN ( SELECT article_tags.article_id FROM article_tags INNER JOIN tags ON article_tags.tag_id = tags.tag_id WHERE tag = ?) ORDER BY published_at DESC');
$stmt->bind_param('s', $tag);
$stmt->execute();
$results = $stmt->get_result();
$results = exec_stmt('SELECT article_id FROM article WHERE article.article_id IN ( SELECT article_tags.article_id FROM article_tags INNER JOIN tags ON article_tags.tag_id = tags.tag_id WHERE tag = ?) ORDER BY published_at DESC', 's', $tag);
$ids = [];
while ($row = $results->fetch_assoc())
@@ -76,17 +64,8 @@ function article_ids_by_tag($tag)
*/
function article_page_from_markdown($article_id)
{
$conn = get_connection();
$stmt = $conn->prepare('SELECT * FROM article WHERE article_id = ?');
$stmt->bind_param('i', $article_id);
$stmt->execute();
$article = $stmt->get_result()->fetch_assoc();
$stmt = $conn->prepare('SELECT user_id, username, profile_picture FROM user WHERE user_id = ?');
$stmt->bind_param('i', $article['author_id']);
$stmt->execute();
$author = $stmt->get_result()->fetch_assoc();
$article = exec_stmt('SELECT * FROM article WHERE article_id = ?', 'i', $article_id)->fetch_assoc();
$author = exec_stmt('SELECT user_id, username, profile_picture FROM user WHERE user_id = ?', 'i', $article['author_id'])->fetch_assoc();
$markdown = read_file_one_string($_ENV['ARTICLES_FQ_PATH'] . $article_id . '/article.md');
$parsedown = new Parsedown();
@@ -142,16 +121,9 @@ function article_filters()
*/
function article_card($article_id)
{
$conn = get_connection();
$stmt = $conn->prepare('SELECT * FROM article WHERE article_id = ?');
$stmt->bind_param('i', $article_id);
$stmt->execute();
$article = $stmt->get_result()->fetch_assoc();
$article = exec_stmt('SELECT * FROM article WHERE article_id = ?', 'i', $article_id)->fetch_assoc();
$stmt = $conn->prepare('SELECT tag FROM article_tags INNER JOIN tags ON article_tags.tag_id = tags.tag_id WHERE article_id = ?');
$stmt->bind_param('i', $article_id);
$stmt->execute();
$tags_results = $stmt->get_result();
$tags_results = exec_stmt('SELECT tag FROM article_tags INNER JOIN tags ON article_tags.tag_id = tags.tag_id WHERE article_id = ?', 'i', $article_id);
$tags_html = '';
if ($tags_results != null) {
@@ -163,10 +135,7 @@ function article_card($article_id)
$tags_html .= '</div>';
}
$stmt = $conn->prepare('SELECT user_id, username, profile_picture FROM user WHERE user_id = ?');
$stmt->bind_param('i', $article['author_id']);
$stmt->execute();
$author = $stmt->get_result()->fetch_assoc();
$author = exec_stmt('SELECT user_id, username, profile_picture FROM user WHERE user_id = ?', 'i', $article['author_id'])->fetch_assoc();
$card = '
<div class="article_card">
@@ -197,11 +166,7 @@ function article_card($article_id)
function create_article($author_id, $title, $excerpt, $tags, $markdown_file_contents)
{
$conn = get_connection();
$stmt = $conn->prepare('INSERT INTO article (author_id, title, excerpt, published_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)');
$stmt->bind_param('iss', $author_id, $title, $excerpt);
$stmt->execute();
$id = mysqli_insert_id($conn);
$id = exec_stmt('INSERT INTO article (author_id, title, excerpt, published_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)', 'iss', $author_id, $title, $excerpt);
$path = $_ENV['ARTICLES_FQ_PATH'] . $id . '/';
mkdir($path);
@@ -212,16 +177,17 @@ function create_article($author_id, $title, $excerpt, $tags, $markdown_file_cont
if (count($tags) > 0) {
$tag_insert_stmt = 'INSERT INTO article_tags (article_id, tag_id) VALUES ';
$types = '';
for ($i = 0; $i < count($tags); $i++) {
$tag_insert_stmt .= '(' . $id . ', ?)';
if ($i < count($tags) - 1)
$tag_insert_stmt .= ', ';
$types .= 'i';
}
$stmt = $conn->prepare($tag_insert_stmt);
$stmt->bind_param('i', ...$tags);
$stmt->execute();
exec_stmt($tag_insert_stmt, $types, ...$tags);
}
return $id;
@@ -229,64 +195,45 @@ function create_article($author_id, $title, $excerpt, $tags, $markdown_file_cont
function update_article($article_id, $title, $excerpt, $tags, $markdown_file_contents)
{
$conn = get_connection();
$stmt = $conn->prepare('UPDATE article SET title = ?, excerpt = ?, updated_at = CURRENT_TIMESTAMP WHERE article_id = ?');
$stmt->bind_param('ssi', $title, $excerpt, $article_id);
$stmt->execute();
exec_stmt('UPDATE article SET title = ?, excerpt = ?, updated_at = CURRENT_TIMESTAMP WHERE article_id = ?', 'ssi', $title, $excerpt, $article_id);
$path = $_ENV['ARTICLES_FQ_PATH'] . $article_id . '/';
$fs = fopen($path . 'article.md', 'w');
fwrite($fs, $markdown_file_contents);
fclose($fs);
$stmt = $conn->prepare('DELETE from article_tags WHERE article_id = ?');
$stmt->bind_param('i', $article_id);
$stmt->execute();
exec_stmt('DELETE from article_tags WHERE article_id = ?', 'i', $article_id);
if (count($tags) > 0) {
$tag_insert_stmt = 'INSERT INTO article_tags (article_id, tag_id) VALUES ';
$input = '';
$types = '';
for ($i = 0; $i < count($tags); $i++) {
$tag_insert_stmt .= '(' . $article_id . ', ?)';
if ($i < count($tags) - 1)
$tag_insert_stmt .= ', ';
$input .= 'i';
$types .= 'i';
}
$stmt = $conn->prepare($tag_insert_stmt);
$stmt->bind_param($input, ...$tags);
$stmt->execute();
exec_stmt($tag_insert_stmt, $types, ...$tags);
}
}
function delete_article($article_id)
{
$conn = get_connection();
$stmt = $conn->prepare('DELETE FROM article_tags WHERE article_id = ?');
$stmt->bind_param('i', $article_id);
$stmt->execute();
$stmt = $conn->prepare('DELETE FROM article WHERE article_id = ?');
$stmt->bind_param('i', $article_id);
$stmt->execute();
exec_stmt('DELETE FROM article_tags WHERE article_id = ?', 'i', $article_id);
exec_stmt('DELETE FROM article WHERE article_id = ?', 'i', $article_id);
delete_dir($_ENV['ARTICLES_FQ_PATH'] . $article_id . '/');
}
function delete_articles_by_author($author_id)
{
$conn = get_connection();
$stmt = $conn->prepare('DELETE FROM article WHERE author_id = ?');
$stmt->bind_param('i', $author_id);
$stmt->execute();
exec_stmt('DELETE FROM article WHERE author_id = ?', 'i', $author_id);
}
function increment_read_counter($article_id)
{
$conn = get_connection();
$stmt = $conn->prepare('UPDATE article SET read_count = read_count + 1 WHERE article_id = ?');
$stmt->bind_param('i', $article_id);
$stmt->execute();
exec_stmt('UPDATE article SET read_count = read_count + 1 WHERE article_id = ?', 'i', $article_id);
}
+13
View File
@@ -22,6 +22,19 @@ if ($_SERVER['HTTP_HOST'] == 'localhost') {
define('DB', 'vintagecoding');
}
function exec_stmt($stmt, $param_types, ...$args)
{
$conn = get_connection();
$stmt = $conn->prepare($stmt);
$stmt->bind_param($param_types, ...$args);
$stmt->execute();
$result = $stmt->get_result();
if ($result)
return $result;
else
return mysqli_insert_id($conn);
}
function get_connection()
{
$conn = new mysqli(HOST, USER, PASS, DB);
+2 -4
View File
@@ -15,9 +15,7 @@ $dotenv->safeLoad();
if (!isset($_SESSION['initialized'])) {
$_SESSION['initialized'] = true;
$_SESSION['theme'] = 'dark';
}
if ($_SESSION['initialized'] && isset($_POST['theme'])) {
} else if (isset($_POST['theme'])) {
$_SESSION['theme'] = $_POST['theme'];
unset($_POST['theme']);
}
@@ -49,7 +47,7 @@ if (isset($_COOKIE['user_id'])) {
if (hash('sha256', $_SERVER['REMOTE_ADDR']) != $remember['remote_addr'] || (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && hash('sha256', $_SERVER['HTTP_X_FORWARDED_FOR']) != $remember['forwarded_for'])) {
// TODO: Log the attempt to use a cookie from a different browser/device than the cookie was created
echo 'Naughty, you are trying to use someone else\'s cookie...';
echo "Naughty, you are trying to use someone else's cookie...";
} else {
$stmt = $conn->prepare('SELECT user_id, username, email, role_id, profile_picture FROM user WHERE user_id = ?');
$stmt->bind_param('s', $remember['user_id']);
+3
View File
@@ -35,6 +35,9 @@ if (isset($_POST['form_id'])) {
);
if ($msg === true)
if (isset($_GET['redirect']))
header('Location: ' . urldecode($_GET['redirect']));
else
header('Location: .');
break;
case 'reset_pw_form':