2026-02-04 23:31:53 +01:00
|
|
|
<?php
|
|
|
|
|
|
2026-02-11 19:28:12 +01:00
|
|
|
namespace MintyPHP\Service\Auth;
|
2026-02-04 23:31:53 +01:00
|
|
|
|
|
|
|
|
use MintyPHP\Auth;
|
2026-03-05 08:26:51 +01:00
|
|
|
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
|
|
|
|
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
2026-02-04 23:31:53 +01:00
|
|
|
use MintyPHP\Router;
|
2026-03-05 11:17:42 +01:00
|
|
|
use MintyPHP\Service\Audit\SystemAuditService;
|
2026-02-23 12:58:19 +01:00
|
|
|
use MintyPHP\Service\User\UserAccountService;
|
|
|
|
|
use MintyPHP\Service\User\UserTenantContextService;
|
|
|
|
|
use MintyPHP\Session;
|
2026-02-04 23:31:53 +01:00
|
|
|
use MintyPHP\Support\Flash;
|
|
|
|
|
|
|
|
|
|
class AuthService
|
|
|
|
|
{
|
2026-02-23 12:58:19 +01:00
|
|
|
public function __construct(
|
2026-03-05 08:26:51 +01:00
|
|
|
private readonly UserReadRepositoryInterface $userReadRepository,
|
|
|
|
|
private readonly UserWriteRepositoryInterface $userWriteRepository,
|
2026-02-23 12:58:19 +01:00
|
|
|
private readonly UserAccountService $userAccountService,
|
|
|
|
|
private readonly UserTenantContextService $userTenantContextService,
|
|
|
|
|
private readonly RememberMeService $rememberMeService,
|
|
|
|
|
private readonly EmailVerificationService $emailVerificationService,
|
|
|
|
|
private readonly AuthPermissionGateway $permissionGateway,
|
|
|
|
|
private readonly AuthTenantSsoGateway $tenantSsoGateway,
|
2026-03-04 15:56:58 +01:00
|
|
|
private readonly AuthSavedFilterGateway $savedFilterGateway,
|
|
|
|
|
private readonly SystemAuditService $systemAuditService
|
2026-02-23 12:58:19 +01:00
|
|
|
) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function login(string $email, string $password): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
|
|
|
|
$email = trim($email);
|
|
|
|
|
|
|
|
|
|
// Check if email is verified before attempting login
|
2026-02-23 12:58:19 +01:00
|
|
|
$existingUser = $this->userReadRepository->findByEmail($email);
|
2026-02-04 23:31:53 +01:00
|
|
|
if ($existingUser && empty($existingUser['email_verified_at'])) {
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
|
|
|
|
'error_code' => 'email_not_verified',
|
|
|
|
|
'metadata' => ['email_hash' => \MintyPHP\Http\RequestContext::hashValue(strtolower($email))],
|
|
|
|
|
]);
|
2026-02-04 23:31:53 +01:00
|
|
|
return [
|
|
|
|
|
'ok' => false,
|
|
|
|
|
'message' => 'Please verify your email first',
|
|
|
|
|
'flash_key' => 'login_not_verified',
|
|
|
|
|
'needs_verification' => true,
|
|
|
|
|
'email' => $email,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user = Auth::login($email, $password);
|
|
|
|
|
|
|
|
|
|
if (!$user) {
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
|
|
|
|
'error_code' => 'invalid_credentials',
|
|
|
|
|
'metadata' => ['email_hash' => \MintyPHP\Http\RequestContext::hashValue(strtolower($email))],
|
|
|
|
|
]);
|
2026-02-04 23:31:53 +01:00
|
|
|
return [
|
|
|
|
|
'ok' => false,
|
|
|
|
|
'message' => 'Email/password not valid',
|
|
|
|
|
'flash_key' => 'login_invalid',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!($_SESSION['user']['active'] ?? 1)) {
|
|
|
|
|
Auth::logout();
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
|
|
|
|
'error_code' => 'account_inactive',
|
|
|
|
|
'metadata' => ['email_hash' => \MintyPHP\Http\RequestContext::hashValue(strtolower($email))],
|
|
|
|
|
]);
|
2026-02-04 23:31:53 +01:00
|
|
|
return [
|
|
|
|
|
'ok' => false,
|
|
|
|
|
'message' => 'Account is inactive',
|
|
|
|
|
'flash_key' => 'login_inactive',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$userId = (int) ($_SESSION['user']['id'] ?? 0);
|
2026-02-23 12:58:19 +01:00
|
|
|
if ($userId > 0 && !$this->isLocalPasswordLoginAllowedForUser($userId)) {
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
Auth::logout();
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
|
|
|
|
'error_code' => 'password_login_disabled',
|
|
|
|
|
'metadata' => ['email_hash' => \MintyPHP\Http\RequestContext::hashValue(strtolower($email))],
|
|
|
|
|
]);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
return [
|
|
|
|
|
'ok' => false,
|
|
|
|
|
'message' => 'Password login is disabled for all assigned tenants',
|
|
|
|
|
'flash_key' => 'login_password_disabled_all_tenants',
|
|
|
|
|
];
|
|
|
|
|
}
|
2026-02-04 23:31:53 +01:00
|
|
|
if ($userId > 0) {
|
2026-02-23 12:58:19 +01:00
|
|
|
$this->permissionGateway->warmUserPermissions($userId, true);
|
|
|
|
|
$this->loadTenantDataIntoSession($userId);
|
2026-02-04 23:31:53 +01:00
|
|
|
if (!empty($_SESSION['no_active_tenant'])) {
|
|
|
|
|
Auth::logout();
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
|
|
|
|
'error_code' => 'no_active_tenant',
|
|
|
|
|
'metadata' => ['email_hash' => \MintyPHP\Http\RequestContext::hashValue(strtolower($email))],
|
|
|
|
|
]);
|
2026-02-04 23:31:53 +01:00
|
|
|
return [
|
|
|
|
|
'ok' => false,
|
|
|
|
|
'message' => 'No active tenant assigned',
|
|
|
|
|
'flash_key' => 'login_no_active_tenant',
|
|
|
|
|
];
|
|
|
|
|
}
|
2026-02-23 12:58:19 +01:00
|
|
|
$this->userWriteRepository->updateLastLogin($userId, 'local');
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.login.success', 'success', [
|
|
|
|
|
'actor_user_id' => $userId > 0 ? $userId : null,
|
|
|
|
|
'actor_tenant_id' => (int) ($_SESSION['current_tenant']['id'] ?? 0),
|
|
|
|
|
]);
|
|
|
|
|
|
2026-02-04 23:31:53 +01:00
|
|
|
return ['ok' => true];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
private function isLocalPasswordLoginAllowedForUser(int $userId): bool
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($userId <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
$availableTenants = $this->userTenantContextService->getAvailableTenants($userId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if (!$availableTenants) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($availableTenants as $tenant) {
|
|
|
|
|
$tenantId = (int) ($tenant['id'] ?? 0);
|
|
|
|
|
if ($tenantId <= 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-02-23 12:58:19 +01:00
|
|
|
if ($this->tenantSsoGateway->isLocalPasswordLoginAllowed($tenantId)) {
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function canLoginToTenant(int $userId, int $tenantId): bool
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($userId <= 0 || $tenantId <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
foreach ($this->userTenantContextService->getAvailableTenants($userId) as $tenant) {
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if ((int) ($tenant['id'] ?? 0) === $tenantId) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function loginUserById(int $userId, ?int $preferredTenantId = null, string $loginProvider = 'local'): array
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($userId <= 0) {
|
|
|
|
|
return ['ok' => false, 'error' => 'user_not_found'];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
$user = $this->userReadRepository->find($userId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
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) {
|
2026-02-23 12:58:19 +01:00
|
|
|
$setTenant = $this->userTenantContextService->setCurrentTenant($userId, $preferredTenantId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if ($setTenant['ok'] ?? false) {
|
|
|
|
|
$_SESSION['user']['current_tenant_id'] = $preferredTenantId;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
$this->permissionGateway->warmUserPermissions($userId, true);
|
|
|
|
|
$this->loadTenantDataIntoSession($userId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if (!empty($_SESSION['no_active_tenant'])) {
|
|
|
|
|
Auth::logout();
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.login.failed', 'failed', [
|
|
|
|
|
'error_code' => 'login_no_active_tenant',
|
|
|
|
|
'actor_user_id' => $userId,
|
|
|
|
|
]);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
return ['ok' => false, 'error' => 'login_no_active_tenant'];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
$this->userWriteRepository->updateLastLogin($userId, $loginProvider);
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.login.success', 'success', [
|
|
|
|
|
'actor_user_id' => $userId,
|
|
|
|
|
'actor_tenant_id' => (int) ($_SESSION['current_tenant']['id'] ?? 0),
|
|
|
|
|
'metadata' => ['provider' => $loginProvider],
|
|
|
|
|
]);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
return ['ok' => true, 'user' => $user];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function loginAndRedirect(
|
2026-02-04 23:31:53 +01:00
|
|
|
string $email,
|
|
|
|
|
string $password,
|
|
|
|
|
string $successTarget = 'admin',
|
|
|
|
|
string $failTarget = 'login',
|
|
|
|
|
bool $remember = false
|
|
|
|
|
): void {
|
2026-02-23 12:58:19 +01:00
|
|
|
$result = $this->login($email, $password);
|
2026-02-04 23:31:53 +01:00
|
|
|
|
|
|
|
|
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) {
|
2026-02-23 12:58:19 +01:00
|
|
|
$this->rememberMeService->rememberUser($userId);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Router::redirect($successTarget);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function register(array $data): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
|
|
|
|
$email = trim((string) ($data['email'] ?? ''));
|
2026-02-23 12:58:19 +01:00
|
|
|
$result = $this->userAccountService->register($data);
|
2026-02-04 23:31:53 +01:00
|
|
|
if (!($result['ok'] ?? false)) {
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the created user to send verification email
|
2026-02-23 12:58:19 +01:00
|
|
|
$createdUser = $this->userReadRepository->findByEmail($email);
|
2026-02-04 23:31:53 +01:00
|
|
|
if ($createdUser && isset($createdUser['id'])) {
|
|
|
|
|
$userId = (int) $createdUser['id'];
|
2026-02-23 12:58:19 +01:00
|
|
|
$this->emailVerificationService->sendVerification($userId);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't login - user needs to verify email first
|
|
|
|
|
return ['ok' => true, 'email' => $email, 'needs_verification' => true];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function logout(): void
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
$actorUserId = (int) ($_SESSION['user']['id'] ?? 0);
|
|
|
|
|
$actorTenantId = (int) ($_SESSION['current_tenant']['id'] ?? 0);
|
2026-02-23 12:58:19 +01:00
|
|
|
$this->rememberMeService->forgetCurrentUser();
|
2026-02-04 23:31:53 +01:00
|
|
|
Auth::logout();
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->recordAuthEvent('auth.logout', 'success', [
|
|
|
|
|
'actor_user_id' => $actorUserId > 0 ? $actorUserId : null,
|
|
|
|
|
'actor_tenant_id' => $actorTenantId > 0 ? $actorTenantId : null,
|
|
|
|
|
]);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function logoutAndRedirect(string $target = 'login'): void
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->logout();
|
2026-02-04 23:31:53 +01:00
|
|
|
Router::redirect($target);
|
|
|
|
|
}
|
2026-03-04 15:56:58 +01:00
|
|
|
|
|
|
|
|
private function recordAuthEvent(string $eventType, string $outcome, array $context = []): void
|
|
|
|
|
{
|
|
|
|
|
$this->systemAuditService->record($eventType, $outcome, $context);
|
|
|
|
|
}
|
2026-02-04 23:31:53 +01:00
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function refreshSessionAuthState(int $userId): array
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($userId <= 0) {
|
|
|
|
|
return [
|
|
|
|
|
'ok' => false,
|
|
|
|
|
'logout_required' => true,
|
|
|
|
|
'reason' => 'user_missing',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
$snapshot = $this->userReadRepository->findAuthzSnapshot($userId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
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) {
|
2026-02-23 12:58:19 +01:00
|
|
|
$user = $this->userReadRepository->find($userId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if (!$user || (int) ($user['active'] ?? 0) !== 1) {
|
|
|
|
|
return [
|
|
|
|
|
'ok' => false,
|
|
|
|
|
'logout_required' => true,
|
|
|
|
|
'reason' => 'inactive',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$_SESSION['user'] = $user;
|
2026-02-23 12:58:19 +01:00
|
|
|
$this->permissionGateway->warmUserPermissions($userId, true);
|
|
|
|
|
$this->loadTenantDataIntoSession($userId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
$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,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function loadTenantDataIntoSession(int $userId): void
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
|
|
|
|
if ($userId <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load available tenants first
|
2026-02-23 12:58:19 +01:00
|
|
|
$availableTenants = $this->userTenantContextService->getAvailableTenants($userId);
|
2026-02-04 23:31:53 +01:00
|
|
|
$_SESSION['available_tenants'] = $availableTenants;
|
2026-02-23 12:58:19 +01:00
|
|
|
$_SESSION['available_departments_by_tenant'] = $this->userTenantContextService->getAvailableDepartmentsByTenant($userId);
|
|
|
|
|
$_SESSION['address_book_saved_filters'] = $this->savedFilterGateway->listAddressBookFilters($userId);
|
2026-02-04 23:31:53 +01:00
|
|
|
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if (!$availableTenants) {
|
2026-02-04 23:31:53 +01:00
|
|
|
$_SESSION['no_active_tenant'] = true;
|
|
|
|
|
unset($_SESSION['current_tenant']);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$_SESSION['no_active_tenant'] = false;
|
|
|
|
|
|
|
|
|
|
// Load current tenant (with fallback to first available)
|
2026-02-23 12:58:19 +01:00
|
|
|
$currentTenant = $this->userTenantContextService->getCurrentTenant($userId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if (!$currentTenant) {
|
2026-02-04 23:31:53 +01:00
|
|
|
// Fallback: use first available tenant
|
|
|
|
|
$currentTenant = $availableTenants[0];
|
|
|
|
|
}
|
|
|
|
|
if ($currentTenant) {
|
|
|
|
|
$_SESSION['current_tenant'] = $currentTenant;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|