first work
This commit is contained in:
@@ -1 +1,2 @@
|
||||
vendor/
|
||||
.phpunit.result.cache
|
||||
|
||||
@@ -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
@@ -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>
|
||||
@@ -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');
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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('/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Rodev\DelegateAuth\Tests\Fixtures;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user