diff --git a/includes/DatabaseHandle.class.php b/includes/DatabaseHandle.class.php index 53417c5..a25d7b8 100644 --- a/includes/DatabaseHandle.class.php +++ b/includes/DatabaseHandle.class.php @@ -25,12 +25,4 @@ class DatabaseHandle { return $stmt; } - - public static function get() { - if (DatabaseHandle::$instance === null) { - DatabaseHandle::$instance = new DatabaseHandle(); - } - - return DatabaseHandle::$instance; - } } diff --git a/includes/User.class.php b/includes/User.class.php index 84f7c28..e44464c 100644 --- a/includes/User.class.php +++ b/includes/User.class.php @@ -6,7 +6,7 @@ class User { public string $username; private function __construct(array $row) { - $this->user_id = intval($row['user_id']); + $this->user_id = intval($row['id']); $this->username = $row['username']; } diff --git a/includes/functions.php b/includes/functions.php index 870abf4..532a34d 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -140,7 +140,7 @@ function recentupdate($conn, $count) { function monthpop($conn, $count) { $query = $conn->prepare( - "SELECT pastes.id AS id, views, title, created_at, visible, tagsys, users.username AS member + "SELECT pastes.id AS id, views, title, created_at, updated_at, visible, tagsys, users.username AS member FROM pastes INNER JOIN users ON users.id = pastes.user_id WHERE MONTH(created_at) = MONTH(NOW()) AND visible = '0' ORDER BY views DESC LIMIT ?"); @@ -178,7 +178,7 @@ function decrypt(string $value) : string { function getRecent($conn, $count) { $query = $conn->prepare(" - SELECT pastes.id, visible, title, created_at, users.username AS member, tagsys + SELECT pastes.id, visible, title, created_at, updated_at, users.username AS member, tagsys FROM pastes INNER JOIN users ON pastes.user_id = users.id WHERE visible = '0' @@ -201,7 +201,7 @@ function getRecentadmin($conn, $count = 5) { function getpopular(DatabaseHandle $conn, int $count) : array { $query = $conn->prepare(" - SELECT pastes.id AS id, visible, title, pastes.created_at AS created_at, views, users.username AS member, tagsys + SELECT pastes.id AS id, visible, title, pastes.created_at AS created_at, updated_at, views, users.username AS member, tagsys FROM pastes INNER JOIN users ON users.id = pastes.user_id WHERE visible = '0' ORDER BY views DESC @@ -213,7 +213,7 @@ function getpopular(DatabaseHandle $conn, int $count) : array { function getrandom(DatabaseHandle $conn, int $count) : array { $query = $conn->prepare(" - SELECT pastes.id, visible, title, created_at, views, users.username AS member, tagsys + SELECT pastes.id, visible, title, created_at, updated_at, views, users.username AS member, tagsys FROM pastes INNER JOIN users ON users.id = pastes.user_id WHERE visible = '0' @@ -231,11 +231,11 @@ function getUserPastes(DatabaseHandle $conn, int $user_id) : array { return $query->fetchAll(); } -function getTotalPastes(DatabaseHandle $conn, string $username) : int { +function getTotalPastes(DatabaseHandle $conn, int $user_id) : int { $query = $conn->prepare("SELECT COUNT(*) AS total_pastes FROM pastes INNER JOIN users ON users.id = pastes.user_id - WHERE users.username = ?"); - $query->execute([$username]); + WHERE users.id = ?"); + $query->execute([$user_id]); return intval($query->fetch(PDO::FETCH_NUM)[0]); } diff --git a/includes/passwords.php b/includes/passwords.php index 7c2b962..1d6e44d 100644 --- a/includes/passwords.php +++ b/includes/passwords.php @@ -22,3 +22,24 @@ function pp_password_verify(string $password, string $hash, bool &$needs_rehash return password_verify($password, $hash); } + +function pp_random_bytes(int $length) : string { + try { + return random_bytes($length); + } catch (Exception) { + /* Out of entropy! */ + die('Error generating random bytes - this should never be seen!'); + } +} + +function pp_random_token() : string { + return hash('SHA512', pp_random_bytes(64)); +} + +function pp_random_password() : string { + /* MD-5 is OK to use here because it is not being used to protect secure data, + * but rather to reduce the size of the string a little bit into something that + * can reasonably be handled by a user. + */ + return hash('MD5', pp_random_bytes(64)); +} \ No newline at end of file diff --git a/login.php b/login.php index 624d853..8787db2 100644 --- a/login.php +++ b/login.php @@ -41,14 +41,14 @@ if (isset($_POST['forgot'])) { $username = trim($_POST['username']); $recovery_code = trim($_POST['recovery_code']); - $query = $conn->prepare("SELECT id, recovery_code_hash FROM users WHERE username = ?"); - $query->execute([$username]); + $query = $conn->query("SELECT id, recovery_code_hash FROM users WHERE username = ?", [$username]); $row = $query->fetch(); + if ($row && pp_password_verify($_POST['recovery_code'], $row['recovery_code_hash'])) { - $new_password = md5(random_bytes(64)); + $new_password = pp_random_password(); $new_password_hash = pp_password_hash($new_password); - $recovery_code = hash('SHA512', random_bytes(64)); + $recovery_code = pp_random_token(); $new_recovery_code_hash = pp_password_hash($recovery_code); $conn->prepare('UPDATE users SET password = ?, recovery_code_hash = ? WHERE id = ?') @@ -63,21 +63,24 @@ if (isset($_POST['forgot'])) { } } else if (isset($_POST['signin'])) { // Login process if (!empty($_POST['username']) && !empty($_POST['password'])) { + $remember_me = (bool) $_POST['remember_me']; $username = trim($_POST['username']); - $query = $conn->prepare("SELECT id, password, banned FROM users WHERE username = ?"); - $query->execute([$username]); - $row = $query->fetch(); + $row = $conn->query("SELECT id, password, banned FROM users WHERE username = ?", [$username]) + ->fetch(); + $needs_rehash = false; - if ($row && pp_password_verify($_POST['password'], $row['password'], $needs_rehash)) { - // Username found - $db_ip = $row['ip']; - $db_id = $row['id']; + + /* This is designed to be a constant time lookup, hence the warning suppression operator so that + * we always call pp_password_verify, even if row is null. + */ + if (pp_password_verify($_POST['password'], @$row['password'], $needs_rehash)) { + $user_id = $row['id']; if ($needs_rehash) { $new_password_hash = pp_password_hash($_POST['password']); - $conn->prepare('UPDATE users SET password = ? WHERE id = ?') - ->execute([$new_password_hash, $row['id']]); + $conn->query('UPDATE users SET password = ? WHERE id = ?', + [$new_password_hash, $user_id]); } if ($row['banned']) { @@ -85,7 +88,19 @@ if (isset($_POST['forgot'])) { $error = $lang['banned']; } else { // Login successful - $_SESSION['user_id'] = $row['id']; + $_SESSION['user_id'] = (string) $user_id; + + if ($remember_me) { + $remember_token = pp_random_token(); + + $conn->query('INSERT INTO user_sessions (user_id, token) VALUES (?, ?)', [$user_id, $remember_token]); + + setcookie(User::REMEMBER_TOKEN_COOKIE, $remember_token, [ + 'secure' => !empty($_SERVER['HTTPS']), /* Local dev environment is non-HTTPS */ + 'httponly' => true, + 'samesite' => 'Lax' + ]); + } header('Location: ' . $_SERVER['HTTP_REFERER']); exit(); @@ -109,13 +124,12 @@ if (isset($_POST['forgot'])) { } elseif (!isValidUsername($username)) { $error = $lang['usrinvalid']; // "Username not valid. Usernames can't contain special characters."; } else { - $query = $conn->prepare('SELECT 1 FROM users WHERE username = ?'); - $query->execute([$username]); + $query = $conn->query('SELECT 1 FROM users WHERE username = ?', [$username]); if ($query->fetch()) { $error = $lang['userexists']; // "Username already taken."; } else { - $recovery_code = hash('SHA512', random_bytes('64')); + $recovery_code = pp_random_token(); $recovery_code_hash = pp_password_hash($recovery_code); $query = $conn->prepare( "INSERT INTO users (username, password, recovery_code_hash, picture, date, ip, badge) VALUES (?, ?, ?, 'NONE', ?, ?, '0')" diff --git a/profile.php b/profile.php index 5e077e9..6f8e116 100644 --- a/profile.php +++ b/profile.php @@ -16,6 +16,7 @@ define('IN_PONEPASTE', 1); require_once('includes/common.php'); require_once('includes/functions.php'); +require_once('includes/passwords.php'); // UTF-8 header('Content-Type: text/html; charset=utf-8'); @@ -35,11 +36,9 @@ if ($current_user === null) { $user_username = $current_user->username; -$query = $conn->prepare('SELECT * FROM users WHERE username = ?'); -$query->execute([$user_username]); +$query = $conn->query('SELECT * FROM users WHERE id = ?', [$current_user->user_id]); $row = $query->fetch(); $user_id = $row['id']; -$user_full_name = $row['full_name']; $user_platform = Trim($row['platform']); $user_date = $row['date']; $user_ip = $row['ip']; @@ -52,8 +51,8 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { if (pp_password_verify($user_old_pass, $user_password)) { $user_new_cpass = pp_password_hash($_POST['password']); - $conn->prepare('UPDATE users SET full_name = ?, password = ? WHERE username = ?') - ->execute([$user_new_full, $user_new_cpass, $user_username]); + $conn->prepare('UPDATE users SET password = ? WHERE id = ?') + ->execute([$user_new_cpass, $user_id]); $success = $lang['profileupdated']; //" Your profile information is updated "; } else { @@ -66,7 +65,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { updatePageViews($conn); -$total_user_pastes = getTotalPastes($conn, $user_username); +$total_user_pastes = getTotalPastes($conn, $current_user->user_id); // Theme require_once('theme/' . $default_theme . '/header.php'); diff --git a/theme/bulma/header.php b/theme/bulma/header.php index 05802ae..9c6652f 100644 --- a/theme/bulma/header.php +++ b/theme/bulma/header.php @@ -222,7 +222,7 @@ $start = $time;