Initial Version
This commit is contained in:
145
core/lib/Service/SecurityService.php
Normal file
145
core/lib/Service/SecurityService.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXC\Service;
|
||||
|
||||
use KTXC\Http\Request\Request;
|
||||
use KTXC\Models\Identity\User;
|
||||
use KTXC\Resource\ProviderManager;
|
||||
use KTXC\SessionTenant;
|
||||
use KTXF\Security\Authentication\AuthenticationProviderInterface;
|
||||
|
||||
/**
|
||||
* Security Service
|
||||
*
|
||||
* Handles request-level authentication (token validation).
|
||||
* Authentication orchestration is handled by AuthenticationManager.
|
||||
*
|
||||
* This service is used by the Kernel to authenticate incoming requests.
|
||||
*/
|
||||
class SecurityService
|
||||
{
|
||||
private string $securityCode;
|
||||
|
||||
public function __construct(
|
||||
private readonly SessionTenant $sessionTenant,
|
||||
private readonly TokenService $tokenService,
|
||||
private readonly UserAccountsService $userService,
|
||||
private readonly ProviderManager $providerManager,
|
||||
) {
|
||||
$this->securityCode = $this->sessionTenant->configuration()->security()->code();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a request and return the user if valid
|
||||
*
|
||||
* @param Request $request The HTTP request to authenticate
|
||||
* @return User|null The authenticated user, or null if not authenticated
|
||||
*/
|
||||
public function authenticate(Request $request): ?User
|
||||
{
|
||||
$authorization = $request->headers->get('Authorization');
|
||||
$cookieToken = $request->cookies->get('accessToken');
|
||||
|
||||
// Cookie token takes precedence
|
||||
if ($cookieToken) {
|
||||
return $this->authenticateJWT($cookieToken);
|
||||
}
|
||||
|
||||
if ($authorization) {
|
||||
if (str_starts_with($authorization, 'Bearer ')) {
|
||||
$token = substr($authorization, 7);
|
||||
return $this->authenticateBearer($token);
|
||||
}
|
||||
|
||||
if (str_starts_with($authorization, 'Basic ')) {
|
||||
$decoded = base64_decode(substr($authorization, 6) ?: '', true);
|
||||
if ($decoded !== false) {
|
||||
[$identity, $secret] = array_pad(explode(':', $decoded, 2), 2, null);
|
||||
if ($identity !== null && $secret !== null) {
|
||||
return $this->authenticateBasic($identity, $secret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate JWT token from cookie or header
|
||||
*/
|
||||
public function authenticateJWT(string $token): ?User
|
||||
{
|
||||
$payload = $this->tokenService->validateToken($token, $this->securityCode);
|
||||
|
||||
if (!$payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify user still exists
|
||||
if ($this->userService->fetchByIdentifier($payload['identifier']) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$user->populate($payload, 'jwt');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate Bearer token
|
||||
*/
|
||||
public function authenticateBearer(string $token): ?User
|
||||
{
|
||||
return $this->authenticateJWT($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate HTTP Basic header (for API access)
|
||||
* Note: This is for request authentication, not login
|
||||
*/
|
||||
private function authenticateBasic(string $identity, string $credentials): ?User
|
||||
{
|
||||
// For Basic auth headers, we need to validate against the provider
|
||||
// This is a simplified flow for API access
|
||||
$providers = $this->providerManager->providers(AuthenticationProviderInterface::TYPE_AUTHENTICATION);
|
||||
if ($providers === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
if ($provider instanceof AuthenticationProviderInterface === false) {
|
||||
continue;
|
||||
}
|
||||
if ($provider->method() !== AuthenticationProviderInterface::METHOD_CREDENTIAL) {
|
||||
continue;
|
||||
}
|
||||
$context = new \KTXF\Security\Authentication\ProviderContext(
|
||||
tenantId: $this->sessionTenant->identifier(),
|
||||
userIdentity: $identity,
|
||||
);
|
||||
$result = $provider->verify($context, $credentials);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($result) && $result->isSuccess()) {
|
||||
return $this->userService->fetchByIdentity($identity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract token claims (for logout to get jti/exp)
|
||||
*/
|
||||
public function extractTokenClaims(string $token): ?array
|
||||
{
|
||||
return $this->tokenService->validateToken($token, $this->securityCode, checkBlacklist: false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user