From f8d88c91fceb78727bc2ffea29426e6af52de151 Mon Sep 17 00:00:00 2001 From: Romulus21 Date: Tue, 26 May 2026 11:22:54 +0200 Subject: [PATCH] first work --- .gitignore | 1 + config/delegate-auth.php | 10 +++ phpunit.xml | 16 +++++ routes/web.php | 10 +++ src/DelegateAuthServiceProvider.php | 2 + .../Controllers/DelegateAuthController.php | 69 +++++++++++++++++++ tests/Feature/LoginTest.php | 64 +++++++++++++++++ tests/Feature/LogoutTest.php | 29 ++++++++ tests/Fixtures/User.php | 10 +++ tests/TestCase.php | 46 +++++++++++++ .../2024_01_01_000000_create_users_table.php | 22 ++++++ 11 files changed, 279 insertions(+) create mode 100644 phpunit.xml create mode 100644 routes/web.php create mode 100644 src/Http/Controllers/DelegateAuthController.php create mode 100644 tests/Feature/LoginTest.php create mode 100644 tests/Feature/LogoutTest.php create mode 100644 tests/Fixtures/User.php create mode 100644 tests/TestCase.php create mode 100644 tests/database/migrations/2024_01_01_000000_create_users_table.php diff --git a/.gitignore b/.gitignore index 48b8bf9..b7aceac 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ vendor/ +.phpunit.result.cache diff --git a/config/delegate-auth.php b/config/delegate-auth.php index ee3f996..e3c7af4 100644 --- a/config/delegate-auth.php +++ b/config/delegate-auth.php @@ -2,4 +2,14 @@ return [ 'encrypt_key' => env('DELEGATE_KEY'), + + // Guard Laravel à utiliser pour la connexion + 'guard' => env('DELEGATE_AUTH_GUARD', 'web'), + + // Champ utilisé pour retrouver l'utilisateur dans la BDD + 'user_field' => env('DELEGATE_AUTH_USER_FIELD', 'email'), + + // Redirections après login/logout + 'redirect_after_login' => env('DELEGATE_AUTH_REDIRECT_LOGIN', '/'), + 'redirect_after_logout' => env('DELEGATE_AUTH_REDIRECT_LOGOUT', '/'), ]; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..2b0ebdd --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + src + + + diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..8a74329 --- /dev/null +++ b/routes/web.php @@ -0,0 +1,10 @@ +group(function () { + // L'app externe redirige le navigateur ici avec le token en query string + Route::get('/delegate-auth/login', [DelegateAuthController::class, 'login'])->name('delegate-auth.login'); + Route::post('/delegate-auth/logout', [DelegateAuthController::class, 'logout'])->name('delegate-auth.logout'); +}); diff --git a/src/DelegateAuthServiceProvider.php b/src/DelegateAuthServiceProvider.php index fdcc4a8..cd47fef 100644 --- a/src/DelegateAuthServiceProvider.php +++ b/src/DelegateAuthServiceProvider.php @@ -19,6 +19,8 @@ class DelegateAuthServiceProvider extends ServiceProvider __DIR__.'/../config/delegate-auth.php' => config_path('delegate-auth.php'), ], 'delegate-auth-config'); + $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); + if ($this->app->runningInConsole()) { $this->commands([ Console\InstallCommand::class, diff --git a/src/Http/Controllers/DelegateAuthController.php b/src/Http/Controllers/DelegateAuthController.php new file mode 100644 index 0000000..0889835 --- /dev/null +++ b/src/Http/Controllers/DelegateAuthController.php @@ -0,0 +1,69 @@ +validate(['token' => 'required|string']); + + $payload = $this->decryptToken($request->input('token')); + + abort_if(! $payload, 401, 'Token invalide.'); + + $field = config('delegate-auth.user_field'); + + abort_if(empty($payload[$field]), 401, 'Token invalide : champ '.$field.' manquant.'); + + $guard = Auth::guard(config('delegate-auth.guard')); + $user = $guard->getProvider()->retrieveByCredentials([$field => $payload[$field]]); + + abort_if(! $user, 401, 'Utilisateur introuvable.'); + + $guard->login($user); + + return redirect(config('delegate-auth.redirect_after_login')); + } + + public function logout(Request $request) + { + $guard = Auth::guard(config('delegate-auth.guard')); + $guard->logout(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return redirect(config('delegate-auth.redirect_after_logout')); + } + + private function decryptToken(string $token): ?array + { + if (!str_contains($token, '.')) { + return null; + } + + [$ivB64, $encrypted] = explode('.', $token, 2); + + $iv = base64_decode($ivB64, strict: true); + $key = substr(hash('sha256', config('delegate-auth.encrypt_key'), binary: true), 0, 32); + + if ($iv === false || strlen($iv) !== 16) { + return null; + } + + $payload = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv); + + if ($payload === false) { + return null; + } + + $data = json_decode($payload, associative: true); + + return is_array($data) ? $data : null; + } +} diff --git a/tests/Feature/LoginTest.php b/tests/Feature/LoginTest.php new file mode 100644 index 0000000..68fab21 --- /dev/null +++ b/tests/Feature/LoginTest.php @@ -0,0 +1,64 @@ + 'user@example.com']); + $token = $this->makeToken(['email' => 'user@example.com']); + + $this->get(route('delegate-auth.login', ['token' => $token])) + ->assertRedirect('/dashboard'); + + $this->assertAuthenticatedAs($user); + } + + public function test_missing_token_fails_validation(): void + { + $this->get(route('delegate-auth.login')) + ->assertSessionHasErrors('token'); + } + + public function test_malformed_token_returns_401(): void + { + $this->get(route('delegate-auth.login', ['token' => 'notavalidtoken'])) + ->assertStatus(401); + } + + public function test_token_encrypted_with_wrong_key_returns_401(): void + { + $wrongKey = substr(hash('sha256', 'wrong-key', binary: true), 0, 32); + $iv = random_bytes(16); + $encrypted = openssl_encrypt(json_encode(['email' => 'user@example.com']), 'AES-256-CBC', $wrongKey, 0, $iv); + $token = base64_encode($iv).'.'.$encrypted; + + User::create(['email' => 'user@example.com']); + + $this->get(route('delegate-auth.login', ['token' => $token])) + ->assertStatus(401); + } + + public function test_token_for_unknown_user_returns_401(): void + { + $token = $this->makeToken(['email' => 'nobody@example.com']); + + $this->get(route('delegate-auth.login', ['token' => $token])) + ->assertStatus(401); + } + + public function test_token_missing_user_field_returns_401(): void + { + $token = $this->makeToken(['foo' => 'bar']); + + $this->get(route('delegate-auth.login', ['token' => $token])) + ->assertStatus(401); + } +} diff --git a/tests/Feature/LogoutTest.php b/tests/Feature/LogoutTest.php new file mode 100644 index 0000000..df50324 --- /dev/null +++ b/tests/Feature/LogoutTest.php @@ -0,0 +1,29 @@ + 'user@example.com']); + + $this->actingAs($user) + ->post(route('delegate-auth.logout')) + ->assertRedirect('/'); + + $this->assertGuest(); + } + + public function test_logout_as_guest_still_redirects(): void + { + $this->post(route('delegate-auth.logout')) + ->assertRedirect('/'); + } +} diff --git a/tests/Fixtures/User.php b/tests/Fixtures/User.php new file mode 100644 index 0000000..503030b --- /dev/null +++ b/tests/Fixtures/User.php @@ -0,0 +1,10 @@ +set('app.key', 'base64:'.base64_encode(str_repeat('a', 32))); + + $app['config']->set('database.default', 'testing'); + $app['config']->set('database.connections.testing', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); + + $app['config']->set('auth.providers.users.model', User::class); + + $app['config']->set('delegate-auth.encrypt_key', 'test-secret-key'); + $app['config']->set('delegate-auth.redirect_after_login', '/dashboard'); + $app['config']->set('delegate-auth.redirect_after_logout', '/'); + } + + protected function defineDatabaseMigrations(): void + { + $this->loadMigrationsFrom(__DIR__.'/database/migrations'); + } + + protected function makeToken(array $payload): string + { + $key = substr(hash('sha256', config('delegate-auth.encrypt_key'), binary: true), 0, 32); + $iv = random_bytes(16); + $encrypted = openssl_encrypt(json_encode($payload), 'AES-256-CBC', $key, 0, $iv); + + return base64_encode($iv).'.'.$encrypted; + } +} diff --git a/tests/database/migrations/2024_01_01_000000_create_users_table.php b/tests/database/migrations/2024_01_01_000000_create_users_table.php new file mode 100644 index 0000000..33b3441 --- /dev/null +++ b/tests/database/migrations/2024_01_01_000000_create_users_table.php @@ -0,0 +1,22 @@ +id(); + $table->string('email')->unique(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('users'); + } +};