diff --git a/backend/config/packages/security.yaml b/backend/config/packages/security.yaml index 056c8ac..4f5a8a6 100644 --- a/backend/config/packages/security.yaml +++ b/backend/config/packages/security.yaml @@ -21,7 +21,7 @@ security: check_path: /api/login username_path: email password_path: password - success_handler: lexik_jwt_authentication.handler.authentication_success + success_handler: App\Security\LoginSuccessHandler failure_handler: lexik_jwt_authentication.handler.authentication_failure # Geschützte API: JWT im Authorization-Header diff --git a/backend/src/Security/LoginSuccessHandler.php b/backend/src/Security/LoginSuccessHandler.php new file mode 100644 index 0000000..a152e45 --- /dev/null +++ b/backend/src/Security/LoginSuccessHandler.php @@ -0,0 +1,45 @@ +getUser(); + if ($user instanceof Employee) { + $tenant = $this->resolver->resolve($request->getHost()) ?? ResolvedTenant::platform(); + if (!$this->policy->canLogin($user, $tenant)) { + return new JsonResponse( + ['message' => 'Für diese Adresse besteht kein Zugang. Bitte die richtige Login-Adresse verwenden.'], + Response::HTTP_FORBIDDEN, + ); + } + } + + return $this->inner->onAuthenticationSuccess($request, $token); + } +} diff --git a/backend/src/Security/TenantAccessPolicy.php b/backend/src/Security/TenantAccessPolicy.php new file mode 100644 index 0000000..9fa059d --- /dev/null +++ b/backend/src/Security/TenantAccessPolicy.php @@ -0,0 +1,56 @@ +getRoles(); + $isPlatform = \in_array(Employee::ROLE_PLATFORM_ADMIN, $roles, true); + $isReseller = \in_array(Employee::ROLE_RESELLER_ADMIN, $roles, true); + + if ($isPlatform) { + return true; + } + + $userReseller = $user->getReseller(); + $userCompany = $user->getCompany(); + + return match ($tenant->kind) { + ResolvedTenant::KIND_PLATFORM => $isReseller, + ResolvedTenant::KIND_RESELLER => null !== $userReseller + && null !== $tenant->reseller + && $userReseller->getId()->equals($tenant->reseller->getId()), + ResolvedTenant::KIND_COMPANY => $this->canLoginCompany($userCompany, $userReseller, $isReseller, $tenant), + default => false, + }; + } + + private function canLoginCompany( + ?\App\Entity\Company $userCompany, + ?\App\Entity\Reseller $userReseller, + bool $isReseller, + ResolvedTenant $tenant, + ): bool { + if (null !== $userCompany && null !== $tenant->company + && $userCompany->getId()->equals($tenant->company->getId())) { + return true; + } + + // Reseller-Admin des zugehörigen Resellers darf den Firmen-Host ebenfalls nutzen + return $isReseller && null !== $userReseller && null !== $tenant->reseller + && $userReseller->getId()->equals($tenant->reseller->getId()); + } +} diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue index 0c96432..a53fd7d 100644 --- a/frontend/src/views/LoginView.vue +++ b/frontend/src/views/LoginView.vue @@ -20,8 +20,11 @@ async function submit() { try { await auth.login(email.value, password.value) router.push((route.query.redirect as string) ?? '/app') - } catch { - error.value = 'Anmeldung fehlgeschlagen. Bitte E-Mail und Passwort prüfen.' + } catch (e: unknown) { + const err = e as { response?: { status?: number; data?: { message?: string } } } + error.value = err.response?.status === 403 && err.response.data?.message + ? err.response.data.message + : 'Anmeldung fehlgeschlagen. Bitte E-Mail und Passwort prüfen.' } finally { loading.value = false }