refactor(kernel): unify HTTP and CLI application lifecycle

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-07-27 00:48:11 -04:00
parent 98826143a8
commit 65c1b5fb75
67 changed files with 2737 additions and 1070 deletions
+14 -14
View File
@@ -5,7 +5,7 @@ namespace KTXC\Service;
use KTXC\Db\DataStore;
use KTXC\Db\Collection;
use KTXC\Db\UTCDateTime;
use KTXC\SessionTenant;
use KTXC\Context\TenantContextInterface;
class ConfigurationService
{
@@ -24,7 +24,7 @@ class ConfigurationService
public function __construct(
DataStore $store,
private readonly SessionTenant $tenant
private readonly TenantContextInterface $tenantContext
) {
// DataStore provides selectCollection method
$this->collection = $store->selectCollection(self::TABLE_NAME);
@@ -36,10 +36,10 @@ class ConfigurationService
*/
public function get(string $path, string $key, mixed $default = null, ?string $tenant = null): mixed
{
if ($tenant === null && !$this->tenant->isConfigured()) {
if ($tenant === null && !$this->tenantContext->configured()) {
throw new \InvalidArgumentException('Tenant must be configured or provided explicitly.');
} elseif ($tenant === null) {
$tenant = $this->tenant->identifier();
$tenant = $this->tenantContext->identifier();
}
$doc = $this->collection->findOne(['did' => $tenant, 'path' => $path, 'key' => $key]);
@@ -54,10 +54,10 @@ class ConfigurationService
*/
public function set(string $path, string $key, mixed $value, mixed $default = null, ?string $tenant = null): bool
{
if ($tenant === null && !$this->tenant->isConfigured()) {
if ($tenant === null && !$this->tenantContext->configured()) {
throw new \InvalidArgumentException('Tenant must be configured or provided explicitly.');
} elseif ($tenant === null) {
$tenant = $this->tenant->identifier();
$tenant = $this->tenantContext->identifier();
}
$type = $this->determineType($value);
@@ -84,10 +84,10 @@ class ConfigurationService
*/
public function getByPath(?string $path = null, bool $subset = false, ?string $tenant = null): array
{
if ($tenant === null && !$this->tenant->isConfigured()) {
if ($tenant === null && !$this->tenantContext->configured()) {
throw new \InvalidArgumentException('Tenant must be configured or provided explicitly.');
} elseif ($tenant === null) {
$tenant = $this->tenant->identifier();
$tenant = $this->tenantContext->identifier();
}
$filter = ['did' => $tenant];
@@ -116,10 +116,10 @@ class ConfigurationService
*/
public function delete(string $path, string $key, ?string $tenant = null): bool
{
if ($tenant === null && !$this->tenant->isConfigured()) {
if ($tenant === null && !$this->tenantContext->configured()) {
throw new \InvalidArgumentException('Tenant must be configured or provided explicitly.');
} elseif ($tenant === null) {
$tenant = $this->tenant->identifier();
$tenant = $this->tenantContext->identifier();
}
$this->collection->deleteOne(['did' => $tenant, 'path' => $path, 'key' => $key]);
@@ -131,10 +131,10 @@ class ConfigurationService
*/
public function deleteByPath(string $path, bool $includeSubPaths = false, ?string $tenant = null): bool
{
if ($tenant === null && !$this->tenant->isConfigured()) {
if ($tenant === null && !$this->tenantContext->configured()) {
throw new \InvalidArgumentException('Tenant must be configured or provided explicitly.');
} elseif ($tenant === null) {
$tenant = $this->tenant->identifier();
$tenant = $this->tenantContext->identifier();
}
$filter = ['did' => $tenant];
@@ -155,10 +155,10 @@ class ConfigurationService
*/
public function exists(string $path, string $key, ?string $tenant = null): bool
{
if ($tenant === null && !$this->tenant->isConfigured()) {
if ($tenant === null && !$this->tenantContext->configured()) {
throw new \InvalidArgumentException('Tenant must be configured or provided explicitly.');
} elseif ($tenant === null) {
$tenant = $this->tenant->identifier();
$tenant = $this->tenantContext->identifier();
}
return $this->collection->countDocuments(['did' => $tenant, 'path' => $path, 'key' => $key]) > 0;
+25 -49
View File
@@ -8,8 +8,8 @@ use KTXC\Http\Request\Request;
use KTXC\Models\Firewall\FirewallRuleObject;
use KTXC\Models\Firewall\FirewallLogObject;
use KTXC\Stores\FirewallStore;
use KTXC\SessionTenant;
use KTXF\Event\EventBus;
use KTXC\Context\TenantContextInterface;
use KTXF\Event\EventDispatcherInterface;
use KTXF\Event\SecurityEvent;
use KTXF\IpUtils;
@@ -41,33 +41,9 @@ class FirewallService
public function __construct(
private readonly FirewallStore $store,
private readonly SessionTenant $tenant,
private readonly EventBus $eventBus
private readonly TenantContextInterface $tenantContext,
private readonly EventDispatcherInterface $events,
) {
// Listen for auth failures to detect brute force
$this->eventBus->subscribe(
SecurityEvent::AUTH_FAILURE,
[$this, 'handleAuthFailure'],
100 // High priority
);
// Log all security events asynchronously
$this->eventBus->subscribeAsync(
SecurityEvent::AUTH_FAILURE,
[$this, 'logSecurityEvent']
);
$this->eventBus->subscribeAsync(
SecurityEvent::AUTH_SUCCESS,
[$this, 'logSecurityEvent']
);
$this->eventBus->subscribeAsync(
SecurityEvent::ACCESS_DENIED,
[$this, 'logSecurityEvent']
);
$this->eventBus->subscribeAsync(
SecurityEvent::BRUTE_FORCE_DETECTED,
[$this, 'logSecurityEvent']
);
}
/**
@@ -100,7 +76,7 @@ class FirewallService
return new FirewallAnalyzeResult(true);
}
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
if (!$tenantId) {
return new FirewallAnalyzeResult(true);
}
@@ -158,7 +134,7 @@ class FirewallService
public function handleAuthFailure(SecurityEvent $event): void
{
$ipAddress = $event->getIpAddress();
$tenantId = $event->getTenantId() ?? $this->tenant->identifier();
$tenantId = $event->getTenantId() ?? $this->tenantContext->identifier();
if (!$ipAddress || !$tenantId) {
return;
@@ -198,8 +174,8 @@ class FirewallService
): void {
// Publish brute force event
$event = SecurityEvent::bruteForceDetected($ipAddress, $failureCount, $windowSeconds);
$event->setTenantId($this->tenant->identifier());
$this->eventBus->publish($event);
$event->setTenantId($this->tenantContext->identifier());
$this->events->dispatch($event);
// Auto-block the IP
$blockDuration = $this->getConfig(
@@ -220,7 +196,7 @@ class FirewallService
*/
public function logSecurityEvent(SecurityEvent $event): void
{
$tenantId = $event->getTenantId() ?? $this->tenant->identifier();
$tenantId = $event->getTenantId() ?? $this->tenantContext->identifier();
if (!$tenantId) {
return;
}
@@ -283,8 +259,8 @@ class FirewallService
$rule->getId(),
$rule->getReason()
);
$event->setTenantId($this->tenant->identifier());
$this->eventBus->publish($event);
$event->setTenantId($this->tenantContext->identifier());
$this->events->dispatch($event);
}
// ========================================
@@ -300,7 +276,7 @@ class FirewallService
?string $createdBy = null,
?int $durationSeconds = null
): FirewallRuleObject {
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
if (!$tenantId) {
throw new \RuntimeException('Cannot create firewall rule: no tenant configured');
}
@@ -340,7 +316,7 @@ class FirewallService
$event->setIpAddress($ipAddress)
->setReason($reason)
->setTenantId($tenantId);
$this->eventBus->publish($event);
$this->events->dispatch($event);
return $rule;
}
@@ -353,7 +329,7 @@ class FirewallService
?string $reason = null,
?string $createdBy = null
): FirewallRuleObject {
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
if (!$tenantId) {
throw new \RuntimeException('Cannot create firewall rule: no tenant configured');
}
@@ -376,7 +352,7 @@ class FirewallService
$event->setIpAddress($ipAddress)
->setReason($reason)
->setTenantId($tenantId);
$this->eventBus->publish($event);
$this->events->dispatch($event);
return $rule;
}
@@ -389,7 +365,7 @@ class FirewallService
?string $reason = null,
?string $createdBy = null
): FirewallRuleObject {
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
if (!$tenantId) {
throw new \RuntimeException('Cannot create firewall rule: no tenant configured');
}
@@ -419,7 +395,7 @@ class FirewallService
?string $createdBy = null,
?int $durationSeconds = null
): FirewallRuleObject {
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
if (!$tenantId) {
throw new \RuntimeException('Cannot create firewall rule: no tenant configured');
}
@@ -448,7 +424,7 @@ class FirewallService
$event->setDeviceFingerprint($fingerprint)
->setReason($reason)
->setTenantId($tenantId);
$this->eventBus->publish($event);
$this->events->dispatch($event);
return $rule;
}
@@ -464,7 +440,7 @@ class FirewallService
}
// Verify tenant ownership
if ($rule->getTenantId() !== $this->tenant->identifier()) {
if ($rule->getTenantId() !== $this->tenantContext->identifier()) {
return false;
}
@@ -485,7 +461,7 @@ class FirewallService
}
// Verify tenant ownership
if ($rule->getTenantId() !== $this->tenant->identifier()) {
if ($rule->getTenantId() !== $this->tenantContext->identifier()) {
return false;
}
@@ -501,7 +477,7 @@ class FirewallService
*/
public function listRules(bool $activeOnly = true): array
{
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
if (!$tenantId) {
return [];
}
@@ -518,7 +494,7 @@ class FirewallService
?string $result = null,
int $limit = 100
): array {
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
if (!$tenantId) {
return [];
}
@@ -531,7 +507,7 @@ class FirewallService
*/
public function getBlockedCount(?\DateTimeImmutable $since = null): int
{
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
if (!$tenantId) {
return 0;
}
@@ -556,7 +532,7 @@ class FirewallService
*/
private function getConfig(string $key, mixed $default = null): mixed
{
$config = $this->tenant->configuration();
$config = $this->tenantContext->configuration();
$parts = explode('.', $key);
foreach ($parts as $part) {
@@ -576,7 +552,7 @@ class FirewallService
private function getActiveRules(): array
{
if ($this->rulesCache === null) {
$tenantId = $this->tenant->identifier();
$tenantId = $this->tenantContext->identifier();
$this->rulesCache = $tenantId
? $this->store->listRules($tenantId, true)
: [];
+4 -4
View File
@@ -7,7 +7,7 @@ namespace KTXC\Service;
use KTXC\Http\Request\Request;
use KTXC\Models\Identity\User;
use KTXC\Resource\ProviderManager;
use KTXC\SessionTenant;
use KTXC\Context\TenantContextInterface;
use KTXF\Security\Authentication\AuthenticationProviderInterface;
/**
@@ -23,12 +23,12 @@ class SecurityService
private string $securityCode;
public function __construct(
private readonly SessionTenant $sessionTenant,
private readonly TenantContextInterface $tenantContext,
private readonly TokenService $tokenService,
private readonly UserAccountsService $userService,
private readonly ProviderManager $providerManager,
) {
$this->securityCode = $this->sessionTenant->configuration()->security()->code();
$this->securityCode = $this->tenantContext->configuration()->security()->code();
}
/**
@@ -118,7 +118,7 @@ class SecurityService
continue;
}
$context = new \KTXF\Security\Authentication\ProviderContext(
tenantId: $this->sessionTenant->identifier(),
tenantId: $this->tenantContext->identifier(),
userIdentity: $identity,
);
$result = $provider->verify($context, $credentials);
+2 -2
View File
@@ -2,7 +2,7 @@
namespace KTXC\Service;
use KTXC\SessionTenant;
use KTXC\Context\TenantContextInterface;
use KTXF\Cache\CacheScope;
use KTXF\Cache\EphemeralCacheInterface;
@@ -26,7 +26,7 @@ class TokenService
private string $algorithm = 'HS256';
public function __construct(
private readonly SessionTenant $sessionTenant,
private readonly TenantContextInterface $tenantContext,
private readonly EphemeralCacheInterface $cache,
) {
}
+16 -16
View File
@@ -3,16 +3,16 @@
namespace KTXC\Service;
use KTXC\Models\Identity\User;
use KTXC\SessionIdentity;
use KTXC\SessionTenant;
use KTXC\Context\IdentityContextInterface;
use KTXC\Context\TenantContextInterface;
use KTXC\Stores\UserAccountsStore;
class UserAccountsService
{
public function __construct(
private readonly SessionTenant $tenantIdentity,
private readonly SessionIdentity $userIdentity,
private readonly TenantContextInterface $tenantContext,
private readonly IdentityContextInterface $identityContext,
private readonly UserAccountsStore $userStore
) {
}
@@ -26,7 +26,7 @@ class UserAccountsService
*/
public function listUsers(array $filters = []): array
{
$users = $this->userStore->listUsers($this->tenantIdentity->identifier(), $filters);
$users = $this->userStore->listUsers($this->tenantContext->identifier(), $filters);
// Remove sensitive data
foreach ($users as &$user) {
@@ -38,7 +38,7 @@ class UserAccountsService
public function fetchByIdentity(string $identifier): User | null
{
$data = $this->userStore->fetchByIdentity($this->tenantIdentity->identifier(), $identifier);
$data = $this->userStore->fetchByIdentity($this->tenantContext->identifier(), $identifier);
if (!$data) {
return null;
}
@@ -50,32 +50,32 @@ class UserAccountsService
public function fetchByIdentifier(string $identifier): array | null
{
return $this->userStore->fetchByIdentifier($this->tenantIdentity->identifier(), $identifier);
return $this->userStore->fetchByIdentifier($this->tenantContext->identifier(), $identifier);
}
public function fetchByIdentityRaw(string $identifier): array | null
{
return $this->userStore->fetchByIdentity($this->tenantIdentity->identifier(), $identifier);
return $this->userStore->fetchByIdentity($this->tenantContext->identifier(), $identifier);
}
public function fetchByProviderSubject(string $provider, string $subject): ?array
{
return $this->userStore->fetchByProviderSubject($this->tenantIdentity->identifier(), $provider, $subject);
return $this->userStore->fetchByProviderSubject($this->tenantContext->identifier(), $provider, $subject);
}
public function createUser(array $userData): array
{
return $this->userStore->createUser($this->tenantIdentity->identifier(), $userData);
return $this->userStore->createUser($this->tenantContext->identifier(), $userData);
}
public function updateUser(string $uid, array $updates): bool
{
return $this->userStore->updateUser($this->tenantIdentity->identifier(), $uid, $updates);
return $this->userStore->updateUser($this->tenantContext->identifier(), $uid, $updates);
}
public function deleteUser(string $uid): bool
{
return $this->userStore->deleteUser($this->tenantIdentity->identifier(), $uid);
return $this->userStore->deleteUser($this->tenantContext->identifier(), $uid);
}
// =========================================================================
@@ -84,7 +84,7 @@ class UserAccountsService
public function fetchProfile(string $uid): ?array
{
return $this->userStore->fetchProfile($this->tenantIdentity->identifier(), $uid);
return $this->userStore->fetchProfile($this->tenantContext->identifier(), $uid);
}
public function storeProfile(string $uid, array $profileFields): bool
@@ -109,7 +109,7 @@ class UserAccountsService
return false;
}
return $this->userStore->storeProfile($this->tenantIdentity->identifier(), $uid, $editableFields);
return $this->userStore->storeProfile($this->tenantContext->identifier(), $uid, $editableFields);
}
// =========================================================================
@@ -118,12 +118,12 @@ class UserAccountsService
public function fetchSettings(array $settings = [], bool $flatten = false): array | null
{
return $this->userStore->fetchSettings($this->tenantIdentity->identifier(), $this->userIdentity->identifier(), $settings, $flatten);
return $this->userStore->fetchSettings($this->tenantContext->identifier(), $this->identityContext->identifier(), $settings, $flatten);
}
public function storeSettings(array $settings): bool
{
return $this->userStore->storeSettings($this->tenantIdentity->identifier(), $this->userIdentity->identifier(), $settings);
return $this->userStore->storeSettings($this->tenantContext->identifier(), $this->identityContext->identifier(), $settings);
}
// =========================================================================
+12 -12
View File
@@ -2,7 +2,7 @@
namespace KTXC\Service;
use KTXC\SessionTenant;
use KTXC\Context\TenantContextInterface;
use KTXC\Stores\UserRolesStore;
use Psr\Log\LoggerInterface;
@@ -12,7 +12,7 @@ use Psr\Log\LoggerInterface;
class UserRolesService
{
public function __construct(
private readonly SessionTenant $tenantIdentity,
private readonly TenantContextInterface $tenantContext,
private readonly UserRolesStore $roleStore,
private readonly LoggerInterface $logger
) {}
@@ -26,7 +26,7 @@ class UserRolesService
*/
public function listRoles(): array
{
return $this->roleStore->listRoles($this->tenantIdentity->identifier());
return $this->roleStore->listRoles($this->tenantContext->identifier());
}
/**
@@ -34,7 +34,7 @@ class UserRolesService
*/
public function getRole(string $rid): ?array
{
return $this->roleStore->fetchByRid($this->tenantIdentity->identifier(), $rid);
return $this->roleStore->fetchByRid($this->tenantContext->identifier(), $rid);
}
/**
@@ -45,11 +45,11 @@ class UserRolesService
$this->validateRoleData($roleData);
$this->logger->info('Creating role', [
'tenant' => $this->tenantIdentity->identifier(),
'tenant' => $this->tenantContext->identifier(),
'label' => $roleData['label'] ?? 'Unnamed'
]);
return $this->roleStore->createRole($this->tenantIdentity->identifier(), $roleData);
return $this->roleStore->createRole($this->tenantContext->identifier(), $roleData);
}
/**
@@ -70,11 +70,11 @@ class UserRolesService
$this->validateRoleData($updates, false);
$this->logger->info('Updating role', [
'tenant' => $this->tenantIdentity->identifier(),
'tenant' => $this->tenantContext->identifier(),
'rid' => $rid
]);
return $this->roleStore->updateRole($this->tenantIdentity->identifier(), $rid, $updates);
return $this->roleStore->updateRole($this->tenantContext->identifier(), $rid, $updates);
}
/**
@@ -93,17 +93,17 @@ class UserRolesService
}
// Check if role is assigned to users
$userCount = $this->roleStore->countUsersInRole($this->tenantIdentity->identifier(), $rid);
$userCount = $this->roleStore->countUsersInRole($this->tenantContext->identifier(), $rid);
if ($userCount > 0) {
throw new \InvalidArgumentException("Cannot delete role assigned to {$userCount} user(s)");
}
$this->logger->info('Deleting role', [
'tenant' => $this->tenantIdentity->identifier(),
'tenant' => $this->tenantContext->identifier(),
'rid' => $rid
]);
return $this->roleStore->deleteRole($this->tenantIdentity->identifier(), $rid);
return $this->roleStore->deleteRole($this->tenantContext->identifier(), $rid);
}
/**
@@ -111,7 +111,7 @@ class UserRolesService
*/
public function getRoleUserCount(string $rid): int
{
return $this->roleStore->countUsersInRole($this->tenantIdentity->identifier(), $rid);
return $this->roleStore->countUsersInRole($this->tenantContext->identifier(), $rid);
}
/**