first work

This commit is contained in:
Romulus21
2026-05-26 11:22:54 +02:00
parent ee415258d1
commit f8d88c91fc
11 changed files with 279 additions and 0 deletions
+1
View File
@@ -1 +1,2 @@
vendor/
.phpunit.result.cache
+10
View File
@@ -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', '/'),
];
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
</phpunit>
+10
View File
@@ -0,0 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
use Rodev\DelegateAuth\Http\Controllers\DelegateAuthController;
Route::middleware('web')->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');
});
+2
View File
@@ -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,
@@ -0,0 +1,69 @@
<?php
namespace Rodev\DelegateAuth\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
class DelegateAuthController extends Controller
{
public function login(Request $request)
{
$request->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;
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace Rodev\DelegateAuth\Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Rodev\DelegateAuth\Tests\Fixtures\User;
use Rodev\DelegateAuth\Tests\TestCase;
class LoginTest extends TestCase
{
use RefreshDatabase;
public function test_valid_token_authenticates_user_and_redirects(): void
{
$user = User::create(['email' => '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);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace Rodev\DelegateAuth\Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Rodev\DelegateAuth\Tests\Fixtures\User;
use Rodev\DelegateAuth\Tests\TestCase;
class LogoutTest extends TestCase
{
use RefreshDatabase;
public function test_logout_disconnects_authenticated_user_and_redirects(): void
{
$user = User::create(['email' => '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('/');
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace Rodev\DelegateAuth\Tests\Fixtures;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $guarded = [];
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace Rodev\DelegateAuth\Tests;
use Orchestra\Testbench\TestCase as OrchestraTestCase;
use Rodev\DelegateAuth\DelegateAuthServiceProvider;
use Rodev\DelegateAuth\Tests\Fixtures\User;
abstract class TestCase extends OrchestraTestCase
{
protected function getPackageProviders($app): array
{
return [DelegateAuthServiceProvider::class];
}
protected function defineEnvironment($app): void
{
$app['config']->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;
}
}
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email')->unique();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};