diff --git a/app/Scopes/TenantScope.php b/app/Scopes/TenantScope.php new file mode 100644 index 0000000..4c7e15d --- /dev/null +++ b/app/Scopes/TenantScope.php @@ -0,0 +1,28 @@ +user(); + + if ($user === null) { + // Default-deny: null-user must NEVER silently see all records + // (prevents leaks in queue jobs, Tinker, etc.) + $builder->whereRaw('0 = 1'); + return; + } + + if ($user->hasRole('platform-admin')) { + return; // platform-admin sees all tenants + } + + $builder->where($model->getTable() . '.tenant_id', $user->tenant_id); + } +} diff --git a/app/Traits/BelongsToTenant.php b/app/Traits/BelongsToTenant.php new file mode 100644 index 0000000..aca57d7 --- /dev/null +++ b/app/Traits/BelongsToTenant.php @@ -0,0 +1,21 @@ +user(); + if ($user && !$user->hasRole('platform-admin')) { + // Force tenant_id — prevents cross-tenant mass-assign attacks + $model->tenant_id = $user->tenant_id; + } + }); + } +} diff --git a/app/Traits/HasUuid.php b/app/Traits/HasUuid.php new file mode 100644 index 0000000..a6a1942 --- /dev/null +++ b/app/Traits/HasUuid.php @@ -0,0 +1,20 @@ +uuid ??= (string) Str::uuid(); + }); + } + + public function getRouteKeyName(): string + { + return 'uuid'; + } +} diff --git a/tests/Pest.php b/tests/Pest.php index b9fb6f7..f6eb1db 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -22,6 +22,9 @@ pest()->extend(TestCase::class) // ->use(RefreshDatabase::class) ->in('Feature'); +pest()->extend(TestCase::class) + ->in('Unit'); + /* |-------------------------------------------------------------------------- | Expectations diff --git a/tests/Unit/Models/HasUuidTest.php b/tests/Unit/Models/HasUuidTest.php new file mode 100644 index 0000000..33b49dc --- /dev/null +++ b/tests/Unit/Models/HasUuidTest.php @@ -0,0 +1,16 @@ +create(); + expect($user->uuid)->toMatch('/^[0-9a-f-]{36}$/'); +}); + +it('nutzt UUID als Route-Key', function () { + $user = User::factory()->create(); + expect($user->getRouteKeyName())->toBe('uuid'); +});