From 245878f7f80254c46a871d9025aab43a94a1e234 Mon Sep 17 00:00:00 2001 From: flashwave Date: Tue, 12 May 2020 18:30:22 +0000 Subject: [PATCH] Modular authentication. --- _manage.php | 2 +- config.example.ini | 24 ++++++++++ public/index.php | 97 ++++---------------------------------- src/Auth/AuthInterface.php | 7 +++ src/Auth/MisuzuAuth.php | 66 ++++++++++++++++++++++++++ src/Auth/NabuccoAuth.php | 35 ++++++++++++++ src/Auth/OAuthAuth.php | 10 ++++ src/Auth/SockChatAuth.php | 28 +++++++++++ 8 files changed, 181 insertions(+), 88 deletions(-) create mode 100644 config.example.ini create mode 100644 src/Auth/AuthInterface.php create mode 100644 src/Auth/MisuzuAuth.php create mode 100644 src/Auth/NabuccoAuth.php create mode 100644 src/Auth/OAuthAuth.php create mode 100644 src/Auth/SockChatAuth.php diff --git a/_manage.php b/_manage.php index 7023f69..6976685 100644 --- a/_manage.php +++ b/_manage.php @@ -3,7 +3,7 @@ namespace EEPROM; function mszDieIfNotAuth(): void { if(!User::hasActive()) { - $mszUserId = checkMszAuth(strval(filter_input(INPUT_COOKIE, 'msz_auth'))); + $mszUserId = (new Auth\MisuzuAuth)->verifyToken(strval(filter_input(INPUT_COOKIE, 'msz_auth'))); if($mszUserId > 0) User::byId($mszUserId)->setActive(); } diff --git a/config.example.ini b/config.example.ini new file mode 100644 index 0000000..b1d8e37 --- /dev/null +++ b/config.example.ini @@ -0,0 +1,24 @@ +[PDO] +dsn = https://www.php.net/manual/en/ref.pdo-mysql.connection.php +username = mariadb username +password = mariadb password + +[Auth] +; Must be implementations of \EEPROM\Auth\AuthInterface +clients[] = \EEPROM\Auth\MisuzuAuth +clients[] = \EEPROM\Auth\SockChatAuth + +[Misuzu] +config = /path/to/misuzu/config.ini + +[Nabucco] +secret = secret key + +[Uploads] +short_domain = i.eeprom.domain + +[CORS] +; List of allowed remote domains +origins[] = flashii.net +origins[] = chat.flashii.net +origins[] = sockchat.flashii.net diff --git a/public/index.php b/public/index.php index 60b2d99..fa81209 100644 --- a/public/index.php +++ b/public/index.php @@ -22,13 +22,12 @@ function eepromOriginAllowed(string $origin): bool { } function eepromByteSymbol(int $bytes, bool $decimal = true, array $symbols = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']): string { - if($bytes < 1) { + if($bytes < 1) return '0 B'; - } $divider = $decimal ? 1000 : 1024; $exp = floor(log($bytes) / log($divider)); - $bytes = $bytes / pow($divider, floor($exp)); + $bytes = $bytes / pow($divider, $exp); $symbol = $symbols[$exp]; return sprintf("%.2f %s%sB", $bytes, $symbol, $symbol !== '' && !$decimal ? 'i' : ''); @@ -62,95 +61,19 @@ if($reqMethod === 'OPTIONS') { return; } -function connectMszDatabase(): \PDO { - global $mszPdo; - - if($mszPdo) - return $mszPdo; - - $configPath = Config::get('Misuzu', 'config', ''); - - if(!is_file($configPath)) - throw new \Exception('Cannot find Misuzu configuration.'); - - $config = parse_ini_file($configPath, true)['Database']; - $dsn = ($config['driver'] ?? 'mysql') . ':'; - - foreach($config as $key => $value) { - if($key === 'driver' || $key === 'username' || $key === 'password') - continue; - if($key === 'database') - $key = 'dbname'; - - $dsn .= $key . '=' . $value . ';'; - } - - try { - $mszPdo = new \PDO($dsn, $config['username'], $config['password'], DB::FLAGS); - } catch(\PDOException $ex) { - throw new \Exception('Unable to connect to Misuzu database.'); - } - - return $mszPdo; -} - -function checkSockChatAuth(string $token): int { - if(strpos($token, '_') === false) - return -1; - - $mszPdo = connectMszDatabase(); - $tokenParts = explode('_', $token, 2); - $userId = intval($tokenParts[0] ?? 0); - $chatToken = strval($tokenParts[1] ?? ''); - - $getUserId = $mszPdo->prepare(' - SELECT `user_id` - FROM `msz_user_chat_tokens` - WHERE `user_id` = :user - AND `token_string` = :token - AND `token_created` > NOW() - INTERVAL 1 WEEK - '); - $getUserId->bindValue('user', $userId); - $getUserId->bindValue('token', $chatToken); - $getUserId->execute(); - - return (int)$getUserId->fetchColumn(); -} - -function checkMszAuth(string $token): int { - $packed = Base64::decode($token, true); - $packed = str_pad($packed, 37, "\x00"); - $unpacked = unpack('Cversion/Nuser/H64token', $packed); - - if($unpacked['version'] !== 1) - return -1; - - $getUserId = connectMszDatabase()->prepare(' - SELECT `user_id` - FROM `msz_sessions` - WHERE `user_id` = :user - AND `session_key` = :token - AND `session_expires` > NOW() - '); - $getUserId->bindValue('user', $unpacked['user']); - $getUserId->bindValue('token', $unpacked['token']); - $getUserId->execute(); - - return (int)$getUserId->fetchColumn(); -} - if(!isset($isShortDomain) && !empty($_SERVER['HTTP_AUTHORIZATION'])) { $authParts = explode(' ', $_SERVER['HTTP_AUTHORIZATION'], 2); $authMethod = strval($authParts[0] ?? ''); $authToken = strval($authParts[1] ?? ''); - switch($authMethod) { - case 'SockChat': - $authUserId = checkSockChatAuth($authToken); - break; - case 'Misuzu': - $authUserId = checkMszAuth($authToken); - break; + $authClients = Config::get('Auth', 'clients', []); + + foreach($authClients as $client) { + $client = new $client; + if($client->getName() !== $authMethod) + continue; + $authUserId = $client->verifyToken($authToken); + break; } if(isset($authUserId) && $authUserId > 0) diff --git a/src/Auth/AuthInterface.php b/src/Auth/AuthInterface.php new file mode 100644 index 0000000..9750974 --- /dev/null +++ b/src/Auth/AuthInterface.php @@ -0,0 +1,7 @@ + $value) { + if($key === 'driver' || $key === 'username' || $key === 'password') + continue; + if($key === 'database') + $key = 'dbname'; + + $dsn .= $key . '=' . $value . ';'; + } + + try { + static::$database = new PDO($dsn, $config['username'], $config['password'], DB::FLAGS); + } catch(PDOException $ex) { + throw new \Exception('Unable to connect to Misuzu database.'); + } + + return static::$database; + } + + public function getName(): string { return 'Misuzu'; } + + public function verifyToken(string $token): int { + $packed = Base64::decode($token, true); + $packed = str_pad($packed, 37, "\x00"); + $unpacked = unpack('Cversion/Nuser/H64token', $packed); + + if($unpacked['version'] !== 1) + return -1; + + $getUserId = $this->getDatabase()->prepare(' + SELECT `user_id` + FROM `msz_sessions` + WHERE `user_id` = :user + AND `session_key` = :token + AND `session_expires` > NOW() + '); + $getUserId->bindValue('user', $unpacked['user']); + $getUserId->bindValue('token', $unpacked['token']); + $getUserId->execute(); + + return (int)$getUserId->fetchColumn(); + } +} diff --git a/src/Auth/NabuccoAuth.php b/src/Auth/NabuccoAuth.php new file mode 100644 index 0000000..f7b306b --- /dev/null +++ b/src/Auth/NabuccoAuth.php @@ -0,0 +1,35 @@ +secretKey = Config::get('Nabucco', 'secret', ''); + } + + public function getName(): string { return 'Nabucco'; } + + public function hashToken(string $token): string { + return hash_hmac('md5', $token, $this->secretKey); + } + + public function verifyToken(string $token): int { + $length = strlen($token); + if($length < 32 || $length > 100) + return -1; + $userHash = substr($token, 0, 32); + $packed = Base64::decode(substr($token, 32), true); + $realHash = $this->hashToken($packed); + if(!hash_equals($realHash, $userHash)) + return -1; + $unpacked = unpack('NuserId/Ntime/CipWidth/a16ipAddr', $packed); + if(empty($unpacked['userId']) || empty($unpacked['time']) + || $unpacked['time'] < strtotime('-1 month')) + return -1; + return intval($unpacked['userId'] ?? -1); + } +} diff --git a/src/Auth/OAuthAuth.php b/src/Auth/OAuthAuth.php new file mode 100644 index 0000000..1cbd39e --- /dev/null +++ b/src/Auth/OAuthAuth.php @@ -0,0 +1,10 @@ +getDatabase()->prepare(' + SELECT `user_id` + FROM `msz_user_chat_tokens` + WHERE `user_id` = :user + AND `token_string` = :token + AND `token_created` > NOW() - INTERVAL 1 WEEK + '); + $getUserId->bindValue('user', $userId); + $getUserId->bindValue('token', $chatToken); + $getUserId->execute(); + + return (int)$getUserId->fetchColumn(); + } +}