From 966301c21e6d8e1dc7323158140d32982633cbe3 Mon Sep 17 00:00:00 2001 From: flashwave Date: Wed, 16 May 2018 04:58:21 +0200 Subject: [PATCH] bye bye ORM (successfully this time!) --- composer.json | 1 - composer.lock | 146 +++------ misuzu.php | 40 ++- misuzu_migrate.php | 1 + misuzu_setup.php | 50 ++- public/auth.php | 160 ++++++--- public/index.php | 39 +-- public/manage/users.php | 98 +++++- public/news.php | 111 ++++++- public/profile.php | 50 +-- public/settings.php | 146 +++++++-- routes.php | 23 -- src/Application.php | 80 +++-- src/Database.php | 2 +- src/DatabaseV1.php | 2 +- src/ExceptionHandler.php | 2 +- src/IO/Directory.php | 7 +- src/Model.php | 14 - src/News/NewsCategory.php | 27 -- src/News/NewsPost.php | 41 --- src/Users/LoginAttempt.php | 117 ------- src/Users/Role.php | 114 ------- src/Users/Session.php | 108 ------ src/Users/User.php | 451 -------------------------- src/Users/UserRole.php | 35 -- src/Users/login_attempt.php | 19 ++ src/Users/validation.php | 95 ++++++ tests/UserTest.php | 15 +- utility.php | 36 +- views/manage/master.twig | 4 +- views/manage/users/listing.twig | 2 +- views/manage/users/roles.twig | 4 +- views/manage/users/view.twig | 4 +- views/mio/home/landing.twig | 2 +- views/mio/master.twig | 10 +- views/mio/news/category.twig | 2 +- views/mio/news/index.twig | 22 +- views/mio/news/macros.twig | 10 +- views/mio/news/post.twig | 14 +- views/mio/settings/account.twig | 2 +- views/mio/settings/avatar.twig | 4 +- views/mio/settings/login-history.twig | 8 +- views/mio/settings/sessions.twig | 18 +- views/mio/user/view.twig | 25 +- 44 files changed, 845 insertions(+), 1316 deletions(-) delete mode 100644 routes.php delete mode 100644 src/Model.php delete mode 100644 src/News/NewsCategory.php delete mode 100644 src/News/NewsPost.php delete mode 100644 src/Users/LoginAttempt.php delete mode 100644 src/Users/Role.php delete mode 100644 src/Users/Session.php delete mode 100644 src/Users/User.php delete mode 100644 src/Users/UserRole.php create mode 100644 src/Users/login_attempt.php create mode 100644 src/Users/validation.php diff --git a/composer.json b/composer.json index b6cac299..49f95c70 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,6 @@ "nesbot/carbon": "~1.22", "illuminate/database": "~5.5", "illuminate/filesystem": "~5.5", - "illuminate/pagination": "~5.5", "doctrine/dbal": "~2.6", "swiftmailer/swiftmailer": "~6.0", "erusev/parsedown": "~1.6", diff --git a/composer.lock b/composer.lock index c0ed8957..ff9e73ad 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bfc5b8cbdbf22514c4b51ae1af8c333b", + "content-hash": "640791cee4f0e5a55e8fe6829d68c99b", "packages": [ { "name": "composer/ca-bundle", @@ -697,16 +697,16 @@ }, { "name": "illuminate/container", - "version": "v5.6.17", + "version": "v5.6.22", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a" + "reference": "1a29b314dd5c7a5a5bce3dd7b9da924cc1ec22ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a", - "reference": "4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a", + "url": "https://api.github.com/repos/illuminate/container/zipball/1a29b314dd5c7a5a5bce3dd7b9da924cc1ec22ec", + "reference": "1a29b314dd5c7a5a5bce3dd7b9da924cc1ec22ec", "shasum": "" }, "require": { @@ -737,20 +737,20 @@ ], "description": "The Illuminate Container package.", "homepage": "https://laravel.com", - "time": "2018-01-21T02:13:38+00:00" + "time": "2018-05-14T12:49:42+00:00" }, { "name": "illuminate/contracts", - "version": "v5.6.17", + "version": "v5.6.22", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "322ec80498b3bf85bc4025d028e130a9b50242b9" + "reference": "3dc639feabe0f302f574157a782ede323881a944" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/322ec80498b3bf85bc4025d028e130a9b50242b9", - "reference": "322ec80498b3bf85bc4025d028e130a9b50242b9", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/3dc639feabe0f302f574157a782ede323881a944", + "reference": "3dc639feabe0f302f574157a782ede323881a944", "shasum": "" }, "require": { @@ -781,20 +781,20 @@ ], "description": "The Illuminate Contracts package.", "homepage": "https://laravel.com", - "time": "2018-04-07T17:05:26+00:00" + "time": "2018-05-11T23:38:58+00:00" }, { "name": "illuminate/database", - "version": "v5.6.17", + "version": "v5.6.22", "source": { "type": "git", "url": "https://github.com/illuminate/database.git", - "reference": "a949e082dbb520fdcb2798e0a5408669724aa197" + "reference": "713d376a2e2b9c7cded8916125c24b7b3adb4984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/a949e082dbb520fdcb2798e0a5408669724aa197", - "reference": "a949e082dbb520fdcb2798e0a5408669724aa197", + "url": "https://api.github.com/repos/illuminate/database/zipball/713d376a2e2b9c7cded8916125c24b7b3adb4984", + "reference": "713d376a2e2b9c7cded8916125c24b7b3adb4984", "shasum": "" }, "require": { @@ -840,20 +840,20 @@ "orm", "sql" ], - "time": "2018-04-17T12:36:27+00:00" + "time": "2018-05-14T22:33:47+00:00" }, { "name": "illuminate/filesystem", - "version": "v5.6.17", + "version": "v5.6.22", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", - "reference": "c9ab9376076cedd88a374d7281d62b619634d578" + "reference": "a4ca4a9c2f969ec227748ab334693144995ba0ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/c9ab9376076cedd88a374d7281d62b619634d578", - "reference": "c9ab9376076cedd88a374d7281d62b619634d578", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/a4ca4a9c2f969ec227748ab334693144995ba0ce", + "reference": "a4ca4a9c2f969ec227748ab334693144995ba0ce", "shasum": "" }, "require": { @@ -892,64 +892,20 @@ ], "description": "The Illuminate Filesystem package.", "homepage": "https://laravel.com", - "time": "2018-04-06T13:15:37+00:00" - }, - { - "name": "illuminate/pagination", - "version": "v5.6.17", - "source": { - "type": "git", - "url": "https://github.com/illuminate/pagination.git", - "reference": "77e9cfd4daf526aab9bf9c75ee1676f3ba6dff51" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/pagination/zipball/77e9cfd4daf526aab9bf9c75ee1676f3ba6dff51", - "reference": "77e9cfd4daf526aab9bf9c75ee1676f3ba6dff51", - "shasum": "" - }, - "require": { - "illuminate/contracts": "5.6.*", - "illuminate/support": "5.6.*", - "php": "^7.1.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.6-dev" - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Pagination\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "The Illuminate Pagination package.", - "homepage": "https://laravel.com", - "time": "2018-03-26T13:17:45+00:00" + "time": "2018-05-02T16:10:37+00:00" }, { "name": "illuminate/support", - "version": "v5.6.17", + "version": "v5.6.22", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "cc8d6f5cef3a901de6bb7d1b362102a6db001085" + "reference": "2ef559ad8840481d5247bd7ebfd04eb37d3f6889" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/cc8d6f5cef3a901de6bb7d1b362102a6db001085", - "reference": "cc8d6f5cef3a901de6bb7d1b362102a6db001085", + "url": "https://api.github.com/repos/illuminate/support/zipball/2ef559ad8840481d5247bd7ebfd04eb37d3f6889", + "reference": "2ef559ad8840481d5247bd7ebfd04eb37d3f6889", "shasum": "" }, "require": { @@ -993,7 +949,7 @@ ], "description": "The Illuminate Support package.", "homepage": "https://laravel.com", - "time": "2018-04-17T12:26:47+00:00" + "time": "2018-05-12T17:43:47+00:00" }, { "name": "maxmind-db/reader", @@ -1099,16 +1055,16 @@ }, { "name": "nesbot/carbon", - "version": "1.26.4", + "version": "1.27.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "e3d9014279133a3cccc01f6a691322a2d5a6a87b" + "reference": "ef81c39b67200dcd7401c24363dcac05ac3a4fe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e3d9014279133a3cccc01f6a691322a2d5a6a87b", - "reference": "e3d9014279133a3cccc01f6a691322a2d5a6a87b", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ef81c39b67200dcd7401c24363dcac05ac3a4fe9", + "reference": "ef81c39b67200dcd7401c24363dcac05ac3a4fe9", "shasum": "" }, "require": { @@ -1143,7 +1099,7 @@ "datetime", "time" ], - "time": "2018-04-17T15:35:42+00:00" + "time": "2018-04-23T09:02:57+00:00" }, { "name": "psr/container", @@ -1299,7 +1255,7 @@ }, { "name": "symfony/finder", - "version": "v4.0.8", + "version": "v4.0.9", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -1348,16 +1304,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.7.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + "reference": "3296adf6a6454a050679cde90f95350ad604b171" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", + "reference": "3296adf6a6454a050679cde90f95350ad604b171", "shasum": "" }, "require": { @@ -1369,7 +1325,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -1403,20 +1359,20 @@ "portable", "shim" ], - "time": "2018-01-30T19:27:44+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/translation", - "version": "v4.0.8", + "version": "v4.0.9", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "e20a9b7f9f62cb33a11638b345c248e7d510c938" + "reference": "ad3abf08eb3450491d8d76513100ef58194cd13e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/e20a9b7f9f62cb33a11638b345c248e7d510c938", - "reference": "e20a9b7f9f62cb33a11638b345c248e7d510c938", + "url": "https://api.github.com/repos/symfony/translation/zipball/ad3abf08eb3450491d8d76513100ef58194cd13e", + "reference": "ad3abf08eb3450491d8d76513100ef58194cd13e", "shasum": "" }, "require": { @@ -1437,7 +1393,7 @@ "symfony/yaml": "~3.4|~4.0" }, "suggest": { - "psr/log": "To use logging capability in translator", + "psr/log-implementation": "To use logging capability in translator", "symfony/config": "", "symfony/yaml": "" }, @@ -1471,7 +1427,7 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2018-02-22T10:50:29+00:00" + "time": "2018-04-30T01:23:47+00:00" }, { "name": "twig/twig", @@ -1896,23 +1852,23 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.5", + "version": "1.7.6", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { @@ -1955,7 +1911,7 @@ "spy", "stub" ], - "time": "2018-02-19T10:16:54+00:00" + "time": "2018-04-18T13:57:24+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/misuzu.php b/misuzu.php index a692949f..9e276882 100644 --- a/misuzu.php +++ b/misuzu.php @@ -4,6 +4,8 @@ namespace Misuzu; require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/src/colour.php'; require_once __DIR__ . '/src/zalgo.php'; +require_once __DIR__ . '/src/Users/login_attempt.php'; +require_once __DIR__ . '/src/Users/validation.php'; $app = new Application( __DIR__ . '/config/config.ini', @@ -31,24 +33,44 @@ if (PHP_SAPI !== 'cli') { exit; } + $app->startTemplating(); + $app->getTemplating()->addPath('mio', __DIR__ . '/views/mio'); + if (isset($_COOKIE['msz_uid'], $_COOKIE['msz_sid'])) { $app->startSession((int)$_COOKIE['msz_uid'], $_COOKIE['msz_sid']); - $session = $app->getSession(); - if ($session !== null) { - $session->user->last_seen = \Carbon\Carbon::now(); - $session->user->last_ip = Net\IPAddress::remote(); - $session->user->save(); + if ($app->hasActiveSession()) { + $db = Database::connection(); + + $bumpUserLast = $db->prepare(' + UPDATE `msz_users` SET + `last_seen` = NOW(), + `last_ip` = INET6_ATON(:last_ip) + WHERE `user_id` = :user_id + '); + $bumpUserLast->bindValue('last_ip', Net\IPAddress::remote()->getString()); + $bumpUserLast->bindValue('user_id', $app->getUserId()); + $bumpUserLast->execute(); + + $getUserDisplayInfo = $db->prepare(' + SELECT + u.`user_id`, u.`username`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `colour` + FROM `msz_users` as u + LEFT JOIN `msz_roles` as r + ON u.`display_role` = r.`role_id` + WHERE `user_id` = :user_id + '); + $getUserDisplayInfo->bindValue('user_id', $app->getUserId()); + $userDisplayInfo = $getUserDisplayInfo->execute() ? $getUserDisplayInfo->fetch() : []; + $app->getTemplating()->var('current_user', $userDisplayInfo); } } $manage_mode = starts_with($_SERVER['REQUEST_URI'], '/manage'); - $app->startTemplating(); - $app->getTemplating()->addPath('mio', __DIR__ . '/views/mio'); - if ($manage_mode) { - if ($app->getSession() === null || $app->getSession()->user->user_id !== 1) { + if ($app->getUserId() !== 1) { http_response_code(403); echo $app->getTemplating()->render('errors.403'); exit; diff --git a/misuzu_migrate.php b/misuzu_migrate.php index 3b052f9b..f1c6980f 100644 --- a/misuzu_migrate.php +++ b/misuzu_migrate.php @@ -6,6 +6,7 @@ namespace Misuzu; +exit; use Illuminate\Database\Migrations\DatabaseMigrationRepository; use Illuminate\Database\Migrations\Migrator; use Illuminate\Filesystem\Filesystem; diff --git a/misuzu_setup.php b/misuzu_setup.php index b01b8728..e755e59f 100644 --- a/misuzu_setup.php +++ b/misuzu_setup.php @@ -6,19 +6,51 @@ namespace Misuzu; -use Misuzu\Users\Role; -use Misuzu\Users\User; +use Misuzu\Database; require_once __DIR__ . '/misuzu.php'; -$role = Role::find(1); +$db = Database::connection(); -if ($role === null) { - $role = Role::createRole('Member'); +$mainRoleId = (int)$db->query(' + SELECT `role_id` + FROM `msz_roles` + WHERE `role_id` = 1 +')->fetchColumn(); + +if ($mainRoleId !== 1) { + $db->query(" + REPLACE INTO `msz_roles` + (`role_id`, `role_name`, `role_hierarchy`, `role_colour`, `role_description`, `created_at`) + VALUES + (1, 'Member', 1, 1073741824, NULL, NOW()) + "); + + $mainRoleId = 1; } -foreach (User::all() as $user) { - if (!$user->hasRole($role)) { - $user->addRole($role); - } +$notInMainRole = $db->query(' + SELECT `user_id` + FROM `msz_users` as u + WHERE NOT EXISTS ( + SELECT 1 + FROM `msz_user_roles` as ur + WHERE `role_id` = 1 + AND u.`user_id` = ur.`user_id` + ) +')->fetchAll(); + +if (count($notInMainRole) < 1) { + exit; +} + +$addToMainRole = $db->prepare(' + INSERT INTO `msz_user_roles` + (`user_id`, `role_id`) + VALUES + (:user_id, 1) +'); + +foreach ($notInMainRole as $user) { + $addToMainRole->execute($user); } diff --git a/public/auth.php b/public/auth.php index df1468f8..e15ebfc5 100644 --- a/public/auth.php +++ b/public/auth.php @@ -1,21 +1,19 @@ getConfig(); $templating = $app->getTemplating(); -$session = $app->getSession(); $username_validation_errors = [ 'trim' => 'Your username may not start or end with spaces!', - 'short' => "Your username is too short, it has to be at least " . User::USERNAME_MIN_LENGTH . " characters!", - 'long' => "Your username is too long, it can't be longer than " . User::USERNAME_MAX_LENGTH . " characters!", + 'short' => "Your username is too short, it has to be at least " . MSZ_USERNAME_MIN_LENGTH . " characters!", + 'long' => "Your username is too long, it can't be longer than " . MSZ_USERNAME_MAX_LENGTH . " characters!", 'double-spaces' => "Your username can't contain double spaces.", 'invalid' => 'Your username contains invalid characters.', 'spacing' => 'Please use either underscores or spaces, not both!', @@ -38,17 +36,20 @@ if (!empty($_REQUEST['email'])) { switch ($mode) { case 'logout': - if ($session === null) { + if (!$app->hasActiveSession()) { header('Location: /'); return; } - // this is temporary, don't scream at me for using md5 if (isset($_GET['s']) && tmp_csrf_verify($_GET['s'])) { set_cookie_m('uid', '', -3600); set_cookie_m('sid', '', -3600); - $session->delete(); - $app->setSession(null); + $deleteSession = $db->prepare(' + DELETE FROM `msz_sessions` + WHERE `session_id` = :session_id + '); + $deleteSession->bindValue('session_id', $app->getSessionId()); + $deleteSession->execute(); header('Location: /'); return; } @@ -57,7 +58,7 @@ switch ($mode) { break; case 'login': - if ($session !== null) { + if ($app->hasActiveSession()) { header('Location: /'); break; } @@ -66,56 +67,103 @@ switch ($mode) { $auth_login_error = ''; while ($_SERVER['REQUEST_METHOD'] === 'POST') { - $ipAddress = IPAddress::remote(); + $ipAddressObj = IPAddress::remote(); + $ipAddress = $ipAddressObj->getString(); if (!isset($_POST['username'], $_POST['password'])) { $auth_login_error = "You didn't fill all the forms!"; break; } - $loginAttempts = LoginAttempt::fromIpAddress(IPAddress::remote()) - ->where('was_successful', false) - ->where('created_at', '>', Carbon::now()->subHour()->toDateTimeString()) - ->get(); + $fetchRemainingAttempts = $db->prepare(' + SELECT 5 - COUNT(`attempt_id`) + FROM `msz_login_attempts` + WHERE `was_successful` = false + AND `created_at` > NOW() - INTERVAL 1 HOUR + AND `attempt_ip` = INET6_ATON(:remote_ip) + '); + $fetchRemainingAttempts->bindValue('remote_ip', $ipAddress); + $remainingAttempts = $fetchRemainingAttempts->execute() + ? (int)$fetchRemainingAttempts->fetchColumn() + : 0; - if ($loginAttempts->count() >= 5) { + if ($remainingAttempts < 1) { $auth_login_error = 'Too many failed login attempts, try again later.'; break; } + $remainingAttempts -= 1; $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; - $user = User::findLogin($username); + $getUser = $db->prepare(' + SELECT `user_id`, `password` + FROM `msz_users` + WHERE LOWER(`email`) = LOWER(:email) + OR LOWER(`username`) = LOWER(:username) + '); + $getUser->bindValue('email', $username); + $getUser->bindValue('username', $username); + $userData = $getUser->execute() ? $getUser->fetch() : []; + $userId = (int)($userData['user_id'] ?? 0); - if ($user === null) { - LoginAttempt::recordFail($ipAddress, null, $user_agent); - $auth_login_error = 'Invalid username or password!'; + $auth_error_str = "Invalid username or password, {$remainingAttempts} attempt(s) remaining."; + + if ($userId < 1) { + user_login_attempt_record(false, null, $ipAddress, $user_agent); + $auth_login_error = $auth_error_str; break; } - if (!$user->verifyPassword($password)) { - LoginAttempt::recordFail($ipAddress, $user, $user_agent); - $auth_login_error = 'Invalid username or password!'; + if (!password_verify($password, $userData['password'])) { + user_login_attempt_record(false, $userId, $ipAddress, $user_agent); + $auth_login_error = $auth_error_str; break; } - LoginAttempt::recordSuccess($ipAddress, $user, $user_agent); + user_login_attempt_record(true, $userId, $ipAddress, $user_agent); - $session = Session::createSession($user, $user_agent, null, $ipAddress); - $app->setSession($session); - $cookie_life = Carbon::now()->addMonth()->timestamp; - set_cookie_m('uid', $session->user_id, $cookie_life); - set_cookie_m('sid', $session->session_key, $cookie_life); + $sessionKey = bin2hex(random_bytes(32)); + + $createSession = $db->prepare(' + INSERT INTO `msz_sessions` + (`user_id`, `session_ip`, `user_agent`, `session_key`, `created_at`, `expires_on`) + VALUES + (:user_id, INET6_ATON(:session_ip), :user_agent, :session_key, NOW(), NOW() + INTERVAL 1 MONTH) + '); + $createSession->bindValue('user_id', $userId); + $createSession->bindValue('session_ip', $ipAddress); + $createSession->bindValue('user_agent', $user_agent); + $createSession->bindValue('session_key', $sessionKey); + + if (!$createSession->execute()) { + $auth_login_error = 'Unable to create new session, contact an administrator.'; + break; + } + + $app->startSession($userId, $sessionKey); + $cookieLife = Carbon::now()->addMonth()->timestamp; + set_cookie_m('uid', $userId, $cookieLife); + set_cookie_m('sid', $sessionKey, $cookieLife); // Temporary key generation for chat login. // Should eventually be replaced with a callback login system. // Also uses different cookies since $httponly is required to be false for these. - $user->user_chat_key = bin2hex(random_bytes(16)); - $user->save(); + $chatKey = bin2hex(random_bytes(16)); + + $setChatKey = $db->prepare(' + UPDATE `msz_users` + SET `user_chat_key` = :user_chat_key + WHERE `user_id` = :user_id + '); + $setChatKey->bindValue('user_chat_key', $chatKey); + $setChatKey->bindValue('user_id', $userId); + + if ($setChatKey->execute()) { + setcookie('msz_tmp_id', $userId, $cookieLife, '/', '.flashii.net'); + setcookie('msz_tmp_key', $chatKey, $cookieLife, '/', '.flashii.net'); + } - setcookie('msz_tmp_id', $user->user_id, $cookie_life, '/', '.flashii.net'); - setcookie('msz_tmp_key', $user->user_chat_key, $cookie_life, '/', '.flashii.net'); header('Location: /'); return; } @@ -128,7 +176,7 @@ switch ($mode) { break; case 'register': - if ($session !== null) { + if ($app->hasActiveSession()) { header('Location: /'); } @@ -149,13 +197,13 @@ switch ($mode) { $password = $_POST['password'] ?? ''; $email = $_POST['email'] ?? ''; - $username_validate = User::validateUsername($username, true); + $username_validate = user_validate_username($username, true); if ($username_validate !== '') { $auth_register_error = $username_validation_errors[$username_validate]; break; } - $email_validate = User::validateEmail($email, true); + $email_validate = user_validate_email($email, true); if ($email_validate !== '') { $auth_register_error = $email_validate === 'in-use' ? 'This e-mail address has already been used!' @@ -163,13 +211,45 @@ switch ($mode) { break; } - if (User::validatePassword($password) !== '') { + if (user_validate_password($password) !== '') { $auth_register_error = 'Your password is too weak!'; break; } - $user = User::createUser($username, $password, $email); - $user->addRole(Role::find(1), true); + $ipAddress = IPAddress::remote()->getString(); + $createUser = $db->prepare(' + INSERT INTO `msz_users` + ( + `username`, `password`, `email`, `register_ip`, + `last_ip`, `user_country`, `created_at`, `display_role` + ) + VALUES + ( + :username, :password, :email, INET6_ATON(:register_ip), + INET6_ATON(:last_ip), :user_country, NOW(), 1 + ) + '); + $createUser->bindValue('username', $username); + $createUser->bindValue('password', password_hash($password, PASSWORD_ARGON2I)); + $createUser->bindValue('email', $email); + $createUser->bindValue('register_ip', $ipAddress); + $createUser->bindValue('last_ip', $ipAddress); + $createUser->bindValue('user_country', get_country_code($ipAddress)); + + if (!$createUser->execute()) { + $auth_register_error = 'Something happened?'; + break; + } + + $addRole = $db->prepare(' + INSERT INTO `msz_user_roles` + (`user_id`, `role_id`) + VALUES + (:user_id, 1) + '); + $addRole->bindValue('user_id', $db->lastInsertId()); + $addRole->execute(); + $templating->var('auth_register_message', 'Welcome to Flashii! You may now log in.'); break; } diff --git a/public/index.php b/public/index.php index 2f97bf50..75aff5b3 100644 --- a/public/index.php +++ b/public/index.php @@ -1,33 +1,24 @@ orderBy('created_at', 'desc')->take(3)->get(); -$featuredNews = []; - -$fetchNews = Database::connection() +$featuredNews = Database::connection() ->query(' -SELECT - p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, - u.`user_id`, u.`username`, - COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour` -FROM `msz_news_posts` as p -LEFT JOIN `msz_users` as u -ON p.`user_id` = u.`user_id` -LEFT JOIN `msz_roles` as r -ON u.`display_role` = r.`role_id` -WHERE p.`is_featured` = true -ORDER BY p.`created_at` DESC -LIMIT 3 - '); - -while (($newsPost = $fetchNews->fetchObject(NewsPost::class)) !== false) { - $featuredNews['post'] = $newsPost; -} - -var_dump($featuredNews); + SELECT + p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, + u.`user_id`, u.`username`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour` + FROM `msz_news_posts` as p + LEFT JOIN `msz_users` as u + ON p.`user_id` = u.`user_id` + LEFT JOIN `msz_roles` as r + ON u.`display_role` = r.`role_id` + WHERE p.`is_featured` = true + ORDER BY p.`created_at` DESC + LIMIT 3 + ')->fetchAll(); +//var_dump(Database::connection()->query('SHOW SESSION STATUS LIKE "Questions"')->fetch()); echo $app->getTemplating()->render('home.landing', compact('featuredNews')); diff --git a/public/manage/users.php b/public/manage/users.php index 1f6ebfeb..ffbab4ae 100644 --- a/public/manage/users.php +++ b/public/manage/users.php @@ -1,9 +1,9 @@ getTemplating(); $is_post_request = $_SERVER['REQUEST_METHOD'] === 'POST'; @@ -11,7 +11,17 @@ $page_id = (int)($_GET['p'] ?? 1); switch ($_GET['v'] ?? null) { case 'listing': - $manage_users = User::paginate(32, ['*'], 'p', $page_id); + $manage_users = $db->query(' + SELECT + u.`user_id`, u.`username`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `colour` + FROM `msz_users` as u + LEFT JOIN `msz_roles` as r + ON u.`display_role` = r.`role_id` + LIMIT 0, 32 + ')->fetchAll(); + + //$manage_users = UserV1::paginate(32, ['*'], 'p', $page_id); $templating->vars(compact('manage_users')); echo $templating->render('@manage.users.listing'); break; @@ -24,19 +34,44 @@ switch ($_GET['v'] ?? null) { break; } - $view_user = User::find($user_id); + $getUser = $db->prepare(' + SELECT + u.*, + INET6_NTOA(u.`register_ip`) as `register_ip_decoded`, + INET6_NTOA(u.`last_ip`) as `last_ip_decoded`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `colour` + FROM `msz_users` as u + LEFT JOIN `msz_roles` as r + ON u.`display_role` = r.`role_id` + WHERE `user_id` = :user_id + '); + $getUser->bindValue('user_id', $user_id); + $getUser->execute(); + $manageUser = $getUser->execute() ? $getUser->fetch() : []; - if ($view_user === null) { + if (!$manageUser) { echo 'Could not find that user.'; break; } - $templating->var('view_user', $view_user); + $templating->var('view_user', $manageUser); echo $templating->render('@manage.users.view'); break; case 'roles': - $manage_roles = Role::paginate(32, ['*'], 'p', $page_id); + $manage_roles = $db->query(' + SELECT + `role_id`, `role_colour`, `role_name`, + ( + SELECT COUNT(`user_id`) + FROM `msz_user_roles` as ur + WHERE ur.`role_id` = r.`role_id` + ) as `users` + FROM `msz_roles` as r + LIMIT 0, 10 + ')->fetchAll(); + + //$manage_roles = Role::paginate(10, ['*'], 'p', $page_id); $templating->vars(compact('manage_roles')); echo $templating->render('@manage.users.roles'); break; @@ -97,15 +132,38 @@ switch ($_GET['v'] ?? null) { break; } - $edit_role = $role_id < 1 ? new Role : Role::find($role_id); - $edit_role->role_name = $role_name; - $edit_role->role_hierarchy = $role_hierarchy; - $edit_role->role_secret = $role_secret; - $edit_role->role_colour = $role_colour; - $edit_role->role_description = $role_description; - $edit_role->save(); + if ($role_id < 1) { + $updateRole = $db->prepare(' + INSERT INTO `msz_roles` + (`role_name`, `role_hierarchy`, `role_secret`, `role_colour`, `role_description`, `created_at`) + VALUES + (:role_name, :role_hierarchy, :role_secret, :role_colour, :role_description, NOW()) + '); + } else { + $updateRole = $db->prepare(' + UPDATE `msz_roles` SET + `role_name` = :role_name, + `role_hierarchy` = :role_hierarchy, + `role_secret` = :role_secret, + `role_colour` = :role_colour, + `role_description` = :role_description + WHERE `role_id` = :role_id + '); + $updateRole->bindValue('role_id', $role_id); + } - header("Location: ?v=role&r={$edit_role->role_id}"); + $updateRole->bindValue('role_name', $role_name); + $updateRole->bindValue('role_hierarchy', $role_hierarchy); + $updateRole->bindValue('role_secret', $role_secret ? 1 : 0); + $updateRole->bindValue('role_colour', $role_colour); + $updateRole->bindValue('role_description', $role_description); + $updateRole->execute(); + + if ($role_id < 1) { + $role_id = (int)$db->lastInsertId(); + } + + header("Location: ?v=role&r={$role_id}"); break; } @@ -115,9 +173,15 @@ switch ($_GET['v'] ?? null) { break; } - $edit_role = Role::find($role_id); + $getEditRole = $db->prepare(' + SELECT * + FROM `msz_roles` + WHERE `role_id` = :role_id + '); + $getEditRole->bindValue('role_id', $role_id); + $edit_role = $getEditRole->execute() ? $getEditRole->fetch() : []; - if ($edit_role === null) { + if (!$edit_role) { echo 'invalid role'; break; } diff --git a/public/news.php b/public/news.php index 25d6fc05..c3386c30 100644 --- a/public/news.php +++ b/public/news.php @@ -1,9 +1,9 @@ getTemplating(); $category_id = isset($_GET['c']) ? (int)$_GET['c'] : null; @@ -11,9 +11,25 @@ $post_id = isset($_GET['n']) ? (int)$_GET['n'] : null; $page_id = (int)($_GET['p'] ?? 1); if ($post_id !== null) { - $post = NewsPost::find($post_id); + $getPost = $db->prepare(' + SELECT + p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, + c.`category_id`, c.`category_name`, + u.`user_id`, u.`username`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour` + FROM `msz_news_posts` as p + LEFT JOIN `msz_news_categories` as c + ON p.`category_id` = c.`category_id` + LEFT JOIN `msz_users` as u + ON p.`user_id` = u.`user_id` + LEFT JOIN `msz_roles` as r + ON u.`display_role` = r.`role_id` + WHERE `post_id` = :post_id + '); + $getPost->bindValue(':post_id', $post_id, PDO::PARAM_INT); + $post = $getPost->execute() ? $getPost->fetch() : false; - if ($post === null) { + if ($post === false) { http_response_code(404); echo $templating->render('errors.404'); return; @@ -24,31 +40,102 @@ if ($post_id !== null) { } if ($category_id !== null) { - $category = NewsCategory::find($category_id); + $getCategory = $db->prepare(' + SELECT + `category_id`, `category_name`, `category_description` + FROM `msz_news_categories` + WHERE `category_id` = :category_id + '); + $getCategory->bindValue(':category_id', $category_id, PDO::PARAM_INT); + $category = $getCategory->execute() ? $getCategory->fetch() : false; - if ($category === null) { + if ($category === false) { http_response_code(404); echo $templating->render('errors.404'); return; } - $posts = $category->posts()->orderBy('created_at', 'desc')->paginate(5, ['*'], 'p', $page_id); + $getPosts = $db->prepare(' + SELECT + p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, + c.`category_id`, c.`category_name`, + u.`user_id`, u.`username`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour` + FROM `msz_news_posts` as p + LEFT JOIN `msz_news_categories` as c + ON p.`category_id` = c.`category_id` + LEFT JOIN `msz_users` as u + ON p.`user_id` = u.`user_id` + LEFT JOIN `msz_roles` as r + ON u.`display_role` = r.`role_id` + WHERE p.`category_id` = :category_id + ORDER BY `created_at` DESC + LIMIT 0, 5 + '); + $getPosts->bindValue('category_id', $category['category_id'], PDO::PARAM_INT); + $posts = $getPosts->execute() ? $getPosts->fetchAll() : false; - if (!is_valid_page($posts, $page_id)) { + //$posts = $category->posts()->orderBy('created_at', 'desc')->paginate(5, ['*'], 'p', $page_id); + + //if (!is_valid_page($posts, $page_id)) { + if ($posts === false) { http_response_code(404); echo $templating->render('errors.404'); return; } - $featured = $category->posts()->where('is_featured', 1)->orderBy('created_at', 'desc')->take(10)->get(); + $getFeatured = $db->prepare(' + SELECT `post_id`, `post_title` + FROM `msz_news_posts` + WHERE `category_id` = :category_id + AND `is_featured` = true + ORDER BY `created_at` DESC + LIMIT 10 + '); + $getFeatured->bindValue('category_id', $category['category_id'], PDO::PARAM_INT); + $featured = $getFeatured->execute() ? $getFeatured->fetchAll() : []; + echo $templating->render('news.category', compact('category', 'posts', 'featured', 'page_id')); return; } -$categories = NewsCategory::where('is_hidden', false)->get(); -$posts = NewsPost::where('is_featured', true)->orderBy('created_at', 'desc')->paginate(5, ['*'], 'p', $page_id); +$getCategories = $db->prepare(' + SELECT + c.`category_id`, c.`category_name`, + COUNT(p.`post_id`) AS count + FROM `msz_news_categories` as c + LEFT JOIN `msz_news_posts` as p + ON c.`category_id` = p.`category_id` + WHERE `is_hidden` = false + GROUP BY c.`category_id` + HAVING count > 0 +'); +$categories = $getCategories->execute() ? $getCategories->fetchAll() : []; -if (!is_valid_page($posts, $page_id)) { +$getPosts = $db->prepare(' + SELECT + p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, + c.`category_id`, c.`category_name`, + u.`user_id`, u.`username`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour` + FROM `msz_news_posts` as p + LEFT JOIN `msz_news_categories` as c + ON p.`category_id` = c.`category_id` + LEFT JOIN `msz_users` as u + ON p.`user_id` = u.`user_id` + LEFT JOIN `msz_roles` as r + ON u.`display_role` = r.`role_id` + WHERE p.`is_featured` = true + AND c.`is_hidden` = false + ORDER BY p.`created_at` DESC + LIMIT 0, 5 +'); +$posts = $getPosts->execute() ? $getPosts->fetchAll() : []; + +//$posts = NewsPost::where('is_featured', true)->orderBy('created_at', 'desc')->paginate(5, ['*'], 'p', $page_id); + +//if (!is_valid_page($posts, $page_id)) { +if ($posts === false) { http_response_code(404); echo $templating->render('errors.404'); return; diff --git a/public/profile.php b/public/profile.php index cce440b4..7054e834 100644 --- a/public/profile.php +++ b/public/profile.php @@ -1,12 +1,11 @@ getConfig()->get('Avatar', 'default_path', 'string', 'public/images/no-avatar.png') ); - if ($profile_user !== null) { - $user_avatar = "{$profile_user->user_id}.msz"; - $cropped_avatar = $app->getStore('avatars/200x200')->filename($user_avatar); + $user_avatar = "{$user_id}.msz"; + $cropped_avatar = $app->getStore('avatars/200x200')->filename($user_avatar); - if (File::exists($cropped_avatar)) { - $avatar_filename = $cropped_avatar; - } else { - $original_avatar = $app->getStore('avatars/original')->filename($user_avatar); + if (File::exists($cropped_avatar)) { + $avatar_filename = $cropped_avatar; + } else { + $original_avatar = $app->getStore('avatars/original')->filename($user_avatar); - if (File::exists($original_avatar)) { - try { - File::writeAll( - $cropped_avatar, - crop_image_centred_path($original_avatar, 200, 200)->getImagesBlob() - ); + if (File::exists($original_avatar)) { + try { + File::writeAll( + $cropped_avatar, + crop_image_centred_path($original_avatar, 200, 200)->getImagesBlob() + ); - $avatar_filename = $cropped_avatar; - } catch (Exception $ex) { - } + $avatar_filename = $cropped_avatar; + } catch (Exception $ex) { } } } @@ -45,13 +42,26 @@ switch ($mode) { default: $templating = $app->getTemplating(); - if ($profile_user === null) { + $getProfile = Database::connection()->prepare(' + SELECT + u.*, + r.`role_title` as `user_title`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour` + FROM `msz_users` as u + LEFT JOIN `msz_roles` as r + ON r.`role_id` = u.`display_role` + WHERE `user_id` = :user_id + '); + $getProfile->bindValue('user_id', $user_id); + $profile = $getProfile->execute() ? $getProfile->fetch() : []; + + if (!$profile) { http_response_code(404); echo $templating->render('user.notfound'); break; } - $templating->var('profile', $profile_user); + $templating->vars(compact('profile')); echo $templating->render('user.view'); break; } diff --git a/public/settings.php b/public/settings.php index 33dcb208..211d6364 100644 --- a/public/settings.php +++ b/public/settings.php @@ -1,17 +1,15 @@ getSession(); +$db = Database::connection(); $templating = $app->getTemplating(); $page_id = (int)($_GET['p'] ?? 1); -if (Application::getInstance()->getSession() === null) { +if (!$app->hasActiveSession()) { http_response_code(403); echo $templating->render('errors.403'); return; @@ -73,8 +71,6 @@ $settings_profile_fields = [ ], ]; -$settings_user = $settings_session->user; - $settings_modes = [ 'account' => 'Account', 'avatar' => 'Avatar', @@ -83,7 +79,7 @@ $settings_modes = [ ]; $settings_mode = $_GET['m'] ?? key($settings_modes); -$templating->vars(compact('settings_mode', 'settings_modes', 'settings_user', 'settings_session')); +$templating->vars(compact('settings_mode', 'settings_modes')); if (!array_key_exists($settings_mode, $settings_modes)) { http_response_code(404); @@ -95,7 +91,7 @@ if (!array_key_exists($settings_mode, $settings_modes)) { $settings_errors = []; $prevent_registration = $app->getConfig()->get('Auth', 'prevent_registration', 'bool', false); -$avatar_filename = "{$settings_user->user_id}.msz"; +$avatar_filename = "{$app->getUserId()}.msz"; $avatar_max_width = $app->getConfig()->get('Avatar', 'max_width', 'int', 4000); $avatar_max_height = $app->getConfig()->get('Avatar', 'max_height', 'int', 4000); $avatar_max_filesize = $app->getConfig()->get('Avatar', 'max_filesize', 'int', 1000000); @@ -109,6 +105,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { break; } + $updatedUserFields = []; + if (isset($_POST['profile']) && is_array($_POST['profile'])) { foreach ($settings_profile_fields as $name => $props) { if (isset($_POST['profile'][$name])) { @@ -129,7 +127,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $field_value = $field_matches[1]; } - $settings_user->{"user_{$name}"} = $field_value; + $updatedUserFields["user_{$name}"] = $field_value; } } } @@ -141,45 +139,69 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { && (!empty($_POST['password']['new']) || !empty($_POST['email']['new'])) ) ) { - if (!$settings_user->verifyPassword($_POST['current_password'])) { - $settings_errors[] = "Your current password was incorrect."; + $fetchPassword = $db->prepare(' + SELECT `password` + FROM `msz_users` + WHERE `user_id` = :user_id + '); + $fetchPassword->bindValue('user_id', $app->getUserId()); + $currentPassword = $fetchPassword->execute() ? $fetchPassword->fetchColumn() : null; + + if (empty($currentPassword)) { + $settings_errors[] = 'Something went horribly wrong.'; + break; + } + + if (!password_verify($_POST['current_password'], $currentPassword)) { + $settings_errors[] = 'Your current password was incorrect.'; break; } if (!empty($_POST['email']['new'])) { - if (empty($_POST['email']['confirm']) || $_POST['email']['new'] !== $_POST['email']['confirm']) { - $settings_errors[] = "The given e-mail addresses did not match."; + if (empty($_POST['email']['confirm']) + || $_POST['email']['new'] !== $_POST['email']['confirm']) { + $settings_errors[] = 'The given e-mail addresses did not match.'; break; } - if ($_POST['email']['new'] === $settings_user->email) { - $settings_errors[] = "This is your e-mail address already!"; + $checkIfAlreadySet = $db->prepare(' + SELECT COUNT(`user_id`) + FROM `msz_users` + WHERE LOWER(:email) = LOWER(:email) + '); + $checkIfAlreadySet->bindValue('email', $_POST['email']['new']); + $isAlreadySet = $checkIfAlreadySet->execute() + ? $checkIfAlreadySet->fetchColumn() > 0 + : false; + + if ($isAlreadySet) { + $settings_errors[] = 'This is your e-mail address already!'; break; } - $email_validate = User::validateEmail($_POST['email']['new'], true); + $email_validate = user_validate_email($_POST['email']['new'], true); if ($email_validate !== '') { switch ($email_validate) { case 'dns': - $settings_errors[] = "No valid MX record exists for this domain."; + $settings_errors[] = 'No valid MX record exists for this domain.'; break; case 'format': - $settings_errors[] = "The given e-mail address was incorrectly formatted."; + $settings_errors[] = 'The given e-mail address was incorrectly formatted.'; break; case 'in-use': - $settings_errors[] = "This e-mail address has already been used by another user."; + $settings_errors[] = 'This e-mail address has already been used by another user.'; break; default: - $settings_errors[] = "Unknown e-mail validation error."; + $settings_errors[] = 'Unknown e-mail validation error.'; } break; } - $settings_user->email = $_POST['email']['new']; + $updatedUserFields['email'] = strtolower($_POST['email']['new']); } if (!empty($_POST['password']['new'])) { @@ -189,20 +211,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { break; } - $password_validate = User::validatePassword($_POST['password']['new']); + $password_validate = user_validate_password($_POST['password']['new']); if ($password_validate !== '') { $settings_errors[] = "The given passwords was too weak."; break; } - $settings_user->password = $_POST['password']['new']; + $updatedUserFields['password'] = password_hash($_POST['password']['new'], PASSWORD_ARGON2I); } } } - if (count($settings_errors) < 1 && $settings_user->isDirty()) { - $settings_user->save(); + if (count($settings_errors) < 1 && count($updatedUserFields) > 0) { + $updateUser = $db->prepare(' + UPDATE `msz_users` + SET ' . pdo_prepare_array_update($updatedUserFields, true) . ' + WHERE `user_id` = :user_id + '); + $updatedUserFields['user_id'] = $app->getUserId(); + $updateUser->execute($updatedUserFields); } break; @@ -308,23 +336,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $session_id = (int)($_POST['session'] ?? 0); if ($session_id < 1) { - $settings_errors[] = 'no'; + $settings_errors[] = 'Invalid session.'; break; } - $session = Session::find($session_id); + $findSession = $db->prepare(' + SELECT `session_id`, `user_id` + FROM `msz_sessions` + WHERE `session_id` = :session_id + '); + $findSession->bindValue('session_id', $session_id); + $session = $findSession->execute() ? $findSession->fetch() : null; - if ($session === null || $session->user_id !== $settings_user->user_id) { + if (!$session || (int)$session['user_id'] !== $app->getUserId()) { $settings_errors[] = 'You may only end your own sessions.'; break; } - if ($session->session_id === $app->getSession()->session_id) { + if ((int)$session['session_id'] === $app->getSessionId()) { header('Location: /auth.php?m=logout&s=' . tmp_csrf_token()); return; } - $session->delete(); + $deleteSession = $db->prepare(' + DELETE FROM `msz_sessions` + WHERE `session_id` = :session_id + '); + $deleteSession->bindValue('session_id', $session['session_id']); + $deleteSession->execute(); break; } } @@ -334,11 +373,21 @@ $templating->var('settings_title', $settings_modes[$settings_mode]); switch ($settings_mode) { case 'account': + $getUserFields = $db->prepare(' + SELECT ' . pdo_prepare_array($settings_profile_fields, true, '`user_%s`') . ' + FROM `msz_users` + WHERE `user_id` = :user_id + '); + $getUserFields->bindValue('user_id', $app->getUserId()); + $userFields = $getUserFields->execute() ? $getUserFields->fetch() : []; + + $templating->var('settings_profile_values', $userFields); $templating->vars(compact('settings_profile_fields', 'prevent_registration')); break; case 'avatar': $user_has_avatar = File::exists($app->getStore('avatars/original')->filename($avatar_filename)); + $templating->var('avatar_user_id', $app->getUserId()); $templating->vars(compact( 'avatar_max_width', 'avatar_max_height', @@ -348,19 +397,44 @@ switch ($settings_mode) { break; case 'sessions': - $sessions = $settings_user->sessions() + /*$sessions = $settings_user->sessions() ->orderBy('session_id', 'desc') - ->paginate(15, ['*'], 'p', $page_id); + ->paginate(15, ['*'], 'p', $page_id);*/ + $getSessions = $db->prepare(' + SELECT + `session_id`, `session_country`, `user_agent`, `created_at`, `expires_on`, + INET6_NTOA(`session_ip`) as `session_ip_decoded` + FROM `msz_sessions` + WHERE `user_id` = :user_id + ORDER BY `session_id` DESC + LIMIT 0, 15 + '); + $getSessions->bindValue('user_id', $app->getUserId()); + $sessions = $getSessions->execute() ? $getSessions->fetchAll() : []; + + $templating->var('active_session_id', $app->getSessionId()); $templating->var('user_sessions', $sessions); break; case 'login-history': - $login_attempts = $settings_user->loginAttempts() + /*$login_attempts = $settings_user->loginAttempts() ->orderBy('attempt_id', 'desc') - ->paginate(15, ['*'], 'p', $page_id); + ->paginate(15, ['*'], 'p', $page_id);*/ - $templating->var('user_login_attempts', $login_attempts); + $getLoginAttempts = $db->prepare(' + SELECT + `attempt_id`, `attempt_country`, `was_successful`, `user_agent`, `created_at`, + INET6_NTOA(`attempt_ip`) as `attempt_ip_decoded` + FROM `msz_login_attempts` + WHERE `user_id` = :user_id + ORDER BY `attempt_id` DESC + LIMIT 0, 15 + '); + $getLoginAttempts->bindValue('user_id', $app->getUserId()); + $loginAttempts = $getLoginAttempts->execute() ? $getLoginAttempts->fetchAll() : []; + + $templating->var('user_login_attempts', $loginAttempts); break; } diff --git a/routes.php b/routes.php deleted file mode 100644 index e7b23a01..00000000 --- a/routes.php +++ /dev/null @@ -1,23 +0,0 @@ -router; - -$routes->get(['/', 'main.index'], [HomeController::class, 'index']); - -$routes->group(['prefix' => '/auth'], function ($routes) { - $routes->get(['/login', 'auth.login'], [AuthController::class, 'login']); - $routes->post(['/login', 'auth.login'], [AuthController::class, 'login']); - - $routes->get(['/logout', 'auth.logout'], [AuthController::class, 'logout']); - - $routes->get(['/register', 'auth.register'], [AuthController::class, 'register']); - $routes->post(['/register', 'auth.register'], [AuthController::class, 'register']); -}); - -$routes->group(['prefix' => '/users'], function ($routes) { - $routes->get(['/{id:i}', 'users.view'], [UserController::class, 'view']); -}); diff --git a/src/Application.php b/src/Application.php index 3e5fe8ce..42b96d04 100644 --- a/src/Application.php +++ b/src/Application.php @@ -1,6 +1,7 @@ where('user_id', $user_id) - ->first(); + $dbc = Database::connection(); - if ($session !== null) { - if ($session->hasExpired()) { - $session->delete(); + $findSession = $dbc->prepare(' + SELECT `session_id`, `expires_on` + FROM `msz_sessions` + WHERE `user_id` = :user_id + AND `session_key` = :session_key + '); + $findSession->bindValue('user_id', $userId); + $findSession->bindValue('session_key', $sessionKey); + $sessionData = $findSession->execute() ? $findSession->fetch() : false; + + if ($sessionData) { + $expiresOn = new Carbon($sessionData['expires_on']); + + if ($expiresOn->isPast()) { + $deleteSession = $dbc->prepare(' + DELETE FROM `msz_sessions` + WHERE `session_id` = :session_id + '); + $deleteSession->bindValue('session_id', $sessionData['session_id']); + $deleteSession->execute(); } else { - $this->setSession($session); + $this->currentSessionId = (int)$sessionData['session_id']; + $this->currentUserId = $userId; } } } - /** - * Gets the current session instance. - * @return Session|null - */ - public function getSession(): ?Session + public function hasActiveSession(): bool { - return $this->sessionInstance; + return $this->getSessionId() > 0; } - /** - * Registers a session. - * @param Session|null $sessionInstance - */ - public function setSession(?Session $sessionInstance): void + public function getSessionId(): int { - $this->sessionInstance = $sessionInstance; + return $this->currentSessionId; + } + + public function getUserId(): int + { + return $this->currentUserId; } /** @@ -206,7 +220,7 @@ class Application extends ApplicationBase throw new UnexpectedValueException('Database module has already been started.'); } - $this->database = new Database($this->configInstance, self::DATABASE_CONNECTIONS[0]); + new Database($this->configInstance, self::DATABASE_CONNECTIONS[0]); $this->databaseInstance = new DatabaseV1($this->configInstance, self::DATABASE_CONNECTIONS[0]); $this->loadDatabaseConnections(); } diff --git a/src/Database.php b/src/Database.php index 1f3b6d0d..fd248a29 100644 --- a/src/Database.php +++ b/src/Database.php @@ -136,7 +136,7 @@ final class Database $dsn .= 'dbname=' . $this->configManager->get($section, 'database', 'string', 'misuzu') . ';'; - $options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'"; + $options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'"; break; } diff --git a/src/DatabaseV1.php b/src/DatabaseV1.php index d252250e..f686fff7 100644 --- a/src/DatabaseV1.php +++ b/src/DatabaseV1.php @@ -125,7 +125,7 @@ class DatabaseV1 extends LaravelDatabaseManager ? $this->configManager->get($section, 'collation', 'string') : 'utf8mb4_bin'; - $args['strict'] = true; + $args['strict'] = false; // breaks mysql 8 $args['engine'] = null; break; diff --git a/src/ExceptionHandler.php b/src/ExceptionHandler.php index 33ab16b8..c744a573 100644 --- a/src/ExceptionHandler.php +++ b/src/ExceptionHandler.php @@ -112,7 +112,7 @@ class ExceptionHandler $is_http = false;//$exception instanceof HttpException; if (PHP_SAPI === 'cli' || (!$is_http && static::$debugMode)) { - if (PHP_SAPI !== 'cli') { + if (PHP_SAPI !== 'cli' && !headers_sent()) { http_response_code(500); header('Content-Type: text/plain'); } diff --git a/src/IO/Directory.php b/src/IO/Directory.php index be25aa62..e1332c8e 100644 --- a/src/IO/Directory.php +++ b/src/IO/Directory.php @@ -94,13 +94,18 @@ class Directory throw new DirectoryExistsException; } + $on_windows = running_on_windows(); $path = Directory::fixSlashes($path); $split_path = explode(self::SEPARATOR, $path); - $existing_path = running_on_windows() ? '' : self::SEPARATOR; + $existing_path = $on_windows ? '' : self::SEPARATOR; foreach ($split_path as $path_part) { $existing_path .= $path_part . self::SEPARATOR; + if ($on_windows && substr($existing_path, 1, 2) === ':\\') { + continue; + } + if (!Directory::exists($existing_path)) { mkdir($existing_path); } diff --git a/src/Model.php b/src/Model.php deleted file mode 100644 index 38399602..00000000 --- a/src/Model.php +++ /dev/null @@ -1,14 +0,0 @@ -hasMany(NewsPost::class, 'category_id'); - } -} diff --git a/src/News/NewsPost.php b/src/News/NewsPost.php deleted file mode 100644 index abfbd918..00000000 --- a/src/News/NewsPost.php +++ /dev/null @@ -1,41 +0,0 @@ -text($this->post_text); - } - - public function getUser(): ?User - { - if (empty($this->user_id) || $this->user_id < 1) { - return null; - } - - return User::find($this->user_id); - } - - public function getCategory(): ?NewsCategory - { - if (empty($this->category_id) || $this->category_id < 1) { - return null; - } - - return NewsCategory::find($this->category_id); - } -} diff --git a/src/Users/LoginAttempt.php b/src/Users/LoginAttempt.php deleted file mode 100644 index b05b6771..00000000 --- a/src/Users/LoginAttempt.php +++ /dev/null @@ -1,117 +0,0 @@ -was_successful = $success; - $attempt->attempt_ip = $ipAddress; - $attempt->user_agent = $userAgent ?? ''; - - if ($user !== null) { - $attempt->user_id = $user->user_id; - } - - $attempt->save(); - - return $attempt; - } - - /** - * Gets all login attempts from a given IP address. - * @param IPAddress $ipAddress - * @return Builder - */ - public static function fromIpAddress(IPAddress $ipAddress): Builder - { - return static::where('attempt_ip', $ipAddress->getRaw()); - } - - /** - * Setter for the IP address property. - * @param IPAddress $ipAddress - */ - public function setAttemptIpAttribute(IPAddress $ipAddress): void - { - $this->attributes['attempt_ip'] = $ipAddress->getRaw(); - $this->attributes['attempt_country'] = $ipAddress->getCountryCode(); - } - - /** - * Getter for the IP address property. - * @param string $ipAddress - * @return IPAddress - */ - public function getAttemptIpAttribute(string $ipAddress): IPAddress - { - return IPAddress::fromRaw($ipAddress); - } - - /** - * Object relation definition for User. - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } -} diff --git a/src/Users/Role.php b/src/Users/Role.php deleted file mode 100644 index 291712fe..00000000 --- a/src/Users/Role.php +++ /dev/null @@ -1,114 +0,0 @@ -role_hierarchy = $hierarchy; - $role->role_name = $name; - $role->role_title = $title; - $role->role_description = $description; - $role->role_secret = $secret; - $role->role_colour = $colour; - $role->save(); - - return $role; - } - - /** - * Adds this role to a user. - * @param User $user - * @param bool $setDisplay - */ - public function addUser(User $user, bool $setDisplay = false): void - { - $user->addRole($this, $setDisplay); - } - - /** - * Removes this role from a user. - * @param User $user - */ - public function removeUser(User $user): void - { - $user->removeRole($this); - } - - /** - * Checks if this user has this role. - * @param User $user - * @return bool - */ - public function hasUser(User $user): bool - { - return $user->hasRole($this); - } - - /** - * Getter for the role_description attribute. - * @param null|string $description - * @return string - */ - public function getRoleDescriptionAttribute(?string $description): string - { - return empty($description) ? '' : $description; - } - - /** - * Setter for the role_description attribute. - * @param string $description - */ - public function setRoleDescriptionAttribute(string $description): void - { - $this->attributes['role_description'] = empty($description) ? null : $description; - } - - /** - * Users relation. - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function users() - { - return $this->hasMany(UserRole::class, 'role_id'); - } -} diff --git a/src/Users/Session.php b/src/Users/Session.php deleted file mode 100644 index 45e86251..00000000 --- a/src/Users/Session.php +++ /dev/null @@ -1,108 +0,0 @@ -addMonth(); - - $session = new Session; - $session->user_id = $user->user_id; - $session->session_ip = $ipAddress; - $session->user_agent = $userAgent; - $session->expires_on = $expires; - $session->session_key = self::generateKey(); - $session->save(); - - return $session; - } - - /** - * Generates a random key. - * @return string - * @throws \Exception - */ - public static function generateKey(): string - { - return bin2hex(random_bytes(32)); - } - - /** - * Returns if a session has expired. - * @return bool - */ - public function hasExpired(): bool - { - return $this->expires_on->isPast(); - } - - /** - * Getter for the session_ip attribute. - * @param string $ipAddress - * @return IPAddress - */ - public function getSessionIpAttribute(string $ipAddress): IPAddress - { - return IPAddress::fromRaw($ipAddress); - } - - /** - * Setter for the session_ip attribute. - * @param IPAddress $ipAddress - */ - public function setSessionIpAttribute(IPAddress $ipAddress): void - { - $this->attributes['session_ip'] = $ipAddress->getRaw(); - $this->attributes['session_country'] = $ipAddress->getCountryCode(); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } -} diff --git a/src/Users/User.php b/src/Users/User.php deleted file mode 100644 index 5391dd4c..00000000 --- a/src/Users/User.php +++ /dev/null @@ -1,451 +0,0 @@ -username = $username; - $user->password = $password; - $user->email = $email; - $user->register_ip = $ipAddress; - $user->last_ip = $ipAddress; - $user->user_country = $ipAddress->getCountryCode(); - $user->save(); - - return $user; - } - - /** - * Tries to find a user for the login page. - * @param string $usernameOrEmail - * @return User|null - */ - public static function findLogin(string $usernameOrEmail): ?User - { - $usernameOrEmail = strtolower($usernameOrEmail); - return User::whereRaw("LOWER(`username`) = '{$usernameOrEmail}'") - ->orWhere('email', $usernameOrEmail) - ->first(); - } - - /** - * Validates a username string. - * @param string $username - * @param bool $checkInUse - * @return string - */ - public static function validateUsername(string $username, bool $checkInUse = false): string - { - $username_length = strlen($username); - - if ($username !== trim($username)) { - return 'trim'; - } - - if ($username_length < self::USERNAME_MIN_LENGTH) { - return 'short'; - } - - if ($username_length > self::USERNAME_MAX_LENGTH) { - return 'long'; - } - - if (strpos($username, ' ') !== false) { - return 'double-spaces'; - } - - if (!preg_match(self::USERNAME_REGEX, $username)) { - return 'invalid'; - } - - if (strpos($username, '_') !== false && strpos($username, ' ') !== false) { - return 'spacing'; - } - - if ($checkInUse && static::whereRaw("LOWER(`username`) = LOWER('{$username}')")->count() > 0) { - return 'in-use'; - } - - return ''; - } - - /** - * Validates an e-mail string. - * @param string $email - * @param bool $checkInUse - * @return string - */ - public static function validateEmail(string $email, bool $checkInUse = false): string - { - if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { - return 'format'; - } - - if (!check_mx_record($email)) { - return 'dns'; - } - - if ($checkInUse && static::whereRaw("LOWER(`email`) = LOWER('{$email}')")->count() > 0) { - return 'in-use'; - } - - return ''; - } - - /** - * Validates a password string. - * @param string $password - * @return string - */ - public static function validatePassword(string $password): string - { - if (password_entropy($password) < self::PASSWORD_MIN_ENTROPY) { - return 'weak'; - } - - return ''; - } - - /** - * Gets the user's display role, it's probably safe to assume that this will always return a valid role. - * @return Role|null - */ - public function getDisplayRole(): ?Role - { - if ($this->displayRoleInstance === null) { - $this->displayRoleInstance = Role::find($this->display_role); - } - - return $this->displayRoleInstance; - } - - /** - * Gets the display colour. - * @return int - */ - public function getColour(): int - { - $role = $this->getDisplayRole(); - return $role === null ? colour_none() : $role->role_colour; - } - - /** - * Gets the correct user title. - * @return string - */ - private function getUserTitlePrivate(): string - { - if (!empty($this->user_title)) { - return $this->user_title; - } - - $role = $this->getDisplayRole(); - - if ($role !== null && !empty($role->role_title)) { - return $role->role_title; - } - - return ''; - } - - /** - * Gets the user title (with memoization). - * @return string - */ - public function getUserTitle(): string - { - if (empty($this->userTitleValue)) { - $this->userTitleValue = $this->getUserTitlePrivate(); - } - - return $this->userTitleValue; - } - - /** - * Assigns a role. - * @param Role $role - * @param bool $setDisplay - */ - public function addRole(Role $role, bool $setDisplay = false): void - { - $relation = new UserRole; - $relation->user_id = $this->user_id; - $relation->role_id = $role->role_id; - $relation->save(); - - if ($setDisplay) { - $this->display_role = $role->role_id; - } - } - - /** - * Removes a role. - * @param Role $role - */ - public function removeRole(Role $role): void - { - UserRole::where('user_id', $this->user_id) - ->where('role_id', $role->user_id) - ->delete(); - } - - /** - * Checks if a role is assigned. - * @param Role $role - * @return bool - */ - public function hasRole(Role $role): bool - { - return UserRole::where('user_id', $this->user_id) - ->where('role_id', $role->role_id) - ->count() > 0; - } - - /** - * Verifies a password. - * @param string $password - * @return bool - */ - public function verifyPassword(string $password): bool - { - if (password_verify($password, $this->password) !== true) { - return false; - } - - if (password_needs_rehash($this->password, self::PASSWORD_HASH_ALGO)) { - $this->password = $password; - $this->save(); - } - - return true; - } - - /** - * Getter for the display_role attribute. - * @param int|null $value - * @return int - */ - public function getDisplayRoleAttribute(?int $value): int - { - if (!$this->displayRoleValidated) { - if ($value === null - || UserRole::where('user_id', $this->user_id)->where('role_id', $value)->count() < 1) { - $highestRole = DatabaseV1::table('roles') - ->join('user_roles', 'roles.role_id', '=', 'user_roles.role_id') - ->where('user_id', $this->user_id) - ->orderBy('roles.role_hierarchy', 'desc') - ->first(['roles.role_id']); - - $value = $highestRole->role_id; - $this->display_role = $value; - $this->save(); - } - - $this->displayRoleValidated = true; - } - - return $value; - } - - /** - * Setter for the display_role attribute. - * @param int $value - */ - public function setDisplayRoleAttribute(int $value): void - { - if (UserRole::where('user_id', $this->user_id)->where('role_id', $value)->count() > 0) { - $this->attributes['display_role'] = $value; - } - } - - /** - * @param null|string $dateTime - * @return Carbon - */ - public function getLastSeenAttribute(?string $dateTime): Carbon - { - return $dateTime === null ? Carbon::createFromTimestamp(-1) : new Carbon($dateTime); - } - - /** - * Getter for the register_ip attribute. - * @param string $ipAddress - * @return IPAddress - */ - public function getRegisterIpAttribute(string $ipAddress): IPAddress - { - return IPAddress::fromRaw($ipAddress); - } - - /** - * Setter for the register_ip attribute. - * @param IPAddress $ipAddress - */ - public function setRegisterIpAttribute(IPAddress $ipAddress): void - { - $this->attributes['register_ip'] = $ipAddress->getRaw(); - } - - /** - * Getter for the last_ip attribute. - * @param string $ipAddress - * @return IPAddress - */ - public function getLastIpAttribute(string $ipAddress): IPAddress - { - return IPAddress::fromRaw($ipAddress); - } - - /** - * Setter for the last_ip attribute. - * @param IPAddress $ipAddress - */ - public function setLastIpAttribute(IPAddress $ipAddress): void - { - $this->attributes['last_ip'] = $ipAddress->getRaw(); - } - - /** - * Setter for the password attribute. - * @param string $password - */ - public function setPasswordAttribute(string $password): void - { - $this->attributes['password'] = password_hash($password, self::PASSWORD_HASH_ALGO); - } - - /** - * Setter for the email attribute. - * @param string $email - */ - public function setEmailAttribute(string $email): void - { - $this->attributes['email'] = strtolower($email); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function sessions() - { - return $this->hasMany(Session::class, 'user_id'); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function roles() - { - return $this->hasMany(UserRole::class, 'user_id'); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function loginAttempts() - { - return $this->hasMany(LoginAttempt::class, 'user_id'); - } -} diff --git a/src/Users/UserRole.php b/src/Users/UserRole.php deleted file mode 100644 index a1a7ece9..00000000 --- a/src/Users/UserRole.php +++ /dev/null @@ -1,35 +0,0 @@ -belongsTo(User::class, 'user_id'); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function role() - { - return $this->belongsTo(Role::class, 'role_id'); - } -} diff --git a/src/Users/login_attempt.php b/src/Users/login_attempt.php new file mode 100644 index 00000000..bbf28bd3 --- /dev/null +++ b/src/Users/login_attempt.php @@ -0,0 +1,19 @@ +prepare(' + INSERT INTO `msz_login_attempts` + (`was_successful`, `attempt_ip`, `attempt_country`, `user_id`, `user_agent`, `created_at`) + VALUES + (:was_successful, INET6_ATON(:attempt_ip), :attempt_country, :user_id, :user_agent, NOW()) + '); + + $storeAttempt->bindValue('was_successful', $success ? 1 : 0); + $storeAttempt->bindValue('attempt_ip', $ipAddress); + $storeAttempt->bindValue('attempt_country', get_country_code($ipAddress)); + $storeAttempt->bindValue('user_agent', $userAgent); + $storeAttempt->bindValue('user_id', $userId, $userId === null ? PDO::PARAM_NULL : PDO::PARAM_INT); + $storeAttempt->execute(); +} diff --git a/src/Users/validation.php b/src/Users/validation.php new file mode 100644 index 00000000..cfa8ec3d --- /dev/null +++ b/src/Users/validation.php @@ -0,0 +1,95 @@ + MSZ_USERNAME_MAX_LENGTH) { + return 'long'; + } + + if (strpos($username, ' ') !== false) { + return 'double-spaces'; + } + + if (!preg_match(MSZ_USERNAME_REGEX, $username)) { + return 'invalid'; + } + + if (strpos($username, '_') !== false && strpos($username, ' ') !== false) { + return 'spacing'; + } + + if ($checkInUse) { + $getUser = Database::connection()->prepare(' + SELECT COUNT(`user_id`) + FROM `msz_users` + WHERE LOWER(`username`) = LOWER(:username) + '); + $getUser->bindValue('username', $username); + $userId = $getUser->execute() ? $getUser->fetchColumn() : 0; + + if ($userId > 0) { + return 'in-use'; + } + } + + return ''; +} + +function user_validate_email(string $email, bool $checkInUse = false): string +{ + if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { + return 'format'; + } + + if (!check_mx_record($email)) { + return 'dns'; + } + + if ($checkInUse) { + $getUser = Database::connection()->prepare(' + SELECT COUNT(`user_id`) + FROM `msz_users` + WHERE LOWER(`email`) = LOWER(:email) + '); + $getUser->bindValue('email', $email); + $userId = $getUser->execute() ? $getUser->fetchColumn() : 0; + + if ($userId > 0) { + return 'in-use'; + } + } + + return ''; +} + +function user_validate_password(string $password): string +{ + if (password_entropy($password) < MSZ_PASSWORD_MIN_ENTROPY) { + return 'weak'; + } + + return ''; +} diff --git a/tests/UserTest.php b/tests/UserTest.php index 1e61268b..a83eee7c 100644 --- a/tests/UserTest.php +++ b/tests/UserTest.php @@ -2,18 +2,17 @@ namespace MisuzuTests; use PHPUnit\Framework\TestCase; -use Misuzu\Users\User; class UserTest extends TestCase { public function testUsernameValidation() { - $this->assertEquals(User::validateUsername('flashwave'), ''); - $this->assertEquals(User::validateUsername(' flash '), 'trim'); - $this->assertEquals(User::validateUsername('f'), 'short'); - $this->assertEquals(User::validateUsername('flaaaaaaaaaaaaaaaash'), 'long'); - $this->assertEquals(User::validateUsername('F|@$h'), 'invalid'); - $this->assertEquals(User::validateUsername('fl ash_wave'), 'spacing'); - $this->assertEquals(User::validateUsername('fl ash'), 'double-spaces'); + $this->assertEquals(user_validate_username('flashwave'), ''); + $this->assertEquals(user_validate_username(' flash '), 'trim'); + $this->assertEquals(user_validate_username('f'), 'short'); + $this->assertEquals(user_validate_username('flaaaaaaaaaaaaaaaash'), 'long'); + $this->assertEquals(user_validate_username('F|@$h'), 'invalid'); + $this->assertEquals(user_validate_username('fl ash_wave'), 'spacing'); + $this->assertEquals(user_validate_username('fl ash'), 'double-spaces'); } } diff --git a/utility.php b/utility.php index d85d1132..c9128906 100644 --- a/utility.php +++ b/utility.php @@ -133,22 +133,14 @@ function get_country_name(string $code): string // this is temporary, don't scream at me for using md5 // BIG TODO: make these functions not dependent on sessions so they can be used outside of those. -function tmp_csrf_verify(string $token, ?\Misuzu\Users\Session $session = null): bool +function tmp_csrf_verify(string $token): bool { - if ($session === null) { - $session = \Misuzu\Application::getInstance()->getSession(); - } - - return hash_equals(tmp_csrf_token($session), $token); + return hash_equals(tmp_csrf_token(), $token); } -function tmp_csrf_token(?\Misuzu\Users\Session $session = null): string +function tmp_csrf_token(): string { - if ($session === null) { - $session = \Misuzu\Application::getInstance()->getSession(); - } - - return md5($session->session_key); + return md5($_COOKIE['msz_sid'] ?? 'this is very insecure lmao'); } function crop_image_centred_path(string $filename, int $target_width, int $target_height): \Imagick @@ -218,3 +210,23 @@ function is_valid_page(\Illuminate\Pagination\LengthAwarePaginator $paginator, i { return $attemptedPage >= 1 && $attemptedPage <= $paginator->lastPage(); } + +function pdo_prepare_array_update(array $keys, bool $useKeys = false, string $format = '%s'): string +{ + return pdo_prepare_array($keys, $useKeys, sprintf($format, '`%1$s` = :%1$s')); +} + +function pdo_prepare_array(array $keys, bool $useKeys = false, string $format = '`%s`'): string +{ + $parts = []; + + if ($useKeys) { + $keys = array_keys($keys); + } + + foreach ($keys as $key) { + $parts[] = sprintf($format, $key); + } + + return implode(', ', $parts); +} diff --git a/views/manage/master.twig b/views/manage/master.twig index 550ec7a1..b129a41b 100644 --- a/views/manage/master.twig +++ b/views/manage/master.twig @@ -136,10 +136,10 @@
- +
diff --git a/views/manage/users/listing.twig b/views/manage/users/listing.twig index fab815eb..2be00332 100644 --- a/views/manage/users/listing.twig +++ b/views/manage/users/listing.twig @@ -16,6 +16,6 @@
- {{ paginate(manage_users, '?v=listing') }} + {# paginate(manage_users, '?v=listing') #}
{% endblock %} diff --git a/views/manage/users/roles.twig b/views/manage/users/roles.twig index 4ecb1daf..6e21d1b8 100644 --- a/views/manage/users/roles.twig +++ b/views/manage/users/roles.twig @@ -11,13 +11,13 @@
{{ role.role_name }} - {{ role.users.count }} users + {{ role.users }} users
{% endfor %}
- {{ paginate(manage_roles, '?v=roles') }} + {# paginate(manage_roles, '?v=roles') #}
{% endblock %} diff --git a/views/manage/users/view.twig b/views/manage/users/view.twig index 885c2292..38a1693c 100644 --- a/views/manage/users/view.twig +++ b/views/manage/users/view.twig @@ -23,14 +23,14 @@ diff --git a/views/mio/home/landing.twig b/views/mio/home/landing.twig index 3a43a4d9..40a9b96f 100644 --- a/views/mio/home/landing.twig +++ b/views/mio/home/landing.twig @@ -5,7 +5,7 @@ {% set canonical_url = '/' %} {% block content %} - {% if app.session != null %} + {% if app.hasActiveSession %}
Welcome
diff --git a/views/mio/master.twig b/views/mio/master.twig index 4a44b0d5..9feeccab 100644 --- a/views/mio/master.twig +++ b/views/mio/master.twig @@ -25,17 +25,17 @@
- {% if app.session is not null %} + {% if app.hasActiveSession %}
-
Hey, {{ app.session.user.username }}!
+
Hey, {{ current_user.username }}!
- +
{% endif %} - {{ paginate(posts, '?c=' ~ category.category_id, 'news__') }} + {# paginate(posts, '?c=' ~ category.category_id, 'news__') #}
diff --git a/views/mio/news/index.twig b/views/mio/news/index.twig index add65609..1afd3df2 100644 --- a/views/mio/news/index.twig +++ b/views/mio/news/index.twig @@ -23,22 +23,18 @@
{% for category in categories %} - {% set post_count = category.posts.count %} - - {% if post_count > 0 %} - -
- {{ category.category_name }} -
-
- {{ post_count }} post{{ post_count == 1 ? '' : 's' }} -
-
- {% endif %} + +
+ {{ category.category_name }} +
+
+ {{ category.count }} post{{ category.count == 1 ? '' : 's' }} +
+
{% endfor %}
- {{ paginate(posts, '', 'news__') }} + {# paginate(posts, '', 'news__') #} diff --git a/views/mio/news/macros.twig b/views/mio/news/macros.twig index a4568316..b98b783e 100644 --- a/views/mio/news/macros.twig +++ b/views/mio/news/macros.twig @@ -5,18 +5,18 @@
- {{ post.html|first_paragraph|raw }} + {{ post.post_text|first_paragraph|raw }}

View full post

diff --git a/views/mio/news/post.twig b/views/mio/news/post.twig index ee089dbc..a57967ef 100644 --- a/views/mio/news/post.twig +++ b/views/mio/news/post.twig @@ -6,27 +6,27 @@ {% block news_content %}
- {{ post.html|raw }} + {{ post.post_text|raw }}
- -
{{ post.user.username }}
-
+
+
{{ post.username }}
+
- diff --git a/views/mio/settings/account.twig b/views/mio/settings/account.twig index b7596106..cdd8355a 100644 --- a/views/mio/settings/account.twig +++ b/views/mio/settings/account.twig @@ -12,7 +12,7 @@ {{ props.name }}
{% endfor %} diff --git a/views/mio/settings/avatar.twig b/views/mio/settings/avatar.twig index 3c0e032e..40e23fc2 100644 --- a/views/mio/settings/avatar.twig +++ b/views/mio/settings/avatar.twig @@ -28,12 +28,12 @@
-
+