From 2bbc116a8f511304190b92347902871b075014b3 Mon Sep 17 00:00:00 2001 From: iamdadmin Date: Mon, 26 Jan 2026 09:03:23 +0000 Subject: [PATCH 1/3] feat(auth): add various improvements to auth and oauth installer including additional stubs (#1888) --- .../src/Installer/AuthenticationInstaller.php | 19 +++++- .../auth/src/Installer/OAuthInstaller.php | 2 - .../basic-user/LoginController.stub.php | 66 +++++++++++++++++++ .../basic-user/MustBeAuthenticated.stub.php | 43 ++++++++++++ .../oauth/CreateUsersTableMigration.stub.php | 28 ++++++++ .../Installer/oauth/OAuthControllerStub.php | 18 +++-- .../src/Installer/oauth/UserModel.stub.php | 27 ++++++++ .../Authentication/CreateUsersTable.php | 26 ++++++++ .../Auth/Installer/OAuthInstallerTest.php | 20 +++--- 9 files changed, 231 insertions(+), 18 deletions(-) create mode 100644 packages/auth/src/Installer/basic-user/LoginController.stub.php create mode 100644 packages/auth/src/Installer/basic-user/MustBeAuthenticated.stub.php create mode 100644 packages/auth/src/Installer/oauth/CreateUsersTableMigration.stub.php create mode 100644 packages/auth/src/Installer/oauth/UserModel.stub.php create mode 100644 src/Tempest/Framework/Authentication/CreateUsersTable.php diff --git a/packages/auth/src/Installer/AuthenticationInstaller.php b/packages/auth/src/Installer/AuthenticationInstaller.php index 29ec896e0..f2015ce93 100644 --- a/packages/auth/src/Installer/AuthenticationInstaller.php +++ b/packages/auth/src/Installer/AuthenticationInstaller.php @@ -19,6 +19,8 @@ final class AuthenticationInstaller implements Installer { use PublishesFiles; + public bool $installOAuth = false; + private(set) string $name = 'auth'; public function __construct( @@ -30,15 +32,26 @@ public function __construct( public function install(): 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')); + // First question, ask whether to also install OAuth, as it changes the stubs to publish + $this->installOAuth = $this->shouldInstallOAuth(); + + // Get the appropriate stubs + $stubPath = $this->installOAuth ? 'oauth' : 'basic-user'; + + // Publish the stubs + $migration = $this->publish(__DIR__ . "/{$stubPath}/CreateUsersTableMigration.stub.php", src_path('Authentication/CreateUsersTable.php')); + $this->publish(__DIR__ . "/{$stubPath}/UserModel.stub.php", src_path('Authentication/User.php')); + $this->publish(__DIR__ . '/basic-user/MustBeAuthenticated.stub.php', src_path('Authentication/MustBeAuthenticated.php')); + $this->publish(__DIR__ . '/basic-user/LoginController.stub.php', src_path('Authentication/LoginController.php')); $this->publishImports(); + // Offer to migrate if ($migration && $this->shouldMigrate()) { $this->migrationManager->up(); } - if ($this->shouldInstallOAuth()) { + // Run the OAuth installer now + if ($this->installOAuth) { $this->container->get(OAuthInstaller::class)->install(); } } diff --git a/packages/auth/src/Installer/OAuthInstaller.php b/packages/auth/src/Installer/OAuthInstaller.php index 8915b44d3..a6a31209f 100644 --- a/packages/auth/src/Installer/OAuthInstaller.php +++ b/packages/auth/src/Installer/OAuthInstaller.php @@ -114,14 +114,12 @@ private function publishController(SupportedOAuthProvider $provider): void 'redirect-route', 'callback-route', "'user-model-fqcn'", - 'provider_db_column', ], replace: [ "\\{$providerFqcn}::{$provider->name}", "/auth/{$name}", "/auth/{$name}/callback", "\\{$userModelFqcn}::class", - "{$name}_id", ], ), ); diff --git a/packages/auth/src/Installer/basic-user/LoginController.stub.php b/packages/auth/src/Installer/basic-user/LoginController.stub.php new file mode 100644 index 000000000..3eb2325af --- /dev/null +++ b/packages/auth/src/Installer/basic-user/LoginController.stub.php @@ -0,0 +1,66 @@ +select() + ->where('email', $request->email) + ->first(); + + if (! $user || ! $this->passwordHasher->verify($request->password, $user->password)) { + return new Redirect('/login')->flash('error', 'Invalid credentials'); + } + + $this->authenticator->authenticate($user); + + // Get the intended URL and redirect there, or default to home + // getIntended() automatically consumes/removes the stored URL + $intendedUrl = $this->previousUrl->getIntended('/dashboard'); + + return new Redirect($intendedUrl) + ->flash('success', 'Logged in successfully'); + } + + #[Post('/auth/logout')] + public function logout(): Redirect + { + $this->authenticator->deauthenticate(); + + return new Redirect('/') + ->flash('success', 'You have been logged out'); + } +} diff --git a/packages/auth/src/Installer/basic-user/MustBeAuthenticated.stub.php b/packages/auth/src/Installer/basic-user/MustBeAuthenticated.stub.php new file mode 100644 index 000000000..f4f70c50c --- /dev/null +++ b/packages/auth/src/Installer/basic-user/MustBeAuthenticated.stub.php @@ -0,0 +1,43 @@ +authenticator->current(); + + if ($user === null) { + // Store the intended URL + $this->previousUrl->setIntended($request->path); + + // Redirect to login if not authenticated + return new Redirect('/auth/login') + ->flash('error', 'You must be logged in to access this page'); + } + + // User is authenticated, continue with request + return $next($request); + } +} diff --git a/packages/auth/src/Installer/oauth/CreateUsersTableMigration.stub.php b/packages/auth/src/Installer/oauth/CreateUsersTableMigration.stub.php new file mode 100644 index 000000000..c5f5537d8 --- /dev/null +++ b/packages/auth/src/Installer/oauth/CreateUsersTableMigration.stub.php @@ -0,0 +1,28 @@ +primary() + ->string('email') + ->string('password', nullable: true) + ->string('name', nullable: true) + ->string('nickname', nullable: true) + ->string('avatar', nullable: true) + ->string('oauth_id', nullable: true) + ->string('oauth_raw', nullable: true) + ->string('oauth_provider', nullable: true); + } +} diff --git a/packages/auth/src/Installer/oauth/OAuthControllerStub.php b/packages/auth/src/Installer/oauth/OAuthControllerStub.php index 293ab874f..f9d9c7693 100644 --- a/packages/auth/src/Installer/oauth/OAuthControllerStub.php +++ b/packages/auth/src/Installer/oauth/OAuthControllerStub.php @@ -11,6 +11,7 @@ use Tempest\Discovery\SkipDiscovery; use Tempest\Http\Request; use Tempest\Http\Responses\Redirect; +use Tempest\Http\Session\PreviousUrl; use Tempest\Router\Get; use function Tempest\Database\query; @@ -21,6 +22,7 @@ public function __construct( #[Tag('tag_name')] private OAuthClient $oauth, + private PreviousUrl $previousUrl, ) {} #[Get('redirect-route')] @@ -32,19 +34,27 @@ public function redirect(): Redirect #[Get('callback-route')] public function callback(Request $request): Redirect { - // TODO: implement, the code below is an example + // TODO: implement, the code below is an example, customise to suit your application $this->oauth->authenticate( request: $request, map: fn (OAuthUser $user): Authenticatable => query('user-model-fqcn')->updateOrCreate([ - 'provider_db_column' => $user->id, + 'oauth_id' => $user->id, ], [ - 'provider_db_column' => $user->id, + 'oauth_id' => $user->id, 'username' => $user->nickname, 'email' => $user->email, + 'name' => $user->name, + 'nickname' => $user->nickname, + 'avatar' => $user->avatar, + 'oauth_raw' => $user->raw, + 'oauth_provider' => 'tag_name', ]), ); - return new Redirect('/'); + $intendedUrl = $this->previousUrl->getIntended('/dashboard'); + + return new Redirect($intendedUrl) + ->flash('success', 'Logged in successfully'); } } diff --git a/packages/auth/src/Installer/oauth/UserModel.stub.php b/packages/auth/src/Installer/oauth/UserModel.stub.php new file mode 100644 index 000000000..b8a28fceb --- /dev/null +++ b/packages/auth/src/Installer/oauth/UserModel.stub.php @@ -0,0 +1,27 @@ +primary() + ->string('email') + ->string('password', nullable: true) + ->string('name', nullable: true) + ->string('nickname', nullable: true) + ->string('avatar', nullable: true) + ->string('oauth_id', nullable: true) + ->string('oauth_raw', nullable: true) + ->string('oauth_provider', nullable: true); + } +} diff --git a/tests/Integration/Auth/Installer/OAuthInstallerTest.php b/tests/Integration/Auth/Installer/OAuthInstallerTest.php index 40a7eb5f1..a4b0d6fd7 100644 --- a/tests/Integration/Auth/Installer/OAuthInstallerTest.php +++ b/tests/Integration/Auth/Installer/OAuthInstallerTest.php @@ -42,15 +42,17 @@ public function install_oauth_provider( ): void { $this->console ->call('install auth --oauth') - ->confirm() - ->deny() - ->deny() - ->input($provider->value) - ->confirm() - ->confirm() - ->confirm() - ->confirm() - ->confirm() + ->confirm() // Running the installer, continue? + ->deny() // Publish CreateUsersTable? + ->deny() // Publish User? + ->deny() // Publish MustBeAuthenticated? + ->deny() // Publish LoginController? + ->input($provider->value) // Pick provider + ->confirm() // Confirm provider + ->confirm() // Publish $ProviderController + ->confirm() // Publish $provider.config.php + ->confirm() // Add to .env + ->confirm() // Install dependencies ->assertSee('The selected OAuth provider is installed in your project') ->assertSuccess(); From a954c507fd000b8ec0ec380197105302459f07d9 Mon Sep 17 00:00:00 2001 From: iamDadmin Date: Tue, 27 Jan 2026 11:01:49 +0000 Subject: [PATCH 2/3] feat(auth): removed unnecessary file from PR This was a temporary file generated in local testing, and shouldn't have been committed. --- .../Authentication/CreateUsersTable.php | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/Tempest/Framework/Authentication/CreateUsersTable.php diff --git a/src/Tempest/Framework/Authentication/CreateUsersTable.php b/src/Tempest/Framework/Authentication/CreateUsersTable.php deleted file mode 100644 index 3d6823d11..000000000 --- a/src/Tempest/Framework/Authentication/CreateUsersTable.php +++ /dev/null @@ -1,26 +0,0 @@ -primary() - ->string('email') - ->string('password', nullable: true) - ->string('name', nullable: true) - ->string('nickname', nullable: true) - ->string('avatar', nullable: true) - ->string('oauth_id', nullable: true) - ->string('oauth_raw', nullable: true) - ->string('oauth_provider', nullable: true); - } -} From ded8eb63d44a31ff3e47cabab3c988a6751a42ad Mon Sep 17 00:00:00 2001 From: iamdadmin Date: Wed, 28 Jan 2026 14:44:22 +0000 Subject: [PATCH 3/3] feat(auth): naming convention stub LoginController to AuthenticateUserController --- packages/auth/src/Installer/AuthenticationInstaller.php | 2 +- ...oller.stub.php => AuthenticateUserController.stub.php} | 8 +++++++- tests/Integration/Auth/Installer/OAuthInstallerTest.php | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) rename packages/auth/src/Installer/basic-user/{LoginController.stub.php => AuthenticateUserController.stub.php} (82%) diff --git a/packages/auth/src/Installer/AuthenticationInstaller.php b/packages/auth/src/Installer/AuthenticationInstaller.php index f2015ce93..fd036f789 100644 --- a/packages/auth/src/Installer/AuthenticationInstaller.php +++ b/packages/auth/src/Installer/AuthenticationInstaller.php @@ -42,7 +42,7 @@ public function install(): void $migration = $this->publish(__DIR__ . "/{$stubPath}/CreateUsersTableMigration.stub.php", src_path('Authentication/CreateUsersTable.php')); $this->publish(__DIR__ . "/{$stubPath}/UserModel.stub.php", src_path('Authentication/User.php')); $this->publish(__DIR__ . '/basic-user/MustBeAuthenticated.stub.php', src_path('Authentication/MustBeAuthenticated.php')); - $this->publish(__DIR__ . '/basic-user/LoginController.stub.php', src_path('Authentication/LoginController.php')); + $this->publish(__DIR__ . '/basic-user/AuthenticateUserController.stub.php', src_path('Authentication/AuthenticateUserController.php')); $this->publishImports(); // Offer to migrate diff --git a/packages/auth/src/Installer/basic-user/LoginController.stub.php b/packages/auth/src/Installer/basic-user/AuthenticateUserController.stub.php similarity index 82% rename from packages/auth/src/Installer/basic-user/LoginController.stub.php rename to packages/auth/src/Installer/basic-user/AuthenticateUserController.stub.php index 3eb2325af..a85eae552 100644 --- a/packages/auth/src/Installer/basic-user/LoginController.stub.php +++ b/packages/auth/src/Installer/basic-user/AuthenticateUserController.stub.php @@ -14,7 +14,7 @@ use function Tempest\Database\query; use function Tempest\View\view; -final readonly class LoginController +final readonly class AuthenticateUserController { public function __construct( private Authenticator $authenticator, @@ -22,11 +22,17 @@ public function __construct( private PreviousUrl $previousUrl, ) {} + // TODO: Customise route paths to suit your application #[Get('/auth/login')] public function showLoginForm(): View { // TODO: implement, the code below is an example, and does not include a login form, customise to suit your application + // HINTS & TIPS: + // If using Tempest|Auth\OAuth your view would include links to login with your OAuth providers + // i.e. Login with Generic + // In an OAuth-only situation, you can remove the #[Post('/auth/login')] and public function login(..) entirely + return view('./your.login.view.php'); } diff --git a/tests/Integration/Auth/Installer/OAuthInstallerTest.php b/tests/Integration/Auth/Installer/OAuthInstallerTest.php index a4b0d6fd7..a4b00e726 100644 --- a/tests/Integration/Auth/Installer/OAuthInstallerTest.php +++ b/tests/Integration/Auth/Installer/OAuthInstallerTest.php @@ -46,7 +46,7 @@ public function install_oauth_provider( ->deny() // Publish CreateUsersTable? ->deny() // Publish User? ->deny() // Publish MustBeAuthenticated? - ->deny() // Publish LoginController? + ->deny() // Publish AuthenticateUserController? ->input($provider->value) // Pick provider ->confirm() // Confirm provider ->confirm() // Publish $ProviderController