From f3ad65ab9c6a5b2cc9d1fece9a6596aac685b4c3 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Sun, 9 Nov 2025 14:24:35 -0600 Subject: [PATCH 01/21] fixes duplicate method issue. --- src/Cms/Controllers/Auth/LoginController.php | 30 -------------------- 1 file changed, 30 deletions(-) diff --git a/src/Cms/Controllers/Auth/LoginController.php b/src/Cms/Controllers/Auth/LoginController.php index 4845bbd..d45d060 100644 --- a/src/Cms/Controllers/Auth/LoginController.php +++ b/src/Cms/Controllers/Auth/LoginController.php @@ -43,36 +43,6 @@ public function __construct( ?Application $app = null ) $this->_CsrfManager = new CsrfTokenManager( $this->_SessionManager ); } - /** - * Validate redirect URL to prevent open redirect vulnerabilities - * - * @param string $url The URL to validate - * @return bool True if the URL is safe to use - */ - private function isValidRedirectUrl( string $url ): bool - { - // Only allow relative URLs or same-origin absolute URLs - if( empty( $url ) ) - { - return false; - } - - // Reject URLs with schemes (http://, https://, javascript:, etc.) - if( preg_match( '#^[a-z][a-z0-9+.-]*:#i', $url ) ) - { - return false; - } - - // Reject protocol-relative URLs (//example.com) - if( str_starts_with( $url, '//' ) ) - { - return false; - } - - // Must start with / for internal path - return str_starts_with( $url, '/' ); - } - /** * Show login form */ From 379e30a4ebb6104db208810ba326d50b4b69e49d Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Mon, 10 Nov 2025 18:06:36 -0600 Subject: [PATCH 02/21] refactors for a more service like architecture. --- .../app/Initializers/AuthInitializer.php | 51 +-- .../Initializers/MaintenanceInitializer.php | 21 +- .../Initializers/PasswordResetInitializer.php | 59 +-- resources/public/index.php | 4 +- resources/views/admin/posts/edit.php | 2 +- resources/views/admin/posts/index.php | 4 +- resources/views/blog/category.php | 2 +- resources/views/blog/tag.php | 2 +- src/Bootstrap.php | 8 +- src/Cms/Auth/AuthManager.php | 152 +++---- src/Cms/Auth/CsrfTokenManager.php | 26 +- src/Cms/Auth/Filters/AuthenticationFilter.php | 42 +- src/Cms/Auth/Filters/CsrfFilter.php | 26 +- src/Cms/Auth/PasswordHasher.php | 102 ++--- src/Cms/Auth/PasswordResetManager.php | 146 +++--- src/Cms/Auth/SessionManager.php | 82 ++-- src/Cms/Auth/helpers.php | 22 +- .../Cli/Commands/Generate/EmailCommand.php | 16 +- .../Cli/Commands/Install/InstallCommand.php | 367 +++++++-------- .../Commands/Maintenance/EnableCommand.php | 16 +- src/Cms/Cli/Commands/Queue/InstallCommand.php | 22 +- src/Cms/Cli/Commands/User/CreateCommand.php | 129 ++---- src/Cms/Cli/Commands/User/DeleteCommand.php | 115 ++--- src/Cms/Cli/Commands/User/ListCommand.php | 116 ++--- .../Controllers/Admin/CategoryController.php | 211 +++++---- .../Controllers/Admin/DashboardController.php | 34 +- src/Cms/Controllers/Admin/PostController.php | 420 +++++++----------- .../Controllers/Admin/ProfileController.php | 102 ++--- src/Cms/Controllers/Admin/TagController.php | 201 ++++----- src/Cms/Controllers/Admin/UserController.php | 192 ++++---- src/Cms/Controllers/Auth/LoginController.php | 84 ++-- .../Auth/PasswordResetController.php | 138 +++--- src/Cms/Controllers/Blog.php | 278 +++++------- src/Cms/Controllers/Content.php | 82 ++-- src/Cms/Database/ConnectionFactory.php | 78 ++++ src/Cms/Email/helpers.php | 14 +- src/Cms/Maintenance/MaintenanceConfig.php | 36 +- src/Cms/Maintenance/MaintenanceFilter.php | 42 +- src/Cms/Maintenance/MaintenanceManager.php | 66 +-- src/Cms/Models/Category.php | 100 ++--- src/Cms/Models/PasswordResetToken.php | 94 ++-- src/Cms/Models/Post.php | 272 ++++++------ src/Cms/Models/Tag.php | 88 ++-- src/Cms/Models/User.php | 222 ++++----- .../DatabaseCategoryRepository.php | 135 +++--- .../DatabasePasswordResetTokenRepository.php | 89 ++-- .../Repositories/DatabasePostRepository.php | 322 +++++++------- .../Repositories/DatabaseTagRepository.php | 109 ++--- .../Repositories/DatabaseUserRepository.php | 181 ++++---- src/Cms/Repositories/ICategoryRepository.php | 20 +- .../IPasswordResetTokenRepository.php | 16 +- src/Cms/Repositories/IPostRepository.php | 64 +-- src/Cms/Repositories/ITagRepository.php | 12 +- src/Cms/Repositories/IUserRepository.php | 14 +- .../{EmailService.php => Email/Sender.php} | 116 ++--- src/Cms/Services/Post/Creator.php | 101 +++++ src/Cms/Services/Post/Deleter.php | 50 +++ src/Cms/Services/Post/Publisher.php | 95 ++++ src/Cms/Services/Post/Updater.php | 92 ++++ src/Cms/Services/Tag/Creator.php | 53 +++ src/Cms/Services/Tag/Resolver.php | 64 +++ tests/BootstrapTest.php | 116 ++--- tests/Cms/Auth/AuthManagerTest.php | 11 +- tests/Cms/BlogControllerTest.php | 94 ++-- tests/Cms/BlogTest.php | 318 ++++++------- .../Commands/Install/InstallCommandTest.php | 59 --- tests/Cms/ContentControllerTest.php | 136 +++--- .../Maintenance/MaintenanceCommandsTest.php | 120 ++--- .../Cms/Maintenance/MaintenanceConfigTest.php | 14 +- .../Cms/Maintenance/MaintenanceFilterTest.php | 56 +-- .../Maintenance/MaintenanceManagerTest.php | 110 ++--- .../DatabaseCategoryRepositoryTest.php | 2 +- .../DatabasePostRepositoryTest.php | 2 +- .../DatabaseTagRepositoryTest.php | 2 +- .../DatabaseUserRepositoryTest.php | 32 +- tests/Cms/Services/Post/CreatorTest.php | 306 +++++++++++++ tests/Cms/Services/Post/DeleterTest.php | 92 ++++ tests/Cms/Services/Post/PublisherTest.php | 199 +++++++++ tests/Cms/Services/Post/UpdaterTest.php | 288 ++++++++++++ tests/Cms/Services/Tag/CreatorTest.php | 143 ++++++ tests/Cms/Services/Tag/ResolverTest.php | 190 ++++++++ 81 files changed, 4659 insertions(+), 3450 deletions(-) create mode 100644 src/Cms/Database/ConnectionFactory.php rename src/Cms/Services/{EmailService.php => Email/Sender.php} (56%) create mode 100644 src/Cms/Services/Post/Creator.php create mode 100644 src/Cms/Services/Post/Deleter.php create mode 100644 src/Cms/Services/Post/Publisher.php create mode 100644 src/Cms/Services/Post/Updater.php create mode 100644 src/Cms/Services/Tag/Creator.php create mode 100644 src/Cms/Services/Tag/Resolver.php create mode 100644 tests/Cms/Services/Post/CreatorTest.php create mode 100644 tests/Cms/Services/Post/DeleterTest.php create mode 100644 tests/Cms/Services/Post/PublisherTest.php create mode 100644 tests/Cms/Services/Post/UpdaterTest.php create mode 100644 tests/Cms/Services/Tag/CreatorTest.php create mode 100644 tests/Cms/Services/Tag/ResolverTest.php diff --git a/resources/app/Initializers/AuthInitializer.php b/resources/app/Initializers/AuthInitializer.php index 3382f0e..a2085d5 100644 --- a/resources/app/Initializers/AuthInitializer.php +++ b/resources/app/Initializers/AuthInitializer.php @@ -20,58 +20,39 @@ class AuthInitializer implements IRunnable { /** * Run the initializer + * @param array $argv + * @return mixed + * @throws \Exception */ - public function run( array $Argv = [] ): mixed + public function run( array $argv = [] ): mixed { // Get Settings from Registry - $Settings = Registry::getInstance()->get( 'Settings' ); + $settings = Registry::getInstance()->get( 'Settings' ); - if( !$Settings || !$Settings instanceof \Neuron\Data\Setting\SettingManager ) + if( !$settings || !$settings instanceof \Neuron\Data\Setting\SettingManager ) { Log::error( "Settings not found in Registry, skipping auth initialization" ); return null; } // Get Application from Registry - $App = Registry::getInstance()->get( 'App' ); + $app = Registry::getInstance()->get( 'App' ); - if( !$App || !$App instanceof \Neuron\Mvc\Application ) + if( !$app || !$app instanceof \Neuron\Mvc\Application ) { Log::error( "Application not found in Registry, skipping auth initialization" ); return null; } - // Get database configuration - $dbConfig = []; + // Check if database is configured try { - $settingNames = $Settings->getSectionSettingNames( 'database' ); + $settingNames = $settings->getSectionSettingNames( 'database' ); if( !empty( $settingNames ) ) - { - foreach( $settingNames as $name ) - { - $value = $Settings->get( 'database', $name ); - if( $value !== null ) - { - // Convert string values to appropriate types - $dbConfig[$name] = ( $name === 'port' ) ? (int)$value : $value; - } - } - } - } - catch( \Exception $e ) - { - Log::error( "Failed to load database config: " . $e->getMessage() ); - } - - // Only register auth filter if database is configured - if( !empty( $dbConfig ) ) - { - try { // Initialize authentication components - $userRepository = new DatabaseUserRepository( $dbConfig ); + $userRepository = new DatabaseUserRepository( $settings ); $sessionManager = new SessionManager(); $passwordHasher = new PasswordHasher(); $authManager = new AuthManager( $userRepository, $sessionManager, $passwordHasher ); @@ -80,15 +61,15 @@ public function run( array $Argv = [] ): mixed $authFilter = new AuthenticationFilter( $authManager, '/login' ); // Register the auth filter with the Router - $App->getRouter()->registerFilter( 'auth', $authFilter ); + $app->getRouter()->registerFilter( 'auth', $authFilter ); // Store AuthManager in Registry for easy access Registry::getInstance()->set( 'AuthManager', $authManager ); } - catch( \Exception $e ) - { - Log::error( "Failed to register auth filter: " . $e->getMessage() ); - } + } + catch( \Exception $e ) + { + Log::error( "Failed to register auth filter: " . $e->getMessage() ); } return null; diff --git a/resources/app/Initializers/MaintenanceInitializer.php b/resources/app/Initializers/MaintenanceInitializer.php index c0bf599..fc44426 100644 --- a/resources/app/Initializers/MaintenanceInitializer.php +++ b/resources/app/Initializers/MaintenanceInitializer.php @@ -19,34 +19,37 @@ class MaintenanceInitializer implements IRunnable { /** * Run the initializer + * @param array $argv + * @return mixed + * @throws \Exception */ - public function run( array $Argv = [] ): mixed + public function run( array $argv = [] ): mixed { // Get Application from Registry - $App = Registry::getInstance()->get( 'App' ); + $app = Registry::getInstance()->get( 'App' ); - if( !$App || !$App instanceof \Neuron\Mvc\Application ) + if( !$app || !$app instanceof \Neuron\Mvc\Application ) { Log::error( "Application not found in Registry, skipping maintenance initialization" ); return null; } // Get base path for maintenance file (use application base path, not cwd) - $basePath = $App->getBasePath(); + $basePath = $app->getBasePath(); // Create maintenance manager $manager = new MaintenanceManager( $basePath ); // Get configuration (if available) $config = null; - $Settings = Registry::getInstance()->get( 'Settings' ); + $settings = Registry::getInstance()->get( 'Settings' ); - if( $Settings && $Settings instanceof \Neuron\Data\Setting\SettingManager ) + if( $settings && $settings instanceof \Neuron\Data\Setting\SettingManager ) { try { // Get the source from the SettingManager - $source = $Settings->getSource(); + $source = $settings->getSource(); $config = MaintenanceConfig::fromSettings( $source ); } catch( \Exception $e ) @@ -62,8 +65,8 @@ public function run( array $Argv = [] ): mixed ); // Register and apply filter globally to all routes - $App->getRouter()->registerFilter( 'maintenance', $filter ); - $App->getRouter()->addFilter( 'maintenance' ); + $app->getRouter()->registerFilter( 'maintenance', $filter ); + $app->getRouter()->addFilter( 'maintenance' ); // Store manager in registry for CLI commands Registry::getInstance()->set( 'maintenance.manager', $manager ); diff --git a/resources/app/Initializers/PasswordResetInitializer.php b/resources/app/Initializers/PasswordResetInitializer.php index ca8dfca..88a2245 100644 --- a/resources/app/Initializers/PasswordResetInitializer.php +++ b/resources/app/Initializers/PasswordResetInitializer.php @@ -20,61 +20,42 @@ class PasswordResetInitializer implements IRunnable { /** * Run the initializer + * @param array $argv + * @return mixed + * @throws \Exception */ - public function run( array $Argv = [] ): mixed + public function run( array $argv = [] ): mixed { // Get Settings from Registry - $Settings = Registry::getInstance()->get( 'Settings' ); + $settings = Registry::getInstance()->get( 'Settings' ); - if( !$Settings || !$Settings instanceof SettingManager ) + if( !$settings || !$settings instanceof SettingManager ) { Log::error( "Settings not found in Registry, skipping password reset initialization" ); return null; } - // Get database configuration - $dbConfig = []; + // Check if database is configured try { - $settingNames = $Settings->getSectionSettingNames( 'database' ); + $settingNames = $settings->getSectionSettingNames( 'database' ); if( !empty( $settingNames ) ) - { - foreach( $settingNames as $name ) - { - $value = $Settings->get( 'database', $name ); - if( $value !== null ) - { - // Convert string values to appropriate types - $dbConfig[$name] = ( $name === 'port' ) ? (int)$value : $value; - } - } - } - } - catch( \Exception $e ) - { - Log::error( "Failed to load database config: " . $e->getMessage() ); - } - - // Only initialize if database is configured - if( !empty( $dbConfig ) ) - { - try { // Get site configuration - $siteUrl = $Settings->get( 'site', 'url' ) ?? 'http://localhost:8000'; - $siteName = $Settings->get( 'site', 'name' ) ?? 'Neuron CMS'; + $siteUrl = $settings->get( 'site', 'url' ) ?? 'http://localhost:8000'; + $siteName = $settings->get( 'site', 'name' ) ?? 'Neuron CMS'; // Get email configuration (with fallbacks) - $fromEmail = $Settings->get( 'mail', 'from_email' ) ?? 'noreply@localhost'; - $fromName = $Settings->get( 'mail', 'from_name' ) ?? $siteName; + $fromEmail = $settings->get( 'mail', 'from_email' ) ?? 'noreply@localhost'; + $fromName = $settings->get( 'mail', 'from_name' ) ?? $siteName; // Get token expiration (default 60 minutes) - $tokenExpiration = $Settings->get( 'auth', 'password_reset_expiration' ) ?? 60; + $tokenExpiration = $settings->get( 'auth', 'password_reset_expiration' ) ?? 60; // Initialize components - $tokenRepository = new DatabasePasswordResetTokenRepository( $dbConfig ); - $userRepository = new DatabaseUserRepository( $dbConfig ); + $tokenRepository = new DatabasePasswordResetTokenRepository( $settings ); + $userRepository = new DatabaseUserRepository( $settings ); $passwordHasher = new PasswordHasher(); // Create password reset URL @@ -95,14 +76,12 @@ public function run( array $Argv = [] ): mixed // Store PasswordResetManager in Registry for easy access Registry::getInstance()->set( 'PasswordResetManager', $resetManager ); - - Log::debug( "Password reset system initialized" ); - } - catch( \Exception $e ) - { - Log::error( "Failed to initialize password reset: " . $e->getMessage() ); } } + catch( \Exception $e ) + { + Log::error( "Failed to initialize password reset: " . $e->getMessage() ); + } return null; } diff --git a/resources/public/index.php b/resources/public/index.php index cff61ef..446f0d5 100644 --- a/resources/public/index.php +++ b/resources/public/index.php @@ -11,6 +11,6 @@ error_reporting( E_ALL ); -$App = boot( '../config' ); +$app = boot( '../config' ); -dispatch( $App ); +dispatch( $app ); diff --git a/resources/views/admin/posts/edit.php b/resources/views/admin/posts/edit.php index eef90ac..5cb011a 100644 --- a/resources/views/admin/posts/edit.php +++ b/resources/views/admin/posts/edit.php @@ -23,7 +23,7 @@
- + Markdown supported
diff --git a/resources/views/admin/posts/index.php b/resources/views/admin/posts/index.php index 1e4e5a2..4ae4461 100644 --- a/resources/views/admin/posts/index.php +++ b/resources/views/admin/posts/index.php @@ -24,9 +24,9 @@ getId() ?> getTitle() ) ?> - getAuthor() ) ?> + getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?> getStatus() ) ?> - getViews() ?> + getViewCount() ?> getCreatedAt() ? $post->getCreatedAt()->format( 'Y-m-d H:i' ) : 'N/A' ?> View diff --git a/resources/views/blog/category.php b/resources/views/blog/category.php index c9a4415..a46cfc5 100644 --- a/resources/views/blog/category.php +++ b/resources/views/blog/category.php @@ -23,7 +23,7 @@

- By getAuthor() ) ?> + By getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?> on getCreatedAt() ? $post->getCreatedAt()->format( 'F j, Y' ) : '' ?>

diff --git a/resources/views/blog/tag.php b/resources/views/blog/tag.php index 1fea0e7..63b2321 100644 --- a/resources/views/blog/tag.php +++ b/resources/views/blog/tag.php @@ -20,7 +20,7 @@

- By getAuthor() ) ?> + By getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?> on getCreatedAt() ? $post->getCreatedAt()->format( 'F j, Y' ) : '' ?>

diff --git a/src/Bootstrap.php b/src/Bootstrap.php index fc87151..eaf5a6e 100644 --- a/src/Bootstrap.php +++ b/src/Bootstrap.php @@ -21,18 +21,18 @@ /** * Bootstrap and initialize a Neuron CMS application. - * + * * This function initializes a complete CMS application with all necessary * components including routing, content management, blog functionality, * and site configuration. It delegates to the MVC boot function but * provides CMS-specific context and naming. * - * @param string $ConfigPath Path to the CMS configuration directory + * @param string $configPath Path to the CMS configuration directory * @return Application Fully configured CMS application instance * @throws \Exception If configuration loading or application initialization fails */ -function boot( string $ConfigPath ) : Application +function boot( string $configPath ) : Application { - return \Neuron\Mvc\boot( $ConfigPath ); + return \Neuron\Mvc\boot( $configPath ); } diff --git a/src/Cms/Auth/AuthManager.php b/src/Cms/Auth/AuthManager.php index 1dda54d..e51b647 100644 --- a/src/Cms/Auth/AuthManager.php +++ b/src/Cms/Auth/AuthManager.php @@ -16,80 +16,80 @@ */ class AuthManager { - private IUserRepository $_UserRepository; - private SessionManager $_SessionManager; - private PasswordHasher $_PasswordHasher; - private int $_MaxLoginAttempts = 5; - private int $_LockoutDuration = 15; // minutes + private IUserRepository $_userRepository; + private SessionManager $_sessionManager; + private PasswordHasher $_passwordHasher; + private int $_maxLoginAttempts = 5; + private int $_lockoutDuration = 15; // minutes public function __construct( - IUserRepository $UserRepository, - SessionManager $SessionManager, - PasswordHasher $PasswordHasher + IUserRepository $userRepository, + SessionManager $sessionManager, + PasswordHasher $passwordHasher ) { - $this->_UserRepository = $UserRepository; - $this->_SessionManager = $SessionManager; - $this->_PasswordHasher = $PasswordHasher; + $this->_userRepository = $userRepository; + $this->_sessionManager = $sessionManager; + $this->_passwordHasher = $passwordHasher; } /** * Attempt to authenticate a user */ - public function attempt( string $Username, string $Password, bool $Remember = false ): bool + public function attempt( string $username, string $password, bool $remember = false ): bool { - $User = $this->_UserRepository->findByUsername( $Username ); + $user = $this->_userRepository->findByUsername( $username ); - if( !$User ) + if( !$user ) { // Perform dummy hash to normalize timing - $this->_PasswordHasher->verify( $Password, '$2y$10$dummyhashtopreventtimingattack1234567890' ); + $this->_passwordHasher->verify( $password, '$2y$10$dummyhashtopreventtimingattack1234567890' ); return false; } // Check if account is locked - if( $User->isLockedOut() ) + if( $user->isLockedOut() ) { return false; } // Check if account is active - if( !$User->isActive() ) + if( !$user->isActive() ) { return false; } // Verify password - if( !$this->validateCredentials( $User, $Password ) ) + if( !$this->validateCredentials( $user, $password ) ) { // Increment failed login attempts - $User->incrementFailedLoginAttempts(); + $user->incrementFailedLoginAttempts(); // Lock account if too many failed attempts - if( $User->getFailedLoginAttempts() >= $this->_MaxLoginAttempts ) + if( $user->getFailedLoginAttempts() >= $this->_maxLoginAttempts ) { - $LockedUntil = (new DateTimeImmutable())->add( new DateInterval( "PT{$this->_LockoutDuration}M" ) ); - $User->setLockedUntil( $LockedUntil ); + $lockedUntil = (new DateTimeImmutable())->add( new DateInterval( "PT{$this->_lockoutDuration}M" ) ); + $user->setLockedUntil( $lockedUntil ); } - $this->_UserRepository->update( $User ); + $this->_userRepository->update( $user ); return false; } // Successful login - reset failed attempts - $User->resetFailedLoginAttempts(); - $User->setLastLoginAt( new DateTimeImmutable() ); + $user->resetFailedLoginAttempts(); + $user->setLastLoginAt( new DateTimeImmutable() ); // Check if password needs rehashing - if( $this->_PasswordHasher->needsRehash( $User->getPasswordHash() ) ) + if( $this->_passwordHasher->needsRehash( $user->getPasswordHash() ) ) { - $User->setPasswordHash( $this->_PasswordHasher->hash( $Password ) ); + $user->setPasswordHash( $this->_passwordHasher->hash( $password ) ); } - $this->_UserRepository->update( $User ); + $this->_userRepository->update( $user ); // Log the user in - $this->login( $User, $Remember ); + $this->login( $user, $remember ); return true; } @@ -97,19 +97,19 @@ public function attempt( string $Username, string $Password, bool $Remember = fa /** * Log a user in */ - public function login( User $User, bool $Remember = false ): void + public function login( User $user, bool $remember = false ): void { // Regenerate session ID to prevent session fixation - $this->_SessionManager->regenerate(); + $this->_sessionManager->regenerate(); // Store user ID in session - $this->_SessionManager->set( 'user_id', $User->getId() ); - $this->_SessionManager->set( 'user_role', $User->getRole() ); + $this->_sessionManager->set( 'user_id', $user->getId() ); + $this->_sessionManager->set( 'user_role', $user->getRole() ); // Handle remember me - if( $Remember ) + if( $remember ) { - $this->setRememberToken( $User ); + $this->setRememberToken( $user ); } } @@ -121,16 +121,16 @@ public function logout(): void // Clear remember token if exists if( $this->check() ) { - $User = $this->user(); - if( $User ) + $user = $this->user(); + if( $user ) { - $User->setRememberToken( null ); - $this->_UserRepository->update( $User ); + $user->setRememberToken( null ); + $this->_userRepository->update( $user ); } } // Destroy session - $this->_SessionManager->destroy(); + $this->_sessionManager->destroy(); // Delete remember me cookie if exists if( isset( $_COOKIE['remember_token'] ) ) @@ -145,7 +145,7 @@ public function logout(): void public function check(): bool { // Check session first - if( $this->_SessionManager->has( 'user_id' ) ) + if( $this->_sessionManager->has( 'user_id' ) ) { return true; } @@ -169,22 +169,22 @@ public function user(): ?User return null; } - $UserId = $this->_SessionManager->get( 'user_id' ); + $userId = $this->_sessionManager->get( 'user_id' ); - if( !$UserId ) + if( !$userId ) { return null; } - $User = $this->_UserRepository->findById( $UserId ); - - if( !$User ) + $user = $this->_userRepository->findById( $userId ); + + if( !$user ) { // Clear stale session if user no longer exists $this->logout(); } - - return $User; + + return $user; } /** @@ -196,37 +196,37 @@ public function id(): ?int { return null; } - - return $this->_SessionManager->get( 'user_id' ); + + return $this->_sessionManager->get( 'user_id' ); } /** * Validate user credentials */ - public function validateCredentials( User $User, string $Password ): bool + public function validateCredentials( User $user, string $password ): bool { - return $this->_PasswordHasher->verify( $Password, $User->getPasswordHash() ); + return $this->_passwordHasher->verify( $password, $user->getPasswordHash() ); } /** * Set remember me token for user */ - private function setRememberToken( User $User ): void + private function setRememberToken( User $user ): void { // Generate secure random token - $Token = bin2hex( random_bytes( 32 ) ); + $token = bin2hex( random_bytes( 32 ) ); // Hash the token before storing - $HashedToken = hash( 'sha256', $Token ); + $hashedToken = hash( 'sha256', $token ); // Store hashed token in user record - $User->setRememberToken( $HashedToken ); - $this->_UserRepository->update( $User ); + $user->setRememberToken( $hashedToken ); + $this->_userRepository->update( $user ); // Set cookie with plain token (30 days) setcookie( 'remember_token', - $Token, + $token, time() + (30 * 24 * 60 * 60), '/', '', @@ -238,20 +238,20 @@ private function setRememberToken( User $User ): void /** * Attempt to log in using remember token */ - public function loginUsingRememberToken( string $Token ): bool + public function loginUsingRememberToken( string $token ): bool { // Hash the token to compare with stored hash - $HashedToken = hash( 'sha256', $Token ); + $hashedToken = hash( 'sha256', $token ); - $User = $this->_UserRepository->findByRememberToken( $HashedToken ); + $user = $this->_userRepository->findByRememberToken( $hashedToken ); - if( !$User || !$User->isActive() ) + if( !$user || !$user->isActive() ) { return false; } // Log the user in - $this->login( $User, true ); + $this->login( $user, true ); return true; } @@ -259,28 +259,28 @@ public function loginUsingRememberToken( string $Token ): bool /** * Set maximum login attempts before lockout */ - public function setMaxLoginAttempts( int $MaxLoginAttempts ): self + public function setMaxLoginAttempts( int $maxLoginAttempts ): self { - $this->_MaxLoginAttempts = $MaxLoginAttempts; + $this->_maxLoginAttempts = $maxLoginAttempts; return $this; } /** * Set lockout duration in minutes */ - public function setLockoutDuration( int $LockoutDuration ): self + public function setLockoutDuration( int $lockoutDuration ): self { - $this->_LockoutDuration = $LockoutDuration; + $this->_lockoutDuration = $lockoutDuration; return $this; } /** * Check if user has a specific role */ - public function hasRole( string $Role ): bool + public function hasRole( string $role ): bool { - $User = $this->user(); - return $User && $User->getRole() === $Role; + $user = $this->user(); + return $user && $user->getRole() === $role; } /** @@ -296,13 +296,13 @@ public function isAdmin(): bool */ public function isEditorOrHigher(): bool { - $User = $this->user(); - if( !$User ) + $user = $this->user(); + if( !$user ) { return false; } - return in_array( $User->getRole(), [User::ROLE_ADMIN, User::ROLE_EDITOR] ); + return in_array( $user->getRole(), [User::ROLE_ADMIN, User::ROLE_EDITOR] ); } /** @@ -310,12 +310,12 @@ public function isEditorOrHigher(): bool */ public function isAuthorOrHigher(): bool { - $User = $this->user(); - if( !$User ) + $user = $this->user(); + if( !$user ) { return false; } - return in_array( $User->getRole(), [User::ROLE_ADMIN, User::ROLE_EDITOR, User::ROLE_AUTHOR] ); + return in_array( $user->getRole(), [User::ROLE_ADMIN, User::ROLE_EDITOR, User::ROLE_AUTHOR] ); } } diff --git a/src/Cms/Auth/CsrfTokenManager.php b/src/Cms/Auth/CsrfTokenManager.php index a525ae7..d16db0b 100644 --- a/src/Cms/Auth/CsrfTokenManager.php +++ b/src/Cms/Auth/CsrfTokenManager.php @@ -12,12 +12,12 @@ */ class CsrfTokenManager { - private SessionManager $_SessionManager; - private string $_TokenKey = 'csrf_token'; + private SessionManager $_sessionManager; + private string $_tokenKey = 'csrf_token'; - public function __construct( SessionManager $SessionManager ) + public function __construct( SessionManager $sessionManager ) { - $this->_SessionManager = $SessionManager; + $this->_sessionManager = $sessionManager; } /** @@ -25,9 +25,9 @@ public function __construct( SessionManager $SessionManager ) */ public function generate(): string { - $Token = bin2hex( random_bytes( 32 ) ); - $this->_SessionManager->set( $this->_TokenKey, $Token ); - return $Token; + $token = bin2hex( random_bytes( 32 ) ); + $this->_sessionManager->set( $this->_tokenKey, $token ); + return $token; } /** @@ -35,28 +35,28 @@ public function generate(): string */ public function getToken(): string { - if( !$this->_SessionManager->has( $this->_TokenKey ) ) + if( !$this->_sessionManager->has( $this->_tokenKey ) ) { return $this->generate(); } - return $this->_SessionManager->get( $this->_TokenKey ); + return $this->_sessionManager->get( $this->_tokenKey ); } /** * Validate a CSRF token */ - public function validate( string $Token ): bool + public function validate( string $token ): bool { - $StoredToken = $this->_SessionManager->get( $this->_TokenKey ); + $storedToken = $this->_sessionManager->get( $this->_tokenKey ); - if( !$StoredToken ) + if( !$storedToken ) { return false; } // Use hash_equals to prevent timing attacks - return hash_equals( $StoredToken, $Token ); + return hash_equals( $storedToken, $token ); } /** diff --git a/src/Cms/Auth/Filters/AuthenticationFilter.php b/src/Cms/Auth/Filters/AuthenticationFilter.php index 76a7025..df21001 100644 --- a/src/Cms/Auth/Filters/AuthenticationFilter.php +++ b/src/Cms/Auth/Filters/AuthenticationFilter.php @@ -17,17 +17,17 @@ */ class AuthenticationFilter extends Filter { - private AuthManager $_AuthManager; - private string $_LoginUrl = '/login'; - private string $_RedirectParam = 'redirect'; + private AuthManager $_authManager; + private string $_loginUrl = '/login'; + private string $_redirectParam = 'redirect'; - public function __construct( AuthManager $AuthManager, string $LoginUrl = '/login' ) + public function __construct( AuthManager $authManager, string $loginUrl = '/login' ) { - $this->_AuthManager = $AuthManager; - $this->_LoginUrl = $LoginUrl; + $this->_authManager = $authManager; + $this->_loginUrl = $loginUrl; parent::__construct( - function( RouteMap $Route ) { $this->checkAuthentication( $Route ); }, + function( RouteMap $route ) { $this->checkAuthentication( $route ); }, null ); } @@ -35,38 +35,38 @@ function( RouteMap $Route ) { $this->checkAuthentication( $Route ); }, /** * Check if user is authenticated */ - protected function checkAuthentication( RouteMap $Route ): void + protected function checkAuthentication( RouteMap $route ): void { - if( !$this->_AuthManager->check() ) + if( !$this->_authManager->check() ) { // Store intended URL for post-login redirect - $IntendedUrl = $_SERVER['REQUEST_URI'] ?? $Route->getPath(); + $intendedUrl = $_SERVER['REQUEST_URI'] ?? $route->getPath(); // Redirect to login page - $separator = ( strpos( $this->_LoginUrl, '?' ) === false ) ? '?' : '&'; - $query = http_build_query( [ $this->_RedirectParam => $IntendedUrl ] ); - $RedirectUrl = $this->_LoginUrl . $separator . $query; + $separator = ( strpos( $this->_loginUrl, '?' ) === false ) ? '?' : '&'; + $query = http_build_query( [ $this->_redirectParam => $intendedUrl ] ); + $redirectUrl = $this->_loginUrl . $separator . $query; - header( 'Location: ' . $RedirectUrl ); + header( 'Location: ' . $redirectUrl ); exit; } // Store authenticated user in Registry for easy access - $User = $this->_AuthManager->user(); - if( $User ) + $user = $this->_authManager->user(); + if( $user ) { - Registry::getInstance()->set( 'Auth.User', $User ); - Registry::getInstance()->set( 'Auth.UserId', $User->getId() ); - Registry::getInstance()->set( 'Auth.UserRole', $User->getRole() ); + Registry::getInstance()->set( 'Auth.User', $user ); + Registry::getInstance()->set( 'Auth.UserId', $user->getId() ); + Registry::getInstance()->set( 'Auth.UserRole', $user->getRole() ); } } /** * Set login URL */ - public function setLoginUrl( string $LoginUrl ): self + public function setLoginUrl( string $loginUrl ): self { - $this->_LoginUrl = $LoginUrl; + $this->_loginUrl = $loginUrl; return $this; } } diff --git a/src/Cms/Auth/Filters/CsrfFilter.php b/src/Cms/Auth/Filters/CsrfFilter.php index 39fea9c..5052ce9 100644 --- a/src/Cms/Auth/Filters/CsrfFilter.php +++ b/src/Cms/Auth/Filters/CsrfFilter.php @@ -17,15 +17,15 @@ */ class CsrfFilter extends Filter { - private CsrfTokenManager $_CsrfManager; - private array $_ExemptMethods = ['GET', 'HEAD', 'OPTIONS']; + private CsrfTokenManager $_csrfManager; + private array $_exemptMethods = ['GET', 'HEAD', 'OPTIONS']; - public function __construct( CsrfTokenManager $CsrfManager ) + public function __construct( CsrfTokenManager $csrfManager ) { - $this->_CsrfManager = $CsrfManager; + $this->_csrfManager = $csrfManager; parent::__construct( - function( RouteMap $Route ) { $this->validateCsrfToken( $Route ); }, + function( RouteMap $route ) { $this->validateCsrfToken( $route ); }, null ); } @@ -33,27 +33,27 @@ function( RouteMap $Route ) { $this->validateCsrfToken( $Route ); }, /** * Validate CSRF token */ - protected function validateCsrfToken( RouteMap $Route ): void + protected function validateCsrfToken( RouteMap $route ): void { - $Method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; // Skip validation for safe methods - if( in_array( strtoupper( $Method ), $this->_ExemptMethods ) ) + if( in_array( strtoupper( $method ), $this->_exemptMethods ) ) { return; } // Get token from request - $Token = $this->getTokenFromRequest(); + $token = $this->getTokenFromRequest(); - if( !$Token ) + if( !$token ) { Log::warning( 'CSRF token missing from request' ); $this->respondForbidden( 'CSRF token missing' ); } // Validate token - if( !$this->_CsrfManager->validate( $Token ) ) + if( !$this->_csrfManager->validate( $token ) ) { Log::warning( 'Invalid CSRF token' ); $this->respondForbidden( 'Invalid CSRF token' ); @@ -83,11 +83,11 @@ private function getTokenFromRequest(): ?string /** * Respond with 403 Forbidden */ - private function respondForbidden( string $Message ): void + private function respondForbidden( string $message ): void { http_response_code( 403 ); echo '

403 Forbidden

'; - echo '

' . htmlspecialchars( $Message ) . '

'; + echo '

' . htmlspecialchars( $message ) . '

'; exit; } } diff --git a/src/Cms/Auth/PasswordHasher.php b/src/Cms/Auth/PasswordHasher.php index b48baa8..1b0b17f 100644 --- a/src/Cms/Auth/PasswordHasher.php +++ b/src/Cms/Auth/PasswordHasher.php @@ -12,72 +12,72 @@ */ class PasswordHasher { - private int $_MinLength = 8; - private bool $_RequireUppercase = true; - private bool $_RequireLowercase = true; - private bool $_RequireNumbers = true; - private bool $_RequireSpecialChars = false; + private int $_minLength = 8; + private bool $_requireUppercase = true; + private bool $_requireLowercase = true; + private bool $_requireNumbers = true; + private bool $_requireSpecialChars = false; /** * Hash a password using Argon2id or Bcrypt */ - public function hash( string $Password ): string + public function hash( string $password ): string { // Use Argon2id if available, otherwise fall back to Bcrypt $algorithm = defined( 'PASSWORD_ARGON2ID' ) ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT; - return password_hash( $Password, $algorithm ); + return password_hash( $password, $algorithm ); } /** * Verify a password against a hash */ - public function verify( string $Password, string $Hash ): bool + public function verify( string $password, string $hash ): bool { - return password_verify( $Password, $Hash ); + return password_verify( $password, $hash ); } /** * Check if a hash needs to be rehashed (algorithm upgrade) */ - public function needsRehash( string $Hash ): bool + public function needsRehash( string $hash ): bool { $algorithm = defined( 'PASSWORD_ARGON2ID' ) ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT; - return password_needs_rehash( $Hash, $algorithm ); + return password_needs_rehash( $hash, $algorithm ); } /** * Check if password meets strength requirements */ - public function meetsRequirements( string $Password ): bool + public function meetsRequirements( string $password ): bool { // Check minimum length - if( strlen( $Password ) < $this->_MinLength ) + if( strlen( $password ) < $this->_minLength ) { return false; } // Check for uppercase letters - if( $this->_RequireUppercase && !preg_match( '/[A-Z]/', $Password ) ) + if( $this->_requireUppercase && !preg_match( '/[A-Z]/', $password ) ) { return false; } // Check for lowercase letters - if( $this->_RequireLowercase && !preg_match( '/[a-z]/', $Password ) ) + if( $this->_requireLowercase && !preg_match( '/[a-z]/', $password ) ) { return false; } // Check for numbers - if( $this->_RequireNumbers && !preg_match( '/[0-9]/', $Password ) ) + if( $this->_requireNumbers && !preg_match( '/[0-9]/', $password ) ) { return false; } // Check for special characters - if( $this->_RequireSpecialChars && !preg_match( '/[^A-Za-z0-9]/', $Password ) ) + if( $this->_requireSpecialChars && !preg_match( '/[^A-Za-z0-9]/', $password ) ) { return false; } @@ -88,111 +88,111 @@ public function meetsRequirements( string $Password ): bool /** * Get password validation error messages */ - public function getValidationErrors( string $Password ): array + public function getValidationErrors( string $password ): array { - $Errors = []; + $errors = []; - if( strlen( $Password ) < $this->_MinLength ) + if( strlen( $password ) < $this->_minLength ) { - $Errors[] = "Password must be at least {$this->_MinLength} characters long"; + $errors[] = "Password must be at least {$this->_minLength} characters long"; } - if( $this->_RequireUppercase && !preg_match( '/[A-Z]/', $Password ) ) + if( $this->_requireUppercase && !preg_match( '/[A-Z]/', $password ) ) { - $Errors[] = 'Password must contain at least one uppercase letter'; + $errors[] = 'Password must contain at least one uppercase letter'; } - if( $this->_RequireLowercase && !preg_match( '/[a-z]/', $Password ) ) + if( $this->_requireLowercase && !preg_match( '/[a-z]/', $password ) ) { - $Errors[] = 'Password must contain at least one lowercase letter'; + $errors[] = 'Password must contain at least one lowercase letter'; } - if( $this->_RequireNumbers && !preg_match( '/[0-9]/', $Password ) ) + if( $this->_requireNumbers && !preg_match( '/[0-9]/', $password ) ) { - $Errors[] = 'Password must contain at least one number'; + $errors[] = 'Password must contain at least one number'; } - if( $this->_RequireSpecialChars && !preg_match( '/[^A-Za-z0-9]/', $Password ) ) + if( $this->_requireSpecialChars && !preg_match( '/[^A-Za-z0-9]/', $password ) ) { - $Errors[] = 'Password must contain at least one special character'; + $errors[] = 'Password must contain at least one special character'; } - return $Errors; + return $errors; } /** * Set minimum password length */ - public function setMinLength( int $MinLength ): self + public function setMinLength( int $minLength ): self { - $this->_MinLength = $MinLength; + $this->_minLength = $minLength; return $this; } /** * Set whether uppercase letters are required */ - public function setRequireUppercase( bool $RequireUppercase ): self + public function setRequireUppercase( bool $requireUppercase ): self { - $this->_RequireUppercase = $RequireUppercase; + $this->_requireUppercase = $requireUppercase; return $this; } /** * Set whether lowercase letters are required */ - public function setRequireLowercase( bool $RequireLowercase ): self + public function setRequireLowercase( bool $requireLowercase ): self { - $this->_RequireLowercase = $RequireLowercase; + $this->_requireLowercase = $requireLowercase; return $this; } /** * Set whether numbers are required */ - public function setRequireNumbers( bool $RequireNumbers ): self + public function setRequireNumbers( bool $requireNumbers ): self { - $this->_RequireNumbers = $RequireNumbers; + $this->_requireNumbers = $requireNumbers; return $this; } /** * Set whether special characters are required */ - public function setRequireSpecialChars( bool $RequireSpecialChars ): self + public function setRequireSpecialChars( bool $requireSpecialChars ): self { - $this->_RequireSpecialChars = $RequireSpecialChars; + $this->_requireSpecialChars = $requireSpecialChars; return $this; } /** * Configure password requirements from settings */ - public function configure( array $Settings ): self + public function configure( array $settings ): self { - if( isset( $Settings['min_length'] ) ) + if( isset( $settings['min_length'] ) ) { - $this->setMinLength( $Settings['min_length'] ); + $this->setMinLength( $settings['min_length'] ); } - if( isset( $Settings['require_uppercase'] ) ) + if( isset( $settings['require_uppercase'] ) ) { - $this->setRequireUppercase( $Settings['require_uppercase'] ); + $this->setRequireUppercase( $settings['require_uppercase'] ); } - if( isset( $Settings['require_lowercase'] ) ) + if( isset( $settings['require_lowercase'] ) ) { - $this->setRequireLowercase( $Settings['require_lowercase'] ); + $this->setRequireLowercase( $settings['require_lowercase'] ); } - if( isset( $Settings['require_numbers'] ) ) + if( isset( $settings['require_numbers'] ) ) { - $this->setRequireNumbers( $Settings['require_numbers'] ); + $this->setRequireNumbers( $settings['require_numbers'] ); } - if( isset( $Settings['require_special_chars'] ) ) + if( isset( $settings['require_special_chars'] ) ) { - $this->setRequireSpecialChars( $Settings['require_special_chars'] ); + $this->setRequireSpecialChars( $settings['require_special_chars'] ); } return $this; diff --git a/src/Cms/Auth/PasswordResetManager.php b/src/Cms/Auth/PasswordResetManager.php index 1b37ca9..113dc80 100644 --- a/src/Cms/Auth/PasswordResetManager.php +++ b/src/Cms/Auth/PasswordResetManager.php @@ -17,47 +17,47 @@ */ class PasswordResetManager { - private IPasswordResetTokenRepository $_TokenRepository; - private IUserRepository $_UserRepository; - private PasswordHasher $_PasswordHasher; - private string $_ResetUrl; - private string $_FromEmail; - private string $_FromName; - private int $_TokenExpirationMinutes = 60; + private IPasswordResetTokenRepository $_tokenRepository; + private IUserRepository $_userRepository; + private PasswordHasher $_passwordHasher; + private string $_resetUrl; + private string $_fromEmail; + private string $_fromName; + private int $_tokenExpirationMinutes = 60; /** * Constructor * - * @param IPasswordResetTokenRepository $TokenRepository Token repository - * @param IUserRepository $UserRepository User repository - * @param PasswordHasher $PasswordHasher Password hasher - * @param string $ResetUrl Base URL for password reset (token will be appended) - * @param string $FromEmail Email address to send from - * @param string $FromName Name to send from + * @param IPasswordResetTokenRepository $tokenRepository Token repository + * @param IUserRepository $userRepository User repository + * @param PasswordHasher $passwordHasher Password hasher + * @param string $resetUrl Base URL for password reset (token will be appended) + * @param string $fromEmail Email address to send from + * @param string $fromName Name to send from */ public function __construct( - IPasswordResetTokenRepository $TokenRepository, - IUserRepository $UserRepository, - PasswordHasher $PasswordHasher, - string $ResetUrl, - string $FromEmail, - string $FromName = 'System' + IPasswordResetTokenRepository $tokenRepository, + IUserRepository $userRepository, + PasswordHasher $passwordHasher, + string $resetUrl, + string $fromEmail, + string $fromName = 'System' ) { - $this->_TokenRepository = $TokenRepository; - $this->_UserRepository = $UserRepository; - $this->_PasswordHasher = $PasswordHasher; - $this->_ResetUrl = $ResetUrl; - $this->_FromEmail = $FromEmail; - $this->_FromName = $FromName; + $this->_tokenRepository = $tokenRepository; + $this->_userRepository = $userRepository; + $this->_passwordHasher = $passwordHasher; + $this->_resetUrl = $resetUrl; + $this->_fromEmail = $fromEmail; + $this->_fromName = $fromName; } /** * Set token expiration time in minutes */ - public function setTokenExpirationMinutes( int $Minutes ): self + public function setTokenExpirationMinutes( int $minutes ): self { - $this->_TokenExpirationMinutes = $Minutes; + $this->_tokenExpirationMinutes = $minutes; return $this; } @@ -66,39 +66,39 @@ public function setTokenExpirationMinutes( int $Minutes ): self * * Generates a secure token, stores it, and sends an email to the user. * - * @param string $Email User's email address + * @param string $email User's email address * @return bool True if reset email was sent, false if user not found * @throws Exception if email sending fails */ - public function requestReset( string $Email ): bool + public function requestReset( string $email ): bool { // Check if user exists - $User = $this->_UserRepository->findByEmail( $Email ); + $user = $this->_userRepository->findByEmail( $email ); - if( !$User ) + if( !$user ) { // Don't reveal whether email exists in system return true; } // Delete any existing tokens for this email - $this->_TokenRepository->deleteByEmail( $Email ); + $this->_tokenRepository->deleteByEmail( $email ); // Generate secure random token - $PlainToken = bin2hex( random_bytes( 32 ) ); - $HashedToken = hash( 'sha256', $PlainToken ); + $plainToken = bin2hex( random_bytes( 32 ) ); + $hashedToken = hash( 'sha256', $plainToken ); // Create and store token - $Token = new PasswordResetToken( - $Email, - $HashedToken, - $this->_TokenExpirationMinutes + $token = new PasswordResetToken( + $email, + $hashedToken, + $this->_tokenExpirationMinutes ); - $this->_TokenRepository->create( $Token ); + $this->_tokenRepository->create( $token ); // Send reset email - $this->sendResetEmail( $Email, $PlainToken ); + $this->sendResetEmail( $email, $plainToken ); return true; } @@ -106,62 +106,62 @@ public function requestReset( string $Email ): bool /** * Validate a reset token * - * @param string $PlainToken Plain text token from URL + * @param string $plainToken Plain text token from URL * @return PasswordResetToken|null Token if valid and not expired, null otherwise */ - public function validateToken( string $PlainToken ): ?PasswordResetToken + public function validateToken( string $plainToken ): ?PasswordResetToken { - $HashedToken = hash( 'sha256', $PlainToken ); + $hashedToken = hash( 'sha256', $plainToken ); - $Token = $this->_TokenRepository->findByToken( $HashedToken ); + $token = $this->_tokenRepository->findByToken( $hashedToken ); - if( !$Token || $Token->isExpired() ) + if( !$token || $token->isExpired() ) { return null; } - return $Token; + return $token; } /** * Reset password using a valid token * - * @param string $PlainToken Plain text token from URL - * @param string $NewPassword New password to set + * @param string $plainToken Plain text token from URL + * @param string $newPassword New password to set * @return bool True if password was reset, false if token invalid or expired * @throws Exception if password doesn't meet requirements or update fails */ - public function resetPassword( string $PlainToken, string $NewPassword ): bool + public function resetPassword( string $plainToken, string $newPassword ): bool { // Validate token - $Token = $this->validateToken( $PlainToken ); + $token = $this->validateToken( $plainToken ); - if( !$Token ) + if( !$token ) { return false; } // Validate new password - if( !$this->_PasswordHasher->meetsRequirements( $NewPassword ) ) + if( !$this->_passwordHasher->meetsRequirements( $newPassword ) ) { - $Errors = $this->_PasswordHasher->getValidationErrors( $NewPassword ); - throw new Exception( implode( ', ', $Errors ) ); + $errors = $this->_passwordHasher->getValidationErrors( $newPassword ); + throw new Exception( implode( ', ', $errors ) ); } // Find user - $User = $this->_UserRepository->findByEmail( $Token->getEmail() ); + $user = $this->_userRepository->findByEmail( $token->getEmail() ); - if( !$User ) + if( !$user ) { return false; } // Update password - $User->setPasswordHash( $this->_PasswordHasher->hash( $NewPassword ) ); - $this->_UserRepository->update( $User ); + $user->setPasswordHash( $this->_passwordHasher->hash( $newPassword ) ); + $this->_userRepository->update( $user ); // Delete the token - $this->_TokenRepository->deleteByToken( hash( 'sha256', $PlainToken ) ); + $this->_tokenRepository->deleteByToken( hash( 'sha256', $plainToken ) ); return true; } @@ -173,23 +173,23 @@ public function resetPassword( string $PlainToken, string $NewPassword ): bool */ public function cleanupExpiredTokens(): int { - return $this->_TokenRepository->deleteExpired(); + return $this->_tokenRepository->deleteExpired(); } /** * Send password reset email * - * @param string $Email Recipient email - * @param string $PlainToken Plain text token to include in URL + * @param string $email Recipient email + * @param string $plainToken Plain text token to include in URL * @throws Exception if email sending fails */ - private function sendResetEmail( string $Email, string $PlainToken ): void + private function sendResetEmail( string $email, string $plainToken ): void { - $ResetLink = $this->_ResetUrl . '?token=' . urlencode( $PlainToken ); + $resetLink = $this->_resetUrl . '?token=' . urlencode( $plainToken ); - $Subject = 'Password Reset Request'; + $subject = 'Password Reset Request'; - $Body = << @@ -210,10 +210,10 @@ private function sendResetEmail( string $Email, string $PlainToken ): void

You have requested to reset your password. Click the button below to proceed:

- Reset Password + Reset Password

Or copy and paste this link into your browser:

-

{$ResetLink}

-

This link will expire in {$this->_TokenExpirationMinutes} minutes.

+

{$resetLink}

+

This link will expire in {$this->_tokenExpirationMinutes} minutes.

If you did not request a password reset, please ignore this email.