From fb4d859ba235d958e3025d33e23c151197e954e4 Mon Sep 17 00:00:00 2001 From: NaysKutzu Date: Tue, 14 Jan 2025 12:48:16 +0100 Subject: [PATCH 1/2] PUSH -> Add rate limiting to combat against DDoS attacks / abuse -> Fixed a memory leak on session page -> Rewrote github api to use guzzle -> Debug info for redis -> Debug info for rate limits --- backend/Dockerfile | 3 +- backend/app/Api/User/Session.php | 274 ------- backend/app/Api/User/Session/Session.php | 283 ++++++++ backend/app/App.php | 40 +- backend/app/Chat/User.php | 674 +++++++++--------- backend/app/Chat/UserActivities.php | 114 +-- backend/app/Hooks/GitHub.php | 57 +- backend/app/Hooks/MythicalAPP.php | 7 + backend/composer.json | 4 +- backend/composer.lock | 665 ++++++++++++++++- backend/public/index.php | 3 +- frontend/src/App.vue | 22 +- frontend/src/assets/main.css | 9 +- .../src/components/client/LayoutDashboard.vue | 379 +++++----- frontend/src/mythicalclient/Settings.ts | 29 +- 15 files changed, 1659 insertions(+), 904 deletions(-) delete mode 100755 backend/app/Api/User/Session.php create mode 100755 backend/app/Api/User/Session/Session.php diff --git a/backend/Dockerfile b/backend/Dockerfile index bd773d5..1fb0606 100755 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -25,7 +25,8 @@ RUN apt-get update && apt-get install -y \ xml \ mbstring \ curl \ - zip + zip \ + redis RUN chown -R www-data:www-data /var/www/html \ && chmod -R 755 /var/www/html diff --git a/backend/app/Api/User/Session.php b/backend/app/Api/User/Session.php deleted file mode 100755 index 130ccf2..0000000 --- a/backend/app/Api/User/Session.php +++ /dev/null @@ -1,274 +0,0 @@ -post('/api/user/session/info/update', function (): void { - App::init(); - $appInstance = App::getInstance(true); - $config = $appInstance->getConfig(); - - $appInstance->allowOnlyPOST(); - $session = new Session($appInstance); - - try { - if (!isset($_POST['first_name']) && $_POST['first_name'] == '') { - $appInstance->BadRequest('First name is missing!', ['error_code' => 'FIRST_NAME_MISSING']); - } - if (!isset($_POST['last_name']) && $_POST['last_name'] == '') { - $appInstance->BadRequest('Last name is missing!', ['error_code' => 'LAST_NAME_MISSING']); - } - if (!isset($_POST['email']) && $_POST['email'] == '') { - $appInstance->BadRequest('Email is missing!', ['error_code' => 'EMAIL_MISSING']); - } - if (!isset($_POST['avatar']) && $_POST['avatar'] == '') { - $appInstance->BadRequest('Avatar is missing!', ['error_code' => 'AVATAR_MISSING']); - } - if (!isset($_POST['background']) && $_POST['background'] == '') { - $appInstance->BadRequest('Background is missing!', ['error_code' => 'BACKGROUND_MISSING']); - } - - if ($_POST['email'] != $session->getInfo(UserColumns::EMAIL, false) && User::exists(UserColumns::EMAIL, $_POST['email'])) { - $appInstance->BadRequest('Email already exists!', ['error_code' => 'EMAIL_EXISTS']); - } - - $session->setInfo(UserColumns::FIRST_NAME, $_POST['first_name'], true); - $session->setInfo(UserColumns::LAST_NAME, $_POST['last_name'], true); - $session->setInfo(UserColumns::EMAIL, $_POST['email'], false); - $session->setInfo(UserColumns::AVATAR, $_POST['avatar'], false); - $session->setInfo(UserColumns::BACKGROUND, $_POST['background'], false); - - $appInstance->OK('User info updated successfully!', []); - } catch (Exception $e) { - $appInstance->getLogger()->error('Failed to update user info! ' . $e->getMessage()); - $appInstance->BadRequest('Bad Request', ['error_code' => 'DB_ERROR', 'error' => $e->getMessage()]); - } -}); - -$router->post('/api/user/session/billing/update', function (): void { - App::init(); - $appInstance = App::getInstance(true); - $config = $appInstance->getConfig(); - - $appInstance->allowOnlyPOST(); - $session = new Session($appInstance); - - try { - if (!isset($_POST['company_name']) && $_POST['company_name'] == '') { - $appInstance->BadRequest('Company name is missing!', ['error_code' => 'COMPANY_NAME_MISSING']); - } - $companyName = $_POST['company_name']; - if (!isset($_POST['vat_number']) && $_POST['vat_number'] == '') { - $appInstance->BadRequest('VAT Number is missing!', ['error_code' => 'VAT_NUMBER_MISSING']); - } - $vatNumber = $_POST['vat_number']; - if (!isset($_POST['address1']) && $_POST['address1'] == '') { - $appInstance->BadRequest('Address 1 is missing', ['error_code' => 'ADDRESS1_MISSING']); - } - $address1 = $_POST['address1']; - if (!isset($_POST['address2']) && $_POST['address2'] == '') { - $appInstance->BadRequest('Address 2 is missing', ['error_code' => 'ADDRESS2_MISSING']); - } - $address2 = $_POST['address2']; - if (!isset($_POST['city']) && $_POST['city'] == '') { - $appInstance->BadRequest('City is missing', ['error_code' => 'CITY_MISSING']); - } - $city = $_POST['city']; - if (!isset($_POST['country']) && $_POST['country'] == '') { - $appInstance->BadRequest('Country is missing', ['error_code' => 'COUNTRY_MISSING']); - } - $country = $_POST['country']; - if (!isset($_POST['state']) && $_POST['state'] == '') { - $appInstance->BadRequest('State is missing', ['error_code' => 'STATE_MISSING']); - } - $state = $_POST['state']; - if (!isset($_POST['postcode']) && $_POST['postcode'] == '') { - $appInstance->BadRequest('PostCode is missing', ['error_code' => 'POSTCODE_MISSING']); - } - $postcode = $_POST['postcode']; - - Billing::updateBilling( - $session->getInfo(UserColumns::UUID, false), - $companyName, - $vatNumber, - $address1, - $address2, - $city, - $country, - $state, - $postcode - ); - - $appInstance->OK('Billing info saved successfully!', []); - } catch (Exception $e) { - $appInstance->getLogger()->error('Failed to save billing info! ' . $e->getMessage()); - $appInstance->BadRequest('Bad Request', ['error_code' => 'DB_ERROR', 'error' => $e->getMessage()]); - } -}); - -$router->get('/api/user/session', function (): void { - App::init(); - $appInstance = App::getInstance(true); - $config = $appInstance->getConfig(); - - $appInstance->allowOnlyGET(); - - $session = new Session($appInstance); - - $accountToken = $session->SESSION_KEY; - try { - $billing = Billing::getBillingData(User::getInfo($accountToken, UserColumns::UUID, false)); - $appInstance->OK('Account token is valid', [ - 'user_info' => [ - 'username' => User::getInfo($accountToken, UserColumns::USERNAME, false), - 'email' => User::getInfo($accountToken, UserColumns::EMAIL, false), - 'verified' => User::getInfo($accountToken, UserColumns::VERIFIED, false), - 'banned' => User::getInfo($accountToken, UserColumns::BANNED, false), - '2fa_blocked' => User::getInfo($accountToken, UserColumns::TWO_FA_BLOCKED, false), - '2fa_enabled' => User::getInfo($accountToken, UserColumns::TWO_FA_ENABLED, false), - '2fa_secret' => User::getInfo($accountToken, UserColumns::TWO_FA_KEY, false), - 'first_name' => User::getInfo($accountToken, UserColumns::FIRST_NAME, true), - 'last_name' => User::getInfo($accountToken, UserColumns::LAST_NAME, true), - 'avatar' => User::getInfo($accountToken, UserColumns::AVATAR, false), - 'uuid' => User::getInfo($accountToken, UserColumns::UUID, false), - 'role_id' => User::getInfo($accountToken, UserColumns::ROLE_ID, false), - 'first_ip' => User::getInfo($accountToken, UserColumns::FIRST_IP, false), - 'last_ip' => User::getInfo($accountToken, UserColumns::LAST_IP, false), - 'deleted' => User::getInfo($accountToken, UserColumns::DELETED, false), - 'last_seen' => User::getInfo($accountToken, UserColumns::LAST_SEEN, false), - 'first_seen' => User::getInfo($accountToken, UserColumns::FIRST_SEEN, false), - 'background' => User::getInfo($accountToken, UserColumns::BACKGROUND, false), - 'role_name' => Roles::getUserRoleName(User::getInfo($accountToken, UserColumns::UUID, false)), - 'role_real_name' => strtolower(Roles::getUserRoleName(User::getInfo($accountToken, UserColumns::UUID, false))), - ], - 'billing' => $billing, - ]); - } catch (Exception $e) { - $appInstance->BadRequest('Bad Request', ['error_code' => 'INVALID_ACCOUNT_TOKEN', 'error' => $e->getMessage()]); - } - -}); - -$router->get('/api/user/session/activities', function (): void { - App::init(); - $appInstance = App::getInstance(true); - $config = $appInstance->getConfig(); - - $appInstance->allowOnlyGET(); - - $session = new Session($appInstance); - - $accountToken = $session->SESSION_KEY; - - $appInstance->OK('User activities', [ - 'activities' => UserActivities::get(User::getInfo($accountToken, UserColumns::UUID, false)), - ]); -}); diff --git a/backend/app/Api/User/Session/Session.php b/backend/app/Api/User/Session/Session.php new file mode 100755 index 0000000..b05d783 --- /dev/null +++ b/backend/app/Api/User/Session/Session.php @@ -0,0 +1,283 @@ +post('/api/user/session/info/update', function (): void { + App::init(); + $appInstance = App::getInstance(true); + $config = $appInstance->getConfig(); + + $appInstance->allowOnlyPOST(); + $session = new Session($appInstance); + + try { + if (!isset($_POST['first_name']) && $_POST['first_name'] == '') { + $appInstance->BadRequest('First name is missing!', ['error_code' => 'FIRST_NAME_MISSING']); + } + if (!isset($_POST['last_name']) && $_POST['last_name'] == '') { + $appInstance->BadRequest('Last name is missing!', ['error_code' => 'LAST_NAME_MISSING']); + } + if (!isset($_POST['email']) && $_POST['email'] == '') { + $appInstance->BadRequest('Email is missing!', ['error_code' => 'EMAIL_MISSING']); + } + if (!isset($_POST['avatar']) && $_POST['avatar'] == '') { + $appInstance->BadRequest('Avatar is missing!', ['error_code' => 'AVATAR_MISSING']); + } + if (!isset($_POST['background']) && $_POST['background'] == '') { + $appInstance->BadRequest('Background is missing!', ['error_code' => 'BACKGROUND_MISSING']); + } + + if ($_POST['email'] != $session->getInfo(UserColumns::EMAIL, false) && User::exists(UserColumns::EMAIL, $_POST['email'])) { + $appInstance->BadRequest('Email already exists!', ['error_code' => 'EMAIL_EXISTS']); + } + + $session->setInfo(UserColumns::FIRST_NAME, $_POST['first_name'], true); + $session->setInfo(UserColumns::LAST_NAME, $_POST['last_name'], true); + $session->setInfo(UserColumns::EMAIL, $_POST['email'], false); + $session->setInfo(UserColumns::AVATAR, $_POST['avatar'], false); + $session->setInfo(UserColumns::BACKGROUND, $_POST['background'], false); + + $appInstance->OK('User info updated successfully!', []); + } catch (Exception $e) { + $appInstance->getLogger()->error('Failed to update user info! ' . $e->getMessage()); + $appInstance->BadRequest('Bad Request', ['error_code' => 'DB_ERROR', 'error' => $e->getMessage()]); + } +}); + +$router->post('/api/user/session/billing/update', function (): void { + App::init(); + $appInstance = App::getInstance(true); + $config = $appInstance->getConfig(); + + $appInstance->allowOnlyPOST(); + $session = new Session($appInstance); + + try { + if (!isset($_POST['company_name']) && $_POST['company_name'] == '') { + $appInstance->BadRequest('Company name is missing!', ['error_code' => 'COMPANY_NAME_MISSING']); + } + $companyName = $_POST['company_name']; + if (!isset($_POST['vat_number']) && $_POST['vat_number'] == '') { + $appInstance->BadRequest('VAT Number is missing!', ['error_code' => 'VAT_NUMBER_MISSING']); + } + $vatNumber = $_POST['vat_number']; + if (!isset($_POST['address1']) && $_POST['address1'] == '') { + $appInstance->BadRequest('Address 1 is missing', ['error_code' => 'ADDRESS1_MISSING']); + } + $address1 = $_POST['address1']; + if (!isset($_POST['address2']) && $_POST['address2'] == '') { + $appInstance->BadRequest('Address 2 is missing', ['error_code' => 'ADDRESS2_MISSING']); + } + $address2 = $_POST['address2']; + if (!isset($_POST['city']) && $_POST['city'] == '') { + $appInstance->BadRequest('City is missing', ['error_code' => 'CITY_MISSING']); + } + $city = $_POST['city']; + if (!isset($_POST['country']) && $_POST['country'] == '') { + $appInstance->BadRequest('Country is missing', ['error_code' => 'COUNTRY_MISSING']); + } + $country = $_POST['country']; + if (!isset($_POST['state']) && $_POST['state'] == '') { + $appInstance->BadRequest('State is missing', ['error_code' => 'STATE_MISSING']); + } + $state = $_POST['state']; + if (!isset($_POST['postcode']) && $_POST['postcode'] == '') { + $appInstance->BadRequest('PostCode is missing', ['error_code' => 'POSTCODE_MISSING']); + } + $postcode = $_POST['postcode']; + + Billing::updateBilling( + $session->getInfo(UserColumns::UUID, false), + $companyName, + $vatNumber, + $address1, + $address2, + $city, + $country, + $state, + $postcode + ); + + $appInstance->OK('Billing info saved successfully!', []); + } catch (Exception $e) { + $appInstance->getLogger()->error('Failed to save billing info! ' . $e->getMessage()); + $appInstance->BadRequest('Bad Request', ['error_code' => 'DB_ERROR', 'error' => $e->getMessage()]); + } +}); + +$router->get('/api/user/session', function (): void { + App::init(); + $appInstance = App::getInstance(true); + $config = $appInstance->getConfig(); + + $appInstance->allowOnlyGET(); + + $session = new Session($appInstance); + + $accountToken = $session->SESSION_KEY; + try { + $billing = Billing::getBillingData(User::getInfo($accountToken, UserColumns::UUID, false)); + $columns = [ + UserColumns::USERNAME, + UserColumns::EMAIL, + UserColumns::VERIFIED, + UserColumns::BANNED, + UserColumns::TWO_FA_BLOCKED, + UserColumns::TWO_FA_ENABLED, + UserColumns::TWO_FA_KEY, + UserColumns::FIRST_NAME, + UserColumns::LAST_NAME, + UserColumns::AVATAR, + UserColumns::UUID, + UserColumns::ROLE_ID, + UserColumns::FIRST_IP, + UserColumns::LAST_IP, + UserColumns::DELETED, + UserColumns::LAST_SEEN, + UserColumns::FIRST_SEEN, + UserColumns::BACKGROUND + ]; + + $info = User::getInfoArray($accountToken, $columns, [ + UserColumns::FIRST_NAME, + UserColumns::LAST_NAME, + UserColumns::TWO_FA_KEY, + ]); + $info['role_name'] = Roles::getUserRoleName($info[UserColumns::UUID]); + $info['role_real_name'] = strtolower($info['role_name']); + + $appInstance->OK('Account token is valid', [ + 'user_info' => $info, + 'billing' => $billing, + ]); + + } catch (Exception $e) { + $appInstance->BadRequest('Bad Request', ['error_code' => 'INVALID_ACCOUNT_TOKEN', 'error' => $e->getMessage()]); + } + +}); + +$router->get('/api/user/session/activities', function (): void { + App::init(); + $appInstance = App::getInstance(true); + $config = $appInstance->getConfig(); + + $appInstance->allowOnlyGET(); + + $session = new Session($appInstance); + + $accountToken = $session->SESSION_KEY; + + $appInstance->OK('User activities', [ + 'activities' => UserActivities::get(User::getInfo($accountToken, UserColumns::UUID, false)), + ]); +}); diff --git a/backend/app/App.php b/backend/app/App.php index 08d4f7f..873ccbb 100755 --- a/backend/app/App.php +++ b/backend/app/App.php @@ -105,6 +105,10 @@ namespace MythicalClient; +use MythicalClient\CloudFlare\CloudFlareRealIP; +use RateLimit\Exception\LimitExceeded; +use RateLimit\Rate; +use RateLimit\RedisRateLimiter; use Router\Router as rt; use MythicalClient\Chat\Database; use MythicalSystems\Utils\XChaCha20; @@ -142,6 +146,33 @@ public function __construct(bool $softBoot) return; } + /** + * Redis. + */ + $redis = new FastChat\Redis(); + if ($redis->testConnection() == false) { + define('REDIS_ENABLED', false); + } else { + define('REDIS_ENABLED', true); + } + + // @phpstan-ignore-next-line + $rateLimiter = new RedisRateLimiter(Rate::perMinute(RATE_LIMIT), new \Redis(), "rate_limiting"); + try { + $rateLimiter->limit(CloudFlareRealIP::getRealIP()); + } catch (LimitExceeded $e) { + self::getLogger()->error('User: '. $e->getMessage()); + self::init(); + self::ServiceUnavailable('You are being rate limited!', ['error_code' => 'RATE_LIMITED']); + } catch (\Exception $e) { + self::getLogger()->error("-----------------------------"); + self::getLogger()->error("REDIS SERVER IS DOWN"); + self::getLogger()->error("RATE LIMITING IS DISABLED"); + self::getLogger()->error("YOU SHOULD FIX THIS ASAP"); + self::getLogger()->error("NO SUPPORT WILL BE PROVIDED"); + self::getLogger()->error("-----------------------------"); + } + /** * Database Connection. */ @@ -158,15 +189,6 @@ public function __construct(bool $softBoot) $this->getConfig()->setSetting('app_url', $_SERVER['HTTP_HOST']); } - /** - * Redis. - */ - $redis = new FastChat\Redis(); - if ($redis->testConnection() == false) { - self::init(); - self::InternalServerError('Failed to connect to Redis', null); - } - /** * Initialize the plugin manager. */ diff --git a/backend/app/Chat/User.php b/backend/app/Chat/User.php index 7da6ca6..921d218 100755 --- a/backend/app/Chat/User.php +++ b/backend/app/Chat/User.php @@ -118,333 +118,359 @@ class User extends Database { - public const TABLE_NAME = 'mythicalclient_users'; - - /** - * Register a new user in the database. - * - * @param string $username The username of the user - * @param string $password The password of the user - * @param string $email The email of the user - * @param string $first_name The first name of the user - * @param string $last_name The last name of the user - * @param string $ip The ip of the user - * - * @return string - */ - public static function register(string $username, string $password, string $email, string $first_name, string $last_name, string $ip): void - { - try { - $first_name = App::getInstance(true)->encrypt($first_name); - $last_name = App::getInstance(true)->encrypt($last_name); - - /** - * The UUID generation and logic. - */ - $uuidMngr = new \MythicalSystems\User\UUIDManager(); - $uuid = $uuidMngr->generateUUID(); - $token = App::getInstance(true)->encrypt(date('Y-m-d H:i:s') . $uuid . random_bytes(16) . base64_encode($email)); - - /** - * GRAvatar Logic. - */ - try { - $gravatar = new Gravatar(['s' => 9001], true); - $avatar = $gravatar->avatar($email); - } catch (\Exception) { - $avatar = 'https://www.gravatar.com/avatar'; - } - - /** - * Get the PDO connection. - */ - $pdoConnection = self::getPdoConnection(); - - /** - * Prepare the statement. - */ - $stmt = $pdoConnection->prepare(' + public const TABLE_NAME = 'mythicalclient_users'; + + /** + * Register a new user in the database. + * + * @param string $username The username of the user + * @param string $password The password of the user + * @param string $email The email of the user + * @param string $first_name The first name of the user + * @param string $last_name The last name of the user + * @param string $ip The ip of the user + * + * @return string + */ + public static function register(string $username, string $password, string $email, string $first_name, string $last_name, string $ip): void + { + try { + $first_name = App::getInstance(true)->encrypt($first_name); + $last_name = App::getInstance(true)->encrypt($last_name); + + /** + * The UUID generation and logic. + */ + $uuidMngr = new \MythicalSystems\User\UUIDManager(); + $uuid = $uuidMngr->generateUUID(); + $token = App::getInstance(true)->encrypt(date('Y-m-d H:i:s') . $uuid . random_bytes(16) . base64_encode($email)); + + /** + * GRAvatar Logic. + */ + try { + $gravatar = new Gravatar(['s' => 9001], true); + $avatar = $gravatar->avatar($email); + } catch (\Exception) { + $avatar = 'https://www.gravatar.com/avatar'; + } + + /** + * Get the PDO connection. + */ + $pdoConnection = self::getPdoConnection(); + + /** + * Prepare the statement. + */ + $stmt = $pdoConnection->prepare(' INSERT INTO ' . self::TABLE_NAME . ' (username, first_name, last_name, email, password, avatar, background, uuid, token, role, first_ip, last_ip, banned, verified) VALUES (:username, :first_name, :last_name, :email, :password, :avatar, :background, :uuid, :token, :role, :first_ip, :last_ip, :banned, :verified) '); - $password = App::getInstance(true)->encrypt($password); - - $stmt->execute([ - ':username' => $username, - ':first_name' => $first_name, - ':last_name' => $last_name, - ':email' => $email, - ':password' => $password, - ':avatar' => $avatar, - ':background' => 'https://cdn.mythicalsystems.xyz/background.gif', - ':uuid' => $uuid, - ':token' => $token, - ':role' => 1, - ':first_ip' => $ip, - ':last_ip' => $ip, - ':banned' => 'NO', - ':verified' => 'false', - ]); - \MythicalClient\MythicalSystems\Telemetry::send(\MythicalClient\MythicalSystems\TelemetryCollection::USER_NEW); - /** - * Check if the mail is enabled. - * - * If it is, the user is not verified. - * - * If it is not, the user is verified. - */ - if (Mail::isEnabled()) { - try { - $verify_token = App::getInstance(true)->generateCode(); - Verification::add($verify_token, $uuid, EmailVerificationColumns::$type_verify); - Verify::sendMail($uuid, $verify_token); - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); - self::updateInfo($token, UserColumns::VERIFIED, 'false', false); - } - } else { - self::updateInfo($token, UserColumns::VERIFIED, 'true', false); - } - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to register user: ' . $e->getMessage()); - throw new \Exception('Failed to register user: ' . $e->getMessage()); - } - } - - /** - * Get the list of users. - * - * @return array The list of users - */ - public static function getList(): array - { - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT * FROM ' . self::TABLE_NAME); - $stmt->execute(); - - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - } - - /** - * Forgot password logic. - * - * @param string $email The email of the user - * - * @return bool If the email was sent - */ - public static function forgotPassword(string $email): bool - { - try { - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT token, uuid FROM ' . self::TABLE_NAME . ' WHERE email = :email'); - $stmt->bindParam(':email', $email); - $stmt->execute(); - $user = $stmt->fetch(\PDO::FETCH_ASSOC); - - if ($user) { - if (Mail::isEnabled()) { - try { - $verify_token = $verify_token = App::getInstance(true)->generateCode(); - Verification::add($verify_token, $user['uuid'], EmailVerificationColumns::$type_password); - ResetPassword::sendMail($user['uuid'], $verify_token); - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); - } - - return true; - } - - return false; - } - - return false; - } catch (\Exception $e) { - return false; - } - } - - /** - * Login the user. - * - * @param string $login The login of the user - * @param string $password The password of the user - * - * @return string If the login was successful - */ - public static function login(string $login, string $password): string - { - try { - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT password, token, uuid FROM ' . self::TABLE_NAME . ' WHERE username = :login OR email = :login'); - $stmt->bindParam(':login', $login); - $stmt->execute(); - $user = $stmt->fetch(\PDO::FETCH_ASSOC); - if ($user) { - if (App::getInstance(true)->decrypt($user['password']) == $password) { - self::logout(); - if (!$user['token'] == '') { - setcookie('user_token', $user['token'], time() + 3600, '/'); - } else { - App::getInstance(true)->getLogger()->error('Failed to login user: Token is empty'); - - return 'false'; - } - if (Mail::isEnabled()) { - try { - NewLogin::sendMail($user['uuid']); - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); - } - } - UserActivities::add($user['uuid'], UserActivitiesTypes::$login, CloudFlare::getRealUserIP()); - - return $user['token']; - } - - return 'false'; - } - - return 'false'; - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to login user: ' . $e->getMessage()); - - return 'false'; - } - } - - /** - * Logout the user. - */ - public static function logout(): void - { - setcookie('user_token', '', time() - 460800 * 460800 * 460800, '/'); - } - - /** - * Does the user info exist? - * - * @param UserColumns $info - */ - public static function exists(UserColumns|string $info, string $value): bool - { - if (!in_array($info, UserColumns::getColumns())) { - throw new \InvalidArgumentException('Invalid column name: ' . $info); - } - - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT * FROM ' . self::TABLE_NAME . ' WHERE ' . $info . ' = :value'); - $stmt->bindParam(':value', $value); - $stmt->execute(); - - return (bool) $stmt->fetchColumn(); - } - - /** - * Get the user info. - * - * @param UserColumns|string $info The column name - * - * @throws \InvalidArgumentException If the column name is invalid - * - * @return string|null The value of the column - */ - public static function getInfo(string $token, UserColumns|string $info, bool $encrypted): ?string - { - if (!in_array($info, UserColumns::getColumns())) { - throw new \InvalidArgumentException('Invalid column name: ' . $info); - } - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT ' . $info . ' FROM ' . self::TABLE_NAME . ' WHERE token = :token'); - $stmt->bindParam(':token', $token); - $stmt->execute(); - if ($encrypted) { - return App::getInstance(true)->decrypt($stmt->fetchColumn()); - } - - return $stmt->fetchColumn(); - - } - - /** - * Update the user info. - * - * @param UserColumns|string $info The column name - * @param string $value The value to update - * @param bool $encrypted If the value is encrypted - * - * @throws \InvalidArgumentException If the column name is invalid - * - * @return bool If the update was successful - */ - public static function updateInfo(string $token, UserColumns|string $info, string $value, bool $encrypted): bool - { - if (!in_array($info, UserColumns::getColumns())) { - throw new \InvalidArgumentException('Invalid column name: ' . $info); - } - $con = self::getPdoConnection(); - if ($encrypted) { - $value = App::getInstance(true)->encrypt($value); - } - $stmt = $con->prepare('UPDATE ' . self::TABLE_NAME . ' SET ' . $info . ' = :value WHERE token = :token'); - $stmt->bindParam(':value', $value); - $stmt->bindParam(':token', $token); - - return $stmt->execute(); - } - - /** - * Get the token from the UUID. - * - * @param string $uuid The UUID - * - * @return string The token - */ - public static function getTokenFromUUID(string $uuid): string - { - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT token FROM ' . self::TABLE_NAME . ' WHERE uuid = :uuid'); - $stmt->bindParam(':uuid', $uuid); - $stmt->execute(); - - return $stmt->fetchColumn(); - } - - /** - * Process the template. - * - * @param string $template The template - * @param string $uuid The UUID - * - * @return string The processed template - */ - public static function processTemplate(string $template, string $uuid): string - { - try { - - $template = str_replace('${uuid}', $uuid, $template); - $template = str_replace('${username}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::USERNAME, false), $template); - $template = str_replace('${email}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::EMAIL, false), $template); - $template = str_replace('${first_name}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_NAME, true), $template); - $template = str_replace('${last_name}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_NAME, true), $template); - $template = str_replace('${avatar}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::AVATAR, false), $template); - $template = str_replace('${background}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::BACKGROUND, false), $template); - $template = str_replace('${role_id}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::ROLE_ID, false), $template); - $template = str_replace('${first_ip}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_IP, false), $template); - $template = str_replace('${last_ip}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_IP, false), $template); - $template = str_replace('${banned}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::BANNED, false), $template); - $template = str_replace('${verified}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::VERIFIED, false), $template); - $template = str_replace('${2fa_enabled}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::TWO_FA_ENABLED, false), $template); - $template = str_replace('${deleted}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::DELETED, false), $template); - $template = str_replace('${last_seen}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_SEEN, false), $template); - $template = str_replace('${first_seen}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_SEEN, false), $template); - - return $template; - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to render email template: ' . $e->getMessage()); - - return null; - } - } + $password = App::getInstance(true)->encrypt($password); + + $stmt->execute([ + ':username' => $username, + ':first_name' => $first_name, + ':last_name' => $last_name, + ':email' => $email, + ':password' => $password, + ':avatar' => $avatar, + ':background' => 'https://cdn.mythicalsystems.xyz/background.gif', + ':uuid' => $uuid, + ':token' => $token, + ':role' => 1, + ':first_ip' => $ip, + ':last_ip' => $ip, + ':banned' => 'NO', + ':verified' => 'false', + ]); + \MythicalClient\MythicalSystems\Telemetry::send(\MythicalClient\MythicalSystems\TelemetryCollection::USER_NEW); + /** + * Check if the mail is enabled. + * + * If it is, the user is not verified. + * + * If it is not, the user is verified. + */ + if (Mail::isEnabled()) { + try { + $verify_token = App::getInstance(true)->generateCode(); + Verification::add($verify_token, $uuid, EmailVerificationColumns::$type_verify); + Verify::sendMail($uuid, $verify_token); + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); + self::updateInfo($token, UserColumns::VERIFIED, 'false', false); + } + } else { + self::updateInfo($token, UserColumns::VERIFIED, 'true', false); + } + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to register user: ' . $e->getMessage()); + throw new \Exception('Failed to register user: ' . $e->getMessage()); + } + } + + /** + * Get the list of users. + * + * @return array The list of users + */ + public static function getList(): array + { + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT * FROM ' . self::TABLE_NAME); + $stmt->execute(); + + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } + + /** + * Forgot password logic. + * + * @param string $email The email of the user + * + * @return bool If the email was sent + */ + public static function forgotPassword(string $email): bool + { + try { + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT token, uuid FROM ' . self::TABLE_NAME . ' WHERE email = :email'); + $stmt->bindParam(':email', $email); + $stmt->execute(); + $user = $stmt->fetch(\PDO::FETCH_ASSOC); + + if ($user) { + if (Mail::isEnabled()) { + try { + $verify_token = $verify_token = App::getInstance(true)->generateCode(); + Verification::add($verify_token, $user['uuid'], EmailVerificationColumns::$type_password); + ResetPassword::sendMail($user['uuid'], $verify_token); + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); + } + + return true; + } + + return false; + } + + return false; + } catch (\Exception $e) { + return false; + } + } + + /** + * Login the user. + * + * @param string $login The login of the user + * @param string $password The password of the user + * + * @return string If the login was successful + */ + public static function login(string $login, string $password): string + { + try { + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT password, token, uuid FROM ' . self::TABLE_NAME . ' WHERE username = :login OR email = :login'); + $stmt->bindParam(':login', $login); + $stmt->execute(); + $user = $stmt->fetch(\PDO::FETCH_ASSOC); + if ($user) { + if (App::getInstance(true)->decrypt($user['password']) == $password) { + self::logout(); + if (!$user['token'] == '') { + setcookie('user_token', $user['token'], time() + 3600, '/'); + } else { + App::getInstance(true)->getLogger()->error('Failed to login user: Token is empty'); + + return 'false'; + } + if (Mail::isEnabled()) { + try { + NewLogin::sendMail($user['uuid']); + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); + } + } + UserActivities::add($user['uuid'], UserActivitiesTypes::$login, CloudFlare::getRealUserIP()); + + return $user['token']; + } + + return 'false'; + } + + return 'false'; + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to login user: ' . $e->getMessage()); + + return 'false'; + } + } + + /** + * Logout the user. + */ + public static function logout(): void + { + setcookie('user_token', '', time() - 460800 * 460800 * 460800, '/'); + } + + /** + * Does the user info exist? + * + * @param UserColumns $info + */ + public static function exists(UserColumns|string $info, string $value): bool + { + if (!in_array($info, UserColumns::getColumns())) { + throw new \InvalidArgumentException('Invalid column name: ' . $info); + } + + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT * FROM ' . self::TABLE_NAME . ' WHERE ' . $info . ' = :value'); + $stmt->bindParam(':value', $value); + $stmt->execute(); + + return (bool) $stmt->fetchColumn(); + } + + /** + * Get the user info. + * + * @param UserColumns|string $info The column name + * + * @throws \InvalidArgumentException If the column name is invalid + * + * @return string|null The value of the column + */ + public static function getInfo(string $token, UserColumns|string $info, bool $encrypted): ?string + { + if (!in_array($info, UserColumns::getColumns())) { + throw new \InvalidArgumentException('Invalid column name: ' . $info); + } + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT ' . $info . ' FROM ' . self::TABLE_NAME . ' WHERE token = :token'); + $stmt->bindParam(':token', $token); + $stmt->execute(); + if ($encrypted) { + return App::getInstance(true)->decrypt($stmt->fetchColumn()); + } + + return $stmt->fetchColumn(); + + } + /** + * Get the user info. + * + * @param string $token The token + * + * @return array The user info + */ + public static function getInfoArray(string $token, array $columns, array $columns_encrypted): array + { + $con = self::getPdoConnection(); + $columns_str = implode(', ', $columns); + $stmt = $con->prepare('SELECT ' . $columns_str . ' FROM ' . self::TABLE_NAME . ' WHERE token = :token'); + $stmt->bindParam(':token', $token); + $stmt->execute(); + + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + + foreach ($columns as $index => $column) { + if (in_array($column, $columns_encrypted)) { + $result[$column] = App::getInstance(true)->decrypt($result[$column]); + } + } + + return $result ? $result : []; + } + + + /** + * Update the user info. + * + * @param UserColumns|string $info The column name + * @param string $value The value to update + * @param bool $encrypted If the value is encrypted + * + * @throws \InvalidArgumentException If the column name is invalid + * + * @return bool If the update was successful + */ + public static function updateInfo(string $token, UserColumns|string $info, string $value, bool $encrypted): bool + { + if (!in_array($info, UserColumns::getColumns())) { + throw new \InvalidArgumentException('Invalid column name: ' . $info); + } + $con = self::getPdoConnection(); + if ($encrypted) { + $value = App::getInstance(true)->encrypt($value); + } + $stmt = $con->prepare('UPDATE ' . self::TABLE_NAME . ' SET ' . $info . ' = :value WHERE token = :token'); + $stmt->bindParam(':value', $value); + $stmt->bindParam(':token', $token); + + return $stmt->execute(); + } + + /** + * Get the token from the UUID. + * + * @param string $uuid The UUID + * + * @return string The token + */ + public static function getTokenFromUUID(string $uuid): string + { + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT token FROM ' . self::TABLE_NAME . ' WHERE uuid = :uuid'); + $stmt->bindParam(':uuid', $uuid); + $stmt->execute(); + + return $stmt->fetchColumn(); + } + + /** + * Process the template. + * + * @param string $template The template + * @param string $uuid The UUID + * + * @return string The processed template + */ + public static function processTemplate(string $template, string $uuid): string + { + try { + + $template = str_replace('${uuid}', $uuid, $template); + $template = str_replace('${username}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::USERNAME, false), $template); + $template = str_replace('${email}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::EMAIL, false), $template); + $template = str_replace('${first_name}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_NAME, true), $template); + $template = str_replace('${last_name}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_NAME, true), $template); + $template = str_replace('${avatar}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::AVATAR, false), $template); + $template = str_replace('${background}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::BACKGROUND, false), $template); + $template = str_replace('${role_id}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::ROLE_ID, false), $template); + $template = str_replace('${first_ip}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_IP, false), $template); + $template = str_replace('${last_ip}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_IP, false), $template); + $template = str_replace('${banned}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::BANNED, false), $template); + $template = str_replace('${verified}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::VERIFIED, false), $template); + $template = str_replace('${2fa_enabled}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::TWO_FA_ENABLED, false), $template); + $template = str_replace('${deleted}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::DELETED, false), $template); + $template = str_replace('${last_seen}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_SEEN, false), $template); + $template = str_replace('${first_seen}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_SEEN, false), $template); + + return $template; + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to render email template: ' . $e->getMessage()); + + return null; + } + } } diff --git a/backend/app/Chat/UserActivities.php b/backend/app/Chat/UserActivities.php index e00458e..db9745b 100755 --- a/backend/app/Chat/UserActivities.php +++ b/backend/app/Chat/UserActivities.php @@ -109,71 +109,71 @@ class UserActivities { - /** - * Add user activity. - * - * @param string $uuid User UUID - * @param string|UserActivitiesTypes $type Activity type - * @param string $ipv4 IP address - */ - public static function add(string $uuid, string|UserActivitiesTypes $type, string $ipv4): bool - { - $dbConn = Database::getPdoConnection(); + /** + * Add user activity. + * + * @param string $uuid User UUID + * @param string|UserActivitiesTypes $type Activity type + * @param string $ipv4 IP address + */ + public static function add(string $uuid, string|UserActivitiesTypes $type, string $ipv4): bool + { + $dbConn = Database::getPdoConnection(); - $stmt = $dbConn->prepare('INSERT INTO ' . self::getTable() . ' (user, action, ip_address) VALUES (:user, :action, :ip_address)'); + $stmt = $dbConn->prepare('INSERT INTO ' . self::getTable() . ' (user, action, ip_address) VALUES (:user, :action, :ip_address)'); - return $stmt->execute([ - ':user' => $uuid, - ':action' => $type, - ':ip_address' => $ipv4, - ]); - } + return $stmt->execute([ + ':user' => $uuid, + ':action' => $type, + ':ip_address' => $ipv4, + ]); + } - /** - * Get user activities. - * - * @param string $uuid User UUID - */ - public static function get(string $uuid): array - { - $dbConn = Database::getPdoConnection(); + /** + * Get user activities. + * + * @param string $uuid User UUID + */ + public static function get(string $uuid): array + { + $dbConn = Database::getPdoConnection(); - $stmt = $dbConn->prepare('SELECT * FROM ' . self::getTable() . ' WHERE user = :user LIMIT 125'); - $stmt->execute([ - ':user' => $uuid, - ]); + $stmt = $dbConn->prepare('SELECT * FROM ' . self::getTable() . ' WHERE user = :user LIMIT 125'); + $stmt->execute([ + ':user' => $uuid, + ]); - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - } + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } - /** - * Get all user activities. - * - * @param int $limit Limit - */ - public static function getAll(int $limit = 50): array - { - try { - $dbConn = Database::getPdoConnection(); + /** + * Get all user activities. + * + * @param int $limit Limit + */ + public static function getAll(int $limit = 50): array + { + try { + $dbConn = Database::getPdoConnection(); - $stmt = $dbConn->prepare('SELECT * FROM ' . self::getTable() . ' LIMIT ' . $limit); - $stmt->execute(); + $stmt = $dbConn->prepare('SELECT * FROM ' . self::getTable() . ' LIMIT ' . $limit); + $stmt->execute(); - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - } catch (\Exception $e) { - Database::db_Error('Failed to get all user activities: ' . $e->getMessage()); + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } catch (\Exception $e) { + Database::db_Error('Failed to get all user activities: ' . $e->getMessage()); - return []; - } - } + return []; + } + } - /** - * Get table name. - * - * @return string Table name - */ - public static function getTable(): string - { - return 'mythicalclient_users_activities'; - } + /** + * Get table name. + * + * @return string Table name + */ + public static function getTable(): string + { + return 'mythicalclient_users_activities'; + } } diff --git a/backend/app/Hooks/GitHub.php b/backend/app/Hooks/GitHub.php index 2749f36..346fe36 100755 --- a/backend/app/Hooks/GitHub.php +++ b/backend/app/Hooks/GitHub.php @@ -106,34 +106,45 @@ namespace MythicalClient\Hooks; use MythicalClient\Cache\Cache; +use GuzzleHttp\Client; class GitHub { - private $cacheKey = 'github_repo_data'; - private $cacheTTL = 3600; // 1 hour in seconds + private $cacheKey = 'github_repo_data'; + private $cacheTTL = 3600; // 1 hour in seconds + private $client; - public function getRepoData() - { - // Check if data is cached - if (Cache::exists($this->cacheKey)) { - return Cache::getJson($this->cacheKey); - } + public function __construct() + { + $this->client = new Client(); + } - // Make GET request to GitHub API - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, 'https://api.github.com/repos/mythicalltd/mythicaldash'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Accept: application/vnd.github+json', - 'X-GitHub-Api-Version: 2022-11-28', - 'User-Agent: MythicalClient', - ]); - $response = curl_exec($ch); - curl_close($ch); + /** + * Retrieves repository data from GitHub API, using cache if available. + * + * @return array The repository data. + */ + public function getRepoData() + { + // Check if data is cached + if (Cache::exists($this->cacheKey)) { + return Cache::getJson($this->cacheKey); + } - // Cache the response - Cache::putJson($this->cacheKey, $response, $this->cacheTTL); + // Make GET request to GitHub API + $response = $this->client->request('GET', 'https://api.github.com/repos/mythicalltd/mythicaldash', [ + 'headers' => [ + 'Accept' => 'application/vnd.github+json', + 'X-GitHub-Api-Version' => '2022-11-28', + 'User-Agent' => 'MythicalClient', + ], + ]); - return $response; - } + $data = json_decode($response->getBody()->getContents(), true); + + // Cache the response + Cache::putJson($this->cacheKey, $data, $this->cacheTTL); + + return $data; + } } diff --git a/backend/app/Hooks/MythicalAPP.php b/backend/app/Hooks/MythicalAPP.php index 9d874cc..d99db61 100755 --- a/backend/app/Hooks/MythicalAPP.php +++ b/backend/app/Hooks/MythicalAPP.php @@ -240,6 +240,13 @@ public static function sendManualResponse(int $code, ?string $error, ?string $me 'debug_debug' => APP_DEBUG, 'debug_version' => APP_VERSION, 'debug_telemetry' => TELEMETRY, + 'debug' => [ + 'useRedis' => REDIS_ENABLED, + 'rateLimit' => [ + 'enabled' => REDIS_ENABLED, + 'limit' => RATE_LIMIT, + ], + ] ], ]; diff --git a/backend/composer.json b/backend/composer.json index deef647..94504a9 100755 --- a/backend/composer.json +++ b/backend/composer.json @@ -27,7 +27,9 @@ "gravatarphp/gravatar": "^1.0", "phpmailer/phpmailer": "^6.9", "pragmarx/google2fa": "^8.0", - "predis/predis": "^2.3" + "predis/predis": "^2.3", + "nikolaposa/rate-limit": "^3.2", + "guzzlehttp/guzzle": "^7.0" }, "require-dev": { "phpunit/phpunit": "^11.1", diff --git a/backend/composer.lock b/backend/composer.lock index 3ccfa2e..7c1edf8 100755 --- a/backend/composer.lock +++ b/backend/composer.lock @@ -4,8 +4,75 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5e6cf053f41993b17ff816c81e138b23", + "content-hash": "04bafc1666990d2084819aa145fe0474", "packages": [ + { + "name": "beberlei/assert", + "version": "v3.3.3", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": ">=6.0.0", + "yoast/phpunit-polyfills": "^0.1.0" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Assert/functions.php" + ], + "psr-4": { + "Assert\\": "lib/Assert" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "support": { + "issues": "https://github.com/beberlei/assert/issues", + "source": "https://github.com/beberlei/assert/tree/v3.3.3" + }, + "time": "2024-07-15T13:18:35+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.1.3", @@ -126,6 +193,331 @@ }, "time": "2016-06-25T11:20:11+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2024-07-24T11:22:20+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2024-10-17T10:06:22+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2024-07-18T11:15:46+00:00" + }, { "name": "mythicalsystems/core", "version": "1.0.0.17", @@ -206,6 +598,73 @@ ], "time": "2024-11-06T23:03:37+00:00" }, + { + "name": "nikolaposa/rate-limit", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/nikolaposa/rate-limit.git", + "reference": "80a1badf8157c6f206074defd752bd00f9cf08e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikolaposa/rate-limit/zipball/80a1badf8157c6f206074defd752bd00f9cf08e9", + "reference": "80a1badf8157c6f206074defd752bd00f9cf08e9", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.2", + "php": "^8.1" + }, + "require-dev": { + "ext-apcu": ">=5.1.12", + "ext-redis": "*", + "friendsofphp/php-cs-fixer": "3.44", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-beberlei-assert": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^10.5", + "predis/predis": "^1.1" + }, + "suggest": { + "ext-apcu": "In order to use ApcuRateLimiter", + "ext-memcached": "In order to use MemcachedRateLimiter", + "ext-redis": "In order to use RedisRateLimiter", + "predis/predis": "In order to use PredisRateLimiter" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "RateLimit\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nikola Poša", + "email": "posa.nikola@gmail.com", + "homepage": "https://www.nikolaposa.in.rs" + } + ], + "description": "General purpose rate limiter implementation.", + "keywords": [ + "rate limit", + "redis" + ], + "support": { + "issues": "https://github.com/nikolaposa/rate-limit/issues", + "source": "https://github.com/nikolaposa/rate-limit/tree/3.2.0" + }, + "time": "2024-05-17T14:39:51+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.0.0", @@ -542,6 +1001,210 @@ ], "time": "2024-11-21T20:00:02+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v3.5.1", diff --git a/backend/public/index.php b/backend/public/index.php index 73eb81e..6471fcb 100755 --- a/backend/public/index.php +++ b/backend/public/index.php @@ -123,7 +123,8 @@ define('SYSTEM_KERNEL_NAME', php_uname('s')); define('TELEMETRY', true); define('APP_VERSION', '4.0.0.0-dev'); -define('APP_UPSTREAM', 'https://github.com/mythicalltd/mythicaldash'); +define('APP_UPSTREAM', 'github.com/mythicalltd/mythicaldash'); +define('RATE_LIMIT', 50); /** * Require the kernel. diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 0670dad..8a6c1aa 100755 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,7 +1,7 @@ diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index dfbb175..0b75e16 100755 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -6,12 +6,13 @@ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); @layer base { - html { - font-family: 'Poppins', sans-serif; - } + html { + font-family: 'Poppins', sans-serif; + } } -html, body { +html, +body { overscroll-behavior-y: none; } diff --git a/frontend/src/components/client/LayoutDashboard.vue b/frontend/src/components/client/LayoutDashboard.vue index ef3d07e..4a38b58 100755 --- a/frontend/src/components/client/LayoutDashboard.vue +++ b/frontend/src/components/client/LayoutDashboard.vue @@ -1,255 +1,254 @@ diff --git a/frontend/src/mythicalclient/Settings.ts b/frontend/src/mythicalclient/Settings.ts index 79c2b16..c70ec4e 100755 --- a/frontend/src/mythicalclient/Settings.ts +++ b/frontend/src/mythicalclient/Settings.ts @@ -31,8 +31,20 @@ class Settings { throw error; } } - static async initializeSettings() { + const response = await fetch('/api/system/settings'); + if (response.status === 503) { + document.body.innerHTML = ` +
+
+

Rate Limited

+

You have been rate limited. Please try again later.

+ +
+
+ `; + return; + } try { const fetchedSettings = await Settings.grabSettings(); const fetchedCore = await Settings.grabCore(); @@ -48,13 +60,14 @@ class Settings { } catch (error) { console.error('Failed to initialize settings:', error); document.body.innerHTML = ` -
-
-

We are so sorry

-

Our backend is down at this moment :(

-
-
- `; +
+
+

We are so sorry

+

Our backend is down at this moment :(

+ +
+
+ `; } } From 466e70e65199e330fb74cbf0995a50b230739da2 Mon Sep 17 00:00:00 2001 From: NaysKutzu Date: Tue, 14 Jan 2025 12:49:45 +0100 Subject: [PATCH 2/2] PUSH -> Lint code --- backend/app/Api/User/Session/Session.php | 278 ++++----- backend/app/App.php | 58 +- backend/app/Chat/User.php | 700 +++++++++++------------ backend/app/Chat/UserActivities.php | 114 ++-- backend/app/Hooks/GitHub.php | 64 +-- backend/app/Hooks/MythicalAPP.php | 14 +- 6 files changed, 614 insertions(+), 614 deletions(-) diff --git a/backend/app/Api/User/Session/Session.php b/backend/app/Api/User/Session/Session.php index b05d783..f624498 100755 --- a/backend/app/Api/User/Session/Session.php +++ b/backend/app/Api/User/Session/Session.php @@ -112,172 +112,172 @@ use MythicalClient\Chat\columns\UserColumns; $router->post('/api/user/session/info/update', function (): void { - App::init(); - $appInstance = App::getInstance(true); - $config = $appInstance->getConfig(); + App::init(); + $appInstance = App::getInstance(true); + $config = $appInstance->getConfig(); - $appInstance->allowOnlyPOST(); - $session = new Session($appInstance); + $appInstance->allowOnlyPOST(); + $session = new Session($appInstance); - try { - if (!isset($_POST['first_name']) && $_POST['first_name'] == '') { - $appInstance->BadRequest('First name is missing!', ['error_code' => 'FIRST_NAME_MISSING']); - } - if (!isset($_POST['last_name']) && $_POST['last_name'] == '') { - $appInstance->BadRequest('Last name is missing!', ['error_code' => 'LAST_NAME_MISSING']); - } - if (!isset($_POST['email']) && $_POST['email'] == '') { - $appInstance->BadRequest('Email is missing!', ['error_code' => 'EMAIL_MISSING']); - } - if (!isset($_POST['avatar']) && $_POST['avatar'] == '') { - $appInstance->BadRequest('Avatar is missing!', ['error_code' => 'AVATAR_MISSING']); - } - if (!isset($_POST['background']) && $_POST['background'] == '') { - $appInstance->BadRequest('Background is missing!', ['error_code' => 'BACKGROUND_MISSING']); - } + try { + if (!isset($_POST['first_name']) && $_POST['first_name'] == '') { + $appInstance->BadRequest('First name is missing!', ['error_code' => 'FIRST_NAME_MISSING']); + } + if (!isset($_POST['last_name']) && $_POST['last_name'] == '') { + $appInstance->BadRequest('Last name is missing!', ['error_code' => 'LAST_NAME_MISSING']); + } + if (!isset($_POST['email']) && $_POST['email'] == '') { + $appInstance->BadRequest('Email is missing!', ['error_code' => 'EMAIL_MISSING']); + } + if (!isset($_POST['avatar']) && $_POST['avatar'] == '') { + $appInstance->BadRequest('Avatar is missing!', ['error_code' => 'AVATAR_MISSING']); + } + if (!isset($_POST['background']) && $_POST['background'] == '') { + $appInstance->BadRequest('Background is missing!', ['error_code' => 'BACKGROUND_MISSING']); + } - if ($_POST['email'] != $session->getInfo(UserColumns::EMAIL, false) && User::exists(UserColumns::EMAIL, $_POST['email'])) { - $appInstance->BadRequest('Email already exists!', ['error_code' => 'EMAIL_EXISTS']); - } + if ($_POST['email'] != $session->getInfo(UserColumns::EMAIL, false) && User::exists(UserColumns::EMAIL, $_POST['email'])) { + $appInstance->BadRequest('Email already exists!', ['error_code' => 'EMAIL_EXISTS']); + } - $session->setInfo(UserColumns::FIRST_NAME, $_POST['first_name'], true); - $session->setInfo(UserColumns::LAST_NAME, $_POST['last_name'], true); - $session->setInfo(UserColumns::EMAIL, $_POST['email'], false); - $session->setInfo(UserColumns::AVATAR, $_POST['avatar'], false); - $session->setInfo(UserColumns::BACKGROUND, $_POST['background'], false); + $session->setInfo(UserColumns::FIRST_NAME, $_POST['first_name'], true); + $session->setInfo(UserColumns::LAST_NAME, $_POST['last_name'], true); + $session->setInfo(UserColumns::EMAIL, $_POST['email'], false); + $session->setInfo(UserColumns::AVATAR, $_POST['avatar'], false); + $session->setInfo(UserColumns::BACKGROUND, $_POST['background'], false); - $appInstance->OK('User info updated successfully!', []); - } catch (Exception $e) { - $appInstance->getLogger()->error('Failed to update user info! ' . $e->getMessage()); - $appInstance->BadRequest('Bad Request', ['error_code' => 'DB_ERROR', 'error' => $e->getMessage()]); - } + $appInstance->OK('User info updated successfully!', []); + } catch (Exception $e) { + $appInstance->getLogger()->error('Failed to update user info! ' . $e->getMessage()); + $appInstance->BadRequest('Bad Request', ['error_code' => 'DB_ERROR', 'error' => $e->getMessage()]); + } }); $router->post('/api/user/session/billing/update', function (): void { - App::init(); - $appInstance = App::getInstance(true); - $config = $appInstance->getConfig(); + App::init(); + $appInstance = App::getInstance(true); + $config = $appInstance->getConfig(); - $appInstance->allowOnlyPOST(); - $session = new Session($appInstance); + $appInstance->allowOnlyPOST(); + $session = new Session($appInstance); - try { - if (!isset($_POST['company_name']) && $_POST['company_name'] == '') { - $appInstance->BadRequest('Company name is missing!', ['error_code' => 'COMPANY_NAME_MISSING']); - } - $companyName = $_POST['company_name']; - if (!isset($_POST['vat_number']) && $_POST['vat_number'] == '') { - $appInstance->BadRequest('VAT Number is missing!', ['error_code' => 'VAT_NUMBER_MISSING']); - } - $vatNumber = $_POST['vat_number']; - if (!isset($_POST['address1']) && $_POST['address1'] == '') { - $appInstance->BadRequest('Address 1 is missing', ['error_code' => 'ADDRESS1_MISSING']); - } - $address1 = $_POST['address1']; - if (!isset($_POST['address2']) && $_POST['address2'] == '') { - $appInstance->BadRequest('Address 2 is missing', ['error_code' => 'ADDRESS2_MISSING']); - } - $address2 = $_POST['address2']; - if (!isset($_POST['city']) && $_POST['city'] == '') { - $appInstance->BadRequest('City is missing', ['error_code' => 'CITY_MISSING']); - } - $city = $_POST['city']; - if (!isset($_POST['country']) && $_POST['country'] == '') { - $appInstance->BadRequest('Country is missing', ['error_code' => 'COUNTRY_MISSING']); - } - $country = $_POST['country']; - if (!isset($_POST['state']) && $_POST['state'] == '') { - $appInstance->BadRequest('State is missing', ['error_code' => 'STATE_MISSING']); - } - $state = $_POST['state']; - if (!isset($_POST['postcode']) && $_POST['postcode'] == '') { - $appInstance->BadRequest('PostCode is missing', ['error_code' => 'POSTCODE_MISSING']); - } - $postcode = $_POST['postcode']; + try { + if (!isset($_POST['company_name']) && $_POST['company_name'] == '') { + $appInstance->BadRequest('Company name is missing!', ['error_code' => 'COMPANY_NAME_MISSING']); + } + $companyName = $_POST['company_name']; + if (!isset($_POST['vat_number']) && $_POST['vat_number'] == '') { + $appInstance->BadRequest('VAT Number is missing!', ['error_code' => 'VAT_NUMBER_MISSING']); + } + $vatNumber = $_POST['vat_number']; + if (!isset($_POST['address1']) && $_POST['address1'] == '') { + $appInstance->BadRequest('Address 1 is missing', ['error_code' => 'ADDRESS1_MISSING']); + } + $address1 = $_POST['address1']; + if (!isset($_POST['address2']) && $_POST['address2'] == '') { + $appInstance->BadRequest('Address 2 is missing', ['error_code' => 'ADDRESS2_MISSING']); + } + $address2 = $_POST['address2']; + if (!isset($_POST['city']) && $_POST['city'] == '') { + $appInstance->BadRequest('City is missing', ['error_code' => 'CITY_MISSING']); + } + $city = $_POST['city']; + if (!isset($_POST['country']) && $_POST['country'] == '') { + $appInstance->BadRequest('Country is missing', ['error_code' => 'COUNTRY_MISSING']); + } + $country = $_POST['country']; + if (!isset($_POST['state']) && $_POST['state'] == '') { + $appInstance->BadRequest('State is missing', ['error_code' => 'STATE_MISSING']); + } + $state = $_POST['state']; + if (!isset($_POST['postcode']) && $_POST['postcode'] == '') { + $appInstance->BadRequest('PostCode is missing', ['error_code' => 'POSTCODE_MISSING']); + } + $postcode = $_POST['postcode']; - Billing::updateBilling( - $session->getInfo(UserColumns::UUID, false), - $companyName, - $vatNumber, - $address1, - $address2, - $city, - $country, - $state, - $postcode - ); + Billing::updateBilling( + $session->getInfo(UserColumns::UUID, false), + $companyName, + $vatNumber, + $address1, + $address2, + $city, + $country, + $state, + $postcode + ); - $appInstance->OK('Billing info saved successfully!', []); - } catch (Exception $e) { - $appInstance->getLogger()->error('Failed to save billing info! ' . $e->getMessage()); - $appInstance->BadRequest('Bad Request', ['error_code' => 'DB_ERROR', 'error' => $e->getMessage()]); - } + $appInstance->OK('Billing info saved successfully!', []); + } catch (Exception $e) { + $appInstance->getLogger()->error('Failed to save billing info! ' . $e->getMessage()); + $appInstance->BadRequest('Bad Request', ['error_code' => 'DB_ERROR', 'error' => $e->getMessage()]); + } }); $router->get('/api/user/session', function (): void { - App::init(); - $appInstance = App::getInstance(true); - $config = $appInstance->getConfig(); + App::init(); + $appInstance = App::getInstance(true); + $config = $appInstance->getConfig(); - $appInstance->allowOnlyGET(); + $appInstance->allowOnlyGET(); - $session = new Session($appInstance); + $session = new Session($appInstance); - $accountToken = $session->SESSION_KEY; - try { - $billing = Billing::getBillingData(User::getInfo($accountToken, UserColumns::UUID, false)); - $columns = [ - UserColumns::USERNAME, - UserColumns::EMAIL, - UserColumns::VERIFIED, - UserColumns::BANNED, - UserColumns::TWO_FA_BLOCKED, - UserColumns::TWO_FA_ENABLED, - UserColumns::TWO_FA_KEY, - UserColumns::FIRST_NAME, - UserColumns::LAST_NAME, - UserColumns::AVATAR, - UserColumns::UUID, - UserColumns::ROLE_ID, - UserColumns::FIRST_IP, - UserColumns::LAST_IP, - UserColumns::DELETED, - UserColumns::LAST_SEEN, - UserColumns::FIRST_SEEN, - UserColumns::BACKGROUND - ]; + $accountToken = $session->SESSION_KEY; + try { + $billing = Billing::getBillingData(User::getInfo($accountToken, UserColumns::UUID, false)); + $columns = [ + UserColumns::USERNAME, + UserColumns::EMAIL, + UserColumns::VERIFIED, + UserColumns::BANNED, + UserColumns::TWO_FA_BLOCKED, + UserColumns::TWO_FA_ENABLED, + UserColumns::TWO_FA_KEY, + UserColumns::FIRST_NAME, + UserColumns::LAST_NAME, + UserColumns::AVATAR, + UserColumns::UUID, + UserColumns::ROLE_ID, + UserColumns::FIRST_IP, + UserColumns::LAST_IP, + UserColumns::DELETED, + UserColumns::LAST_SEEN, + UserColumns::FIRST_SEEN, + UserColumns::BACKGROUND, + ]; - $info = User::getInfoArray($accountToken, $columns, [ - UserColumns::FIRST_NAME, - UserColumns::LAST_NAME, - UserColumns::TWO_FA_KEY, - ]); - $info['role_name'] = Roles::getUserRoleName($info[UserColumns::UUID]); - $info['role_real_name'] = strtolower($info['role_name']); + $info = User::getInfoArray($accountToken, $columns, [ + UserColumns::FIRST_NAME, + UserColumns::LAST_NAME, + UserColumns::TWO_FA_KEY, + ]); + $info['role_name'] = Roles::getUserRoleName($info[UserColumns::UUID]); + $info['role_real_name'] = strtolower($info['role_name']); - $appInstance->OK('Account token is valid', [ - 'user_info' => $info, - 'billing' => $billing, - ]); + $appInstance->OK('Account token is valid', [ + 'user_info' => $info, + 'billing' => $billing, + ]); - } catch (Exception $e) { - $appInstance->BadRequest('Bad Request', ['error_code' => 'INVALID_ACCOUNT_TOKEN', 'error' => $e->getMessage()]); - } + } catch (Exception $e) { + $appInstance->BadRequest('Bad Request', ['error_code' => 'INVALID_ACCOUNT_TOKEN', 'error' => $e->getMessage()]); + } }); $router->get('/api/user/session/activities', function (): void { - App::init(); - $appInstance = App::getInstance(true); - $config = $appInstance->getConfig(); + App::init(); + $appInstance = App::getInstance(true); + $config = $appInstance->getConfig(); - $appInstance->allowOnlyGET(); + $appInstance->allowOnlyGET(); - $session = new Session($appInstance); + $session = new Session($appInstance); - $accountToken = $session->SESSION_KEY; + $accountToken = $session->SESSION_KEY; - $appInstance->OK('User activities', [ - 'activities' => UserActivities::get(User::getInfo($accountToken, UserColumns::UUID, false)), - ]); + $appInstance->OK('User activities', [ + 'activities' => UserActivities::get(User::getInfo($accountToken, UserColumns::UUID, false)), + ]); }); diff --git a/backend/app/App.php b/backend/app/App.php index 873ccbb..36e0097 100755 --- a/backend/app/App.php +++ b/backend/app/App.php @@ -105,16 +105,16 @@ namespace MythicalClient; -use MythicalClient\CloudFlare\CloudFlareRealIP; -use RateLimit\Exception\LimitExceeded; use RateLimit\Rate; -use RateLimit\RedisRateLimiter; use Router\Router as rt; +use RateLimit\RedisRateLimiter; use MythicalClient\Chat\Database; use MythicalSystems\Utils\XChaCha20; use MythicalClient\Hooks\MythicalAPP; +use RateLimit\Exception\LimitExceeded; use MythicalClient\Config\ConfigFactory; use MythicalClient\Logger\LoggerFactory; +use MythicalClient\CloudFlare\CloudFlareRealIP; class App extends MythicalAPP { @@ -146,32 +146,32 @@ public function __construct(bool $softBoot) return; } - /** - * Redis. - */ - $redis = new FastChat\Redis(); - if ($redis->testConnection() == false) { - define('REDIS_ENABLED', false); - } else { - define('REDIS_ENABLED', true); - } - - // @phpstan-ignore-next-line - $rateLimiter = new RedisRateLimiter(Rate::perMinute(RATE_LIMIT), new \Redis(), "rate_limiting"); - try { - $rateLimiter->limit(CloudFlareRealIP::getRealIP()); - } catch (LimitExceeded $e) { - self::getLogger()->error('User: '. $e->getMessage()); - self::init(); - self::ServiceUnavailable('You are being rate limited!', ['error_code' => 'RATE_LIMITED']); - } catch (\Exception $e) { - self::getLogger()->error("-----------------------------"); - self::getLogger()->error("REDIS SERVER IS DOWN"); - self::getLogger()->error("RATE LIMITING IS DISABLED"); - self::getLogger()->error("YOU SHOULD FIX THIS ASAP"); - self::getLogger()->error("NO SUPPORT WILL BE PROVIDED"); - self::getLogger()->error("-----------------------------"); - } + /** + * Redis. + */ + $redis = new FastChat\Redis(); + if ($redis->testConnection() == false) { + define('REDIS_ENABLED', false); + } else { + define('REDIS_ENABLED', true); + } + + // @phpstan-ignore-next-line + $rateLimiter = new RedisRateLimiter(Rate::perMinute(RATE_LIMIT), new \Redis(), 'rate_limiting'); + try { + $rateLimiter->limit(CloudFlareRealIP::getRealIP()); + } catch (LimitExceeded $e) { + self::getLogger()->error('User: ' . $e->getMessage()); + self::init(); + self::ServiceUnavailable('You are being rate limited!', ['error_code' => 'RATE_LIMITED']); + } catch (\Exception $e) { + self::getLogger()->error('-----------------------------'); + self::getLogger()->error('REDIS SERVER IS DOWN'); + self::getLogger()->error('RATE LIMITING IS DISABLED'); + self::getLogger()->error('YOU SHOULD FIX THIS ASAP'); + self::getLogger()->error('NO SUPPORT WILL BE PROVIDED'); + self::getLogger()->error('-----------------------------'); + } /** * Database Connection. diff --git a/backend/app/Chat/User.php b/backend/app/Chat/User.php index 921d218..95df370 100755 --- a/backend/app/Chat/User.php +++ b/backend/app/Chat/User.php @@ -118,359 +118,359 @@ class User extends Database { - public const TABLE_NAME = 'mythicalclient_users'; - - /** - * Register a new user in the database. - * - * @param string $username The username of the user - * @param string $password The password of the user - * @param string $email The email of the user - * @param string $first_name The first name of the user - * @param string $last_name The last name of the user - * @param string $ip The ip of the user - * - * @return string - */ - public static function register(string $username, string $password, string $email, string $first_name, string $last_name, string $ip): void - { - try { - $first_name = App::getInstance(true)->encrypt($first_name); - $last_name = App::getInstance(true)->encrypt($last_name); - - /** - * The UUID generation and logic. - */ - $uuidMngr = new \MythicalSystems\User\UUIDManager(); - $uuid = $uuidMngr->generateUUID(); - $token = App::getInstance(true)->encrypt(date('Y-m-d H:i:s') . $uuid . random_bytes(16) . base64_encode($email)); - - /** - * GRAvatar Logic. - */ - try { - $gravatar = new Gravatar(['s' => 9001], true); - $avatar = $gravatar->avatar($email); - } catch (\Exception) { - $avatar = 'https://www.gravatar.com/avatar'; - } - - /** - * Get the PDO connection. - */ - $pdoConnection = self::getPdoConnection(); - - /** - * Prepare the statement. - */ - $stmt = $pdoConnection->prepare(' + public const TABLE_NAME = 'mythicalclient_users'; + + /** + * Register a new user in the database. + * + * @param string $username The username of the user + * @param string $password The password of the user + * @param string $email The email of the user + * @param string $first_name The first name of the user + * @param string $last_name The last name of the user + * @param string $ip The ip of the user + * + * @return string + */ + public static function register(string $username, string $password, string $email, string $first_name, string $last_name, string $ip): void + { + try { + $first_name = App::getInstance(true)->encrypt($first_name); + $last_name = App::getInstance(true)->encrypt($last_name); + + /** + * The UUID generation and logic. + */ + $uuidMngr = new \MythicalSystems\User\UUIDManager(); + $uuid = $uuidMngr->generateUUID(); + $token = App::getInstance(true)->encrypt(date('Y-m-d H:i:s') . $uuid . random_bytes(16) . base64_encode($email)); + + /** + * GRAvatar Logic. + */ + try { + $gravatar = new Gravatar(['s' => 9001], true); + $avatar = $gravatar->avatar($email); + } catch (\Exception) { + $avatar = 'https://www.gravatar.com/avatar'; + } + + /** + * Get the PDO connection. + */ + $pdoConnection = self::getPdoConnection(); + + /** + * Prepare the statement. + */ + $stmt = $pdoConnection->prepare(' INSERT INTO ' . self::TABLE_NAME . ' (username, first_name, last_name, email, password, avatar, background, uuid, token, role, first_ip, last_ip, banned, verified) VALUES (:username, :first_name, :last_name, :email, :password, :avatar, :background, :uuid, :token, :role, :first_ip, :last_ip, :banned, :verified) '); - $password = App::getInstance(true)->encrypt($password); - - $stmt->execute([ - ':username' => $username, - ':first_name' => $first_name, - ':last_name' => $last_name, - ':email' => $email, - ':password' => $password, - ':avatar' => $avatar, - ':background' => 'https://cdn.mythicalsystems.xyz/background.gif', - ':uuid' => $uuid, - ':token' => $token, - ':role' => 1, - ':first_ip' => $ip, - ':last_ip' => $ip, - ':banned' => 'NO', - ':verified' => 'false', - ]); - \MythicalClient\MythicalSystems\Telemetry::send(\MythicalClient\MythicalSystems\TelemetryCollection::USER_NEW); - /** - * Check if the mail is enabled. - * - * If it is, the user is not verified. - * - * If it is not, the user is verified. - */ - if (Mail::isEnabled()) { - try { - $verify_token = App::getInstance(true)->generateCode(); - Verification::add($verify_token, $uuid, EmailVerificationColumns::$type_verify); - Verify::sendMail($uuid, $verify_token); - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); - self::updateInfo($token, UserColumns::VERIFIED, 'false', false); - } - } else { - self::updateInfo($token, UserColumns::VERIFIED, 'true', false); - } - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to register user: ' . $e->getMessage()); - throw new \Exception('Failed to register user: ' . $e->getMessage()); - } - } - - /** - * Get the list of users. - * - * @return array The list of users - */ - public static function getList(): array - { - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT * FROM ' . self::TABLE_NAME); - $stmt->execute(); - - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - } - - /** - * Forgot password logic. - * - * @param string $email The email of the user - * - * @return bool If the email was sent - */ - public static function forgotPassword(string $email): bool - { - try { - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT token, uuid FROM ' . self::TABLE_NAME . ' WHERE email = :email'); - $stmt->bindParam(':email', $email); - $stmt->execute(); - $user = $stmt->fetch(\PDO::FETCH_ASSOC); - - if ($user) { - if (Mail::isEnabled()) { - try { - $verify_token = $verify_token = App::getInstance(true)->generateCode(); - Verification::add($verify_token, $user['uuid'], EmailVerificationColumns::$type_password); - ResetPassword::sendMail($user['uuid'], $verify_token); - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); - } - - return true; - } - - return false; - } - - return false; - } catch (\Exception $e) { - return false; - } - } - - /** - * Login the user. - * - * @param string $login The login of the user - * @param string $password The password of the user - * - * @return string If the login was successful - */ - public static function login(string $login, string $password): string - { - try { - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT password, token, uuid FROM ' . self::TABLE_NAME . ' WHERE username = :login OR email = :login'); - $stmt->bindParam(':login', $login); - $stmt->execute(); - $user = $stmt->fetch(\PDO::FETCH_ASSOC); - if ($user) { - if (App::getInstance(true)->decrypt($user['password']) == $password) { - self::logout(); - if (!$user['token'] == '') { - setcookie('user_token', $user['token'], time() + 3600, '/'); - } else { - App::getInstance(true)->getLogger()->error('Failed to login user: Token is empty'); - - return 'false'; - } - if (Mail::isEnabled()) { - try { - NewLogin::sendMail($user['uuid']); - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); - } - } - UserActivities::add($user['uuid'], UserActivitiesTypes::$login, CloudFlare::getRealUserIP()); - - return $user['token']; - } - - return 'false'; - } - - return 'false'; - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to login user: ' . $e->getMessage()); - - return 'false'; - } - } - - /** - * Logout the user. - */ - public static function logout(): void - { - setcookie('user_token', '', time() - 460800 * 460800 * 460800, '/'); - } - - /** - * Does the user info exist? - * - * @param UserColumns $info - */ - public static function exists(UserColumns|string $info, string $value): bool - { - if (!in_array($info, UserColumns::getColumns())) { - throw new \InvalidArgumentException('Invalid column name: ' . $info); - } - - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT * FROM ' . self::TABLE_NAME . ' WHERE ' . $info . ' = :value'); - $stmt->bindParam(':value', $value); - $stmt->execute(); - - return (bool) $stmt->fetchColumn(); - } - - /** - * Get the user info. - * - * @param UserColumns|string $info The column name - * - * @throws \InvalidArgumentException If the column name is invalid - * - * @return string|null The value of the column - */ - public static function getInfo(string $token, UserColumns|string $info, bool $encrypted): ?string - { - if (!in_array($info, UserColumns::getColumns())) { - throw new \InvalidArgumentException('Invalid column name: ' . $info); - } - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT ' . $info . ' FROM ' . self::TABLE_NAME . ' WHERE token = :token'); - $stmt->bindParam(':token', $token); - $stmt->execute(); - if ($encrypted) { - return App::getInstance(true)->decrypt($stmt->fetchColumn()); - } - - return $stmt->fetchColumn(); - - } - /** - * Get the user info. - * - * @param string $token The token - * - * @return array The user info - */ - public static function getInfoArray(string $token, array $columns, array $columns_encrypted): array - { - $con = self::getPdoConnection(); - $columns_str = implode(', ', $columns); - $stmt = $con->prepare('SELECT ' . $columns_str . ' FROM ' . self::TABLE_NAME . ' WHERE token = :token'); - $stmt->bindParam(':token', $token); - $stmt->execute(); - - $result = $stmt->fetch(\PDO::FETCH_ASSOC); - - foreach ($columns as $index => $column) { - if (in_array($column, $columns_encrypted)) { - $result[$column] = App::getInstance(true)->decrypt($result[$column]); - } - } - - return $result ? $result : []; - } - - - /** - * Update the user info. - * - * @param UserColumns|string $info The column name - * @param string $value The value to update - * @param bool $encrypted If the value is encrypted - * - * @throws \InvalidArgumentException If the column name is invalid - * - * @return bool If the update was successful - */ - public static function updateInfo(string $token, UserColumns|string $info, string $value, bool $encrypted): bool - { - if (!in_array($info, UserColumns::getColumns())) { - throw new \InvalidArgumentException('Invalid column name: ' . $info); - } - $con = self::getPdoConnection(); - if ($encrypted) { - $value = App::getInstance(true)->encrypt($value); - } - $stmt = $con->prepare('UPDATE ' . self::TABLE_NAME . ' SET ' . $info . ' = :value WHERE token = :token'); - $stmt->bindParam(':value', $value); - $stmt->bindParam(':token', $token); - - return $stmt->execute(); - } - - /** - * Get the token from the UUID. - * - * @param string $uuid The UUID - * - * @return string The token - */ - public static function getTokenFromUUID(string $uuid): string - { - $con = self::getPdoConnection(); - $stmt = $con->prepare('SELECT token FROM ' . self::TABLE_NAME . ' WHERE uuid = :uuid'); - $stmt->bindParam(':uuid', $uuid); - $stmt->execute(); - - return $stmt->fetchColumn(); - } - - /** - * Process the template. - * - * @param string $template The template - * @param string $uuid The UUID - * - * @return string The processed template - */ - public static function processTemplate(string $template, string $uuid): string - { - try { - - $template = str_replace('${uuid}', $uuid, $template); - $template = str_replace('${username}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::USERNAME, false), $template); - $template = str_replace('${email}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::EMAIL, false), $template); - $template = str_replace('${first_name}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_NAME, true), $template); - $template = str_replace('${last_name}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_NAME, true), $template); - $template = str_replace('${avatar}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::AVATAR, false), $template); - $template = str_replace('${background}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::BACKGROUND, false), $template); - $template = str_replace('${role_id}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::ROLE_ID, false), $template); - $template = str_replace('${first_ip}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_IP, false), $template); - $template = str_replace('${last_ip}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_IP, false), $template); - $template = str_replace('${banned}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::BANNED, false), $template); - $template = str_replace('${verified}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::VERIFIED, false), $template); - $template = str_replace('${2fa_enabled}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::TWO_FA_ENABLED, false), $template); - $template = str_replace('${deleted}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::DELETED, false), $template); - $template = str_replace('${last_seen}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_SEEN, false), $template); - $template = str_replace('${first_seen}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_SEEN, false), $template); - - return $template; - } catch (\Exception $e) { - App::getInstance(true)->getLogger()->error('Failed to render email template: ' . $e->getMessage()); - - return null; - } - } + $password = App::getInstance(true)->encrypt($password); + + $stmt->execute([ + ':username' => $username, + ':first_name' => $first_name, + ':last_name' => $last_name, + ':email' => $email, + ':password' => $password, + ':avatar' => $avatar, + ':background' => 'https://cdn.mythicalsystems.xyz/background.gif', + ':uuid' => $uuid, + ':token' => $token, + ':role' => 1, + ':first_ip' => $ip, + ':last_ip' => $ip, + ':banned' => 'NO', + ':verified' => 'false', + ]); + \MythicalClient\MythicalSystems\Telemetry::send(\MythicalClient\MythicalSystems\TelemetryCollection::USER_NEW); + /** + * Check if the mail is enabled. + * + * If it is, the user is not verified. + * + * If it is not, the user is verified. + */ + if (Mail::isEnabled()) { + try { + $verify_token = App::getInstance(true)->generateCode(); + Verification::add($verify_token, $uuid, EmailVerificationColumns::$type_verify); + Verify::sendMail($uuid, $verify_token); + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); + self::updateInfo($token, UserColumns::VERIFIED, 'false', false); + } + } else { + self::updateInfo($token, UserColumns::VERIFIED, 'true', false); + } + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to register user: ' . $e->getMessage()); + throw new \Exception('Failed to register user: ' . $e->getMessage()); + } + } + + /** + * Get the list of users. + * + * @return array The list of users + */ + public static function getList(): array + { + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT * FROM ' . self::TABLE_NAME); + $stmt->execute(); + + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } + + /** + * Forgot password logic. + * + * @param string $email The email of the user + * + * @return bool If the email was sent + */ + public static function forgotPassword(string $email): bool + { + try { + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT token, uuid FROM ' . self::TABLE_NAME . ' WHERE email = :email'); + $stmt->bindParam(':email', $email); + $stmt->execute(); + $user = $stmt->fetch(\PDO::FETCH_ASSOC); + + if ($user) { + if (Mail::isEnabled()) { + try { + $verify_token = $verify_token = App::getInstance(true)->generateCode(); + Verification::add($verify_token, $user['uuid'], EmailVerificationColumns::$type_password); + ResetPassword::sendMail($user['uuid'], $verify_token); + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); + } + + return true; + } + + return false; + } + + return false; + } catch (\Exception $e) { + return false; + } + } + + /** + * Login the user. + * + * @param string $login The login of the user + * @param string $password The password of the user + * + * @return string If the login was successful + */ + public static function login(string $login, string $password): string + { + try { + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT password, token, uuid FROM ' . self::TABLE_NAME . ' WHERE username = :login OR email = :login'); + $stmt->bindParam(':login', $login); + $stmt->execute(); + $user = $stmt->fetch(\PDO::FETCH_ASSOC); + if ($user) { + if (App::getInstance(true)->decrypt($user['password']) == $password) { + self::logout(); + if (!$user['token'] == '') { + setcookie('user_token', $user['token'], time() + 3600, '/'); + } else { + App::getInstance(true)->getLogger()->error('Failed to login user: Token is empty'); + + return 'false'; + } + if (Mail::isEnabled()) { + try { + NewLogin::sendMail($user['uuid']); + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to send email: ' . $e->getMessage()); + } + } + UserActivities::add($user['uuid'], UserActivitiesTypes::$login, CloudFlare::getRealUserIP()); + + return $user['token']; + } + + return 'false'; + } + + return 'false'; + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to login user: ' . $e->getMessage()); + + return 'false'; + } + } + + /** + * Logout the user. + */ + public static function logout(): void + { + setcookie('user_token', '', time() - 460800 * 460800 * 460800, '/'); + } + + /** + * Does the user info exist? + * + * @param UserColumns $info + */ + public static function exists(UserColumns|string $info, string $value): bool + { + if (!in_array($info, UserColumns::getColumns())) { + throw new \InvalidArgumentException('Invalid column name: ' . $info); + } + + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT * FROM ' . self::TABLE_NAME . ' WHERE ' . $info . ' = :value'); + $stmt->bindParam(':value', $value); + $stmt->execute(); + + return (bool) $stmt->fetchColumn(); + } + + /** + * Get the user info. + * + * @param UserColumns|string $info The column name + * + * @throws \InvalidArgumentException If the column name is invalid + * + * @return string|null The value of the column + */ + public static function getInfo(string $token, UserColumns|string $info, bool $encrypted): ?string + { + if (!in_array($info, UserColumns::getColumns())) { + throw new \InvalidArgumentException('Invalid column name: ' . $info); + } + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT ' . $info . ' FROM ' . self::TABLE_NAME . ' WHERE token = :token'); + $stmt->bindParam(':token', $token); + $stmt->execute(); + if ($encrypted) { + return App::getInstance(true)->decrypt($stmt->fetchColumn()); + } + + return $stmt->fetchColumn(); + + } + + /** + * Get the user info. + * + * @param string $token The token + * + * @return array The user info + */ + public static function getInfoArray(string $token, array $columns, array $columns_encrypted): array + { + $con = self::getPdoConnection(); + $columns_str = implode(', ', $columns); + $stmt = $con->prepare('SELECT ' . $columns_str . ' FROM ' . self::TABLE_NAME . ' WHERE token = :token'); + $stmt->bindParam(':token', $token); + $stmt->execute(); + + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + + foreach ($columns as $index => $column) { + if (in_array($column, $columns_encrypted)) { + $result[$column] = App::getInstance(true)->decrypt($result[$column]); + } + } + + return $result ? $result : []; + } + + /** + * Update the user info. + * + * @param UserColumns|string $info The column name + * @param string $value The value to update + * @param bool $encrypted If the value is encrypted + * + * @throws \InvalidArgumentException If the column name is invalid + * + * @return bool If the update was successful + */ + public static function updateInfo(string $token, UserColumns|string $info, string $value, bool $encrypted): bool + { + if (!in_array($info, UserColumns::getColumns())) { + throw new \InvalidArgumentException('Invalid column name: ' . $info); + } + $con = self::getPdoConnection(); + if ($encrypted) { + $value = App::getInstance(true)->encrypt($value); + } + $stmt = $con->prepare('UPDATE ' . self::TABLE_NAME . ' SET ' . $info . ' = :value WHERE token = :token'); + $stmt->bindParam(':value', $value); + $stmt->bindParam(':token', $token); + + return $stmt->execute(); + } + + /** + * Get the token from the UUID. + * + * @param string $uuid The UUID + * + * @return string The token + */ + public static function getTokenFromUUID(string $uuid): string + { + $con = self::getPdoConnection(); + $stmt = $con->prepare('SELECT token FROM ' . self::TABLE_NAME . ' WHERE uuid = :uuid'); + $stmt->bindParam(':uuid', $uuid); + $stmt->execute(); + + return $stmt->fetchColumn(); + } + + /** + * Process the template. + * + * @param string $template The template + * @param string $uuid The UUID + * + * @return string The processed template + */ + public static function processTemplate(string $template, string $uuid): string + { + try { + + $template = str_replace('${uuid}', $uuid, $template); + $template = str_replace('${username}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::USERNAME, false), $template); + $template = str_replace('${email}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::EMAIL, false), $template); + $template = str_replace('${first_name}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_NAME, true), $template); + $template = str_replace('${last_name}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_NAME, true), $template); + $template = str_replace('${avatar}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::AVATAR, false), $template); + $template = str_replace('${background}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::BACKGROUND, false), $template); + $template = str_replace('${role_id}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::ROLE_ID, false), $template); + $template = str_replace('${first_ip}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_IP, false), $template); + $template = str_replace('${last_ip}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_IP, false), $template); + $template = str_replace('${banned}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::BANNED, false), $template); + $template = str_replace('${verified}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::VERIFIED, false), $template); + $template = str_replace('${2fa_enabled}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::TWO_FA_ENABLED, false), $template); + $template = str_replace('${deleted}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::DELETED, false), $template); + $template = str_replace('${last_seen}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::LAST_SEEN, false), $template); + $template = str_replace('${first_seen}', self::getInfo(self::getTokenFromUUID($uuid), UserColumns::FIRST_SEEN, false), $template); + + return $template; + } catch (\Exception $e) { + App::getInstance(true)->getLogger()->error('Failed to render email template: ' . $e->getMessage()); + + return null; + } + } } diff --git a/backend/app/Chat/UserActivities.php b/backend/app/Chat/UserActivities.php index db9745b..e00458e 100755 --- a/backend/app/Chat/UserActivities.php +++ b/backend/app/Chat/UserActivities.php @@ -109,71 +109,71 @@ class UserActivities { - /** - * Add user activity. - * - * @param string $uuid User UUID - * @param string|UserActivitiesTypes $type Activity type - * @param string $ipv4 IP address - */ - public static function add(string $uuid, string|UserActivitiesTypes $type, string $ipv4): bool - { - $dbConn = Database::getPdoConnection(); + /** + * Add user activity. + * + * @param string $uuid User UUID + * @param string|UserActivitiesTypes $type Activity type + * @param string $ipv4 IP address + */ + public static function add(string $uuid, string|UserActivitiesTypes $type, string $ipv4): bool + { + $dbConn = Database::getPdoConnection(); - $stmt = $dbConn->prepare('INSERT INTO ' . self::getTable() . ' (user, action, ip_address) VALUES (:user, :action, :ip_address)'); + $stmt = $dbConn->prepare('INSERT INTO ' . self::getTable() . ' (user, action, ip_address) VALUES (:user, :action, :ip_address)'); - return $stmt->execute([ - ':user' => $uuid, - ':action' => $type, - ':ip_address' => $ipv4, - ]); - } + return $stmt->execute([ + ':user' => $uuid, + ':action' => $type, + ':ip_address' => $ipv4, + ]); + } - /** - * Get user activities. - * - * @param string $uuid User UUID - */ - public static function get(string $uuid): array - { - $dbConn = Database::getPdoConnection(); + /** + * Get user activities. + * + * @param string $uuid User UUID + */ + public static function get(string $uuid): array + { + $dbConn = Database::getPdoConnection(); - $stmt = $dbConn->prepare('SELECT * FROM ' . self::getTable() . ' WHERE user = :user LIMIT 125'); - $stmt->execute([ - ':user' => $uuid, - ]); + $stmt = $dbConn->prepare('SELECT * FROM ' . self::getTable() . ' WHERE user = :user LIMIT 125'); + $stmt->execute([ + ':user' => $uuid, + ]); - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - } + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } - /** - * Get all user activities. - * - * @param int $limit Limit - */ - public static function getAll(int $limit = 50): array - { - try { - $dbConn = Database::getPdoConnection(); + /** + * Get all user activities. + * + * @param int $limit Limit + */ + public static function getAll(int $limit = 50): array + { + try { + $dbConn = Database::getPdoConnection(); - $stmt = $dbConn->prepare('SELECT * FROM ' . self::getTable() . ' LIMIT ' . $limit); - $stmt->execute(); + $stmt = $dbConn->prepare('SELECT * FROM ' . self::getTable() . ' LIMIT ' . $limit); + $stmt->execute(); - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - } catch (\Exception $e) { - Database::db_Error('Failed to get all user activities: ' . $e->getMessage()); + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } catch (\Exception $e) { + Database::db_Error('Failed to get all user activities: ' . $e->getMessage()); - return []; - } - } + return []; + } + } - /** - * Get table name. - * - * @return string Table name - */ - public static function getTable(): string - { - return 'mythicalclient_users_activities'; - } + /** + * Get table name. + * + * @return string Table name + */ + public static function getTable(): string + { + return 'mythicalclient_users_activities'; + } } diff --git a/backend/app/Hooks/GitHub.php b/backend/app/Hooks/GitHub.php index 346fe36..8b858d6 100755 --- a/backend/app/Hooks/GitHub.php +++ b/backend/app/Hooks/GitHub.php @@ -105,46 +105,46 @@ namespace MythicalClient\Hooks; -use MythicalClient\Cache\Cache; use GuzzleHttp\Client; +use MythicalClient\Cache\Cache; class GitHub { - private $cacheKey = 'github_repo_data'; - private $cacheTTL = 3600; // 1 hour in seconds - private $client; + private $cacheKey = 'github_repo_data'; + private $cacheTTL = 3600; // 1 hour in seconds + private $client; - public function __construct() - { - $this->client = new Client(); - } + public function __construct() + { + $this->client = new Client(); + } - /** - * Retrieves repository data from GitHub API, using cache if available. - * - * @return array The repository data. - */ - public function getRepoData() - { - // Check if data is cached - if (Cache::exists($this->cacheKey)) { - return Cache::getJson($this->cacheKey); - } + /** + * Retrieves repository data from GitHub API, using cache if available. + * + * @return array the repository data + */ + public function getRepoData() + { + // Check if data is cached + if (Cache::exists($this->cacheKey)) { + return Cache::getJson($this->cacheKey); + } - // Make GET request to GitHub API - $response = $this->client->request('GET', 'https://api.github.com/repos/mythicalltd/mythicaldash', [ - 'headers' => [ - 'Accept' => 'application/vnd.github+json', - 'X-GitHub-Api-Version' => '2022-11-28', - 'User-Agent' => 'MythicalClient', - ], - ]); + // Make GET request to GitHub API + $response = $this->client->request('GET', 'https://api.github.com/repos/mythicalltd/mythicaldash', [ + 'headers' => [ + 'Accept' => 'application/vnd.github+json', + 'X-GitHub-Api-Version' => '2022-11-28', + 'User-Agent' => 'MythicalClient', + ], + ]); - $data = json_decode($response->getBody()->getContents(), true); + $data = json_decode($response->getBody()->getContents(), true); - // Cache the response - Cache::putJson($this->cacheKey, $data, $this->cacheTTL); + // Cache the response + Cache::putJson($this->cacheKey, $data, $this->cacheTTL); - return $data; - } + return $data; + } } diff --git a/backend/app/Hooks/MythicalAPP.php b/backend/app/Hooks/MythicalAPP.php index d99db61..0b15966 100755 --- a/backend/app/Hooks/MythicalAPP.php +++ b/backend/app/Hooks/MythicalAPP.php @@ -240,13 +240,13 @@ public static function sendManualResponse(int $code, ?string $error, ?string $me 'debug_debug' => APP_DEBUG, 'debug_version' => APP_VERSION, 'debug_telemetry' => TELEMETRY, - 'debug' => [ - 'useRedis' => REDIS_ENABLED, - 'rateLimit' => [ - 'enabled' => REDIS_ENABLED, - 'limit' => RATE_LIMIT, - ], - ] + 'debug' => [ + 'useRedis' => REDIS_ENABLED, + 'rateLimit' => [ + 'enabled' => REDIS_ENABLED, + 'limit' => RATE_LIMIT, + ], + ], ], ];