agent foundation

This commit is contained in:
2026-03-06 00:44:52 +01:00
parent 9819cba733
commit 9a08f96c11
199 changed files with 8522 additions and 1880 deletions

View File

@@ -4,10 +4,13 @@ namespace MintyPHP\Http;
use MintyPHP\Service\Audit\ApiAuditService;
use MintyPHP\Service\Security\RateLimiterService;
use MintyPHP\Service\Settings\SettingGateway;
use MintyPHP\Service\Settings\SettingsApiPolicyGateway;
// Entry point bootstrapper called once at the top of every API action.
// Handles: request context, CORS, audit start, rate limiting, and optional Bearer auth.
class ApiBootstrap
{
// Two separate rate-limit buckets: one per IP (broad), one per token+IP (fine-grained).
private const API_RATE_SCOPE_TOKEN = 'api.token_ip';
private const API_RATE_SCOPE_IP = 'api.ip';
private const API_RATE_LIMIT_TOKEN = 300;
@@ -19,8 +22,8 @@ class ApiBootstrap
private static bool $shutdownRegistered = false;
/** @var (callable(): ApiAuditService)|null */
private static $apiAuditServiceResolver = null;
/** @var (callable(): SettingGateway)|null */
private static $settingGatewayResolver = null;
/** @var (callable(): SettingsApiPolicyGateway)|null */
private static $settingsApiPolicyGatewayResolver = null;
/** @var (callable(): RateLimiterService)|null */
private static $rateLimiterServiceResolver = null;
/** @var (callable(): ApiSystemAuditReporter)|null */
@@ -28,12 +31,12 @@ class ApiBootstrap
public static function configure(
callable $apiAuditServiceResolver,
callable $settingGatewayResolver,
callable $settingsApiPolicyGatewayResolver,
callable $rateLimiterServiceResolver,
callable $apiSystemAuditReporterResolver
): void {
self::$apiAuditServiceResolver = $apiAuditServiceResolver;
self::$settingGatewayResolver = $settingGatewayResolver;
self::$settingsApiPolicyGatewayResolver = $settingsApiPolicyGatewayResolver;
self::$rateLimiterServiceResolver = $rateLimiterServiceResolver;
self::$apiSystemAuditReporterResolver = $apiSystemAuditReporterResolver;
}
@@ -93,7 +96,7 @@ class ApiBootstrap
return;
}
$allowedOrigins = array_fill_keys(self::settings()->getApiCorsAllowedOrigins(), true);
$allowedOrigins = array_fill_keys(self::settingsApiPolicy()->getApiCorsAllowedOrigins(), true);
if (!isset($allowedOrigins[$origin])) {
return;
}
@@ -105,6 +108,7 @@ class ApiBootstrap
header('Access-Control-Max-Age: 86400');
}
// Guarantees audit records are written even on uncaught fatal errors.
private static function registerShutdownHandler(): void
{
if (self::$shutdownRegistered) {
@@ -216,6 +220,8 @@ class ApiBootstrap
}
}
// API tokens are formatted as "selector:verifier". The selector (24-char hex) is used
// as the rate-limit key so a single token can't bypass the per-IP bucket by rotating IPs.
private static function extractBearerSelector(): string
{
$token = ApiAuth::extractBearerToken();
@@ -232,9 +238,13 @@ class ApiBootstrap
return $selector;
}
private static function settings(): SettingGateway
private static function settingsApiPolicy(): SettingsApiPolicyGateway
{
return self::resolveDependency(self::$settingGatewayResolver, SettingGateway::class, 'ApiBootstrap');
return self::resolveDependency(
self::$settingsApiPolicyGatewayResolver,
SettingsApiPolicyGateway::class,
'ApiBootstrap'
);
}
private static function rateLimiter(): RateLimiterService