agent foundation
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace MintyPHP\Service\Auth;
|
||||
|
||||
use MintyPHP\Auth;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
||||
use MintyPHP\Router;
|
||||
@@ -23,8 +24,9 @@ class AuthService
|
||||
private readonly EmailVerificationService $emailVerificationService,
|
||||
private readonly AuthPermissionGateway $permissionGateway,
|
||||
private readonly AuthTenantSsoGateway $tenantSsoGateway,
|
||||
private readonly AuthSavedFilterGateway $savedFilterGateway,
|
||||
private readonly SystemAuditService $systemAuditService
|
||||
private readonly AuthSessionTenantContextService $authSessionTenantContextService,
|
||||
private readonly SystemAuditService $systemAuditService,
|
||||
private readonly SessionStoreInterface $sessionStore
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -62,7 +64,8 @@ class AuthService
|
||||
];
|
||||
}
|
||||
|
||||
if (!($_SESSION['user']['active'] ?? 1)) {
|
||||
$sessionUser = $this->sessionUser();
|
||||
if (!($sessionUser['active'] ?? 1)) {
|
||||
Auth::logout();
|
||||
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
||||
'error_code' => 'account_inactive',
|
||||
@@ -75,7 +78,7 @@ class AuthService
|
||||
];
|
||||
}
|
||||
|
||||
$userId = (int) ($_SESSION['user']['id'] ?? 0);
|
||||
$userId = (int) ($sessionUser['id'] ?? 0);
|
||||
if ($userId > 0 && !$this->isLocalPasswordLoginAllowedForUser($userId)) {
|
||||
Auth::logout();
|
||||
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
||||
@@ -91,7 +94,7 @@ class AuthService
|
||||
if ($userId > 0) {
|
||||
$this->permissionGateway->warmUserPermissions($userId, true);
|
||||
$this->loadTenantDataIntoSession($userId);
|
||||
if (!empty($_SESSION['no_active_tenant'])) {
|
||||
if ($this->noActiveTenant()) {
|
||||
Auth::logout();
|
||||
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
||||
'error_code' => 'no_active_tenant',
|
||||
@@ -108,12 +111,14 @@ class AuthService
|
||||
|
||||
$this->recordAuthEvent('auth.login.success', 'success', [
|
||||
'actor_user_id' => $userId > 0 ? $userId : null,
|
||||
'actor_tenant_id' => (int) ($_SESSION['current_tenant']['id'] ?? 0),
|
||||
'actor_tenant_id' => $this->currentTenantIdFromSession(),
|
||||
]);
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
// Returns true if at least ONE assigned tenant permits local login.
|
||||
// A user is only blocked if ALL tenants enforce Microsoft-only login.
|
||||
private function isLocalPasswordLoginAllowedForUser(int $userId): bool
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
@@ -168,18 +173,20 @@ class AuthService
|
||||
}
|
||||
|
||||
Session::regenerate();
|
||||
$_SESSION['user'] = $user;
|
||||
$this->sessionStore->set('user', $user);
|
||||
|
||||
if ($preferredTenantId !== null && $preferredTenantId > 0) {
|
||||
$setTenant = $this->userTenantContextService->setCurrentTenant($userId, $preferredTenantId);
|
||||
if ($setTenant['ok'] ?? false) {
|
||||
$_SESSION['user']['current_tenant_id'] = $preferredTenantId;
|
||||
$sessionUser = $this->sessionUser();
|
||||
$sessionUser['current_tenant_id'] = $preferredTenantId;
|
||||
$this->sessionStore->set('user', $sessionUser);
|
||||
}
|
||||
}
|
||||
|
||||
$this->permissionGateway->warmUserPermissions($userId, true);
|
||||
$this->loadTenantDataIntoSession($userId);
|
||||
if (!empty($_SESSION['no_active_tenant'])) {
|
||||
if ($this->noActiveTenant()) {
|
||||
Auth::logout();
|
||||
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
||||
'error_code' => 'login_no_active_tenant',
|
||||
@@ -191,7 +198,7 @@ class AuthService
|
||||
$this->userWriteRepository->updateLastLogin($userId, $loginProvider);
|
||||
$this->recordAuthEvent('auth.login.success', 'success', [
|
||||
'actor_user_id' => $userId,
|
||||
'actor_tenant_id' => (int) ($_SESSION['current_tenant']['id'] ?? 0),
|
||||
'actor_tenant_id' => $this->currentTenantIdFromSession(),
|
||||
'metadata' => ['provider' => $loginProvider],
|
||||
]);
|
||||
return ['ok' => true, 'user' => $user];
|
||||
@@ -209,7 +216,7 @@ class AuthService
|
||||
if (!($result['ok'] ?? false)) {
|
||||
// Check if user needs to verify email
|
||||
if (!empty($result['needs_verification'])) {
|
||||
$_SESSION['email_verification_email'] = $result['email'] ?? $email;
|
||||
$this->sessionStore->set('email_verification_email', $result['email'] ?? $email);
|
||||
Flash::error(t('Please verify your email first'), 'verify-email', 'login_not_verified');
|
||||
Router::redirect('verify-email');
|
||||
}
|
||||
@@ -221,7 +228,7 @@ class AuthService
|
||||
}
|
||||
|
||||
if ($remember) {
|
||||
$userId = (int) ($_SESSION['user']['id'] ?? 0);
|
||||
$userId = (int) ($this->sessionUser()['id'] ?? 0);
|
||||
if ($userId > 0) {
|
||||
$this->rememberMeService->rememberUser($userId);
|
||||
}
|
||||
@@ -250,8 +257,8 @@ class AuthService
|
||||
|
||||
public function logout(): void
|
||||
{
|
||||
$actorUserId = (int) ($_SESSION['user']['id'] ?? 0);
|
||||
$actorTenantId = (int) ($_SESSION['current_tenant']['id'] ?? 0);
|
||||
$actorUserId = (int) ($this->sessionUser()['id'] ?? 0);
|
||||
$actorTenantId = $this->currentTenantIdFromSession();
|
||||
$this->rememberMeService->forgetCurrentUser();
|
||||
Auth::logout();
|
||||
$this->recordAuthEvent('auth.logout', 'success', [
|
||||
@@ -298,7 +305,9 @@ class AuthService
|
||||
];
|
||||
}
|
||||
|
||||
$sessionVersion = (int) ($_SESSION['user']['authz_version'] ?? 0);
|
||||
// authz_version is bumped in DB whenever permissions/tenants change.
|
||||
// A mismatch means the session is stale and needs to be refreshed.
|
||||
$sessionVersion = (int) ($this->sessionUser()['authz_version'] ?? 0);
|
||||
$dbVersion = max(1, (int) ($snapshot['authz_version'] ?? 1));
|
||||
$refreshed = false;
|
||||
|
||||
@@ -312,13 +321,13 @@ class AuthService
|
||||
];
|
||||
}
|
||||
|
||||
$_SESSION['user'] = $user;
|
||||
$this->sessionStore->set('user', $user);
|
||||
$this->permissionGateway->warmUserPermissions($userId, true);
|
||||
$this->loadTenantDataIntoSession($userId);
|
||||
$refreshed = true;
|
||||
}
|
||||
|
||||
if (!empty($_SESSION['no_active_tenant'])) {
|
||||
if ($this->noActiveTenant()) {
|
||||
return [
|
||||
'ok' => false,
|
||||
'logout_required' => true,
|
||||
@@ -336,31 +345,34 @@ class AuthService
|
||||
|
||||
public function loadTenantDataIntoSession(int $userId): void
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return;
|
||||
}
|
||||
$this->authSessionTenantContextService->hydrateForUser($userId);
|
||||
}
|
||||
|
||||
// Load available tenants first
|
||||
$availableTenants = $this->userTenantContextService->getAvailableTenants($userId);
|
||||
$_SESSION['available_tenants'] = $availableTenants;
|
||||
$_SESSION['available_departments_by_tenant'] = $this->userTenantContextService->getAvailableDepartmentsByTenant($userId);
|
||||
$_SESSION['address_book_saved_filters'] = $this->savedFilterGateway->listAddressBookFilters($userId);
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sessionUser(): array
|
||||
{
|
||||
$user = $this->sessionStore->get('user', []);
|
||||
return is_array($user) ? $user : [];
|
||||
}
|
||||
|
||||
if (!$availableTenants) {
|
||||
$_SESSION['no_active_tenant'] = true;
|
||||
unset($_SESSION['current_tenant']);
|
||||
return;
|
||||
}
|
||||
$_SESSION['no_active_tenant'] = false;
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sessionCurrentTenant(): array
|
||||
{
|
||||
$tenant = $this->sessionStore->get('current_tenant', []);
|
||||
return is_array($tenant) ? $tenant : [];
|
||||
}
|
||||
|
||||
// Load current tenant (with fallback to first available)
|
||||
$currentTenant = $this->userTenantContextService->getCurrentTenant($userId);
|
||||
if (!$currentTenant) {
|
||||
// Fallback: use first available tenant
|
||||
$currentTenant = $availableTenants[0];
|
||||
}
|
||||
if ($currentTenant) {
|
||||
$_SESSION['current_tenant'] = $currentTenant;
|
||||
}
|
||||
private function currentTenantIdFromSession(): int
|
||||
{
|
||||
return (int) ($this->sessionCurrentTenant()['id'] ?? 0);
|
||||
}
|
||||
|
||||
private function noActiveTenant(): bool
|
||||
{
|
||||
return (bool) $this->sessionStore->get('no_active_tenant', false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user