false, 'message' => 'Please verify your email first', 'flash_key' => 'login_not_verified', 'needs_verification' => true, 'email' => $email, ]; } $user = Auth::login($email, $password); if (!$user) { return [ 'ok' => false, 'message' => 'Email/password not valid', 'flash_key' => 'login_invalid', ]; } if (!($_SESSION['user']['active'] ?? 1)) { Auth::logout(); return [ 'ok' => false, 'message' => 'Account is inactive', 'flash_key' => 'login_inactive', ]; } $userId = (int) ($_SESSION['user']['id'] ?? 0); if ($userId > 0 && !self::isLocalPasswordLoginAllowedForUser($userId)) { Auth::logout(); return [ 'ok' => false, 'message' => 'Password login is disabled for all assigned tenants', 'flash_key' => 'login_password_disabled_all_tenants', ]; } if ($userId > 0) { PermissionService::getUserPermissions($userId, true); self::loadTenantDataIntoSession($userId); if (!empty($_SESSION['no_active_tenant'])) { Auth::logout(); return [ 'ok' => false, 'message' => 'No active tenant assigned', 'flash_key' => 'login_no_active_tenant', ]; } UserRepository::updateLastLogin($userId, 'local'); } return ['ok' => true]; } private static function isLocalPasswordLoginAllowedForUser(int $userId): bool { if ($userId <= 0) { return false; } $availableTenants = UserService::getAvailableTenants($userId); if (!$availableTenants) { return true; } foreach ($availableTenants as $tenant) { $tenantId = (int) ($tenant['id'] ?? 0); if ($tenantId <= 0) { continue; } if (TenantSsoService::isLocalPasswordLoginAllowed($tenantId)) { return true; } } return false; } public static function canLoginToTenant(int $userId, int $tenantId): bool { if ($userId <= 0 || $tenantId <= 0) { return false; } foreach (UserService::getAvailableTenants($userId) as $tenant) { if ((int) ($tenant['id'] ?? 0) === $tenantId) { return true; } } return false; } public static function loginUserById(int $userId, ?int $preferredTenantId = null, string $loginProvider = 'local'): array { if ($userId <= 0) { return ['ok' => false, 'error' => 'user_not_found']; } $user = UserRepository::find($userId); if (!$user) { return ['ok' => false, 'error' => 'user_not_found']; } if (!((int) ($user['active'] ?? 1) === 1)) { return ['ok' => false, 'error' => 'user_inactive']; } Session::regenerate(); $_SESSION['user'] = $user; if ($preferredTenantId !== null && $preferredTenantId > 0) { $setTenant = UserService::setCurrentTenant($userId, $preferredTenantId); if ($setTenant['ok'] ?? false) { $_SESSION['user']['current_tenant_id'] = $preferredTenantId; } } PermissionService::getUserPermissions($userId, true); self::loadTenantDataIntoSession($userId); if (!empty($_SESSION['no_active_tenant'])) { Auth::logout(); return ['ok' => false, 'error' => 'login_no_active_tenant']; } UserRepository::updateLastLogin($userId, $loginProvider); return ['ok' => true, 'user' => $user]; } public static function loginAndRedirect( string $email, string $password, string $successTarget = 'admin', string $failTarget = 'login', bool $remember = false ): void { $result = self::login($email, $password); if (!($result['ok'] ?? false)) { // Check if user needs to verify email if (!empty($result['needs_verification'])) { $_SESSION['email_verification_email'] = $result['email'] ?? $email; Flash::error(t('Please verify your email first'), 'verify-email', 'login_not_verified'); Router::redirect('verify-email'); } $message = $result['message'] ?? 'Email/password not valid'; $key = $result['flash_key'] ?? 'login_invalid'; Flash::error($message, $failTarget, $key); Router::redirect($failTarget); } if ($remember) { $userId = (int) ($_SESSION['user']['id'] ?? 0); if ($userId > 0) { RememberMeService::rememberUser($userId); } } Router::redirect($successTarget); } public static function register(array $data): array { $email = trim((string) ($data['email'] ?? '')); $result = UserService::register($data); if (!($result['ok'] ?? false)) { return $result; } // Get the created user to send verification email $createdUser = UserRepository::findByEmail($email); if ($createdUser && isset($createdUser['id'])) { $userId = (int) $createdUser['id']; EmailVerificationService::sendVerification($userId); } // Don't login - user needs to verify email first return ['ok' => true, 'email' => $email, 'needs_verification' => true]; } public static function logout(): void { RememberMeService::forgetCurrentUser(); Auth::logout(); } public static function logoutAndRedirect(string $target = 'login'): void { RememberMeService::forgetCurrentUser(); Auth::logout(); Router::redirect($target); } public static function refreshSessionAuthState(int $userId): array { if ($userId <= 0) { return [ 'ok' => false, 'logout_required' => true, 'reason' => 'user_missing', ]; } $snapshot = UserRepository::findAuthzSnapshot($userId); if (!$snapshot) { return [ 'ok' => false, 'logout_required' => true, 'reason' => 'user_not_found', ]; } if ((int) ($snapshot['active'] ?? 0) !== 1) { return [ 'ok' => false, 'logout_required' => true, 'reason' => 'inactive', ]; } $sessionVersion = (int) ($_SESSION['user']['authz_version'] ?? 0); $dbVersion = max(1, (int) ($snapshot['authz_version'] ?? 1)); $refreshed = false; if ($sessionVersion !== $dbVersion) { $user = UserRepository::find($userId); if (!$user || (int) ($user['active'] ?? 0) !== 1) { return [ 'ok' => false, 'logout_required' => true, 'reason' => 'inactive', ]; } $_SESSION['user'] = $user; PermissionService::getUserPermissions($userId, true); self::loadTenantDataIntoSession($userId); $refreshed = true; } if (!empty($_SESSION['no_active_tenant'])) { return [ 'ok' => false, 'logout_required' => true, 'reason' => 'no_active_tenant', 'refreshed' => $refreshed, ]; } return [ 'ok' => true, 'logout_required' => false, 'refreshed' => $refreshed, ]; } public static function loadTenantDataIntoSession(int $userId): void { if ($userId <= 0) { return; } // Load available tenants first $availableTenants = UserService::getAvailableTenants($userId); $_SESSION['available_tenants'] = $availableTenants; $_SESSION['available_departments_by_tenant'] = UserService::getAvailableDepartmentsByTenant($userId); $_SESSION['address_book_saved_filters'] = UserSavedFilterService::listForAddressBook($userId); if (!$availableTenants) { $_SESSION['no_active_tenant'] = true; unset($_SESSION['current_tenant']); return; } $_SESSION['no_active_tenant'] = false; // Load current tenant (with fallback to first available) $currentTenant = UserService::getCurrentTenant($userId); if (!$currentTenant) { // Fallback: use first available tenant $currentTenant = $availableTenants[0]; } if ($currentTenant) { $_SESSION['current_tenant'] = $currentTenant; } } }