diff --git a/src/Database/Models/ImpersonationToken.php b/src/Database/Models/ImpersonationToken.php index 8161aca7..05d17ad4 100644 --- a/src/Database/Models/ImpersonationToken.php +++ b/src/Database/Models/ImpersonationToken.php @@ -5,9 +5,12 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\Models; use Carbon\Carbon; +use Illuminate\Contracts\Auth\StatefulGuard; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; use Stancl\Tenancy\Database\Concerns\CentralConnection; +use Stancl\Tenancy\Exceptions\StatefulGuardRequiredException; /** * @property string $token @@ -38,9 +41,15 @@ class ImpersonationToken extends Model public static function booted(): void { static::creating(function ($model) { + $authGuard = $model->auth_guard ?? config('auth.defaults.guard'); + + if (! Auth::guard($authGuard) instanceof StatefulGuard) { + throw new StatefulGuardRequiredException($authGuard); + } + $model->created_at = $model->created_at ?? $model->freshTimestamp(); $model->token = $model->token ?? Str::random(128); - $model->auth_guard = $model->auth_guard ?? config('auth.defaults.guard'); + $model->auth_guard = $authGuard; }); } } diff --git a/src/Exceptions/StatefulGuardRequiredException.php b/src/Exceptions/StatefulGuardRequiredException.php new file mode 100644 index 00000000..fe8bef6e --- /dev/null +++ b/src/Exceptions/StatefulGuardRequiredException.php @@ -0,0 +1,15 @@ +artisan('migrate', [ @@ -223,6 +225,46 @@ test('impersonation works with multiple models and guards', function () { }); }); +test('impersonation tokens can be created only with stateful guards', function () { + config([ + 'auth.guards' => [ + 'nonstateful' => [ + 'driver' => 'nonstateful', + 'provider' => 'provider', + ], + 'stateful' => [ + 'driver' => 'session', + 'provider' => 'provider', + ], + ], + 'auth.providers.provider' => [ + 'driver' => 'eloquent', + 'model' => ImpersonationUser::class, + ], + ]); + + $tenant = Tenant::create(); + migrateTenants(); + + $user = $tenant->run(function () { + return ImpersonationUser::create([ + 'name' => 'Joe', + 'email' => 'joe@local', + 'password' => bcrypt('secret'), + ]); + }); + + Auth::extend('nonstateful', fn($app, $name, array $config) => new TokenGuard(Auth::createUserProvider($config['provider']), request())); + + expect(fn() => tenancy()->impersonate($tenant, $user->id, '/dashboard', 'nonstateful')) + ->toThrow(StatefulGuardRequiredException::class); + + Auth::extend('stateful', fn ($app, $name, array $config) => new SessionGuard($name, Auth::createUserProvider($config['provider']), session())); + + expect(tenancy()->impersonate($tenant, $user->id, '/dashboard', 'stateful')) + ->toBeInstanceOf(ImpersonationToken::class); +}); + function migrateTenants() { pest()->artisan('tenants:migrate')->assertExitCode(0);