From 8bd45209a22d8740a4853779861f0ceaea7881cd Mon Sep 17 00:00:00 2001 From: flashwave Date: Thu, 28 May 2020 17:52:31 +0000 Subject: [PATCH] Made two factor auth session code OOP. --- misuzu.php | 3 -- public/auth/login.php | 3 +- public/auth/twofactor.php | 26 +++++++--- public/settings/account.php | 6 ++- src/Users/UserAuthSession.php | 93 +++++++++++++++++++++++++++++++++++ src/Users/UserChatToken.php | 3 +- src/Users/auth.php | 50 ------------------- 7 files changed, 119 insertions(+), 65 deletions(-) create mode 100644 src/Users/UserAuthSession.php delete mode 100644 src/Users/auth.php diff --git a/misuzu.php b/misuzu.php index 703f815e..5ffede9f 100644 --- a/misuzu.php +++ b/misuzu.php @@ -2,8 +2,6 @@ namespace Misuzu; use PDO; -use Misuzu\Database\Database; -use Misuzu\Database\DatabaseMigrationManager; use Misuzu\Net\GeoIP; use Misuzu\Net\IPAddress; use Misuzu\Users\User; @@ -80,7 +78,6 @@ require_once 'src/Forum/poll.php'; require_once 'src/Forum/post.php'; require_once 'src/Forum/topic.php'; require_once 'src/Forum/validate.php'; -require_once 'src/Users/auth.php'; require_once 'src/Users/avatar.php'; require_once 'src/Users/background.php'; require_once 'src/Users/recovery.php'; diff --git a/public/auth/login.php b/public/auth/login.php index 73fd74b6..3dd25103 100644 --- a/public/auth/login.php +++ b/public/auth/login.php @@ -5,6 +5,7 @@ use Misuzu\AuthToken; use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; +use Misuzu\Users\UserAuthSession; use Misuzu\Users\UserLoginAttempt; use Misuzu\Users\UserSession; use Misuzu\Users\UserSessionCreationFailedException; @@ -85,7 +86,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) { if($userInfo->hasTOTP()) { url_redirect('auth-two-factor', [ - 'token' => user_auth_tfa_token_create($userInfo->getId()), + 'token' => UserAuthSession::create($userInfo)->getToken(), ]); return; } diff --git a/public/auth/twofactor.php b/public/auth/twofactor.php index 231034b4..761de491 100644 --- a/public/auth/twofactor.php +++ b/public/auth/twofactor.php @@ -6,6 +6,8 @@ use Misuzu\Users\User; use Misuzu\Users\UserLoginAttempt; use Misuzu\Users\UserSession; use Misuzu\Users\UserSessionCreationFailedException; +use Misuzu\Users\UserAuthSession; +use Misuzu\Users\UserAuthSessionNotFoundException; require_once '../../misuzu.php'; @@ -18,13 +20,21 @@ $twofactor = !empty($_POST['twofactor']) && is_array($_POST['twofactor']) ? $_PO $notices = []; $ipAddress = IPAddress::remote(); $remainingAttempts = UserLoginAttempt::remaining(); -$tokenInfo = user_auth_tfa_token_info( - !empty($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : ( - !empty($twofactor['token']) && is_string($twofactor['token']) ? $twofactor['token'] : '' - ) -); -$userInfo = User::byId($tokenInfo['user_id']); +try { + $tokenInfo = UserAuthSession::byToken( + !empty($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : ( + !empty($twofactor['token']) && is_string($twofactor['token']) ? $twofactor['token'] : '' + ) + ); +} catch(UserAuthSessionNotFoundException $ex) {} + +if(empty($tokenInfo) || $tokenInfo->hasExpired()) { + url_redirect('auth-login'); + return; +} + +$userInfo = $tokenInfo->getUser(); // checking user_totp_key specifically because there's a fringe chance that // there's a token present, but totp is actually disabled @@ -63,7 +73,7 @@ while(!empty($twofactor)) { } UserLoginAttempt::create(true, $userInfo); - user_auth_tfa_token_invalidate($tokenInfo['tfa_token']); + $tokenInfo->delete(); try { $sessionInfo = UserSession::create($userInfo); @@ -88,5 +98,5 @@ Template::render('auth.twofactor', [ 'twofactor_notices' => $notices, 'twofactor_redirect' => !empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : url('index'), 'twofactor_attempts_remaining' => $remainingAttempts, - 'twofactor_token' => $tokenInfo['tfa_token'], + 'twofactor_token' => $tokenInfo->getToken(), ]); diff --git a/public/settings/account.php b/public/settings/account.php index bc81b67b..9fa7f3bf 100644 --- a/public/settings/account.php +++ b/public/settings/account.php @@ -2,6 +2,7 @@ namespace Misuzu; use Misuzu\AuditLog; +use Misuzu\Config; use Misuzu\Users\User; use Misuzu\Users\UserSession; use chillerlan\QRCode\QRCode; @@ -46,13 +47,14 @@ if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) { if($isVerifiedRequest && isset($_POST['tfa']['enable']) && (bool)$twoFactorInfo['totp_enabled'] !== (bool)$_POST['tfa']['enable']) { if((bool)$_POST['tfa']['enable']) { $tfaKey = TOTP::generateKey(); + $tfaIssuer = Config::get('site.name', Config::TYPE_STR, 'Misuzu'); $tfaQrcode = (new QRCode(new QROptions([ 'version' => 5, 'outputType' => QRCode::OUTPUT_IMAGE_JPG, 'eccLevel' => QRCode::ECC_L, - ])))->render(sprintf('otpauth://totp/Flashii:%s?%s', $twoFactorInfo['username'], http_build_query([ + ])))->render(sprintf('otpauth://totp/%s:%s?%s', $tfaIssuer, $twoFactorInfo['username'], http_build_query([ 'secret' => $tfaKey, - 'issuer' => 'Flashii', + 'issuer' => $tfaIssuer, ]))); Template::set([ diff --git a/src/Users/UserAuthSession.php b/src/Users/UserAuthSession.php new file mode 100644 index 00000000..9f984e44 --- /dev/null +++ b/src/Users/UserAuthSession.php @@ -0,0 +1,93 @@ +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->tfa_token; + } + + public function getCreationTime(): int { + return $this->tfa_created === null ? -1 : $this->tfa_created; + } + public function getExpirationTime(): int { + return $this->getCreationTime() + self::TOKEN_LIFETIME; + } + public function hasExpired(): bool { + return $this->getExpirationTime() <= time(); + } + + public function delete(): void { + DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `tfa_token` = :token') + ->bind('token', $this->tfa_token) + ->execute(); + } + + public static function generateToken(): string { + return bin2hex(random_bytes(self::TOKEN_WIDTH)); + } + + public static function create(User $user, bool $return = true): ?self { + $token = self::generateToken(); + $created = DB::prepare('INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`user_id`, `tfa_token`) VALUES (:user, :token)') + ->bind('user', $user->getId()) + ->bind('token', $token) + ->execute(); + + if(!$created) + throw new UserAuthSessionCreationFailedException; + if(!$return) + return null; + + try { + $object = self::byToken($token); + $object->user = $user; + return $object; + } catch(UserAuthSessionNotFoundException $ex) { + throw new UserAuthSessionCreationFailedException; + } + } + + private static function byQueryBase(): string { + return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE)); + } + public static function byToken(string $token): self { + $object = DB::prepare(self::byQueryBase() . ' WHERE `tfa_token` = :token') + ->bind('token', $token) + ->fetchObject(self::class); + + if(!$object) + throw new UserAuthSessionNotFoundException; + + return $object; + } +} diff --git a/src/Users/UserChatToken.php b/src/Users/UserChatToken.php index 1c32b87d..547b8b95 100644 --- a/src/Users/UserChatToken.php +++ b/src/Users/UserChatToken.php @@ -15,6 +15,7 @@ class UserChatToken { private $user = null; + public const TOKEN_WIDTH = 32; public const TOKEN_LIFETIME = 60 * 60 * 24 * 7; public const TABLE = 'user_chat_tokens'; @@ -56,7 +57,7 @@ class UserChatToken { } public static function generateToken(): string { - return bin2hex(random_bytes(32)); + return bin2hex(random_bytes(self::TOKEN_WIDTH)); } public static function create(User $user): self { diff --git a/src/Users/auth.php b/src/Users/auth.php deleted file mode 100644 index 824e2727..00000000 --- a/src/Users/auth.php +++ /dev/null @@ -1,50 +0,0 @@ -bind('user_id', $userId); - $createToken->bind('token', $token); - - if(!$createToken->execute()) - return ''; - - return $token; -} - -function user_auth_tfa_token_invalidate(string $token): void { - $deleteToken = \Misuzu\DB::prepare(' - DELETE FROM `msz_auth_tfa` - WHERE `tfa_token` = :token - '); - $deleteToken->bind('token', $token); - $deleteToken->execute(); -} - -function user_auth_tfa_token_info(string $token): array { - $getTokenInfo = \Misuzu\DB::prepare(' - SELECT - at.`user_id`, at.`tfa_token`, at.`tfa_created`, u.`user_totp_key` - FROM `msz_auth_tfa` AS at - LEFT JOIN `msz_users` AS u - ON u.`user_id` = at.`user_id` - WHERE at.`tfa_token` = :token - AND at.`tfa_created` >= NOW() - INTERVAL 15 MINUTE - '); - $getTokenInfo->bind('token', $token); - return $getTokenInfo->fetch(); -}