diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..e8fb19b --- /dev/null +++ b/Caddyfile @@ -0,0 +1,6 @@ +:80 { + root * /var/www/public + php_server { + index index.php + } +} diff --git a/Dockerfile b/Dockerfile index 999b365..600cb8d 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,9 @@ -FROM php:8.5-cli-alpine -RUN apk add --no-cache libzip-dev zip \ - && docker-php-ext-install zip +FROM dunglas/frankenphp:php8.5-alpine + +RUN install-php-extensions zip + COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + WORKDIR /var/www -COPY . . -CMD ["php", "-S", "0.0.0.0:8001", "-t", "public"] \ No newline at end of file + +COPY . . \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c70f887..5211038 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,11 +4,24 @@ services: context: . dockerfile: Dockerfile container_name: cloud + restart: unless-stopped ports: - - "8001:8001" + - "127.0.0.1:8001:80" volumes: - .:/var/www - ./db:/var/db - ./php/php.ini:/usr/local/etc/php/conf.d/uploads.ini + - ./Caddyfile:/etc/frankenphp/Caddyfile + - caddy_data:/data + - caddy_config:/config env_file: - - .env \ No newline at end of file + - .env + tty: false + healthcheck: + test: [ "CMD", "curl", "-kf", "https://localhost/" ] + interval: 30s + timeout: 10s + retries: 3 +volumes: + caddy_data: + caddy_config: \ No newline at end of file diff --git a/public/index.php b/public/index.php index 013bf53..a1a5541 100755 --- a/public/index.php +++ b/public/index.php @@ -15,8 +15,6 @@ use Din9xtrCloud\Router; use Din9xtrCloud\Storage\Drivers\LocalStorageDriver; use Din9xtrCloud\Storage\Drivers\StorageDriverInterface; use Din9xtrCloud\Storage\UserStorageInitializer; -use Din9xtrCloud\ViewModels\BaseViewModel; -use Din9xtrCloud\ViewModels\LayoutConfig; use FastRoute\RouteCollector; use Monolog\Handler\StreamHandler; use Monolog\Logger; @@ -138,6 +136,8 @@ try { $container->beginRequest(); $app->dispatch(); } catch (Throwable $e) { + /** @noinspection PhpUnhandledExceptionInspection */ + $container->get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]); http_response_code(500); header('Content-Type: text/plain; charset=utf-8'); echo 'Internal Server Error'; diff --git a/resources/views/layouts/app.php b/resources/views/layouts/app.php index fbf02bb..eac8928 100755 --- a/resources/views/layouts/app.php +++ b/resources/views/layouts/app.php @@ -17,7 +17,7 @@ $page = $viewModel->page; - + diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php index c0d9b20..499514d 100755 --- a/src/Controllers/AuthController.php +++ b/src/Controllers/AuthController.php @@ -40,7 +40,7 @@ final readonly class AuthController $username = (string)($data['username'] ?? ''); $password = (string)($data['password'] ?? ''); - $ip = $request->getServerParams()['REMOTE_ADDR'] ?? null; + $ip = getClientIp(); $ua = $request->getHeaderLine('User-Agent') ?: null; $this->logger->info('Login submitted', [ diff --git a/src/Helpers/helpers.php b/src/Helpers/helpers.php index d4cb7d2..db78f4d 100644 --- a/src/Helpers/helpers.php +++ b/src/Helpers/helpers.php @@ -21,3 +21,76 @@ if (!function_exists('getStoragePercent')) { return $totalBytes > 0 ? ($categoryBytes / $totalBytes * 100) : 0; } } +if (!function_exists('getClientIp')) { + function getClientIp(): string + { + $server = $_SERVER; + + $candidates = [ + $server['HTTP_CF_CONNECTING_IP'] ?? null, + + extractForwardedIp($server['HTTP_FORWARDED'] ?? null), + extractForwardedIp($server['HTTP_X_FORWARDED_FOR'] ?? null), + extractForwardedIp($server['HTTP_FORWARDED_FOR'] ?? null), + + $server['HTTP_X_REAL_IP'] ?? null, + $server['HTTP_CLIENT_IP'] ?? null, + $server['REMOTE_ADDR'] ?? null, + ]; + + foreach ($candidates as $ip) { + if ($ip === null) { + continue; + } + + if ($valid = validateIp($ip)) { + return $valid; + } + } + + return 'unknown'; + } +} + +if (!function_exists('extractForwardedIp')) { + function extractForwardedIp(?string $value): ?string + { + if (!$value) { + return null; + } + + if (str_contains($value, 'for=')) { + if (preg_match('/for="?([^";, ]+)/i', $value, $m)) { + return $m[1]; + } + } + + $parts = explode(',', $value); + + return trim($parts[0]) ?: null; + } +} + +if (!function_exists('validateIp')) { + function validateIp(string $ip): ?string + { + $ip = trim($ip); + + if ($ip === '') { + return null; + } + + if ( + str_contains($ip, ':') && + !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) + ) { + $ip = explode(':', $ip, 2)[0]; + } + + $flags = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6; + + return filter_var($ip, FILTER_VALIDATE_IP, $flags) + ? $ip + : null; + } +} diff --git a/src/Middlewares/AuthMiddleware.php b/src/Middlewares/AuthMiddleware.php index 7deccd3..c6f89c9 100755 --- a/src/Middlewares/AuthMiddleware.php +++ b/src/Middlewares/AuthMiddleware.php @@ -10,6 +10,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Psr\Log\LoggerInterface; final class AuthMiddleware implements MiddlewareInterface { @@ -26,6 +27,7 @@ final class AuthMiddleware implements MiddlewareInterface public function __construct( private readonly SessionRepository $sessions, private readonly UserRepository $users, + private readonly LoggerInterface $logger, ) { } @@ -35,6 +37,8 @@ final class AuthMiddleware implements MiddlewareInterface RequestHandlerInterface $handler ): ResponseInterface { + $this->logger->debug('getClientIp: ' . getClientIp()); + $path = $request->getUri()->getPath(); $token = $request->getCookieParams()['auth_token'] ?? null; diff --git a/src/Middlewares/ThrottleMiddleware.php b/src/Middlewares/ThrottleMiddleware.php index 4940745..224e2f5 100755 --- a/src/Middlewares/ThrottleMiddleware.php +++ b/src/Middlewares/ThrottleMiddleware.php @@ -20,9 +20,8 @@ final class ThrottleMiddleware implements MiddlewareInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - $ip = $request->getHeaderLine('X-Forwarded-For') ?: $request->getServerParams()['REMOTE_ADDR'] ?? 'unknown'; - $ip = explode(',', $ip)[0]; - + $ip = getClientIp(); + $stmt = $this->db->prepare("SELECT * FROM login_throttle WHERE ip = :ip ORDER BY id DESC LIMIT 1"); $stmt->execute(['ip' => $ip]); $row = $stmt->fetch(PDO::FETCH_ASSOC);