diff --git a/database/2020_05_25_152331_sessions_table_fixes.php b/database/2020_05_25_152331_sessions_table_fixes.php new file mode 100644 index 00000000..9820beb9 --- /dev/null +++ b/database/2020_05_25_152331_sessions_table_fixes.php @@ -0,0 +1,22 @@ +<?php +namespace Misuzu\DatabaseMigrations\SessionsTableFixes; + +use PDO; + +function migrate_up(PDO $conn): void { + $conn->exec(" + ALTER TABLE `msz_sessions` + CHANGE COLUMN `session_key` `session_key` BINARY(64) NOT NULL AFTER `user_id`, + CHANGE COLUMN `session_expires` `session_expires` TIMESTAMP NOT NULL DEFAULT DATE_ADD(NOW(), INTERVAL 1 MONTH) AFTER `session_country`, + ADD INDEX `sessions_created_index` (`session_created`); + "); +} + +function migrate_down(PDO $conn): void { + $conn->exec(" + ALTER TABLE `msz_sessions` + CHANGE COLUMN `session_key` `session_key` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_bin' AFTER `user_id`, + CHANGE COLUMN `session_expires` `session_expires` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP() AFTER `session_country`, + DROP INDEX `sessions_created_index`; + "); +} diff --git a/misuzu.php b/misuzu.php index f7112a0e..41140f9f 100644 --- a/misuzu.php +++ b/misuzu.php @@ -8,6 +8,8 @@ use Misuzu\Net\GeoIP; use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; +use Misuzu\Users\UserSession; +use Misuzu\Users\UserSessionNotFoundException; define('MSZ_STARTUP', microtime(true)); define('MSZ_ROOT', __DIR__); @@ -418,19 +420,49 @@ MIG; exit; } - if(!empty($_COOKIE['msz_uid']) && !empty($_COOKIE['msz_sid']) - && ctype_digit($_COOKIE['msz_uid']) && ctype_xdigit($_COOKIE['msz_sid']) - && strlen($_COOKIE['msz_sid']) === 64) { - $_COOKIE['msz_auth'] = Base64::decode(user_session_cookie_pack($_COOKIE['msz_uid'], $_COOKIE['msz_sid']), true); - setcookie('msz_auth', $_COOKIE['msz_auth'], strtotime('1 year'), '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); + if(isset($_COOKIE['msz_uid']) && isset($_COOKIE['msz_sid'])) { + $authToken = (new AuthToken) + ->setUserId(filter_input(INPUT_COOKIE, 'msz_uid', FILTER_SANITIZE_NUMBER_INT) ?? 0) + ->setSessionToken(filter_input(INPUT_COOKIE, 'msz_sid', FILTER_SANITIZE_STRING) ?? ''); + + if($authToken->isValid()) + setcookie('msz_auth', $authToken->pack(), strtotime('1 year'), '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); + setcookie('msz_uid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true); setcookie('msz_sid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true); } - if(!empty($_COOKIE['msz_auth']) && is_string($_COOKIE['msz_auth'])) { - $cookieData = user_session_cookie_unpack(Base64::decode($_COOKIE['msz_auth'], true)); + if(!isset($authToken)) + $authToken = AuthToken::unpack(filter_input(INPUT_COOKIE, 'msz_auth', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH) ?? ''); + if($authToken->isValid()) { + try { + $sessionInfo = $authToken->getSession(); + if($sessionInfo->hasExpired()) { + $sessionInfo->delete(); + } elseif($sessionInfo->getUserId() === $authToken->getUserId()) { + $userInfo = $sessionInfo->getUser(); + if(!$userInfo->isDeleted()) { + $sessionInfo->setCurrent(); + $userInfo->setCurrent(); - if(!empty($cookieData) && user_session_start($cookieData['user_id'], $cookieData['session_token'])) { + $sessionInfo->bump(); + + if($sessionInfo->shouldBumpExpire()) + setcookie('msz_auth', $authToken->pack(), $sessionInfo->getExpiresTime(), '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); + } + } + } catch(UserNotFoundException $ex) { + UserSession::unsetCurrent(); + User::unsetCurrent(); + } catch(UserSessionNotFoundException $ex) { + UserSession::unsetCurrent(); + User::unsetCurrent(); + } + + if(!UserSession::hasCurrent()) { + setcookie('msz_auth', '', -9001, '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); + setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true); + } else { $userDisplayInfo = DB::prepare(' SELECT u.`user_id`, u.`username`, u.`user_background_settings`, u.`user_deleted`, @@ -439,38 +471,19 @@ MIG; LEFT JOIN `msz_roles` AS r ON u.`display_role` = r.`role_id` WHERE `user_id` = :user_id - '); - $userDisplayInfo->bind('user_id', $cookieData['user_id']); - $userDisplayInfo = $userDisplayInfo->fetch(); + ') ->bind('user_id', $userInfo->getId()) + ->fetch(); - if($userDisplayInfo) { - if(!is_null($userDisplayInfo['user_deleted'])) { - setcookie('msz_auth', '', -9001, '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); - setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true); - user_session_stop(true); - $userDisplayInfo = []; - } else { - try { - User::byId($cookieData['user_id'])->setCurrent(); - } catch(UserNotFoundException $ex) {} + user_bump_last_active($userInfo->getId()); - user_bump_last_active($cookieData['user_id']); - user_session_bump_active(user_session_current('session_id')); - - if(user_session_current('session_expires_bump')) { - setcookie('msz_auth', $_COOKIE['msz_auth'], strtotime('1 month'), '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); - } - - $userDisplayInfo['perms'] = perms_get_user($userDisplayInfo['user_id']); - $userDisplayInfo['ban_expiration'] = user_warning_check_expiration($userDisplayInfo['user_id'], MSZ_WARN_BAN); - $userDisplayInfo['silence_expiration'] = $userDisplayInfo['ban_expiration'] > 0 ? 0 : user_warning_check_expiration($userDisplayInfo['user_id'], MSZ_WARN_SILENCE); - } - } + $userDisplayInfo['perms'] = perms_get_user($userInfo->getId()); + $userDisplayInfo['ban_expiration'] = user_warning_check_expiration($userInfo->getId(), MSZ_WARN_BAN); + $userDisplayInfo['silence_expiration'] = $userDisplayInfo['ban_expiration'] > 0 ? 0 : user_warning_check_expiration($userInfo->getId(), MSZ_WARN_SILENCE); } } CSRF::setGlobalSecretKey(Config::get('csrf.secret', Config::TYPE_STR, 'soup')); - CSRF::setGlobalIdentity(empty($userDisplayInfo) ? IPAddress::remote() : $cookieData['session_token']); + CSRF::setGlobalIdentity(UserSession::hasCurrent() ? UserSession::getCurrent()->getToken() : IPAddress::remote()); if(Config::get('private.enabled', Config::TYPE_BOOL)) { $onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login'); @@ -478,14 +491,16 @@ MIG; $misuzuBypassLockdown = !empty($misuzuBypassLockdown) || $onLoginPage; if(!$misuzuBypassLockdown) { - if(user_session_active()) { + if(UserSession::hasCurrent()) { $privatePermCat = Config::get('private.perm.cat', Config::TYPE_STR); $privatePermVal = Config::get('private.perm.val', Config::TYPE_INT); if(!empty($privatePermCat) && $privatePermVal > 0) { - if(!perms_check_user($privatePermCat, $userDisplayInfo['user_id'], $privatePermVal)) { + if(!perms_check_user($privatePermCat, User::getCurrent()->getId(), $privatePermVal)) { + // au revoir unset($userDisplayInfo); - user_session_stop(); // au revoir + UserSession::unsetCurrent(); + User::unsetCurrent(); } } } elseif(!$onLoginPage && !($onPasswordPage && Config::get('private.allow_password_reset', Config::TYPE_BOOL, true))) { @@ -495,14 +510,13 @@ MIG; } } - if(!empty($userDisplayInfo)) { + if(!empty($userDisplayInfo)) // delete this Template::set('current_user', $userDisplayInfo); - } $inManageMode = starts_with($_SERVER['REQUEST_URI'], '/manage'); - $hasManageAccess = !empty($userDisplayInfo['user_id']) - && !user_warning_check_restriction($userDisplayInfo['user_id']) - && perms_check_user(MSZ_PERMS_GENERAL, $userDisplayInfo['user_id'], MSZ_PERM_GENERAL_CAN_MANAGE); + $hasManageAccess = User::hasCurrent() + && !user_warning_check_restriction(User::getCurrent()->getId()) + && perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_CAN_MANAGE); Template::set('has_manage_access', $hasManageAccess); if($inManageMode) { @@ -511,6 +525,6 @@ MIG; exit; } - Template::set('manage_menu', manage_get_menu($userDisplayInfo['user_id'] ?? 0)); + Template::set('manage_menu', manage_get_menu(User::getCurrent()->getId())); } } diff --git a/public/auth/login.php b/public/auth/login.php index 9f4ec1b8..73fd74b6 100644 --- a/public/auth/login.php +++ b/public/auth/login.php @@ -1,14 +1,17 @@ <?php namespace Misuzu; +use Misuzu\AuthToken; use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; use Misuzu\Users\UserLoginAttempt; +use Misuzu\Users\UserSession; +use Misuzu\Users\UserSessionCreationFailedException; require_once '../../misuzu.php'; -if(user_session_active()) { +if(UserSession::hasCurrent()) { url_redirect('index'); return; } @@ -23,7 +26,6 @@ $notices = []; $siteIsPrivate = Config::get('private.enable', Config::TYPE_BOOL); $loginPermCat = $siteIsPrivate ? Config::get('private.perm.cat', Config::TYPE_STR) : ''; $loginPermVal = $siteIsPrivate ? Config::get('private.perm.val', Config::TYPE_INT) : 0; -$ipAddress = IPAddress::remote(); $remainingAttempts = UserLoginAttempt::remaining(); while(!empty($_POST['login']) && is_array($_POST['login'])) { @@ -32,7 +34,6 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) { break; } - $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $loginRedirect = empty($_POST['login']['redirect']) || !is_string($_POST['login']['redirect']) ? '' : $_POST['login']['redirect']; if(empty($_POST['login']['username']) || empty($_POST['login']['password']) @@ -54,58 +55,56 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) { $loginFailedError = "Invalid username or password, {$attemptsRemainingError}."; try { - $userData = User::findForLogin($_POST['login']['username']); + $userInfo = User::findForLogin($_POST['login']['username']); } catch(UserNotFoundException $ex) { UserLoginAttempt::create(false); $notices[] = $loginFailedError; break; } - if(!$userData->hasPassword()) { + if(!$userInfo->hasPassword()) { $notices[] = 'Your password has been invalidated, please reset it.'; break; } - if($userData->isDeleted() || !$userData->checkPassword($_POST['login']['password'])) { - UserLoginAttempt::create(false, $userData); + if($userInfo->isDeleted() || !$userInfo->checkPassword($_POST['login']['password'])) { + UserLoginAttempt::create(false, $userInfo); $notices[] = $loginFailedError; break; } - if($userData->passwordNeedsRehash()) { - $userData->setPassword($_POST['login']['password']); + if($userInfo->passwordNeedsRehash()) { + $userInfo->setPassword($_POST['login']['password']); } - if(!empty($loginPermCat) && $loginPermVal > 0 && !perms_check_user($loginPermCat, $userData->getId(), $loginPermVal)) { + if(!empty($loginPermCat) && $loginPermVal > 0 && !perms_check_user($loginPermCat, $userInfo->getId(), $loginPermVal)) { $notices[] = "Login succeeded, but you're not allowed to browse the site right now."; - UserLoginAttempt::create(true, $userData); + UserLoginAttempt::create(true, $userInfo); break; } - if($userData->hasTOTP()) { + if($userInfo->hasTOTP()) { url_redirect('auth-two-factor', [ - 'token' => user_auth_tfa_token_create($userData->getId()), + 'token' => user_auth_tfa_token_create($userInfo->getId()), ]); return; } - UserLoginAttempt::create(true, $userData); - $sessionKey = user_session_create($userData->getId(), $ipAddress, $userAgent); + UserLoginAttempt::create(true, $userInfo); - if(empty($sessionKey)) { + try { + $sessionInfo = UserSession::create($userInfo); + $sessionInfo->setCurrent(); + } catch(UserSessionCreationFailedException $ex) { $notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!"; break; } - user_session_start($userData->getId(), $sessionKey); + $authToken = AuthToken::create($userInfo, $sessionInfo); + setcookie('msz_auth', $authToken->pack(), $sessionInfo->getExpiresTime(), '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); - $cookieLife = strtotime(user_session_current('session_expires')); - $cookieValue = Base64::encode(user_session_cookie_pack($userData->getId(), $sessionKey), true); - setcookie('msz_auth', $cookieValue, $cookieLife, '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); - - if(!is_local_url($loginRedirect)) { + if(!is_local_url($loginRedirect)) $loginRedirect = url('index'); - } redirect($loginRedirect); return; diff --git a/public/auth/logout.php b/public/auth/logout.php index c6e8d66f..e7640aa3 100644 --- a/public/auth/logout.php +++ b/public/auth/logout.php @@ -1,9 +1,12 @@ <?php namespace Misuzu; +use Misuzu\Users\User; +use Misuzu\Users\UserSession; + require_once '../../misuzu.php'; -if(!user_session_active()) { +if(!UserSession::hasCurrent()) { url_redirect('index'); return; } @@ -11,7 +14,9 @@ if(!user_session_active()) { if(CSRF::validateRequest()) { setcookie('msz_auth', '', -9001, '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true); - user_session_stop(true); + UserSession::getCurrent()->delete(); + UserSession::unsetCurrent(); + User::unsetCurrent(); url_redirect('index'); return; } diff --git a/public/auth/password.php b/public/auth/password.php index ab6d0c1b..821a3c26 100644 --- a/public/auth/password.php +++ b/public/auth/password.php @@ -6,10 +6,11 @@ use Misuzu\AuditLog; use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\UserLoginAttempt; +use Misuzu\Users\UserSession; require_once '../../misuzu.php'; -if(user_session_active()) { +if(UserSession::hasCurrent()) { url_redirect('settings-account'); return; } diff --git a/public/auth/register.php b/public/auth/register.php index a961171b..b6f68ca6 100644 --- a/public/auth/register.php +++ b/public/auth/register.php @@ -5,10 +5,11 @@ use Misuzu\Net\IPAddress; use Misuzu\Net\IPAddressBlacklist; use Misuzu\Users\User; use Misuzu\Users\UserLoginAttempt; +use Misuzu\Users\UserSession; require_once '../../misuzu.php'; -if(user_session_active()) { +if(UserSession::hasCurrent()) { url_redirect('index'); return; } diff --git a/public/auth/twofactor.php b/public/auth/twofactor.php index c7fdf5f0..231034b4 100644 --- a/public/auth/twofactor.php +++ b/public/auth/twofactor.php @@ -4,10 +4,12 @@ namespace Misuzu; use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\UserLoginAttempt; +use Misuzu\Users\UserSession; +use Misuzu\Users\UserSessionCreationFailedException; require_once '../../misuzu.php'; -if(user_session_active()) { +if(UserSession::hasCurrent()) { url_redirect('index'); return; } @@ -22,11 +24,11 @@ $tokenInfo = user_auth_tfa_token_info( ) ); -$userData = User::byId($tokenInfo['user_id']); +$userInfo = User::byId($tokenInfo['user_id']); // checking user_totp_key specifically because there's a fringe chance that // there's a token present, but totp is actually disabled -if(!$userData->hasTOTP()) { +if(!$userInfo->hasTOTP()) { url_redirect('auth-login'); return; } @@ -50,30 +52,29 @@ while(!empty($twofactor)) { break; } - if(!in_array($twofactor['code'], $userData->getValidTOTPTokens())) { + if(!in_array($twofactor['code'], $userInfo->getValidTOTPTokens())) { $notices[] = sprintf( "Invalid two factor code, %d attempt%s remaining", $remainingAttempts - 1, $remainingAttempts === 2 ? '' : 's' ); - UserLoginAttempt::create(false, $userData); + UserLoginAttempt::create(false, $userInfo); break; } - UserLoginAttempt::create(true, $userData); - $sessionKey = user_session_create($tokenInfo['user_id'], $ipAddress, $userAgent); + UserLoginAttempt::create(true, $userInfo); + user_auth_tfa_token_invalidate($tokenInfo['tfa_token']); - if(empty($sessionKey)) { + try { + $sessionInfo = UserSession::create($userInfo); + $sessionInfo->setCurrent(); + } catch(UserSessionCreationFailedException $ex) { $notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!"; break; } - user_auth_tfa_token_invalidate($tokenInfo['tfa_token']); - user_session_start($tokenInfo['user_id'], $sessionKey); - - $cookieLife = strtotime(user_session_current('session_expires')); - $cookieValue = Base64::encode(user_session_cookie_pack($tokenInfo['user_id'], $sessionKey), true); - setcookie('msz_auth', $cookieValue, $cookieLife, '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); + $authToken = AuthToken::create($userInfo, $sessionInfo); + setcookie('msz_auth', $authToken->pack(), $sessionInfo->getExpiresTime(), '/', '.' . $_SERVER['HTTP_HOST'], !empty($_SERVER['HTTPS']), true); if(!is_local_url($redirect)) { $redirect = url('index'); diff --git a/public/forum/forum.php b/public/forum/forum.php index 4b698244..4e4d29eb 100644 --- a/public/forum/forum.php +++ b/public/forum/forum.php @@ -1,6 +1,8 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../misuzu.php'; $forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0; @@ -12,7 +14,8 @@ if($forumId === 0) { } $forum = forum_get($forumId); -$forumUserId = user_session_current('user_id', 0); +$forumUser = User::getCurrent(); +$forumUserId = $forumUser === null ? 0 : $forumUser->getId(); if(empty($forum) || ($forum['forum_type'] == MSZ_FORUM_TYPE_LINK && empty($forum['forum_link']))) { echo render_error(404); diff --git a/public/forum/index.php b/public/forum/index.php index f2b2608d..e168c337 100644 --- a/public/forum/index.php +++ b/public/forum/index.php @@ -1,25 +1,27 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../misuzu.php'; $indexMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : ''; $forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0; +$currentUser = User::getCurrent(); +$currentUserId = $currentUser === null ? 0 : $currentUser->getId(); + switch($indexMode) { case 'mark': url_redirect($forumId < 1 ? 'forum-mark-global' : 'forum-mark-single', ['forum' => $forumId]); break; default: - $categories = forum_get_root_categories(user_session_current('user_id', 0)); + $categories = forum_get_root_categories($currentUserId); $blankForum = count($categories) < 1; foreach($categories as $key => $category) { - $categories[$key]['forum_subforums'] = forum_get_children( - $category['forum_id'], - user_session_current('user_id', 0) - ); + $categories[$key]['forum_subforums'] = forum_get_children($category['forum_id'], $currentUserId); foreach($categories[$key]['forum_subforums'] as $skey => $sub) { if(!forum_may_have_children($sub['forum_type'])) { @@ -27,10 +29,7 @@ switch($indexMode) { } $categories[$key]['forum_subforums'][$skey]['forum_subforums'] - = forum_get_children( - $sub['forum_id'], - user_session_current('user_id', 0) - ); + = forum_get_children($sub['forum_id'], $currentUserId); } } diff --git a/public/forum/leaderboard.php b/public/forum/leaderboard.php index 90783cb5..97eda9b9 100644 --- a/public/forum/leaderboard.php +++ b/public/forum/leaderboard.php @@ -1,9 +1,11 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_FORUM, user_session_current('user_id'), MSZ_PERM_FORUM_VIEW_LEADERBOARD)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_FORUM, User::getCurrent()->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD)) { echo render_error(403); return; } @@ -15,7 +17,7 @@ $leaderboardId = !empty($_GET['id']) && is_string($_GET['id']) : MSZ_FORUM_LEADERBOARD_CATEGORY_ALL; $leaderboardIdLength = strlen($leaderboardId); -$leaderboardYear = $leaderboardIdLength === 4 || $leaderboardIdLength === 6 ? substr($leaderboardId, 0, 4) : null; +$leaderboardYear = $leaderboardIdLength === 4 || $leaderboardIdLength === 6 ? substr($leaderboardId, 0, 4) : null; $leaderboardMonth = $leaderboardIdLength === 6 ? substr($leaderboardId, 4, 2) : null; $unrankedForums = !empty($_GET['allow_unranked']) ? [] : Config::get('forum_leader.unranked.forum', Config::TYPE_ARR); diff --git a/public/forum/poll.php b/public/forum/poll.php index 69bca4c2..fe3832ef 100644 --- a/public/forum/poll.php +++ b/public/forum/poll.php @@ -1,6 +1,8 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../misuzu.php'; $redirect = !empty($_SERVER['HTTP_REFERER']) && empty($_SERVER['HTTP_X_MISUZU_XHR']) ? $_SERVER['HTTP_REFERER'] : ''; @@ -18,12 +20,14 @@ if(!CSRF::validateRequest()) { return; } -if(!user_session_active()) { +$currentUser = User::getCurrent(); + +if($currentUser === null) { echo render_info_or_json($isXHR, 'You must be logged in to vote on polls.', 401); return; } -$currentUserId = user_session_current('user_id', 0); +$currentUserId = $currentUser->getId(); if(user_warning_check_expiration($currentUserId, MSZ_WARN_BAN) > 0) { echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); diff --git a/public/forum/post.php b/public/forum/post.php index 9c0bf257..2663142c 100644 --- a/public/forum/post.php +++ b/public/forum/post.php @@ -2,6 +2,8 @@ namespace Misuzu; use Misuzu\AuditLog; +use Misuzu\Users\User; +use Misuzu\Users\UserSession; require_once '../../misuzu.php'; @@ -23,12 +25,13 @@ if($isXHR) { $postRequestVerified = CSRF::validateRequest(); -if(!empty($postMode) && !user_session_active()) { +if(!empty($postMode) && !UserSession::hasCurrent()) { echo render_info_or_json($isXHR, 'You must be logged in to manage posts.', 401); return; } -$currentUserId = (int)user_session_current('user_id', 0); +$currentUser = User::getCurrent(): +$currentUserId = $currentUser === null ? 0 : $currentUser->getId(); if(user_warning_check_expiration($currentUserId, MSZ_WARN_BAN) > 0) { echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); @@ -251,7 +254,7 @@ switch($postMode) { break; } - $postFind = forum_post_find($postInfo['post_id'], user_session_current('user_id', 0)); + $postFind = forum_post_find($postInfo['post_id'], $currentUserId); if(empty($postFind)) { echo render_error(404); diff --git a/public/forum/posting.php b/public/forum/posting.php index 2827e514..efaf5908 100644 --- a/public/forum/posting.php +++ b/public/forum/posting.php @@ -3,15 +3,20 @@ namespace Misuzu; use Misuzu\Net\IPAddress; use Misuzu\Parsers\Parser; +use Misuzu\Users\User; require_once '../../misuzu.php'; -if(!user_session_active()) { +$currentUser = User::getCurrent(); + +if($currentUser === null) { echo render_error(401); return; } -if(user_warning_check_restriction(user_session_current('user_id', 0))) { +$currentUserId = $currentUser->getId(); + +if(user_warning_check_restriction($currentUserId)) { echo render_error(403); return; } @@ -83,7 +88,7 @@ if(empty($forum)) { return; } -$perms = forum_perms_get_user($forum['forum_id'], user_session_current('user_id'))[MSZ_FORUM_PERMS_GENERAL]; +$perms = forum_perms_get_user($forum['forum_id'], $currentUserId)[MSZ_FORUM_PERMS_GENERAL]; if($forum['forum_archived'] || (!empty($topic['topic_locked']) && !perms_check($perms, MSZ_FORUM_PERM_LOCK_TOPIC)) @@ -121,7 +126,7 @@ if($mode === 'edit') { return; } - if(!perms_check($perms, $post['poster_id'] === user_session_current('user_id') ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) { + if(!perms_check($perms, $post['poster_id'] === $currentUserId ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) { echo render_error(403); return; } @@ -142,7 +147,7 @@ if(!empty($_POST)) { $isEditingTopic = empty($topic) || ($mode === 'edit' && $post['is_opening_post']); if($mode === 'create') { - $timeoutCheck = max(1, forum_timeout($forumId, user_session_current('user_id'))); + $timeoutCheck = max(1, forum_timeout($forumId, $currentUserId)); if($timeoutCheck < 5) { $notices[] = sprintf("You're posting too quickly! Please wait %s seconds before posting again.", number_format($timeoutCheck)); @@ -195,7 +200,7 @@ if(!empty($_POST)) { } else { $topicId = forum_topic_create( $forum['forum_id'], - user_session_current('user_id', 0), + $currentUserId, $topicTitle, $topicType ); @@ -204,13 +209,13 @@ if(!empty($_POST)) { $postId = forum_post_create( $topicId, $forum['forum_id'], - user_session_current('user_id', 0), + $currentUserId, IPAddress::remote(), $postText, $postParser, $postSignature ); - forum_topic_mark_read(user_session_current('user_id', 0), $topicId, $forum['forum_id']); + forum_topic_mark_read($currentUserId, $topicId, $forum['forum_id']); forum_count_increase($forum['forum_id'], empty($topic)); break; @@ -248,7 +253,7 @@ if($mode === 'edit') { // $post is pretty much sure to be populated at this poin Template::set('posting_post', $post); } -$displayInfo = forum_posting_info(user_session_current('user_id')); +$displayInfo = forum_posting_info($currentUserId); Template::render('forum.posting', [ 'posting_breadcrumbs' => forum_get_breadcrumbs($forumId), diff --git a/public/forum/topic-priority.php b/public/forum/topic-priority.php index 7ac9b0aa..8f206158 100644 --- a/public/forum/topic-priority.php +++ b/public/forum/topic-priority.php @@ -1,14 +1,16 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../misuzu.php'; -if(!MSZ_DEBUG) { +if(!MSZ_DEBUG) return; -} $topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0; -$topicUserId = user_session_current('user_id', 0); +$topicUser = User::getCurrent(); +$topicUserId = $topicUser === null ? 0 : $topicUser->getId(); if($topicUserId < 1) { echo render_error(403); @@ -48,6 +50,6 @@ if(!forum_has_priority_voting($topic['forum_type'])) { return; } -forum_topic_priority_increase($topicId, user_session_current('user_id', 0)); +forum_topic_priority_increase($topicId, $topicUserId); url_redirect('forum-topic', ['topic' => $topicId]); diff --git a/public/forum/topic.php b/public/forum/topic.php index 409b1e6c..b06329df 100644 --- a/public/forum/topic.php +++ b/public/forum/topic.php @@ -2,6 +2,8 @@ namespace Misuzu; use Misuzu\AuditLog; +use Misuzu\Users\User; +use Misuzu\Users\UserSession; require_once '../../misuzu.php'; @@ -10,7 +12,8 @@ $topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0; $moderationMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : ''; $submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1'; -$topicUserId = user_session_current('user_id', 0); +$topicUser = User::getCurrent(); +$topicUserId = $topicUser === null ? 0 : $this->getId(); if($topicId < 1 && $postId > 0) { $postInfo = forum_post_find($postId, $topicUserId); @@ -91,7 +94,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { header(CSRF::header()); - if(!user_session_active()) { + if(!UserSession::hasCurrent()) { echo render_info_or_json($isXHR, 'You must be logged in to manage posts.', 401); return; } diff --git a/public/manage/changelog/change.php b/public/manage/changelog/change.php index c7abeb6d..8d836e87 100644 --- a/public/manage/changelog/change.php +++ b/public/manage/changelog/change.php @@ -11,7 +11,7 @@ use Misuzu\Users\UserNotFoundException; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_CHANGELOG, user_session_current('user_id'), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { echo render_error(403); return; } diff --git a/public/manage/changelog/index.php b/public/manage/changelog/index.php index f0a15623..022a507c 100644 --- a/public/manage/changelog/index.php +++ b/public/manage/changelog/index.php @@ -2,10 +2,11 @@ namespace Misuzu; use Misuzu\Changelog\ChangelogChange; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_CHANGELOG, user_session_current('user_id'), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { echo render_error(403); return; } diff --git a/public/manage/changelog/tag.php b/public/manage/changelog/tag.php index 870d0270..497cba05 100644 --- a/public/manage/changelog/tag.php +++ b/public/manage/changelog/tag.php @@ -8,7 +8,7 @@ use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_CHANGELOG, user_session_current('user_id'), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) { echo render_error(403); return; } diff --git a/public/manage/changelog/tags.php b/public/manage/changelog/tags.php index 98ad4d5a..297cea2a 100644 --- a/public/manage/changelog/tags.php +++ b/public/manage/changelog/tags.php @@ -2,10 +2,11 @@ namespace Misuzu; use Misuzu\Changelog\ChangelogTag; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_CHANGELOG, user_session_current('user_id'), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) { echo render_error(403); return; } diff --git a/public/manage/forum/category.php b/public/manage/forum/category.php index 897e9684..22a23f36 100644 --- a/public/manage/forum/category.php +++ b/public/manage/forum/category.php @@ -1,9 +1,11 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), MSZ_PERM_FORUM_MANAGE_FORUMS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) { echo render_error(403); return; } diff --git a/public/manage/forum/index.php b/public/manage/forum/index.php index 346dcce8..1cb0ca80 100644 --- a/public/manage/forum/index.php +++ b/public/manage/forum/index.php @@ -1,9 +1,11 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), MSZ_PERM_FORUM_MANAGE_FORUMS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) { echo render_error(403); return; } diff --git a/public/manage/general/blacklist.php b/public/manage/general/blacklist.php index cb3d6207..98c2538f 100644 --- a/public/manage/general/blacklist.php +++ b/public/manage/general/blacklist.php @@ -2,10 +2,11 @@ namespace Misuzu; use Misuzu\Net\IPAddressBlacklist; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), MSZ_PERM_GENERAL_MANAGE_BLACKLIST)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_BLACKLIST)) { echo render_error(403); return; } diff --git a/public/manage/general/emoticon.php b/public/manage/general/emoticon.php index 9922ceb5..5a150015 100644 --- a/public/manage/general/emoticon.php +++ b/public/manage/general/emoticon.php @@ -1,9 +1,11 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { echo render_error(403); return; } diff --git a/public/manage/general/emoticons.php b/public/manage/general/emoticons.php index a85c9809..40417ecb 100644 --- a/public/manage/general/emoticons.php +++ b/public/manage/general/emoticons.php @@ -1,9 +1,11 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { echo render_error(403); return; } diff --git a/public/manage/general/index.php b/public/manage/general/index.php index 824fbb90..5cf0e996 100644 --- a/public/manage/general/index.php +++ b/public/manage/general/index.php @@ -144,11 +144,11 @@ $statistics = DB::query(' FROM `msz_ip_blacklist` ) AS `stat_blacklist`, ( - SELECT COUNT(`attempt_id`) + SELECT COUNT(*) FROM `msz_login_attempts` ) AS `stat_login_attempts_total`, ( - SELECT COUNT(`attempt_id`) + SELECT COUNT(*) FROM `msz_login_attempts` WHERE `attempt_success` = 0 ) AS `stat_login_attempts_failed`, diff --git a/public/manage/general/logs.php b/public/manage/general/logs.php index f8a766b9..a8bd18e3 100644 --- a/public/manage/general/logs.php +++ b/public/manage/general/logs.php @@ -3,10 +3,11 @@ namespace Misuzu; use Misuzu\AuditLog; use Misuzu\Pagination; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), MSZ_PERM_GENERAL_VIEW_LOGS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_VIEW_LOGS)) { echo render_error(403); return; } diff --git a/public/manage/general/settings.php b/public/manage/general/settings.php index d37a3a53..e1843cd3 100644 --- a/public/manage/general/settings.php +++ b/public/manage/general/settings.php @@ -1,9 +1,11 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), MSZ_PERM_GENERAL_MANAGE_CONFIG)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) { echo render_error(403); return; } diff --git a/public/manage/news/categories.php b/public/manage/news/categories.php index f1b5b1b5..cb3a0b8a 100644 --- a/public/manage/news/categories.php +++ b/public/manage/news/categories.php @@ -2,10 +2,11 @@ namespace Misuzu; use Misuzu\News\NewsCategory; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_NEWS, user_session_current('user_id'), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) { echo render_error(403); return; } diff --git a/public/manage/news/category.php b/public/manage/news/category.php index c660884f..12f34f3b 100644 --- a/public/manage/news/category.php +++ b/public/manage/news/category.php @@ -4,10 +4,11 @@ namespace Misuzu; use Misuzu\AuditLog; use Misuzu\News\NewsCategory; use Misuzu\News\NewsCategoryNotFoundException; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_NEWS, user_session_current('user_id'), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) { echo render_error(403); return; } diff --git a/public/manage/news/post.php b/public/manage/news/post.php index 08ccc46d..ccdb0b7e 100644 --- a/public/manage/news/post.php +++ b/public/manage/news/post.php @@ -5,10 +5,11 @@ use Misuzu\AuditLog; use Misuzu\News\NewsCategory; use Misuzu\News\NewsPost; use Misuzu\News\NewsPostNotFoundException; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_NEWS, user_session_current('user_id'), MSZ_PERM_NEWS_MANAGE_POSTS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) { echo render_error(403); return; } @@ -31,7 +32,7 @@ if(!empty($_POST['post']) && CSRF::validateRequest()) { $isNew = true; } - $currentUserId = user_session_current('user_id'); + $currentUserId = User::getCurrent()->getId(); $postInfo->setTitle( $_POST['post']['title']) ->setText($_POST['post']['text']) ->setCategoryId($_POST['post']['category']) diff --git a/public/manage/news/posts.php b/public/manage/news/posts.php index 8ac3f2e3..faeb4dde 100644 --- a/public/manage/news/posts.php +++ b/public/manage/news/posts.php @@ -2,10 +2,11 @@ namespace Misuzu; use Misuzu\News\NewsPost; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_NEWS, user_session_current('user_id'), MSZ_PERM_NEWS_MANAGE_POSTS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) { echo render_error(403); return; } diff --git a/public/manage/users/index.php b/public/manage/users/index.php index ce27eae0..c8387a1b 100644 --- a/public/manage/users/index.php +++ b/public/manage/users/index.php @@ -1,9 +1,11 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_USER, user_session_current('user_id'), MSZ_PERM_USER_MANAGE_USERS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_USERS)) { echo render_error(403); return; } diff --git a/public/manage/users/role.php b/public/manage/users/role.php index 564f043e..1beea5a5 100644 --- a/public/manage/users/role.php +++ b/public/manage/users/role.php @@ -1,17 +1,18 @@ <?php +// TODO: UNFUCK THIS FILE namespace Misuzu; -// TODO: UNFUCK THIS FILE +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_USER, user_session_current('user_id'), MSZ_PERM_USER_MANAGE_ROLES)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) { echo render_error(403); return; } $roleId = $_GET['r'] ?? null; -$currentUserId = user_session_current('user_id'); +$currentUserId = User::getCurrent()->getId(); /*$isSuperUser = user_check_super($currentUserId); $canEdit = $isSuperUser || user_check_authority($currentUserId, $userId);*/ $canEditPerms = /*$canEdit && */perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_PERMS); diff --git a/public/manage/users/roles.php b/public/manage/users/roles.php index 02b14a40..a431e636 100644 --- a/public/manage/users/roles.php +++ b/public/manage/users/roles.php @@ -1,9 +1,11 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_USER, user_session_current('user_id'), MSZ_PERM_USER_MANAGE_ROLES)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) { echo render_error(403); return; } diff --git a/public/manage/users/user.php b/public/manage/users/user.php index 256927c5..f9e73df7 100644 --- a/public/manage/users/user.php +++ b/public/manage/users/user.php @@ -1,16 +1,18 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_USER, user_session_current('user_id'), MSZ_PERM_USER_MANAGE_USERS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_USERS)) { echo render_error(403); return; } $notices = []; $userId = (int)($_GET['u'] ?? 0); -$currentUserId = user_session_current('user_id'); +$currentUserId = User::getCurrent()->getId(); if($userId < 1) { echo render_error(404); diff --git a/public/manage/users/warnings.php b/public/manage/users/warnings.php index 53384c29..f87bcaa0 100644 --- a/public/manage/users/warnings.php +++ b/public/manage/users/warnings.php @@ -2,16 +2,17 @@ namespace Misuzu; use Misuzu\Net\IPAddress; +use Misuzu\Users\User; require_once '../../../misuzu.php'; -if(!perms_check_user(MSZ_PERMS_USER, user_session_current('user_id'), MSZ_PERM_USER_MANAGE_WARNINGS)) { +if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) { echo render_error(403); return; } $notices = []; -$currentUserId = user_session_current('user_id'); +$currentUserId = User::getCurrent()->getId(); if(!empty($_POST['lookup']) && is_string($_POST['lookup'])) { url_redirect('manage-users-warnings', ['user' => user_id_from_username($_POST['lookup'])]); diff --git a/public/members.php b/public/members.php index 2a189e65..989256fb 100644 --- a/public/members.php +++ b/public/members.php @@ -1,6 +1,8 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../misuzu.php'; $roleId = !empty($_GET['r']) && is_string($_GET['r']) ? (int)$_GET['r'] : MSZ_ROLE_MAIN; @@ -75,10 +77,7 @@ if(empty($orderDir)) { return; } -$canManageUsers = perms_check_user( - MSZ_PERMS_USER, user_session_current('user_id', 0), - MSZ_PERM_USER_MANAGE_USERS -); +$canManageUsers = perms_check_user(MSZ_PERMS_USER, User::hasCurrent() ? User::getCurrent()->getId() : 0, MSZ_PERM_USER_MANAGE_USERS); $role = user_role_get($roleId); @@ -153,7 +152,7 @@ $getUsers = DB::prepare(sprintf( $usersPagination->getRange() )); $getUsers->bind('role_id', $role['role_id']); -$getUsers->bind('current_user_id', user_session_current('user_id', 0)); +$getUsers->bind('current_user_id', User::hasCurrent() ? User::getCurrent()->getId() : 0); $users = $getUsers->fetchAll(); if(empty($users)) { diff --git a/public/profile.php b/public/profile.php index 82b72868..cb25fe23 100644 --- a/public/profile.php +++ b/public/profile.php @@ -4,6 +4,7 @@ namespace Misuzu; use Misuzu\Parsers\Parser; use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; +use Misuzu\Users\UserSession; require_once '../misuzu.php'; @@ -21,15 +22,16 @@ try { $notices = []; -$currentUserId = user_session_current('user_id', 0); -$viewingAsGuest = $currentUserId === 0; +$currentUser = User::getCurrent(); +$viewingAsGuest = $currentUser === null; +$currentUserId = $viewingAsGuest ? 0 : $currentUser->getId(); $viewingOwnProfile = $currentUserId === $profileUser->getId(); $isBanned = user_warning_check_restriction($profileUser->getId()); $userPerms = perms_get_user($currentUserId)[MSZ_PERMS_USER]; $canManageWarnings = perms_check($userPerms, MSZ_PERM_USER_MANAGE_WARNINGS); $canEdit = !$isBanned - && user_session_active() + && UserSession::hasCurrent() && ( $viewingOwnProfile || user_check_super($currentUserId) @@ -297,7 +299,7 @@ $profileStats = DB::prepare(sprintf(' WHERE `user_id` = :user_id ', MSZ_USER_RELATION_FOLLOW))->bind('user_id', $profileUser->getId())->fetch(); -$relationInfo = user_session_active() +$relationInfo = UserSession::hasCurrent() ? user_relation_info($currentUserId, $profileUser->getId()) : []; diff --git a/public/relations.php b/public/relations.php index 8e3bb363..a13ae3bf 100644 --- a/public/relations.php +++ b/public/relations.php @@ -1,6 +1,8 @@ <?php namespace Misuzu; +use Misuzu\Users\User; + require_once '../misuzu.php'; // basing whether or not this is an xhr request on whether a referrer header is present @@ -22,14 +24,14 @@ if(!CSRF::validateRequest()) { header(CSRF::header()); -if(!user_session_active()) { +$currentUser = User::getCurrent(); + +if($currentUser === null) { echo render_info_or_json($isXHR, 'You must be logged in to manage relations.', 401); return; } -$userId = (int)user_session_current('user_id'); - -if(user_warning_check_expiration($userId, MSZ_WARN_BAN) > 0) { +if(user_warning_check_expiration($currentUser->getId(), MSZ_WARN_BAN) > 0) { echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); return; } @@ -42,12 +44,12 @@ if(!user_relation_is_valid_type($relationType)) { return; } -if($userId < 1 || $subjectId < 1) { +if($currentUser->getId() < 1 || $subjectId < 1) { echo render_info_or_json($isXHR, "That user doesn't exist.", 400); return; } -if(!user_relation_set($userId, $subjectId, $relationType)) { +if(!user_relation_set($currentUser->getId(), $subjectId, $relationType)) { echo render_info_or_json($isXHR, "Failed to save relation.", 500); return; } @@ -55,7 +57,7 @@ if(!user_relation_set($userId, $subjectId, $relationType)) { if(($relationType === MSZ_USER_RELATION_NONE || $relationType === MSZ_USER_RELATION_FOLLOW) && in_array($subjectId, Config::get('relations.replicate', Config::TYPE_ARR))) { - user_relation_set($subjectId, $userId, $relationType); + user_relation_set($subjectId, $currentUser->getId(), $relationType); } if(!$isXHR) { @@ -64,7 +66,7 @@ if(!$isXHR) { } echo json_encode([ - 'user_id' => $userId, + 'user_id' => $currentUser->getId(), 'subject_id' => $subjectId, 'relation_type' => $relationType, ]); diff --git a/public/search.php b/public/search.php index ee6e6da6..6a81afb9 100644 --- a/public/search.php +++ b/public/search.php @@ -2,13 +2,14 @@ namespace Misuzu; use Misuzu\News\NewsPost; +use Misuzu\Users\User; require_once '../misuzu.php'; $searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : ''; if(!empty($searchQuery)) { - $forumTopics = forum_topic_listing_search($searchQuery, user_session_current('user_id', 0)); + $forumTopics = forum_topic_listing_search($searchQuery, User::hasCurrent() ? User::getCurrent()->getId() : 0); $forumPosts = forum_post_search($searchQuery); $newsPosts = NewsPost::bySearchQuery($searchQuery); @@ -67,7 +68,7 @@ if(!empty($searchQuery)) { MSZ_USER_RELATION_FOLLOW )); $findUsers->bind('query', $searchQuery); - $findUsers->bind('current_user_id', user_session_current('user_id', 0)); + $findUsers->bind('current_user_id', User::hasCurrent() ? User::getCurrent()->getId() : 0); $users = $findUsers->fetchAll(); } diff --git a/public/settings/account.php b/public/settings/account.php index 935d7fb3..479ef0fd 100644 --- a/public/settings/account.php +++ b/public/settings/account.php @@ -3,12 +3,13 @@ namespace Misuzu; use Misuzu\AuditLog; use Misuzu\Users\User; +use Misuzu\Users\UserSession; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; require_once '../../misuzu.php'; -if(!user_session_active()) { +if(!UserSession::hasCurrent()) { echo render_error(401); return; } diff --git a/public/settings/data.php b/public/settings/data.php index 4b4037ca..7b2d30f7 100644 --- a/public/settings/data.php +++ b/public/settings/data.php @@ -4,10 +4,11 @@ namespace Misuzu; use ZipArchive; use Misuzu\AuditLog; use Misuzu\Users\User; +use Misuzu\Users\UserSession; require_once '../../misuzu.php'; -if(!user_session_active()) { +if(!UserSession::hasCurrent()) { echo render_error(401); return; } diff --git a/public/settings/index.php b/public/settings/index.php index 7b25723f..865505c6 100644 --- a/public/settings/index.php +++ b/public/settings/index.php @@ -1,13 +1,13 @@ <?php namespace Misuzu; +use Misuzu\Users\UserSession; + require_once '../../misuzu.php'; -if(!user_session_active()) { +if(!UserSession::hasCurrent()) { echo render_error(401); return; } -// do something with this page - url_redirect('settings-account'); diff --git a/public/settings/sessions.php b/public/settings/sessions.php index fa7d6f6a..2e1064a6 100644 --- a/public/settings/sessions.php +++ b/public/settings/sessions.php @@ -2,17 +2,22 @@ namespace Misuzu; use Misuzu\AuditLog; +use Misuzu\Users\User; +use Misuzu\Users\UserSession; +use Misuzu\Users\UserSessionNotFoundException; require_once '../../misuzu.php'; -if(!user_session_active()) { +if(!User::hasCurrent()) { echo render_error(401); return; } $errors = []; -$currentUserId = user_session_current('user_id'); -$sessionActive = user_session_current('session_id'); +$currentUser = User::getCurrent(); +$currentSession = UserSession::getCurrent(); +$currentUserId = $currentUser->getId(); +$sessionActive = $currentSession->getId();; if(!empty($_POST['session']) && CSRF::validateRequest()) { $currentSessionKilled = false; @@ -20,23 +25,24 @@ if(!empty($_POST['session']) && CSRF::validateRequest()) { if(is_array($_POST['session'])) { foreach($_POST['session'] as $sessionId) { $sessionId = intval($sessionId); - $session = user_session_find($sessionId); - if(!$session || (int)$session['user_id'] !== $currentUserId) { + try { + $sessionInfo = UserSession::byId($sessionId); + } catch(UserSessionNotFoundException $ex) {} + + if(empty($sessionInfo) || $sessionInfo->getUserId() !== $currentUser->getId()) { $errors[] = "Session #{$sessionId} does not exist."; continue; - } elseif((int)$session['session_id'] === $sessionActive) { + } elseif($sessionInfo->getId() === $sessionActive) { $currentSessionKilled = true; } - user_session_delete($session['session_id']); - AuditLog::create(AuditLog::PERSONAL_SESSION_DESTROY, [ - $session['session_id'], - ]); + $sessionInfo->delete(); + AuditLog::create(AuditLog::PERSONAL_SESSION_DESTROY, [$sessionInfo->getId()]); } } elseif($_POST['session'] === 'all') { $currentSessionKilled = true; - user_session_purge_all($currentUserId); + UserSession::purgeUser($currentUser); AuditLog::create(AuditLog::PERSONAL_SESSION_DESTROY_ALL); } @@ -46,17 +52,11 @@ if(!empty($_POST['session']) && CSRF::validateRequest()) { } } -$sessionPagination = new Pagination(user_session_count($currentUserId), 15); - -$sessionList = user_session_list( - $sessionPagination->getOffset(), - $sessionPagination->getRange(), - $currentUserId -); +$pagination = new Pagination(UserSession::countAll($currentUser), 15); Template::render('settings.sessions', [ 'errors' => $errors, - 'session_list' => $sessionList, - 'session_active_id' => $sessionActive, - 'session_pagination' => $sessionPagination, + 'session_list' => UserSession::all($pagination, $currentUser), + 'session_current' => $currentSession, + 'session_pagination' => $pagination, ]); diff --git a/public/user-assets.php b/public/user-assets.php index fc52d4ac..73d07867 100644 --- a/public/user-assets.php +++ b/public/user-assets.php @@ -22,7 +22,7 @@ $canViewImages = !$userExists || !user_warning_check_expiration($userId, MSZ_WARN_BAN) || ( parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_PATH) === url('user-profile') - && perms_check_user(MSZ_PERMS_USER, user_session_current('user_id', 0), MSZ_PERM_USER_MANAGE_USERS) + && perms_check_user(MSZ_PERMS_USER, User::hasCurrent() ? User::getCurrent()->getId() : 0, MSZ_PERM_USER_MANAGE_USERS) ); switch($userAssetsMode) { diff --git a/src/AuthToken.php b/src/AuthToken.php new file mode 100644 index 00000000..90dda21b --- /dev/null +++ b/src/AuthToken.php @@ -0,0 +1,90 @@ +<?php +namespace Misuzu; + +use Misuzu\Users\User; +use Misuzu\Users\UserSession; + +class AuthToken { + public const VERSION = 1; + public const WIDTH = 37; + + private $userId = -1; + private $sessionToken = ''; + + private $user = null; + private $session = null; + + public function isValid(): bool { + return $this->getUserId() > 0 + && !empty($this->getSessionToken()); + } + + public function getUserId(): int { + return $this->userId < 1 ? -1 : $this->userId; + } + public function setUserId(int $userId): self { + $this->user = null; + $this->userId = $userId; + return $this; + } + public function getUser(): User { + if($this->user === null) + $this->user = User::byId($this->getUserId()); + return $this->user; + } + public function setUser(User $user): self { + $this->user = $user; + $this->userId = $user->getId(); + return $this; + } + + public function getSessionToken(): string { + return $this->sessionToken ?? ''; + } + public function setSessionToken(string $token): self { + $this->session = null; + $this->sessionToken = $token; + return $this; + } + public function getSession(): UserSession { + if($this->session === null) + $this->session = UserSession::byToken($this->getSessionToken()); + return $this->session; + } + public function setSession(UserSession $session): self { + $this->session = $session; + $this->sessionToken = $session->getToken(); + return $this; + } + + public function pack(bool $base64 = true): string { + $packed = pack('CNH*', self::VERSION, $this->getUserId(), $this->getSessionToken()); + if($base64) + $packed = Base64::encode($packed, true); + return $packed; + } + + public static function unpack(string $data, bool $base64 = true): self { + $obj = new static; + + if(empty($data)) + return $obj; + if($base64) + $data = Base64::decode($data, true); + + $data = str_pad($data, self::WIDTH, "\x00"); + $data = unpack('Cversion/Nuser/H*token', $data); + + if($data['version'] >= 1) + $obj->setUserId($data['user']) + ->setSessionToken($data['token']); + + return $obj; + } + + public static function create(User $user, UserSession $session): self { + return (new static) + ->setUser($user) + ->setSession($session); + } +} diff --git a/src/Http/Filters/EnforceLogInFilter.php b/src/Http/Filters/EnforceLogInFilter.php index e665b5ec..73fe69fa 100644 --- a/src/Http/Filters/EnforceLogInFilter.php +++ b/src/Http/Filters/EnforceLogInFilter.php @@ -3,10 +3,12 @@ namespace Misuzu\Http\Filters; use Misuzu\Http\HttpResponseMessage; use Misuzu\Http\HttpRequestMessage; +use Misuzu\Users\User; +use Misuzu\Users\UserSession; class EnforceLogInFilter implements FilterInterface { public function process(HttpRequestMessage $request): ?HttpResponseMessage { - if(!user_session_active()) + if(!UserSession::hasCurrent() || !User::hasCurrent()) return new HttpResponseMessage(403); return null; diff --git a/src/Http/Filters/EnforceLogOutFilter.php b/src/Http/Filters/EnforceLogOutFilter.php index 1a6bd182..1f4175cb 100644 --- a/src/Http/Filters/EnforceLogOutFilter.php +++ b/src/Http/Filters/EnforceLogOutFilter.php @@ -3,10 +3,12 @@ namespace Misuzu\Http\Filters; use Misuzu\Http\HttpResponseMessage; use Misuzu\Http\HttpRequestMessage; +use Misuzu\Users\User; +use Misuzu\Users\UserSession; class EnforceLogOutFilter implements FilterInterface { public function process(HttpRequestMessage $request): ?HttpResponseMessage { - if(user_session_active()) + if(UserSession::hasCurrent() || User::hasCurrent()) return new HttpResponseMessage(404); return null; diff --git a/src/Http/Handlers/ForumHandler.php b/src/Http/Handlers/ForumHandler.php index 35f46763..9afb40b1 100644 --- a/src/Http/Handlers/ForumHandler.php +++ b/src/Http/Handlers/ForumHandler.php @@ -4,6 +4,7 @@ namespace Misuzu\Http\Handlers; use HttpResponse; use HttpRequest; use Misuzu\CSRF; +use Misuzu\Users\User; final class ForumHandler extends Handler { public function markAsReadGET(HttpResponse $response, HttpRequest $request): void { @@ -20,7 +21,7 @@ final class ForumHandler extends Handler { public function markAsReadPOST(HttpResponse $response, HttpRequest $request) { $forumId = (int)$request->getBodyParam('forum', FILTER_SANITIZE_NUMBER_INT); - forum_mark_read($forumId, user_session_current('user_id')); + forum_mark_read($forumId, User::getCurrent()->getId()); $response->redirect( url($forumId ? 'forum-category' : 'forum-index', ['forum' => $forumId]), diff --git a/src/Http/Handlers/HomeHandler.php b/src/Http/Handlers/HomeHandler.php index 562b21c1..5efe747e 100644 --- a/src/Http/Handlers/HomeHandler.php +++ b/src/Http/Handlers/HomeHandler.php @@ -8,6 +8,7 @@ use Misuzu\DB; use Misuzu\Pagination; use Misuzu\Changelog\ChangelogChange; use Misuzu\News\NewsPost; +use Misuzu\Users\UserSession; final class HomeHandler extends Handler { public function index(HttpResponse $response, HttpRequest $request): void { @@ -58,7 +59,7 @@ final class HomeHandler extends Handler { $changelog = ChangelogChange::all(new Pagination(10)); - $birthdays = user_session_active() ? user_get_birthdays() : []; + $birthdays = UserSession::hasCurrent() ? user_get_birthdays() : []; $latestUser = DB::query(' SELECT diff --git a/src/Http/Handlers/SockChatHandler.php b/src/Http/Handlers/SockChatHandler.php index 693a3fdd..ba8dd084 100644 --- a/src/Http/Handlers/SockChatHandler.php +++ b/src/Http/Handlers/SockChatHandler.php @@ -3,6 +3,7 @@ namespace Misuzu\Http\Handlers; use HttpResponse; use HttpRequest; +use Misuzu\AuthToken; use Misuzu\Base64; use Misuzu\Config; use Misuzu\DB; @@ -13,6 +14,8 @@ use Misuzu\Users\UserNotFoundException; use Misuzu\Users\UserChatToken; use Misuzu\Users\UserChatTokenNotFoundException; use Misuzu\Users\UserChatTokenCreationFailedException; +use Misuzu\Users\UserSession; +use Misuzu\Users\UserSessionNotFoundException; final class SockChatHandler extends Handler { private string $hashKey = 'woomy'; @@ -222,21 +225,27 @@ final class SockChatHandler extends Handler { // $userId = $authInfo->user_id; } elseif($authMethod === 'SESS:') { $sessionToken = mb_substr($authInfo->token, 5); - $tokenData = user_session_cookie_unpack( - Base64::decode($sessionToken, true), - true - ); - if(isset($tokenData['session_token'])) - $sessionToken = $tokenData['session_token']; + $authToken = AuthToken::unpack($sessionToken); + if($authToken->isValid()) + $sessionToken = $authToken->getSessionToken(); - user_session_start($authInfo->user_id, $sessionToken); + try { + $sessionInfo = UserSession::byToken($sessionToken); + } catch(UserSessionNotFoundException $ex) { + return ['success' => false, 'reason' => 'token']; + } - if(user_session_active()) { - $userId = user_session_current('user_id'); - user_bump_last_active($userId); - user_session_bump_active(user_session_current('session_id')); - } else return ['success' => false, 'reason' => 'expired']; + if($sessionInfo->getUserId() !== $userInfo->getId()) + return ['success' => false, 'reason' => 'user']; + + if($sessionInfo->hasExpired()) { + $sessionInfo->delete(); + return ['success' => false, 'reason' => 'expired']; + } + + $sessionInfo->bump(); + user_bump_last_active($userInfo->getId()); } else { try { $token = UserChatToken::byExact($userInfo, $authInfo->token); diff --git a/src/Users/User.php b/src/Users/User.php index f904baeb..1b89526b 100644 --- a/src/Users/User.php +++ b/src/Users/User.php @@ -173,16 +173,19 @@ class User { public static function unsetCurrent(): void { self::$localUser = null; } - public static function getCurrent(): ?User { + public static function getCurrent(): ?self { return self::$localUser; } + public static function hasCurrent(): bool { + return self::$localUser !== null; + } public static function create( string $username, string $password, string $email, string $ipAddress - ): ?User { + ): ?self { $createUser = DB::prepare(' INSERT INTO `msz_users` ( `username`, `password`, `email`, `register_ip`, @@ -210,7 +213,7 @@ class User { return $memoizer; } - public static function byId(int $userId): ?User { + public static function byId(int $userId): ?self { return self::getMemoizer()->find($userId, function() use ($userId) { $user = DB::prepare(self::USER_SELECT . 'WHERE `user_id` = :user_id') ->bind('user_id', $userId) @@ -220,7 +223,7 @@ class User { return $user; }); } - public static function findForLogin(string $usernameOrEmail): ?User { + public static function findForLogin(string $usernameOrEmail): ?self { $usernameOrEmailLower = mb_strtolower($usernameOrEmail); return self::getMemoizer()->find(function($user) use ($usernameOrEmailLower) { return mb_strtolower($user->getUsername()) === $usernameOrEmailLower @@ -235,7 +238,7 @@ class User { return $user; }); } - public static function findForProfile($userIdOrName): ?User { + public static function findForProfile($userIdOrName): ?self { $userIdOrNameLower = mb_strtolower($userIdOrName); return self::getMemoizer()->find(function($user) use ($userIdOrNameLower) { return $user->getId() == $userIdOrNameLower || mb_strtolower($user->getUsername()) === $userIdOrNameLower; diff --git a/src/Users/UserLoginAttempt.php b/src/Users/UserLoginAttempt.php index 6c77e474..a08bb3a5 100644 --- a/src/Users/UserLoginAttempt.php +++ b/src/Users/UserLoginAttempt.php @@ -67,7 +67,7 @@ class UserLoginAttempt { } public static function remaining(?string $remoteAddr = null): int { - $remoteAddr = $ipAddress ?? IPAddress::remote(); + $remoteAddr = $remoteAddr ?? IPAddress::remote(); return (int)DB::prepare( 'SELECT 5 - COUNT(*)' . ' FROM `' . DB::PREFIX . self::TABLE . '`' @@ -79,7 +79,7 @@ class UserLoginAttempt { } public static function create(bool $success, ?User $user = null, ?string $remoteAddr = null, string $userAgent = null): void { - $remoteAddr = $ipAddress ?? IPAddress::remote(); + $remoteAddr = $remoteAddr ?? IPAddress::remote(); $userAgent = $userAgent ?? filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH) ?? ''; $createLog = DB::prepare( 'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`user_id`, `attempt_success`, `attempt_ip`, `attempt_country`, `attempt_user_agent`)' diff --git a/src/Users/UserSession.php b/src/Users/UserSession.php new file mode 100644 index 00000000..afaac3fd --- /dev/null +++ b/src/Users/UserSession.php @@ -0,0 +1,256 @@ +<?php +namespace Misuzu\Users; + +use Misuzu\DB; +use Misuzu\Pagination; +use Misuzu\Net\IPAddress; +use WhichBrowser\Parser as UserAgentParser; + +class UserSessionException extends UsersException {} +class UserSessionCreationFailedException extends UserSessionException {} +class UserSessionNotFoundException extends UserSessionException {} + +class UserSession { + public const TOKEN_SIZE = 64; + public const LIFETIME = 60 * 60 * 24 * 31; + + // Database fields + private $session_id = -1; + private $user_id = -1; + private $session_key = ''; + private $session_ip = '::1'; + private $session_ip_last = null; + private $session_user_agent = ''; + private $session_country = 'XX'; + private $session_expires = null; + private $session_expires_bump = 1; + private $session_created = null; + private $session_active = null; + + private $user = null; + private $uaInfo = null; + + private static $localSession = null; + + public const TABLE = 'sessions'; + private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE; + private const SELECT = '%1$s.`session_id`, %1$s.`user_id`, %1$s.`session_key`, %1$s.`session_user_agent`, %1$s.`session_country`, %1$s.`session_expires_bump`' + . ', INET6_NTOA(%1$s.`session_ip`) AS `session_ip`' + . ', INET6_NTOA(%1$s.`session_ip_last`) AS `session_ip_last`' + . ', UNIX_TIMESTAMP(%1$s.`session_created`) AS `session_created`' + . ', UNIX_TIMESTAMP(%1$s.`session_active`) AS `session_active`' + . ', UNIX_TIMESTAMP(%1$s.`session_expires`) AS `session_expires`'; + + public function getId(): int { + return $this->session_id < 1 ? -1 : $this->session_id; + } + + public function getUserId(): int { + return $this->user_id < 1 ? -1 : $this->user_id; + } + public function getUser(): User { + if($this->user === null) + $this->user = User::byId($this->getUserId()); + return $this->user; + } + + public function getToken(): string { + return $this->session_key; + } + + public function getInitialRemoteAddress(): string { + return $this->session_ip; + } + + public function getLastRemoteAddress(): string { + return $this->session_ip_last ?? ''; + } + public function hasLastRemoteAddress(): bool { + return !empty($this->session_ip_last); + } + public function setLastRemoteAddress(string $remoteAddr): self { + $this->session_ip_last = $remoteAddr; + return $this; + } + + public function getUserAgent(): string { + return $this->session_user_agent; + } + public function getUserAgentInfo(): UserAgentParser { + if($this->uaInfo === null) + $this->uaInfo = new UserAgentParser($this->getUserAgent()); + return $this->uaInfo; + } + + public function getCountry(): string { + return $this->session_country; + } + public function getCountryName(): string { + return get_country_name($this->getCountry()); + } + + public function getCreatedTime(): int { + return $this->session_created === null ? -1 : $this->session_created; + } + + public function getActiveTime(): int { + return $this->session_active === null ? -1 : $this->session_active; + } + public function hasActiveTime(): bool { + return $this->session_active !== null; + } + public function setActiveTime(int $timestamp): self { + if($timestamp > $this->session_active) + $this->session_active = $timestamp; + return $this; + } + + public function getExpiresTime(): int { + return $this->session_expires === null ? -1 : $this->session_expires; + } + public function setExpiresTime(int $timestamp): self { + $this->session_expires = $timestamp; + return $this; + } + public function hasExpired(): bool { + return $this->getExpiresTime() <= time(); + } + + public function shouldBumpExpire(): bool { + return boolval($this->session_expires_bump); + } + + public function bump(bool $callUpdate = true, ?int $timestamp = null, ?string $remoteAddr = null): void { + $timestamp = $timestamp ?? time(); + $remoteAddr = $remoteAddr ?? IPAddress::remote(); + + $this->setActiveTime($timestamp) + ->setLastRemoteAddress($remoteAddr); + + if($this->shouldBumpExpire()) + $this->setExpiresTime($timestamp + self::LIFETIME); + + if($callUpdate) + $this->update(); + } + + public function delete(): void { + DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `session_id` = :session') + ->bind('session', $this->getId()) + ->execute(); + } + + public static function purgeUser(User $user): void { + DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `user_id` = :user') + ->bind('user', $user->getId()) + ->execute(); + } + + public function setCurrent(): void { + self::$localSession = $this; + } + public static function unsetCurrent(): void { + self::$localSession = null; + } + public static function getCurrent(): ?self { + return self::$localSession; + } + public static function hasCurrent(): bool { + return self::$localSession !== null; + } + + public static function generateToken(): string { + return bin2hex(random_bytes(self::TOKEN_SIZE / 2)); + } + + public function update(): void { + DB::prepare( + 'UPDATE `' . DB::PREFIX . self::TABLE . '`' + . ' SET `session_active` = FROM_UNIXTIME(:active), `session_ip_last` = INET6_ATON(:remote_addr), `session_expires` = FROM_UNIXTIME(:expires)' + . ' WHERE `session_id` = :session' + ) ->bind('active', $this->session_active) + ->bind('remote_addr', $this->session_ip_last) + ->bind('expires', $this->session_expires) + ->bind('session', $this->session_id) + ->execute(); + } + + public static function create(User $user, ?string $remoteAddr = null, ?string $userAgent = null, ?string $token = null): self { + $remoteAddr = $remoteAddr ?? IPAddress::remote(); + $userAgent = $userAgent ?? filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH) ?? ''; + $token = $token ?? self::generateToken(); + + $sessionId = DB::prepare( + 'INSERT INTO `' . DB::PREFIX . self::TABLE . '`' + . ' (`user_id`, `session_ip`, `session_country`, `session_user_agent`, `session_key`, `session_created`, `session_expires`)' + . ' VALUES (:user, INET6_ATON(:remote_addr), :country, :user_agent, :token, NOW(), NOW() + INTERVAL :expires SECOND)' + ) ->bind('user', $user->getId()) + ->bind('remote_addr', $remoteAddr) + ->bind('country', IPAddress::country($remoteAddr)) + ->bind('user_agent', $userAgent) + ->bind('token', $token) + ->bind('expires', self::LIFETIME) + ->executeGetId(); + + if($sessionId < 1) + throw new UserSessionCreationFailedException; + + return self::byId($sessionId); + } + + private static function countQueryBase(): string { + return sprintf(self::QUERY_SELECT, sprintf('COUNT(*)', self::TABLE)); + } + public static function countAll(?User $user = null): int { + $getCount = DB::prepare( + self::countQueryBase() + . ($user === null ? '' : ' WHERE `user_id` = :user') + ); + if($user !== null) + $getCount->bind('user', $user->getId()); + return (int)$getCount->fetchColumn(); + } + + private static function byQueryBase(): string { + return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE)); + } + public static function byId(int $sessionId): self { + $session = DB::prepare(self::byQueryBase() . ' WHERE `session_id` = :session_id') + ->bind('session_id', $sessionId) + ->fetchObject(self::class); + + if(!$session) + throw new UserSessionNotFoundException; + + return $session; + } + public static function byToken(string $token): self { + $session = DB::prepare(self::byQueryBase() . ' WHERE `session_key` = :token') + ->bind('token', $token) + ->fetchObject(self::class); + + if(!$session) + throw new UserSessionNotFoundException; + + return $session; + } + public static function all(?Pagination $pagination = null, ?User $user = null): array { + $sessionsQuery = self::byQueryBase() + . ($user === null ? '' : ' WHERE `user_id` = :user') + . ' ORDER BY `session_created` DESC'; + + if($pagination !== null) + $sessionsQuery .= ' LIMIT :range OFFSET :offset'; + + $getSessions = DB::prepare($sessionsQuery); + + if($user !== null) + $getSessions->bind('user', $user->getId()); + + if($pagination !== null) + $getSessions->bind('range', $pagination->getRange()) + ->bind('offset', $pagination->getOffset()); + + return $getSessions->fetchObjects(self::class); + } +} diff --git a/src/Users/session.php b/src/Users/session.php index e50748a1..3e466145 100644 --- a/src/Users/session.php +++ b/src/Users/session.php @@ -1,211 +1,90 @@ <?php -define('MSZ_SESSION_KEY_SIZE', 64); +// These functions are used by external scripts that hook into Misuzu. +// They will remain in a backwards compatible manner for the time being. -function user_session_create( - int $userId, - string $ipAddress, - string $userAgent -): string { - $sessionKey = user_session_generate_key(); - - $createSession = \Misuzu\DB::prepare(' - INSERT INTO `msz_sessions` - ( - `user_id`, `session_ip`, `session_country`, - `session_user_agent`, `session_key`, `session_created`, `session_expires` - ) - VALUES - ( - :user_id, INET6_ATON(:session_ip), :session_country, - :session_user_agent, :session_key, NOW(), NOW() + INTERVAL 1 MONTH - ) - '); - $createSession->bind('user_id', $userId); - $createSession->bind('session_ip', $ipAddress); - $createSession->bind('session_country', \Misuzu\Net\IPAddress::country($ipAddress)); - $createSession->bind('session_user_agent', $userAgent); - $createSession->bind('session_key', $sessionKey); - - return $createSession->execute() ? $sessionKey : ''; -} - -function user_session_find($sessionId, bool $byKey = false): array { - if(!$byKey && $sessionId < 1) { - return []; +function user_session_create(int $userId, string $ipAddress, string $userAgent): string { + try { + $userInfo = \Misuzu\Users\User::byId($userId); + } catch(\Misuzu\Users\UserNotFoundException $ex) { + return ''; } - $findSession = \Misuzu\DB::prepare(sprintf(' - SELECT - `session_id`, `user_id`, - INET6_NTOA(`session_ip`) as `session_ip`, - INET6_NTOA(`session_ip_last`) as `session_ip_last`, - `session_country`, `session_user_agent`, `session_key`, `session_created`, - `session_expires`, `session_active`, `session_expires_bump` - FROM `msz_sessions` - WHERE `%s` = :session_id - ', $byKey ? 'session_key' : 'session_id')); - $findSession->bind('session_id', $sessionId); - return $findSession->fetch(); -} - -function user_session_delete(int $sessionId): void { - $deleteSession = \Misuzu\DB::prepare(' - DELETE FROM `msz_sessions` - WHERE `session_id` = :session_id - '); - $deleteSession->bind('session_id', $sessionId); - $deleteSession->execute(); -} - -function user_session_generate_key(): string { - return bin2hex(random_bytes(MSZ_SESSION_KEY_SIZE / 2)); -} - -function user_session_purge_all(int $userId): void { - \Misuzu\DB::prepare(' - DELETE FROM `msz_sessions` - WHERE `user_id` = :user_id - ')->execute([ - 'user_id' => $userId, - ]); -} - -function user_session_count($userId = 0): int { - $getCount = \Misuzu\DB::prepare(sprintf(' - SELECT COUNT(`session_id`) - FROM `msz_sessions` - %s - ', $userId < 1 ? '' : 'WHERE `user_id` = :user_id')); - - if($userId >= 1) { - $getCount->bind('user_id', $userId); + try { + $sessionInfo = \Misuzu\Users\UserSession::create($userInfo, $ipAddress, $userAgent); + } catch(\Misuzu\Users\UserSessionCreationFailedException $ex) { + return ''; } - return (int)$getCount->fetchColumn(); + return $sessionInfo->getToken(); } -function user_session_list(int $offset, int $take, int $userId = 0): array { - $offset = max(0, $offset); - $take = max(1, $take); - - $getSessions = \Misuzu\DB::prepare(sprintf(' - SELECT - `session_id`, `session_country`, `session_user_agent`, `session_created`, - `session_expires`, `session_active`, `session_expires_bump`, - INET6_NTOA(`session_ip`) as `session_ip`, - INET6_NTOA(`session_ip_last`) as `session_ip_last` - FROM `msz_sessions` - WHERE %s - ORDER BY `session_id` DESC - LIMIT :offset, :take - ', $userId < 1 ? '1' : '`user_id` = :user_id')); - - if($userId > 0) { - $getSessions->bind('user_id', $userId); - } - - $getSessions->bind('offset', $offset); - $getSessions->bind('take', $take); - - return $getSessions->fetchAll(); -} - -function user_session_bump_active(int $sessionId, string $ipAddress = null): void { - if($sessionId < 1) { +function user_session_bump_active(int $sessionId, ?string $ipAddress = null): void { + try { + $sessionInfo = \Misuzu\Users\UserSession::byId($sessionId); + } catch(\Misuzu\Users\UserSessionNotFoundException $ex) { return; } - $bump = \Misuzu\DB::prepare(' - UPDATE `msz_sessions` - SET `session_active` = NOW(), - `session_ip_last` = INET6_ATON(:last_ip), - `session_expires` = IF(`session_expires_bump`, NOW() + INTERVAL 1 MONTH, `session_expires`) - WHERE `session_id` = :session_id - '); - $bump->bind('session_id', $sessionId); - $bump->bind('last_ip', $ipAddress ?? \Misuzu\Net\IPAddress::remote()); - $bump->execute(); -} + if($ipAddress !== null) + $sessionInfo->setLastRemoteAddress($ipAddress); -// the functions below this line are imperative - -function user_session_data(?array $newData = null): array { - static $data = []; - - if(!is_null($newData)) { - $data = $newData; - } - - return $data; + $sessionInfo->bump(); } function user_session_start(int $userId, string $sessionKey): bool { - $session = user_session_find($sessionKey, true); + $session = \Misuzu\Users\UserSession::getCurrent(); - if(!$session || $session['user_id'] !== $userId) { + if($session !== null + && $session->getToken() === $sessionKey + && $session->getUserId() === $userId) + return true; + + try { + $session = \Misuzu\Users\UserSession::byToken($sessionKey); + } catch(\Misuzu\Users\UserSessionNotFoundException $ex) { return false; } - if(time() >= strtotime($session['session_expires'])) { - user_session_delete($session['session_id']); + if($session->getUserId() !== $userId) + return false; + + if($session->hasExpired()) { + $session->delete(); return false; } - user_session_data($session); + $session->setCurrent(); return true; } -function user_session_stop(bool $delete = false): void { - if(empty(user_session_data())) { - return; - } - - if($delete) { - user_session_delete(user_session_data()['session_id']); - } - - user_session_data([]); -} - function user_session_current(?string $variable = null, $default = null) { - if(empty($variable)) { - return user_session_data() ?? []; - } + $getVar = !empty($variable); + $session = \Misuzu\Users\UserSession::getCurrent(); - return user_session_data()[$variable] ?? $default; + if($session === null) + return $getVar ? $default : []; + + $data = [ + 'session_id' => $session->getId(), + 'user_id' => $session->getUserId(), + 'session_ip' => $session->getInitialRemoteAddress(), + 'session_ip_last' => $session->getLastRemoteAddress(), + 'session_country' => $session->getCountry(), + 'session_user_agent' => $session->getUserAgent(), + 'session_key' => $session->getToken(), + 'session_created' => $session->getCreatedTime(), + 'session_expires' => $session->getExpiresTime(), + 'session_active' => ($date = $session->getActiveTime()) < 0 ? null : $date, + 'session_expires_bump' => $session->shouldBumpExpire() ? 1 : 0, + ]; + + if(!$getVar) + return $data; + + return $data[$variable] ?? $default; } function user_session_active(): bool { - return !empty(user_session_data()) - && time() < strtotime(user_session_data()['session_expires']); -} - -define('MSZ_SESSION_COOKIE_VERSION', 1); -// make sure to match this to the final fixed size of the cookie string -// it'll pad older tokens out for backwards compatibility -define('MSZ_SESSION_COOKIE_SIZE', 37); - -function user_session_cookie_pack(int $userId, string $sessionToken): ?string { - if(strlen($sessionToken) !== MSZ_SESSION_KEY_SIZE) { - return null; - } - - return pack('CNH64', MSZ_SESSION_COOKIE_VERSION, $userId, $sessionToken); -} - -function user_session_cookie_unpack(string $packed): array { - $packed = str_pad($packed, MSZ_SESSION_COOKIE_SIZE, "\x00"); - $unpacked = unpack('Cversion/Nuser/H64token', $packed); - - if($unpacked['version'] < 1 || $unpacked['version'] > MSZ_SESSION_COOKIE_VERSION) { - return []; - } - - // Make sure this contains all fields with a default for version > 1 exclusive stuff - $data = [ - 'user_id' => $unpacked['user'], - 'session_token' => $unpacked['token'], - ]; - - return $data; + return \Misuzu\Users\UserSession::hasCurrent() + && !\Misuzu\Users\UserSession::getCurrent()->hasExpired(); } diff --git a/src/manage.php b/src/manage.php index 9b242f4e..d7e51da3 100644 --- a/src/manage.php +++ b/src/manage.php @@ -22,8 +22,8 @@ function manage_get_menu(int $userId): array { $menu['Users & Roles']['Users'] = url('manage-users'); if(perms_check_user(MSZ_PERMS_USER, $userId, MSZ_PERM_USER_MANAGE_ROLES)) $menu['Users & Roles']['Roles'] = url('manage-roles'); - if(perms_check_user(MSZ_PERMS_USER, $userId, MSZ_PERM_USER_MANAGE_REPORTS)) - $menu['Users & Roles']['Reports'] = url('manage-users-reports'); + //if(perms_check_user(MSZ_PERMS_USER, $userId, MSZ_PERM_USER_MANAGE_REPORTS)) + // $menu['Users & Roles']['Reports'] = url('manage-users-reports'); if(perms_check_user(MSZ_PERMS_USER, $userId, MSZ_PERM_USER_MANAGE_WARNINGS)) $menu['Users & Roles']['Warnings'] = url('manage-users-warnings'); diff --git a/templates/settings/sessions.twig b/templates/settings/sessions.twig index c9f4e201..5c13bbb2 100644 --- a/templates/settings/sessions.twig +++ b/templates/settings/sessions.twig @@ -31,7 +31,7 @@ <div class="settings__sessions__list"> {% for session in session_list %} - {{ user_session(session, session.session_id == session_active_id) }} + {{ user_session(session, session_current.id == session.id) }} {% endfor %} </div> diff --git a/templates/user/macros.twig b/templates/user/macros.twig index 99ba6cf2..f188f85a 100644 --- a/templates/user/macros.twig +++ b/templates/user/macros.twig @@ -123,18 +123,18 @@ {% macro user_session(session, is_current_session) %} {% from '_layout/input.twig' import input_hidden, input_csrf, input_checkbox_raw %} - <div class="settings__session{% if is_current_session %} settings__session--current{% endif %}" id="session-{{ session.session_id }}"> + <div class="settings__session{% if is_current_session %} settings__session--current{% endif %}" id="session-{{ session.id }}"> <div class="settings__session__container"> <div class="settings__session__important"> - <div class="flag flag--{{ session.session_country|lower }} settings__session__flag" title="{{ session.session_country|country_name }}">{{ session.session_country }}</div> + <div class="flag flag--{{ session.country|lower }} settings__session__flag" title="{{ session.countryName }}">{{ session.country }}</div> <div class="settings__session__description"> - {{ session.session_user_agent|default('')|as_platform }} + {{ session.userAgentInfo.toString }} </div> <form class="settings__session__actions" method="post" action="{{ url('settings-sessions') }}"> {{ input_csrf() }} - {{ input_hidden('session[]', session.session_id) }} + {{ input_hidden('session[]', session.id) }} <button class="settings__session__action" title="{{ is_current_session ? 'Logout' : 'End Session' }}"> {% if is_current_session %} @@ -152,46 +152,46 @@ Created from IP </div> <div class="settings__session__detail__value"> - {{ session.session_ip }} + {{ session.initialRemoteAddress }} </div> </div> - {% if session.session_ip_last is not null %} + {% if session.hasLastRemoteAddress %} <div class="settings__session__detail"> <div class="settings__session__detail__title"> Last used from IP </div> <div class="settings__session__detail__value"> - {{ session.session_ip_last }} + {{ session.lastRemoteAddress }} </div> </div> {% endif %} - <div class="settings__session__detail" title="{{ session.session_created|date('r') }}"> + <div class="settings__session__detail" title="{{ session.createdTime|date('r') }}"> <div class="settings__session__detail__title"> Created </div> - <time class="settings__session__detail__value" datetime="{{ session.session_created|date('c') }}"> - {{ session.session_created|time_diff }} + <time class="settings__session__detail__value" datetime="{{ session.createdTime|date('c') }}"> + {{ session.createdTime|time_diff }} </time> </div> - <div class="settings__session__detail" title="{{ session.session_expires|date('r') }}"> + <div class="settings__session__detail" title="{{ session.expiresTime|date('r') }}"> <div class="settings__session__detail__title"> - Expires{% if not session.session_expires_bump %} (static){% endif %} + Expires{% if not session.shouldBumpExpire %} (static){% endif %} </div> - <time class="settings__session__detail__value" datetime="{{ session.session_expires|date('c') }}"> - {{ session.session_expires|time_diff }} + <time class="settings__session__detail__value" datetime="{{ session.expiresTime|date('c') }}"> + {{ session.expiresTime|time_diff }} </time> </div> - {% if session.session_active is not null %} - <div class="settings__session__detail" title="{{ session.session_active|date('r') }}"> + {% if session.hasActiveTime %} + <div class="settings__session__detail" title="{{ session.activeTime|date('r') }}"> <div class="settings__session__detail__title"> Last Active </div> - <time class="settings__session__detail__value" datetime="{{ session.session_active|date('c') }}"> - {{ session.session_active|time_diff }} + <time class="settings__session__detail__value" datetime="{{ session.activeTime|date('c') }}"> + {{ session.activeTime|time_diff }} </time> </div> {% endif %} @@ -201,7 +201,7 @@ User Agent </div> <div class="settings__session__detail__value"> - {{ session.session_user_agent|length > 0 ? session.session_user_agent : 'None' }} + {{ session.userAgent is empty ? 'None' : session.userAgent }} </div> </div> </div>