Skip to content

Commit

Permalink
Merge pull request #210 from MythicalLTD/209-memory-leak-slow-perform…
Browse files Browse the repository at this point in the history
…ance

Fix performance and integrate rate limiting!
  • Loading branch information
NaysKutzu authored Jan 14, 2025
2 parents 1318b5a + 466e70e commit 3852301
Show file tree
Hide file tree
Showing 13 changed files with 1,014 additions and 259 deletions.
3 changes: 2 additions & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,31 +226,40 @@
$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' => [
'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))),
],
'user_info' => $info,
'billing' => $billing,
]);

} catch (Exception $e) {
$appInstance->BadRequest('Bad Request', ['error_code' => 'INVALID_ACCOUNT_TOKEN', 'error' => $e->getMessage()]);
}
Expand Down
40 changes: 31 additions & 9 deletions backend/app/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,16 @@

namespace MythicalClient;

use RateLimit\Rate;
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
{
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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.
*/
Expand Down
26 changes: 26 additions & 0 deletions backend/app/Chat/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,32 @@ public static function getInfo(string $token, UserColumns|string $info, bool $en

}

/**
* 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.
*
Expand Down
33 changes: 22 additions & 11 deletions backend/app/Hooks/GitHub.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,25 @@

namespace MythicalClient\Hooks;

use GuzzleHttp\Client;
use MythicalClient\Cache\Cache;

class GitHub
{
private $cacheKey = 'github_repo_data';
private $cacheTTL = 3600; // 1 hour in seconds
private $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
Expand All @@ -120,20 +132,19 @@ public function getRepoData()
}

// 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 = $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',
],
]);
$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response->getBody()->getContents(), true);

// Cache the response
Cache::putJson($this->cacheKey, $response, $this->cacheTTL);
Cache::putJson($this->cacheKey, $data, $this->cacheTTL);

return $response;
return $data;
}
}
7 changes: 7 additions & 0 deletions backend/app/Hooks/MythicalAPP.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
],
],
],
];

Expand Down
4 changes: 3 additions & 1 deletion backend/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading

0 comments on commit 3852301

Please sign in to comment.