From 43357f8300ba9ed3ebe2971d1fcb01b56666c730 Mon Sep 17 00:00:00 2001 From: brendt Date: Wed, 24 Jun 2026 10:25:28 +0200 Subject: [PATCH 1/4] wip --- .../src/Installer/AuthenticationInstaller.php | 2 ++ .../src/Installer/basic-user/Auth.stub.php | 18 ++++++++++++ .../basic-user/AuthMiddleware.stub.php | 28 +++++++++++++++++++ phpstan.neon.dist | 1 + .../Framework/Testing/IntegrationTest.php | 7 +++++ .../Installer/AuthenticationInstallerTest.php | 12 ++++++++ .../Framework/Authentication/AuthTest.php | 12 ++++++++ .../Integration/Testing/ModelFactoryTest.php | 6 ++-- 8 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 packages/auth/src/Installer/basic-user/Auth.stub.php create mode 100644 packages/auth/src/Installer/basic-user/AuthMiddleware.stub.php create mode 100644 tests/Integration/Framework/Authentication/AuthTest.php diff --git a/packages/auth/src/Installer/AuthenticationInstaller.php b/packages/auth/src/Installer/AuthenticationInstaller.php index 17f4b58027..f5854523ea 100644 --- a/packages/auth/src/Installer/AuthenticationInstaller.php +++ b/packages/auth/src/Installer/AuthenticationInstaller.php @@ -25,6 +25,8 @@ public function install(?bool $migrate = null, ?bool $oauth = null): void { $migration = $this->publish(__DIR__ . '/basic-user/CreateUsersTableMigration.stub.php', src_path('Authentication/CreateUsersTable.php')); $this->publish(__DIR__ . '/basic-user/UserModel.stub.php', src_path('Authentication/User.php')); + $this->publish(__DIR__ . '/basic-user/AuthMiddleware.stub.php', src_path('Authentication/AuthMiddleware.php')); + $this->publish(__DIR__ . '/basic-user/Auth.stub.php', src_path('Authentication/Auth.php')); $this->publishImports(); if ($migration && $this->shouldMigrate($migrate)) { diff --git a/packages/auth/src/Installer/basic-user/Auth.stub.php b/packages/auth/src/Installer/basic-user/Auth.stub.php new file mode 100644 index 0000000000..b0bf19338a --- /dev/null +++ b/packages/auth/src/Installer/basic-user/Auth.stub.php @@ -0,0 +1,18 @@ +middleware[] = AuthMiddleware::class; + + return $route; + } +} \ No newline at end of file diff --git a/packages/auth/src/Installer/basic-user/AuthMiddleware.stub.php b/packages/auth/src/Installer/basic-user/AuthMiddleware.stub.php new file mode 100644 index 0000000000..a8b4a437cc --- /dev/null +++ b/packages/auth/src/Installer/basic-user/AuthMiddleware.stub.php @@ -0,0 +1,28 @@ +authenticator->current()) { + return new Forbidden(); + } + + return $next($request); + } +} \ No newline at end of file diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c6158827bf..daf793dab6 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -27,6 +27,7 @@ parameters: excludePaths: analyse: - packages/upgrade/tests/*/Fixtures/*.php + - packages/auth/src/Installer/basic-user reportUnmatchedIgnoredErrors: true disallowedFunctionCalls: diff --git a/src/Tempest/Framework/Testing/IntegrationTest.php b/src/Tempest/Framework/Testing/IntegrationTest.php index a51b95fb21..e52dfcc637 100644 --- a/src/Tempest/Framework/Testing/IntegrationTest.php +++ b/src/Tempest/Framework/Testing/IntegrationTest.php @@ -7,6 +7,8 @@ use Closure; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; +use Tempest\Auth\Authentication\Authenticatable; +use Tempest\Auth\Authentication\Authenticator; use Tempest\Auth\OAuth\Testing\OAuthTester; use Tempest\Cache\Testing\CacheTester; use Tempest\Clock\Clock; @@ -264,4 +266,9 @@ protected function assertException(string $expectedExceptionClass, Closure $hand Assert::fail($message ?? "Expected exception {$expectedExceptionClass} was not thrown"); } + + protected function login(Authenticatable $user): void + { + $this->container->get(Authenticator::class)->authenticate($user); + } } diff --git a/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php b/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php index dae2801108..d9b8fbf543 100644 --- a/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php +++ b/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php @@ -29,6 +29,18 @@ protected function tearDown(): void parent::tearDown(); } + #[Test] + public function test_route_decorator_and_middleware_get_published(): void + { + $this->console + ->call('install auth --force --migrate') + ->assertSuccess(); + + $this->installer + ->assertFileExists('App/Authentication/AuthMiddleware.php') + ->assertFileExists('App/Authentication/Auth.php'); + } + #[Test] public function install_oauth_provider_with_migrations(): void { diff --git a/tests/Integration/Framework/Authentication/AuthTest.php b/tests/Integration/Framework/Authentication/AuthTest.php new file mode 100644 index 0000000000..145e74c66d --- /dev/null +++ b/tests/Integration/Framework/Authentication/AuthTest.php @@ -0,0 +1,12 @@ +make(); $this->assertInstanceOf(DateTime::class, $withDateTime->tempestDate); - $this->assertInstanceOf(\DateTimeImmutable::class, $withDateTime->phpDate); + $this->assertInstanceOf(DateTimeImmutable::class, $withDateTime->phpDate); } } @@ -190,5 +191,6 @@ class AuthorWithEnum class WithDateTime { public DateTime $tempestDate; - public \DateTimeImmutable $phpDate; + + public DateTimeImmutable $phpDate; } From 068da4afa087dbf9811fdf6bbf533646b9f73b6b Mon Sep 17 00:00:00 2001 From: brendt Date: Thu, 25 Jun 2026 10:03:49 +0200 Subject: [PATCH 2/4] wip --- packages/auth/src/Installer/AuthenticationInstaller.php | 4 ++-- .../{Auth.stub.php => MustBeAuthenticated.stub.php} | 4 ++-- ...leware.stub.php => MustBeAuthenticatedMiddleware.stub.php} | 2 +- .../Auth/Installer/AuthenticationInstallerTest.php | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename packages/auth/src/Installer/basic-user/{Auth.stub.php => MustBeAuthenticated.stub.php} (61%) rename packages/auth/src/Installer/basic-user/{AuthMiddleware.stub.php => MustBeAuthenticatedMiddleware.stub.php} (89%) diff --git a/packages/auth/src/Installer/AuthenticationInstaller.php b/packages/auth/src/Installer/AuthenticationInstaller.php index f5854523ea..d945aa8e1c 100644 --- a/packages/auth/src/Installer/AuthenticationInstaller.php +++ b/packages/auth/src/Installer/AuthenticationInstaller.php @@ -25,8 +25,8 @@ public function install(?bool $migrate = null, ?bool $oauth = null): void { $migration = $this->publish(__DIR__ . '/basic-user/CreateUsersTableMigration.stub.php', src_path('Authentication/CreateUsersTable.php')); $this->publish(__DIR__ . '/basic-user/UserModel.stub.php', src_path('Authentication/User.php')); - $this->publish(__DIR__ . '/basic-user/AuthMiddleware.stub.php', src_path('Authentication/AuthMiddleware.php')); - $this->publish(__DIR__ . '/basic-user/Auth.stub.php', src_path('Authentication/Auth.php')); + $this->publish(__DIR__ . '/basic-user/MustBeAuthenticatedMiddleware.stub.php', src_path('Authentication/MustBeAuthenticatedMiddleware.php')); + $this->publish(__DIR__ . '/basic-user/MustBeAuthenticated.stub.php', src_path('Authentication/MustBeAuthenticated.php')); $this->publishImports(); if ($migration && $this->shouldMigrate($migrate)) { diff --git a/packages/auth/src/Installer/basic-user/Auth.stub.php b/packages/auth/src/Installer/basic-user/MustBeAuthenticated.stub.php similarity index 61% rename from packages/auth/src/Installer/basic-user/Auth.stub.php rename to packages/auth/src/Installer/basic-user/MustBeAuthenticated.stub.php index b0bf19338a..c401816f58 100644 --- a/packages/auth/src/Installer/basic-user/Auth.stub.php +++ b/packages/auth/src/Installer/basic-user/MustBeAuthenticated.stub.php @@ -7,11 +7,11 @@ use Tempest\Router\RouteDecorator; #[Attribute] -final readonly class Auth implements RouteDecorator +final readonly class MustBeAuthenticated implements RouteDecorator { public function decorate(Route $route): Route { - $route->middleware[] = AuthMiddleware::class; + $route->middleware[] = MustBeAuthenticatedMiddleware::class; return $route; } diff --git a/packages/auth/src/Installer/basic-user/AuthMiddleware.stub.php b/packages/auth/src/Installer/basic-user/MustBeAuthenticatedMiddleware.stub.php similarity index 89% rename from packages/auth/src/Installer/basic-user/AuthMiddleware.stub.php rename to packages/auth/src/Installer/basic-user/MustBeAuthenticatedMiddleware.stub.php index a8b4a437cc..629a1a870b 100644 --- a/packages/auth/src/Installer/basic-user/AuthMiddleware.stub.php +++ b/packages/auth/src/Installer/basic-user/MustBeAuthenticatedMiddleware.stub.php @@ -11,7 +11,7 @@ use Tempest\Router\HttpMiddlewareCallable; #[SkipDiscovery] -final readonly class AuthMiddleware implements HttpMiddleware +final readonly class MustBeAuthenticatedMiddleware implements HttpMiddleware { public function __construct( private Authenticator $authenticator, diff --git a/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php b/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php index d9b8fbf543..29aae773b3 100644 --- a/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php +++ b/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php @@ -37,8 +37,8 @@ public function test_route_decorator_and_middleware_get_published(): void ->assertSuccess(); $this->installer - ->assertFileExists('App/Authentication/AuthMiddleware.php') - ->assertFileExists('App/Authentication/Auth.php'); + ->assertFileExists('App/Authentication/MustBeAuthenticatedMiddleware.php') + ->assertFileExists('App/Authentication/MustBeAuthenticated.php'); } #[Test] From 651974e5d69912d9201d7ab9c1dd7716969aef8c Mon Sep 17 00:00:00 2001 From: brendt Date: Thu, 25 Jun 2026 10:07:12 +0200 Subject: [PATCH 3/4] wip --- .../Integration/Auth/Installer/OAuthInstallerTest.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/Integration/Auth/Installer/OAuthInstallerTest.php b/tests/Integration/Auth/Installer/OAuthInstallerTest.php index 40a7eb5f1f..7645716ae2 100644 --- a/tests/Integration/Auth/Installer/OAuthInstallerTest.php +++ b/tests/Integration/Auth/Installer/OAuthInstallerTest.php @@ -41,16 +41,8 @@ public function install_oauth_provider( string $expectedControllerPath, ): void { $this->console - ->call('install auth --oauth') - ->confirm() - ->deny() - ->deny() + ->call('install auth --oauth --force') ->input($provider->value) - ->confirm() - ->confirm() - ->confirm() - ->confirm() - ->confirm() ->assertSee('The selected OAuth provider is installed in your project') ->assertSuccess(); From a621acd694c49c970aa78b6eeaccc4d85f5b8e28 Mon Sep 17 00:00:00 2001 From: brendt Date: Thu, 25 Jun 2026 10:18:10 +0200 Subject: [PATCH 4/4] wip --- docs/2-features/04-authentication.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/2-features/04-authentication.md b/docs/2-features/04-authentication.md index 943e7d7976..2e248a2f68 100644 --- a/docs/2-features/04-authentication.md +++ b/docs/2-features/04-authentication.md @@ -223,6 +223,24 @@ final class RequestAuthenticator implements Authenticator } ``` +### HTTP middleware and route decorator + +Once you've installed `auth` in your app, you'll also have a basic `MustBeAuthenticatedMiddleware` and `MustBeAuthenticated` route decorator available. You can use these in your controller actions to mark them as requiring authentication. Since these files are installed in your project code, you're free to change them to accommodate your own authentication needs. + +```php +use App\Authentication\MustBeAuthenticated; +use App\Authentication\MustBeAuthenticatedMiddleware; + +final readonly class BookController +{ + #[MustBeAuthenticated, {:hl-type:Get:}('/books')] + public function index(): Response { /* … */ } + + #[Get('/books/list', middleware: [MustBeAuthenticatedMiddleware::class])] + public function list(): Response { /* … */ } +} +``` + ## Access control In most applications, it is necessary to restrict access to certain resources depending on many factors. For instance, you may want to allow only the author of a post to edit it, or allow only administrators to delete other users.