frankenphp

This commit is contained in:
2026-01-15 04:32:49 +07:00
parent e4e1aa04ac
commit a21a62c483
9 changed files with 111 additions and 14 deletions

6
Caddyfile Normal file
View File

@@ -0,0 +1,6 @@
:80 {
root * /var/www/public
php_server {
index index.php
}
}

View File

@@ -1,7 +1,9 @@
FROM php:8.5-cli-alpine FROM dunglas/frankenphp:php8.5-alpine
RUN apk add --no-cache libzip-dev zip \
&& docker-php-ext-install zip RUN install-php-extensions zip
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www WORKDIR /var/www
COPY . .
CMD ["php", "-S", "0.0.0.0:8001", "-t", "public"] COPY . .

View File

@@ -4,11 +4,24 @@ services:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: cloud container_name: cloud
restart: unless-stopped
ports: ports:
- "8001:8001" - "127.0.0.1:8001:80"
volumes: volumes:
- .:/var/www - .:/var/www
- ./db:/var/db - ./db:/var/db
- ./php/php.ini:/usr/local/etc/php/conf.d/uploads.ini - ./php/php.ini:/usr/local/etc/php/conf.d/uploads.ini
- ./Caddyfile:/etc/frankenphp/Caddyfile
- caddy_data:/data
- caddy_config:/config
env_file: env_file:
- .env - .env
tty: false
healthcheck:
test: [ "CMD", "curl", "-kf", "https://localhost/" ]
interval: 30s
timeout: 10s
retries: 3
volumes:
caddy_data:
caddy_config:

View File

@@ -15,8 +15,6 @@ use Din9xtrCloud\Router;
use Din9xtrCloud\Storage\Drivers\LocalStorageDriver; use Din9xtrCloud\Storage\Drivers\LocalStorageDriver;
use Din9xtrCloud\Storage\Drivers\StorageDriverInterface; use Din9xtrCloud\Storage\Drivers\StorageDriverInterface;
use Din9xtrCloud\Storage\UserStorageInitializer; use Din9xtrCloud\Storage\UserStorageInitializer;
use Din9xtrCloud\ViewModels\BaseViewModel;
use Din9xtrCloud\ViewModels\LayoutConfig;
use FastRoute\RouteCollector; use FastRoute\RouteCollector;
use Monolog\Handler\StreamHandler; use Monolog\Handler\StreamHandler;
use Monolog\Logger; use Monolog\Logger;
@@ -138,6 +136,8 @@ try {
$container->beginRequest(); $container->beginRequest();
$app->dispatch(); $app->dispatch();
} catch (Throwable $e) { } catch (Throwable $e) {
/** @noinspection PhpUnhandledExceptionInspection */
$container->get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]);
http_response_code(500); http_response_code(500);
header('Content-Type: text/plain; charset=utf-8'); header('Content-Type: text/plain; charset=utf-8');
echo 'Internal Server Error'; echo 'Internal Server Error';

View File

@@ -17,7 +17,7 @@ $page = $viewModel->page;
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png"> <link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest"> <link rel="manifest" href="/site.webmanifest">
<link rel="stylesheet" href=/assets/cloud.css> <link rel="stylesheet" href="/assets/cloud.css">
</head> </head>
<body> <body>

View File

@@ -40,7 +40,7 @@ final readonly class AuthController
$username = (string)($data['username'] ?? ''); $username = (string)($data['username'] ?? '');
$password = (string)($data['password'] ?? ''); $password = (string)($data['password'] ?? '');
$ip = $request->getServerParams()['REMOTE_ADDR'] ?? null; $ip = getClientIp();
$ua = $request->getHeaderLine('User-Agent') ?: null; $ua = $request->getHeaderLine('User-Agent') ?: null;
$this->logger->info('Login submitted', [ $this->logger->info('Login submitted', [

View File

@@ -21,3 +21,76 @@ if (!function_exists('getStoragePercent')) {
return $totalBytes > 0 ? ($categoryBytes / $totalBytes * 100) : 0; 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;
}
}

View File

@@ -10,6 +10,7 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
final class AuthMiddleware implements MiddlewareInterface final class AuthMiddleware implements MiddlewareInterface
{ {
@@ -26,6 +27,7 @@ final class AuthMiddleware implements MiddlewareInterface
public function __construct( public function __construct(
private readonly SessionRepository $sessions, private readonly SessionRepository $sessions,
private readonly UserRepository $users, private readonly UserRepository $users,
private readonly LoggerInterface $logger,
) )
{ {
} }
@@ -35,6 +37,8 @@ final class AuthMiddleware implements MiddlewareInterface
RequestHandlerInterface $handler RequestHandlerInterface $handler
): ResponseInterface ): ResponseInterface
{ {
$this->logger->debug('getClientIp: ' . getClientIp());
$path = $request->getUri()->getPath(); $path = $request->getUri()->getPath();
$token = $request->getCookieParams()['auth_token'] ?? null; $token = $request->getCookieParams()['auth_token'] ?? null;

View File

@@ -20,9 +20,8 @@ final class ThrottleMiddleware implements MiddlewareInterface
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{ {
$ip = $request->getHeaderLine('X-Forwarded-For') ?: $request->getServerParams()['REMOTE_ADDR'] ?? 'unknown'; $ip = getClientIp();
$ip = explode(',', $ip)[0];
$stmt = $this->db->prepare("SELECT * FROM login_throttle WHERE ip = :ip ORDER BY id DESC LIMIT 1"); $stmt = $this->db->prepare("SELECT * FROM login_throttle WHERE ip = :ip ORDER BY id DESC LIMIT 1");
$stmt->execute(['ip' => $ip]); $stmt->execute(['ip' => $ip]);
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);