Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ coverage*
.env.*.local

var/

# Runtime directories (logs, cache, temporary files)
storage/
resources/storage/
2 changes: 1 addition & 1 deletion config/neuron.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

system:
timezone: UTC
base_path: resources
base_path: .
routes_path: resources/config

logging:
Expand Down
7 changes: 6 additions & 1 deletion resources/views/http_codes/404.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<div class="row">
<div class="col">
<h1 class="centered">The page you are looking for does not exist.</h1>
<h1 class="centered text-secondary">404</h1>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="centered">The page you are looking for does not exist.</h2>
Comment thread
ljonesfl marked this conversation as resolved.
Comment thread
ljonesfl marked this conversation as resolved.
</div>
</div>

Expand Down
2 changes: 1 addition & 1 deletion resources/views/layouts/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
</div>
<?php endif; ?>
<div class="text-center small">
Powered by <a href="https://neuronphp.com" target="_blank">NeuronPHP</a>.
Powered by <a href="https://neuronphp.com" target="_blank">NeuronCMS</a>.
</div>
</footer>
</div>
Expand Down
2 changes: 1 addition & 1 deletion resources/views/layouts/member.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
</div>
<?php endif; ?>
<div class="text-center small">
Powered by <a href="https://neuronphp.com" target="_blank">NeuronPHP</a>.
Powered by <a href="https://neuronphp.com" target="_blank">NeuronCMS</a>.
</div>
</footer>
</div>
Expand Down
258 changes: 258 additions & 0 deletions src/Cms/Cli/Commands/User/ResetPasswordCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
<?php

namespace Neuron\Cms\Cli\Commands\User;

use Neuron\Core\Registry\RegistryKeys;
use Neuron\Cli\Commands\Command;
use Neuron\Cms\Repositories\DatabaseUserRepository;
use Neuron\Cms\Auth\PasswordHasher;
use Neuron\Patterns\Registry;

/**
* Reset user password
*/
class ResetPasswordCommand extends Command
{

/**
* @inheritDoc
*/
public function getName(): string
{
return 'cms:user:reset-password';
}

/**
* @inheritDoc
*/
public function getDescription(): string
{
return 'Reset a user\'s password';
}

/**
* Configure the command
*/
public function configure(): void
{
$this->addOption( 'username', 'u', true, 'Username of the user' );
$this->addOption( 'email', 'e', true, 'Email of the user' );
}

/**
* Execute the command
*/
public function execute( array $parameters = [] ): int
{
$this->output->writeln( "\n╔═══════════════════════════════════════╗" );
$this->output->writeln( "║ Neuron CMS - Reset User Password ║" );
$this->output->writeln( "╚═══════════════════════════════════════╝\n" );

// Load database configuration
$repository = $this->getUserRepository();
if( !$repository )
{
return 1;
}

$hasher = new PasswordHasher();
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Load password policy from configuration
try
{
$settings = Registry::getInstance()->get( RegistryKeys::SETTINGS );

if( $settings )
{
// Read password policy from auth.passwords section
$minLength = $settings->get( 'auth', 'passwords', 'min_length' );
$requireUppercase = $settings->get( 'auth', 'passwords', 'require_uppercase' );
$requireLowercase = $settings->get( 'auth', 'passwords', 'require_lowercase' );
$requireNumbers = $settings->get( 'auth', 'passwords', 'require_numbers' );
$requireSpecialChars = $settings->get( 'auth', 'passwords', 'require_special_chars' );

// Configure hasher with policy
if( $minLength !== null )
{
$hasher->setMinLength( (int)$minLength );
}

if( $requireUppercase !== null )
{
$hasher->setRequireUppercase( (bool)$requireUppercase );
}

if( $requireLowercase !== null )
{
$hasher->setRequireLowercase( (bool)$requireLowercase );
}

if( $requireNumbers !== null )
{
$hasher->setRequireNumbers( (bool)$requireNumbers );
}

if( $requireSpecialChars !== null )
{
$hasher->setRequireSpecialChars( (bool)$requireSpecialChars );
}
}
}
catch( \Exception $e )
{
// Fall back to defaults on any exception
}

// Get username or email from options or prompt
$username = $this->input->getOption( 'username' );
$email = $this->input->getOption( 'email' );

// If neither provided, prompt for identifier
if( !$username && !$email )
{
$identifier = $this->prompt( "Enter username or email: " );
$identifier = trim( $identifier );

if( empty( $identifier ) )
{
$this->output->error( "Username or email is required!" );
return 1;
}

// Determine if it's an email or username
if( filter_var( $identifier, FILTER_VALIDATE_EMAIL ) )
{
$email = $identifier;
}
else
{
$username = $identifier;
}
}

// Find the user
$user = null;
if( $username )
{
$user = $repository->findByUsername( $username );
if( !$user )
{
$this->output->error( "User '$username' not found!" );
return 1;
}
}
elseif( $email )
{
$user = $repository->findByEmail( $email );
if( !$user )
{
$this->output->error( "User with email '$email' not found!" );
return 1;
}
}

// Display user info
$this->output->writeln( "User found:" );
$this->output->writeln( " ID: " . $user->getId() );
$this->output->writeln( " Username: " . $user->getUsername() );
$this->output->writeln( " Email: " . $user->getEmail() );
$this->output->writeln( " Role: " . $user->getRole() );
$this->output->writeln( "" );

// Confirm action
$confirm = $this->prompt( "Reset password for this user? (yes/no) [no]: " );
if( strtolower( trim( $confirm ) ) !== 'yes' )
{
$this->output->warning( "Password reset cancelled." );
return 0;
}

// Get new password
$password = $this->secret( "\nEnter new password (min 8 characters): " );

if( strlen( $password ) < 8 )
{
$this->output->error( "Password must be at least 8 characters long!" );
return 1;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated

// Validate password
if( !$hasher->meetsRequirements( $password ) )
{
$this->output->error( "Password does not meet requirements:" );

foreach( $hasher->getValidationErrors( $password ) as $error )
{
$this->output->writeln( " - $error" );
}

$this->output->writeln( "" );
return 1;
}

// Confirm password
$confirmPassword = $this->secret( "Confirm new password: " );

if( $password !== $confirmPassword )
{
$this->output->error( "Passwords do not match!" );
return 1;
}

// Update password
try
{
$user->setPasswordHash( $hasher->hash( $password ) );
$user->setUpdatedAt( new \DateTimeImmutable() );

// Clear any lockout
$user->setFailedLoginAttempts( 0 );
$user->setLockedUntil( null );

$success = $repository->update( $user );

if( !$success )
{
$this->output->error( "Failed to update password in database" );
return 1;
}

$this->output->success( "Password reset successfully for user: " . $user->getUsername() );
Comment thread
cursor[bot] marked this conversation as resolved.
$this->output->writeln( "" );

return 0;
}
catch( \Exception $e )
{
$this->output->error( "Error resetting password: " . $e->getMessage() );
return 1;
}
}

/**
* Get user repository
*
* Protected to allow mocking in tests
*/
protected function getUserRepository(): ?DatabaseUserRepository
{
try
{
$settings = Registry::getInstance()->get( RegistryKeys::SETTINGS );

if( !$settings )
{
$this->output->error( "Application not initialized: Settings not found in Registry" );
$this->output->writeln( "This is a configuration error - the application should load settings into the Registry" );
return null;
}

return new DatabaseUserRepository( $settings );
}
catch( \Exception $e )
{
$this->output->error( "Database connection failed: " . $e->getMessage() );
return null;
}
}
}
5 changes: 5 additions & 0 deletions src/Cms/Cli/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public static function register( Registry $registry ): void
'Neuron\\Cms\\Cli\\Commands\\User\\DeleteCommand'
);

$registry->register(
'cms:user:reset-password',
'Neuron\\Cms\\Cli\\Commands\\User\\ResetPasswordCommand'
);

// Maintenance mode commands
$registry->register(
'cms:maintenance:enable',
Expand Down
Loading