| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- <?php
- declare(strict_types=1);
- namespace App\App;
- use App\Auth\LocalLoginController;
- use App\Auth\LogoutController;
- use App\Auth\OidcController;
- use App\Controllers\AllowlistController;
- use App\Controllers\AuditController;
- use App\Controllers\CategoriesController;
- use App\Controllers\ConsumersController;
- use App\Controllers\DashboardController;
- use App\Controllers\HealthzController;
- use App\Controllers\HomeController;
- use App\Controllers\IpsController;
- use App\Controllers\ManualBlocksController;
- use App\Controllers\MeController;
- use App\Controllers\NoAccessController;
- use App\Controllers\PoliciesController;
- use App\Controllers\ReportersController;
- use App\Controllers\SearchController;
- use App\Controllers\SettingsController;
- use App\Controllers\TokensController;
- use App\Http\AuthRequiredMiddleware;
- use App\Http\CsrfMiddleware;
- use App\Http\JsonExceptionHandler;
- use App\Http\SessionMiddleware;
- use App\Http\TwigGlobalsMiddleware;
- use Psr\Container\ContainerInterface;
- use Psr\Log\LoggerInterface;
- use Slim\App;
- use Slim\Factory\AppFactory as SlimAppFactory;
- use Slim\Routing\RouteCollectorProxy;
- /**
- * Builds the configured Slim app for the UI.
- *
- * Middleware order (Slim is LIFO; bottom = outermost):
- * 1. Session — always start the PHP session first.
- * 2. Csrf — needs a session to read/write the token.
- * 3. TwigGlobals — needs both above.
- * 4. Routing + body parsing.
- *
- * `/app/*` routes get an additional AuthRequiredMiddleware on the
- * route group so anonymous users bounce to /login.
- */
- final class AppFactory
- {
- /**
- * @return App<ContainerInterface|null>
- */
- public static function build(ContainerInterface $container): App
- {
- SlimAppFactory::setContainer($container);
- $app = SlimAppFactory::create();
- $app->addRoutingMiddleware();
- /** @var LoggerInterface $logger */
- $logger = $container->get(LoggerInterface::class);
- /** @var bool $isDev */
- $isDev = $container->get('settings.is_dev');
- $errorMiddleware = $app->addErrorMiddleware($isDev, true, true, $logger);
- /** @var JsonExceptionHandler $handler */
- $handler = $container->get(JsonExceptionHandler::class);
- $errorMiddleware->setDefaultErrorHandler($handler);
- // Slim middleware is LIFO — the last `add()` call runs first.
- // Order on the incoming request:
- // 1. Session (needs to start before anything reads $_SESSION)
- // 2. BodyParsing (so CSRF can read form fields)
- // 3. CSRF (reads from session + parsed body)
- // 4. TwigGlobals (after CSRF token is set on the request attr)
- // 5. AuthRequired (per /app/* group)
- /** @var TwigGlobalsMiddleware $globals */
- $globals = $container->get(TwigGlobalsMiddleware::class);
- /** @var CsrfMiddleware $csrf */
- $csrf = $container->get(CsrfMiddleware::class);
- /** @var SessionMiddleware $session */
- $session = $container->get(SessionMiddleware::class);
- $app->add($globals);
- $app->add($csrf);
- $app->addBodyParsingMiddleware();
- $app->add($session);
- /** @var HomeController $home */
- $home = $container->get(HomeController::class);
- $app->get('/', $home);
- /** @var HealthzController $healthz */
- $healthz = $container->get(HealthzController::class);
- $app->get('/healthz', $healthz);
- /** @var LocalLoginController $local */
- $local = $container->get(LocalLoginController::class);
- $app->get('/login', [$local, 'showLogin']);
- $app->post('/login/local', [$local, 'postLocal']);
- /** @var OidcController $oidc */
- $oidc = $container->get(OidcController::class);
- $app->get('/login/oidc', [$oidc, 'initiate']);
- $app->get('/oidc/callback', [$oidc, 'callback']);
- /** @var LogoutController $logout */
- $logout = $container->get(LogoutController::class);
- $app->post('/logout', $logout);
- /** @var NoAccessController $noAccess */
- $noAccess = $container->get(NoAccessController::class);
- $app->get('/no-access', $noAccess);
- /** @var AuthRequiredMiddleware $authRequired */
- $authRequired = $container->get(AuthRequiredMiddleware::class);
- $app->group('/app', function (RouteCollectorProxy $group) use ($container): void {
- /** @var MeController $me */
- $me = $container->get(MeController::class);
- $group->get('/me', $me);
- /** @var DashboardController $dashboard */
- $dashboard = $container->get(DashboardController::class);
- $group->get('/dashboard', $dashboard);
- /** @var IpsController $ips */
- $ips = $container->get(IpsController::class);
- $group->get('/ips', [$ips, 'index']);
- // {ip:.+} so v6 colons don't break Slim's default segment regex.
- $group->get('/ips/{ip:.+}', [$ips, 'show']);
- /** @var ManualBlocksController $manualBlocks */
- $manualBlocks = $container->get(ManualBlocksController::class);
- $group->get('/manual-blocks', [$manualBlocks, 'index']);
- $group->post('/manual-blocks', [$manualBlocks, 'create']);
- $group->post('/manual-blocks/{id}/delete', [$manualBlocks, 'delete']);
- // /app/subnets is an alias filtered to kind=subnet so the
- // sidebar's "Subnets" link lands on a focused list.
- $group->get('/subnets', [$manualBlocks, 'subnetsIndex']);
- /** @var AllowlistController $allowlist */
- $allowlist = $container->get(AllowlistController::class);
- $group->get('/allowlist', [$allowlist, 'index']);
- $group->post('/allowlist', [$allowlist, 'create']);
- $group->post('/allowlist/{id}/delete', [$allowlist, 'delete']);
- /** @var PoliciesController $policies */
- $policies = $container->get(PoliciesController::class);
- $group->get('/policies', [$policies, 'index']);
- $group->post('/policies', [$policies, 'create']);
- $group->get('/policies/{id}', [$policies, 'edit']);
- $group->post('/policies/{id}', [$policies, 'update']);
- $group->post('/policies/{id}/delete', [$policies, 'delete']);
- // GET-only XHR proxy used by the edit page's preview pane.
- $group->get('/policies/{id}/preview-proxy', [$policies, 'previewProxy']);
- /** @var ReportersController $reporters */
- $reporters = $container->get(ReportersController::class);
- $group->get('/reporters', [$reporters, 'index']);
- $group->post('/reporters', [$reporters, 'create']);
- $group->get('/reporters/{id}', [$reporters, 'edit']);
- $group->post('/reporters/{id}', [$reporters, 'update']);
- $group->post('/reporters/{id}/delete', [$reporters, 'delete']);
- /** @var ConsumersController $consumers */
- $consumers = $container->get(ConsumersController::class);
- $group->get('/consumers', [$consumers, 'index']);
- $group->post('/consumers', [$consumers, 'create']);
- $group->get('/consumers/{id}', [$consumers, 'edit']);
- $group->post('/consumers/{id}', [$consumers, 'update']);
- $group->post('/consumers/{id}/delete', [$consumers, 'delete']);
- /** @var TokensController $tokens */
- $tokens = $container->get(TokensController::class);
- $group->get('/tokens', [$tokens, 'index']);
- $group->post('/tokens', [$tokens, 'create']);
- $group->post('/tokens/{id}/delete', [$tokens, 'delete']);
- /** @var CategoriesController $categories */
- $categories = $container->get(CategoriesController::class);
- $group->get('/categories', [$categories, 'index']);
- $group->post('/categories', [$categories, 'create']);
- $group->get('/categories/{id}', [$categories, 'edit']);
- $group->post('/categories/{id}', [$categories, 'update']);
- $group->post('/categories/{id}/delete', [$categories, 'delete']);
- /** @var AuditController $audit */
- $audit = $container->get(AuditController::class);
- $group->get('/audit', [$audit, 'index']);
- /** @var SearchController $search */
- $search = $container->get(SearchController::class);
- $group->get('/search', [$search, 'index']);
- /** @var SettingsController $settings */
- $settings = $container->get(SettingsController::class);
- $group->get('/settings', [$settings, 'index']);
- $group->post('/settings/jobs/trigger/{name}', [$settings, 'trigger']);
- $group->post('/settings/maintenance/purge', [$settings, 'purge']);
- $group->post('/settings/maintenance/seed-demo', [$settings, 'seedDemo']);
- })->add($authRequired);
- $app->map(
- ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
- '/{routes:.+}',
- function ($request, $response) {
- return $response->withStatus(404)->withHeader('Content-Type', 'text/plain');
- }
- );
- return $app;
- }
- }
|