1
0
Files
breadcrumb-the-shire/lib/Http/ApiAuth.php
2026-02-23 12:58:19 +01:00

189 lines
5.8 KiB
PHP

<?php
namespace MintyPHP\Http;
use MintyPHP\Service\Access\AccessServicesFactory;
use MintyPHP\Service\Auth\AuthScopeGateway;
use MintyPHP\Service\Auth\AuthServicesFactory;
use MintyPHP\Service\User\UserServicesFactory;
class ApiAuth
{
private static ?array $currentUser = null;
private static ?array $currentPermissions = null;
private static ?int $currentTenantId = null;
private static ?int $tokenTenantId = null;
private static ?array $currentTokenRecord = null;
private static bool $authenticated = false;
/**
* Extract the full Bearer token from the Authorization header.
*/
public static function extractBearerToken(): string
{
$header = trim((string) ($_SERVER['HTTP_AUTHORIZATION'] ?? ''));
if ($header === '') {
$header = trim((string) ($_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? ''));
}
if ($header === '' || stripos($header, 'Bearer ') !== 0) {
return '';
}
return trim(substr($header, 7));
}
/**
* Attempt to authenticate from Authorization: Bearer header.
*/
public static function authenticate(): bool
{
self::$currentUser = null;
self::$currentPermissions = null;
self::$currentTenantId = null;
self::$tokenTenantId = null;
self::$currentTokenRecord = null;
self::$authenticated = false;
$bearerToken = self::extractBearerToken();
if ($bearerToken === '') {
return false;
}
$authServicesFactory = new AuthServicesFactory();
$scopeGateway = $authServicesFactory->createAuthScopeGateway();
$result = $authServicesFactory->createApiTokenService()->validate($bearerToken);
if ($result === null) {
return false;
}
$user = $result['user'];
$userId = (int) ($user['id'] ?? 0);
$userServicesFactory = new UserServicesFactory();
$userRoleRepository = $userServicesFactory->createUserRoleRepository();
$rolePermissionRepository = (new AccessServicesFactory())->createRolePermissionRepository();
$userTenantContextService = $userServicesFactory->createUserTenantContextService();
// Load permissions directly (bypass session cache)
$roleIds = $userRoleRepository->listRoleIdsByUserId($userId);
$permissions = $rolePermissionRepository->listPermissionKeysByRoleIds($roleIds);
// Resolve tenant context
$tokenTenantId = $result['tenant_id'];
if ($tokenTenantId !== null) {
$userTenantIds = $scopeGateway->getUserTenantIds($userId);
if (!in_array($tokenTenantId, $userTenantIds, true)) {
return false;
}
$currentTenantId = $tokenTenantId;
self::$tokenTenantId = $tokenTenantId;
} else {
$currentTenantId = $userTenantContextService->getCurrentTenantId($userId);
self::$tokenTenantId = null;
}
self::$currentUser = $user;
self::$currentPermissions = $permissions;
self::$currentTenantId = $currentTenantId;
self::$currentTokenRecord = $result['token_record'];
self::$authenticated = true;
return true;
}
public static function isAuthenticated(): bool
{
return self::$authenticated;
}
public static function user(): ?array
{
return self::$currentUser;
}
public static function userId(): int
{
return (int) (self::$currentUser['id'] ?? 0);
}
public static function permissions(): array
{
return self::$currentPermissions ?? [];
}
public static function hasPermission(string $key): bool
{
return in_array($key, self::$currentPermissions ?? [], true);
}
public static function tenantId(): ?int
{
return self::$currentTenantId;
}
public static function scopedTenantId(): ?int
{
return self::$tokenTenantId;
}
public static function tokenRecord(): ?array
{
return self::$currentTokenRecord;
}
public static function tokenId(): ?int
{
$id = (int) (self::$currentTokenRecord['id'] ?? 0);
return $id > 0 ? $id : null;
}
public static function isTenantScopedToken(): bool
{
return (self::$tokenTenantId ?? 0) > 0;
}
/**
* Require both tenant-scope and token-tenant access for a resource.
*/
public static function requireResourceAccess(string $resource, int $resourceId): void
{
$scopeGateway = (new AuthServicesFactory())->createAuthScopeGateway();
if (!$scopeGateway->canAccess($resource, $resourceId, self::userId())) {
ApiResponse::notFound();
}
self::requireTokenTenantAccess($resource, $resourceId);
}
public static function requireTokenTenantAccess(string $resource, int $resourceId): void
{
if (!self::isTenantScopedToken()) {
return;
}
$tenantId = (int) (self::$tokenTenantId ?? 0);
if ($tenantId <= 0) {
ApiResponse::forbidden();
}
$scopeGateway = (new AuthServicesFactory())->createAuthScopeGateway();
if (!$scopeGateway->resourceBelongsToTenant($resource, $resourceId, $tenantId)) {
ApiResponse::notFound();
}
}
/**
* Check if the current user can self-manage API tokens.
*/
public static function canSelfManageTokens(): bool
{
return self::hasPermission(\MintyPHP\Service\Access\PermissionService::USERS_SELF_UPDATE)
|| self::hasPermission(\MintyPHP\Service\Access\PermissionService::API_TOKENS_MANAGE);
}
public static function requireSelfManageTokens(): void
{
if (!self::canSelfManageTokens()) {
ApiResponse::forbidden('api_tokens_self_manage_forbidden');
}
}
}