From 4ab33ad57fda35d184b4ee69d459c65ec14b95e8 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 12 Nov 2025 11:35:25 -0600 Subject: [PATCH 1/6] renames config.yaml to neuron.yaml. Updates homepage. --- examples/config/{config.yaml => neuron.yaml} | 0 readme.md | 8 ++++---- resources/config/database.yaml.example | 2 +- resources/config/maintenance.yaml.example | 2 +- resources/views/home/index.php | 15 +++++++------- specs/authentication-implementation-plan.md | 10 +++++----- .../Cli/Commands/Install/InstallCommand.php | 4 ++-- .../Commands/Maintenance/EnableCommand.php | 2 +- src/Cms/Cli/Commands/Queue/InstallCommand.php | 10 +++++----- src/Cms/Maintenance/README.md | 4 ++-- tests/BootstrapTest.php | 6 +++--- .../Cli/Commands/User/CreateCommandTest.php | 2 +- .../Cms/Maintenance/MaintenanceConfigTest.php | 20 +++++++++---------- versionlog.md | 3 +++ 14 files changed, 45 insertions(+), 43 deletions(-) rename examples/config/{config.yaml => neuron.yaml} (100%) diff --git a/examples/config/config.yaml b/examples/config/neuron.yaml similarity index 100% rename from examples/config/config.yaml rename to examples/config/neuron.yaml diff --git a/readme.md b/readme.md index 7445484..7f7fffd 100644 --- a/readme.md +++ b/readme.md @@ -68,7 +68,7 @@ The installer will: 1. Create the complete directory structure (app/, config/, db/, public/, resources/, storage/) 2. Publish all view templates (admin panel, blog, auth, layouts) 3. Publish application initializers -4. Create configuration files (config.yaml, routes.yaml, auth.yaml, event-listeners.yaml) +4. Create configuration files (neuron.yaml, routes.yaml, auth.yaml, event-listeners.yaml) 5. Generate the front controller (public/index.php) 6. Set up database migrations 7. Optionally run migrations to create database tables @@ -97,7 +97,7 @@ your-project/ │ ├── config/ │ ├── auth.yaml # Authentication configuration -│ ├── config.yaml # Main application config +│ ├── neuron.yaml # Main application config │ ├── event-listeners.yaml # Event listener configuration │ └── routes.yaml # Route definitions │ @@ -156,7 +156,7 @@ Log in with the admin credentials you created during installation. ### Optional Configuration -If you need to customize settings, edit `config/config.yaml`: +If you need to customize settings, edit `config/neuron.yaml`: ```yaml site: @@ -265,7 +265,7 @@ You can customize routes by editing `config/routes.yaml`. ### Email -Configure email in `config/config.yaml`: +Configure email in `config/neuron.yaml`: ```yaml email: diff --git a/resources/config/database.yaml.example b/resources/config/database.yaml.example index f708280..1dea521 100644 --- a/resources/config/database.yaml.example +++ b/resources/config/database.yaml.example @@ -1,7 +1,7 @@ # Database Configuration # # This file provides database configuration for the CMS component. -# Copy sections to your config/config.yaml +# Copy sections to your config/neuron.yaml database: # Database adapter (mysql, pgsql, sqlite) diff --git a/resources/config/maintenance.yaml.example b/resources/config/maintenance.yaml.example index 7beecf5..03d9db5 100644 --- a/resources/config/maintenance.yaml.example +++ b/resources/config/maintenance.yaml.example @@ -1,7 +1,7 @@ # Maintenance Mode Configuration # # This file provides configuration defaults for the CMS maintenance mode system. -# Copy this file to your config directory and rename to config.yaml (or merge with existing config.yaml) +# Copy this file to your config directory and rename to neuron.yaml (or merge with existing neuron.yaml) maintenance: # Default maintenance message shown to users diff --git a/resources/views/home/index.php b/resources/views/home/index.php index f75fe14..70b6349 100644 --- a/resources/views/home/index.php +++ b/resources/views/home/index.php @@ -10,32 +10,31 @@
- Visit Blog - Admin Dashboard + Blog
-

Content Management

-

Create and manage blog posts, categories, and tags with an intuitive admin interface.

+

Blog

+

Fully functional blog with posts, categories, and tags.

-

User Management

-

Manage users with role-based access control and secure authentication.

+

Member Portal

+

Customizable member section with profile and timezone support.

-

Modern & Fast

-

Built with PHP 8.4+ using modern practices and the Neuron MVC framework.

+

Admin Portal

+

Manage users and blog posts in an easy to use back end.

diff --git a/specs/authentication-implementation-plan.md b/specs/authentication-implementation-plan.md index 9221c10..a50c225 100644 --- a/specs/authentication-implementation-plan.md +++ b/specs/authentication-implementation-plan.md @@ -617,7 +617,7 @@ cms/src/Cms/Auth/Filters/ApiKeyFilter.php **Configure Tiered Rate Limits** ```yaml -# config/config.yaml +# config/neuron.yaml api_limit_free: enabled: true storage: redis @@ -944,7 +944,7 @@ cms/ │ └── show.php ├── config/ │ ├── auth.yaml (new - auth configuration) -│ └── config.yaml (existing - add auth settings) +│ └── neuron.yaml (existing - add auth settings) ├── storage/ │ └── users/ │ └── users.json (file-based user storage) @@ -1166,7 +1166,7 @@ auth: ### Rate Limiting Configuration -**File**: `config/config.yaml` (add to existing) +**File**: `config/neuron.yaml` (add to existing) ```yaml # API Rate Limiting by Tier @@ -1574,8 +1574,8 @@ php neuron user:create admin --role=admin **Step 5: Update Configuration** ```bash -# Add auth configuration to config/config.yaml -cat >> config/config.yaml << 'EOF' +# Add auth configuration to config/neuron.yaml +cat >> config/neuron.yaml << 'EOF' # Authentication auth: diff --git a/src/Cms/Cli/Commands/Install/InstallCommand.php b/src/Cms/Cli/Commands/Install/InstallCommand.php index a0a6f90..e728f30 100644 --- a/src/Cms/Cli/Commands/Install/InstallCommand.php +++ b/src/Cms/Cli/Commands/Install/InstallCommand.php @@ -688,7 +688,7 @@ private function configurePostgresql(): array */ private function saveConfig( array $config ): bool { - $configFile = $this->_projectPath . '/config/config.yaml'; + $configFile = $this->_projectPath . '/config/neuron.yaml'; // If config exists, merge with existing $existingConfig = []; @@ -731,7 +731,7 @@ private function saveConfig( array $config ): bool return false; } - $this->_messages[] = "Created: config/config.yaml"; + $this->_messages[] = "Created: config/neuron.yaml"; return true; } diff --git a/src/Cms/Cli/Commands/Maintenance/EnableCommand.php b/src/Cms/Cli/Commands/Maintenance/EnableCommand.php index 283dedb..58d3564 100644 --- a/src/Cms/Cli/Commands/Maintenance/EnableCommand.php +++ b/src/Cms/Cli/Commands/Maintenance/EnableCommand.php @@ -168,7 +168,7 @@ private function parseAllowedIps( ?string $ipOption, ?MaintenanceConfig $config */ private function loadConfiguration( string $configPath ): ?MaintenanceConfig { - $configFile = $configPath . '/config.yaml'; + $configFile = $configPath . '/neuron.yaml'; if( !file_exists( $configFile ) ) { diff --git a/src/Cms/Cli/Commands/Queue/InstallCommand.php b/src/Cms/Cli/Commands/Queue/InstallCommand.php index 610583e..e6c70ab 100644 --- a/src/Cms/Cli/Commands/Queue/InstallCommand.php +++ b/src/Cms/Cli/Commands/Queue/InstallCommand.php @@ -93,12 +93,12 @@ public function execute( array $parameters = [] ): int if( $this->addQueueConfig() ) { - $this->output->success( "Queue configuration added to config.yaml" ); + $this->output->success( "Queue configuration added to neuron.yaml" ); } else { $this->output->warning( "Could not add queue configuration automatically" ); - $this->output->info( "Please add the following to config/config.yaml:" ); + $this->output->info( "Please add the following to config/neuron.yaml:" ); $this->output->write( "\n" ); $this->output->write( "queue:\n" ); $this->output->write( " driver: database\n" ); @@ -167,7 +167,7 @@ private function isAlreadyInstalled(): bool } // Check for queue config - $configFile = $this->_projectPath . '/config/config.yaml'; + $configFile = $this->_projectPath . '/config/neuron.yaml'; if( !file_exists( $configFile ) ) { @@ -288,11 +288,11 @@ public function change() } /** - * Add queue configuration to config.yaml + * Add queue configuration to neuron.yaml */ private function addQueueConfig(): bool { - $configFile = $this->_projectPath . '/config/config.yaml'; + $configFile = $this->_projectPath . '/config/neuron.yaml'; if( !file_exists( $configFile ) ) { diff --git a/src/Cms/Maintenance/README.md b/src/Cms/Maintenance/README.md index 3f54907..d18eed8 100644 --- a/src/Cms/Maintenance/README.md +++ b/src/Cms/Maintenance/README.md @@ -55,7 +55,7 @@ neuron cms:maintenance:status --json ## Configuration -Add maintenance mode settings to your `config/config.yaml`: +Add maintenance mode settings to your `config/neuron.yaml`: ```yaml maintenance: @@ -159,7 +159,7 @@ You can create a custom maintenance page template: ``` -2. Configure it in `config.yaml`: +2. Configure it in `neuron.yaml`: ```yaml maintenance: diff --git a/tests/BootstrapTest.php b/tests/BootstrapTest.php index 8e02270..fed29db 100644 --- a/tests/BootstrapTest.php +++ b/tests/BootstrapTest.php @@ -44,7 +44,7 @@ protected function tearDown(): void */ public function testBootWithValidConfig() { - // Create config.yaml + // Create neuron.yaml $configContent = <<at( $this->root ) ->setContent( $configContent ); @@ -81,7 +81,7 @@ public function testBootWithValidConfig() // Update config to use virtual filesystem path $configContent = str_replace( '/app', $basePath, $configContent ); - $this->root->getChild( 'config.yaml' )->setContent( $configContent ); + $this->root->getChild( 'neuron.yaml' )->setContent( $configContent ); // Boot the application $app = Boot( $basePath ); diff --git a/tests/Cms/Cli/Commands/User/CreateCommandTest.php b/tests/Cms/Cli/Commands/User/CreateCommandTest.php index 63e79f0..4a817e0 100644 --- a/tests/Cms/Cli/Commands/User/CreateCommandTest.php +++ b/tests/Cms/Cli/Commands/User/CreateCommandTest.php @@ -25,7 +25,7 @@ protected function setUp(): void name: :memory: YAML; - vfsStream::newFile('config/config.yaml') + vfsStream::newFile('config/neuron.yaml') ->at($this->root) ->setContent($configContent); } diff --git a/tests/Cms/Maintenance/MaintenanceConfigTest.php b/tests/Cms/Maintenance/MaintenanceConfigTest.php index a09f886..657c38c 100644 --- a/tests/Cms/Maintenance/MaintenanceConfigTest.php +++ b/tests/Cms/Maintenance/MaintenanceConfigTest.php @@ -55,11 +55,11 @@ public function testConfigFromSettingsSource(): void show_countdown: true YAML; - vfsStream::newFile( 'config.yaml' ) + vfsStream::newFile( 'neuron.yaml' ) ->at( $this->root ) ->setContent( $configContent ); - $settings = new Yaml( vfsStream::url( 'test/config.yaml' ) ); + $settings = new Yaml( vfsStream::url( 'test/neuron.yaml' ) ); $config = new MaintenanceConfig( $settings ); $this->assertTrue( $config->isEnabled() ); @@ -82,11 +82,11 @@ public function testFromSettingsFactory(): void default_message: "Site maintenance" YAML; - vfsStream::newFile( 'config.yaml' ) + vfsStream::newFile( 'neuron.yaml' ) ->at( $this->root ) ->setContent( $configContent ); - $settings = new Yaml( vfsStream::url( 'test/config.yaml' ) ); + $settings = new Yaml( vfsStream::url( 'test/neuron.yaml' ) ); $config = MaintenanceConfig::fromSettings( $settings ); $this->assertInstanceOf( MaintenanceConfig::class, $config ); @@ -104,11 +104,11 @@ public function testAllowedIpsAsString(): void allowed_ips: "192.168.1.1, 10.0.0.1, 172.16.0.0/16" YAML; - vfsStream::newFile( 'config.yaml' ) + vfsStream::newFile( 'neuron.yaml' ) ->at( $this->root ) ->setContent( $configContent ); - $settings = new Yaml( vfsStream::url( 'test/config.yaml' ) ); + $settings = new Yaml( vfsStream::url( 'test/neuron.yaml' ) ); $config = new MaintenanceConfig( $settings ); $ips = $config->getAllowedIps(); @@ -128,11 +128,11 @@ public function testPartialConfigUsesDefaults(): void default_message: "Only message specified" YAML; - vfsStream::newFile( 'config.yaml' ) + vfsStream::newFile( 'neuron.yaml' ) ->at( $this->root ) ->setContent( $configContent ); - $settings = new Yaml( vfsStream::url( 'test/config.yaml' ) ); + $settings = new Yaml( vfsStream::url( 'test/neuron.yaml' ) ); $config = new MaintenanceConfig( $settings ); $this->assertEquals( 'Only message specified', $config->getDefaultMessage() ); @@ -152,11 +152,11 @@ public function testEmptyConfigUsesDefaults(): void name: Test Site YAML; - vfsStream::newFile( 'config.yaml' ) + vfsStream::newFile( 'neuron.yaml' ) ->at( $this->root ) ->setContent( $configContent ); - $settings = new Yaml( vfsStream::url( 'test/config.yaml' ) ); + $settings = new Yaml( vfsStream::url( 'test/neuron.yaml' ) ); $config = new MaintenanceConfig( $settings ); // Should all be defaults diff --git a/versionlog.md b/versionlog.md index 8301633..627d762 100644 --- a/versionlog.md +++ b/versionlog.md @@ -2,6 +2,9 @@ * Large refactoring. * Updated to use the new ORM component. +* Added user timezone support. +* Renamed config.yaml to neuron.yaml + ## 0.8.2 2025-11-11 From 88fd7d9cf560eae0369f4378dd056df07f0a2b46 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 12 Nov 2025 18:12:14 -0600 Subject: [PATCH 2/6] more refactoring. adds memmber portal. --- examples/config/neuron.yaml | 22 ++ readme.md | 3 +- .../Initializers/RegistrationInitializer.php | 128 +++++++ .../app/Initializers/ViewDataInitializer.php | 74 ++++ resources/config/auth.yaml | 12 + resources/config/neuron.yaml | 69 ++++ resources/config/neuron.yaml.example | 78 ++++ resources/config/routes.yaml | 52 +++ .../20250111000000_create_users_table.php | 50 +++ ...create_email_verification_tokens_table.php | 27 ++ resources/views/emails/email-verification.php | 183 ++++++++++ resources/views/home/index.php | 5 +- resources/views/layouts/admin.php | 4 +- resources/views/layouts/auth.php | 4 +- resources/views/layouts/default.php | 12 +- resources/views/layouts/member.php | 72 ++++ resources/views/member/dashboard/index.php | 64 ++++ resources/views/member/profile/edit.php | 118 ++++++ .../member/registration/email-verified.php | 42 +++ .../views/member/registration/register.php | 85 +++++ .../registration/registration-disabled.php | 26 ++ .../member/registration/verify-email-sent.php | 45 +++ src/Cms/Auth/EmailVerificationManager.php | 243 ++++++++++++ .../Filters/MemberAuthenticationFilter.php | 105 ++++++ .../Cli/Commands/Install/InstallCommand.php | 137 ++----- src/Cms/Controllers/Home.php | 10 + src/Cms/Controllers/Member/Dashboard.php | 61 ++++ src/Cms/Controllers/Member/Profile.php | 135 +++++++ src/Cms/Controllers/Member/Registration.php | 283 ++++++++++++++ src/Cms/Dtos/RegisterUserDto.yaml | 28 ++ src/Cms/Models/EmailVerificationToken.php | 178 +++++++++ ...tabaseEmailVerificationTokenRepository.php | 135 +++++++ .../IEmailVerificationTokenRepository.php | 62 ++++ .../Services/Member/RegistrationService.php | 214 +++++++++++ .../Cms/Auth/EmailVerificationManagerTest.php | 345 ++++++++++++++++++ .../Auth/MemberAuthenticationFilterTest.php | 161 ++++++++ tests/Cms/BlogControllerTest.php | 9 + .../Commands/Install/InstallCommandTest.php | 34 +- .../Cms/Models/EmailVerificationTokenTest.php | 137 +++++++ ...seEmailVerificationTokenRepositoryTest.php | 247 +++++++++++++ .../Cms/Services/RegistrationServiceTest.php | 300 +++++++++++++++ tests/bootstrap.php | 11 +- 42 files changed, 3869 insertions(+), 141 deletions(-) create mode 100644 resources/app/Initializers/RegistrationInitializer.php create mode 100644 resources/app/Initializers/ViewDataInitializer.php create mode 100644 resources/config/neuron.yaml create mode 100644 resources/config/neuron.yaml.example create mode 100644 resources/database/migrate/20250111000000_create_users_table.php create mode 100644 resources/database/migrate/20250112000000_create_email_verification_tokens_table.php create mode 100644 resources/views/emails/email-verification.php create mode 100644 resources/views/layouts/member.php create mode 100644 resources/views/member/dashboard/index.php create mode 100644 resources/views/member/profile/edit.php create mode 100644 resources/views/member/registration/email-verified.php create mode 100644 resources/views/member/registration/register.php create mode 100644 resources/views/member/registration/registration-disabled.php create mode 100644 resources/views/member/registration/verify-email-sent.php create mode 100644 src/Cms/Auth/EmailVerificationManager.php create mode 100644 src/Cms/Auth/Filters/MemberAuthenticationFilter.php create mode 100644 src/Cms/Controllers/Member/Dashboard.php create mode 100644 src/Cms/Controllers/Member/Profile.php create mode 100644 src/Cms/Controllers/Member/Registration.php create mode 100644 src/Cms/Dtos/RegisterUserDto.yaml create mode 100644 src/Cms/Models/EmailVerificationToken.php create mode 100644 src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php create mode 100644 src/Cms/Repositories/IEmailVerificationTokenRepository.php create mode 100644 src/Cms/Services/Member/RegistrationService.php create mode 100644 tests/Cms/Auth/EmailVerificationManagerTest.php create mode 100644 tests/Cms/Auth/MemberAuthenticationFilterTest.php create mode 100644 tests/Cms/Models/EmailVerificationTokenTest.php create mode 100644 tests/Cms/Repositories/DatabaseEmailVerificationTokenRepositoryTest.php create mode 100644 tests/Cms/Services/RegistrationServiceTest.php diff --git a/examples/config/neuron.yaml b/examples/config/neuron.yaml index 570a7c6..154004f 100644 --- a/examples/config/neuron.yaml +++ b/examples/config/neuron.yaml @@ -33,3 +33,25 @@ site: blog: path: 'blog' title: 'Neuron Blog' + +# Member Registration and Management Configuration +member: + # Enable or disable user registration + # Set to false to prevent new users from signing up + registration_enabled: true + + # Default role assigned to new users + # Options: admin, editor, author, subscriber + default_role: subscriber + + # Require email verification before users can log in + # When true, users must verify their email before accessing member areas + require_email_verification: true + + # Email verification URL path + # This is appended to the site URL to create the verification link + verification_url: /verify-email + + # Email verification token expiration time (in minutes) + # Tokens older than this will be considered invalid + verification_token_expiration_minutes: 60 diff --git a/readme.md b/readme.md index 7f7fffd..03c1300 100644 --- a/readme.md +++ b/readme.md @@ -88,7 +88,8 @@ your-project/ │ ├── Initializers/ # Application initializers │ │ ├── AuthInitializer.php │ │ ├── MaintenanceInitializer.php -│ │ └── PasswordResetInitializer.php +│ │ ├── PasswordResetInitializer.php +│ │ └── ViewDataInitializer.php │ ├── Jobs/ # Background jobs │ ├── Listeners/ # Event listeners │ ├── Models/ # Domain models diff --git a/resources/app/Initializers/RegistrationInitializer.php b/resources/app/Initializers/RegistrationInitializer.php new file mode 100644 index 0000000..d85353a --- /dev/null +++ b/resources/app/Initializers/RegistrationInitializer.php @@ -0,0 +1,128 @@ +get( 'Settings' ); + + if( !$settings || !$settings instanceof \Neuron\Data\Setting\SettingManager ) + { + Log::error( "Settings not found in Registry, skipping registration initialization" ); + return null; + } + + // Get Application from Registry + $app = Registry::getInstance()->get( 'App' ); + + if( !$app || !$app instanceof \Neuron\Mvc\Application ) + { + Log::error( "Application not found in Registry, skipping registration initialization" ); + return null; + } + + // Get AuthManager from Registry (must be initialized first by AuthInitializer) + $authManager = Registry::getInstance()->get( 'AuthManager' ); + + if( !$authManager ) + { + Log::error( "AuthManager not found in Registry, skipping registration initialization" ); + return null; + } + + // Check if database is configured + try + { + $settingNames = $settings->getSectionSettingNames( 'database' ); + + if( !empty( $settingNames ) ) + { + // Initialize registration components + $userRepository = new DatabaseUserRepository( $settings ); + $tokenRepository = new DatabaseEmailVerificationTokenRepository( $settings ); + $passwordHasher = new PasswordHasher(); + + // Get base path for email templates + $basePath = $settings->get( 'system', 'base_path' ) ?? getcwd(); + + // Get verification URL from settings + $siteUrl = $settings->get( 'site', 'url' ) ?? 'http://localhost:8000'; + $verificationPath = $settings->get( 'member', 'verification_url' ) ?? '/verify-email'; + $verificationUrl = rtrim( $siteUrl, '/' ) . $verificationPath; + + // Create EmailVerificationManager + $verificationManager = new EmailVerificationManager( + $tokenRepository, + $userRepository, + $settings, + $basePath, + $verificationUrl + ); + + // Set token expiration from settings + $tokenExpiration = $settings->get( 'member', 'verification_token_expiration_minutes' ) ?? 60; + $verificationManager->setTokenExpirationMinutes( $tokenExpiration ); + + // Get event emitter if available + $emitter = Registry::getInstance()->get( 'EventEmitter' ); + + // Create RegistrationService + $registrationService = new RegistrationService( + $userRepository, + $passwordHasher, + $verificationManager, + $settings, + $emitter + ); + + // Create member authentication filter + $requireVerification = $settings->get( 'member', 'require_email_verification' ) ?? true; + $memberFilter = new MemberAuthenticationFilter( + $authManager, + '/login', + $requireVerification + ); + + // Register the member filter with the Router + $app->getRouter()->registerFilter( 'member', $memberFilter ); + + // Store services in Registry for easy access + Registry::getInstance()->set( 'EmailVerificationManager', $verificationManager ); + Registry::getInstance()->set( 'RegistrationService', $registrationService ); + + Log::info( "Registration system initialized successfully" ); + } + } + catch( \Exception $e ) + { + Log::error( "Failed to initialize registration system: " . $e->getMessage() ); + } + + return null; + } +} diff --git a/resources/app/Initializers/ViewDataInitializer.php b/resources/app/Initializers/ViewDataInitializer.php new file mode 100644 index 0000000..845a6eb --- /dev/null +++ b/resources/app/Initializers/ViewDataInitializer.php @@ -0,0 +1,74 @@ +share( 'siteName', function() use ( $registry ) { + return $registry->get( 'name' ) ?? 'Neuron CMS'; + }); + + // Application version + $provider->share( 'appVersion', function() use ( $registry ) { + return $registry->get( 'version' ) ?? ''; + }); + + // Current authenticated user + $provider->share( 'currentUser', function() use ( $registry ) { + return $registry->get( 'Auth.User' ); + }); + + // Theme from settings with sensible defaults per context + $provider->share( 'theme', function() use ( $registry ) { + $settings = $registry->get( 'Settings' ); + return $settings?->get( 'theme', 'default' ) ?? 'sandstone'; + }); + + // Current year (useful for copyright notices) + $provider->share( 'currentYear', fn() => date( 'Y' ) ); + + // Check if user is authenticated (convenience helper) + $provider->share( 'isAuthenticated', function() use ( $registry ) { + $authManager = $registry->get( 'AuthManager' ); + return $authManager && $authManager->isAuthenticated(); + }); + + return null; + } +} diff --git a/resources/config/auth.yaml b/resources/config/auth.yaml index 8eeeff4..3752d9e 100644 --- a/resources/config/auth.yaml +++ b/resources/config/auth.yaml @@ -38,3 +38,15 @@ auth: storage: type: file # file or database (future) path: storage/users # Path for file-based storage + +# Member Portal Configuration +member: + # Registration settings + registration_enabled: true # Enable/disable public user registration + require_email_verification: true # Require email verification before account activation + default_role: subscriber # Default role for new members + + # Email verification settings + verification_token_expiration_minutes: 60 # Token validity in minutes + verification_url: /verify-email # Verification endpoint URL + resend_throttle_seconds: 60 # Minimum time between resend requests diff --git a/resources/config/neuron.yaml b/resources/config/neuron.yaml new file mode 100644 index 0000000..a8e27d5 --- /dev/null +++ b/resources/config/neuron.yaml @@ -0,0 +1,69 @@ +# Neuron CMS Configuration +# Main configuration file for the Neuron CMS application + +system: + timezone: UTC + base_path: resources + routes_path: resources/config + +logging: + destination: \Neuron\Log\Destination\File + format: \Neuron\Log\Format\PlainText + file: storage/logs/app.log + level: info + +cache: + enabled: false + storage: file + path: storage/cache/views + ttl: 3600 + html: true + markdown: true + json: false + xml: false + +views: + path: resources/views + +site: + name: 'Neuron CMS' + url: 'https://localhost' + description: 'A modern content management system built with Neuron.' + title: 'Neuron CMS' + +# Theme Configuration +# Controls the Bootswatch theme for different portals +# Available themes: cerulean, cosmo, cyborg, darkly, flatly, journal, litera, +# lumen, lux, materia, minty, morph, pulse, quartz, sandstone, simplex, sketchy, +# slate, solar, spacelab, superhero, united, vapor, yeti, zephyr +theme: + # Admin portal theme (for admin role users) + admin: vapor + + # Member portal theme (for subscriber, editor, author roles) + subscriber: sandstone + + # Guest theme (for public website and login/registration pages) + guest: sandstone + +# Member Registration and Management Configuration +member: + # Enable or disable user registration + # Set to false to prevent new users from signing up + registration_enabled: true + + # Default role assigned to new users + # Options: admin, editor, author, subscriber + default_role: subscriber + + # Require email verification before users can log in + # When true, users must verify their email before accessing member areas + require_email_verification: true + + # Email verification URL path + # This is appended to the site URL to create the verification link + verification_url: /verify-email + + # Email verification token expiration time (in minutes) + # Tokens older than this will be considered invalid + verification_token_expiration_minutes: 60 diff --git a/resources/config/neuron.yaml.example b/resources/config/neuron.yaml.example new file mode 100644 index 0000000..047afbf --- /dev/null +++ b/resources/config/neuron.yaml.example @@ -0,0 +1,78 @@ +# Neuron CMS Configuration Example +# Copy this file to neuron.yaml and customize for your site +# Main configuration file for the Neuron CMS application + +system: + timezone: UTC # Server timezone (e.g., America/New_York, Europe/London, UTC) + base_path: resources # Base path for resources + routes_path: resources/config # Path to routes configuration + +logging: + destination: \Neuron\Log\Destination\File # Log destination class + format: \Neuron\Log\Format\PlainText # Log format class + file: storage/logs/app.log # Log file path + level: info # Log level: debug, info, warning, error + +cache: + enabled: true # Enable view caching + storage: file # Cache storage type + path: storage/cache/views # Cache storage path + ttl: 3600 # Cache time-to-live in seconds + html: true # Cache HTML views + markdown: true # Cache Markdown views + json: false # Cache JSON views + xml: false # Cache XML views + +views: + path: resources/views # Path to view templates + +site: + name: 'Neuron CMS' # Site name + url: 'https://example.com' # Site URL (without trailing slash) + description: 'A modern content management system built with Neuron.' # Site description + title: 'Neuron CMS' # Default page title + +# Theme Configuration +# Controls the Bootswatch theme for different portals +# Themes are role-based: admin, subscriber (member), and guest (public/auth) +# +# Available Bootswatch 5.3+ themes: +# - cerulean: A calm blue sky theme +# - cosmo: An ode to Metro +# - cyborg: Jet black and electric blue +# - darkly: Flatly in night mode +# - flatly: Flat and modern +# - journal: Crisp like a new sheet of paper +# - litera: The medium is the message +# - lumen: Light and shadow +# - lux: A touch of class +# - materia: Material is the metaphor +# - minty: A fresh feel +# - morph: A neumorphic design +# - pulse: A trace of purple +# - quartz: A glassmorphism design +# - sandstone: A touch of warmth +# - simplex: Mini and minimalist +# - sketchy: A hand-drawn look +# - slate: Shades of gunmetal gray +# - solar: A spin on Solarized +# - spacelab: Silvery and sleek +# - superhero: The brave and the blue +# - united: Ubuntu orange and unique font +# - vapor: A cyberpunk aesthetic +# - yeti: A friendly foundation +# - zephyr: Breezy and beautiful +# +# Preview themes at: https://bootswatch.com/ +theme: + # Admin portal theme (for users with admin role) + # Recommended: vapor (dark), darkly, cyborg, slate + admin: vapor + + # Member portal theme (for users with subscriber, editor, author roles) + # Recommended: sandstone, flatly, cosmo, lux, materia + subscriber: sandstone + + # Guest theme (for public website and login/registration pages) + # Recommended: sandstone, flatly, cosmo, litera, lumen, minty + guest: sandstone diff --git a/resources/config/routes.yaml b/resources/config/routes.yaml index a7d1575..68655a0 100644 --- a/resources/config/routes.yaml +++ b/resources/config/routes.yaml @@ -250,3 +250,55 @@ routes: method: GET route: /rss controller: Neuron\Cms\Controllers\Blog@feed + + # Member Registration Routes + register: + method: GET + route: /register + controller: Neuron\Cms\Controllers\Member\Registration@showRegistrationForm + + register_post: + method: POST + route: /register + controller: Neuron\Cms\Controllers\Member\Registration@processRegistration + + verify_email: + method: GET + route: /verify-email + controller: Neuron\Cms\Controllers\Member\Registration@verify + + verify_email_sent: + method: GET + route: /verify-email-sent + controller: Neuron\Cms\Controllers\Member\Registration@showVerificationSent + + resend_verification: + method: POST + route: /resend-verification + controller: Neuron\Cms\Controllers\Member\Registration@resendVerification + + # Member Dashboard Routes + member: + method: GET + route: /member + controller: Neuron\Cms\Controllers\Member\Dashboard@index + filter: member + + member_dashboard: + method: GET + route: /member/dashboard + controller: Neuron\Cms\Controllers\Member\Dashboard@index + filter: member + + # Member Profile Routes + member_profile: + method: GET + route: /member/profile + controller: Neuron\Cms\Controllers\Member\Profile@edit + filter: member + + member_profile_update: + method: PUT + route: /member/profile + controller: Neuron\Cms\Controllers\Member\Profile@update + filter: member diff --git a/resources/database/migrate/20250111000000_create_users_table.php b/resources/database/migrate/20250111000000_create_users_table.php new file mode 100644 index 0000000..70d2774 --- /dev/null +++ b/resources/database/migrate/20250111000000_create_users_table.php @@ -0,0 +1,50 @@ +table( 'users' ); + + $usersTable->addColumn( 'username', 'string', [ 'limit' => 255 ] ) + ->addColumn( 'email', 'string', [ 'limit' => 255 ] ) + ->addColumn( 'password_hash', 'string', [ 'limit' => 255 ] ) + ->addColumn( 'role', 'string', [ 'limit' => 50, 'default' => 'subscriber' ] ) + ->addColumn( 'status', 'string', [ 'limit' => 50, 'default' => 'active' ] ) + ->addColumn( 'email_verified', 'boolean', [ 'default' => false ] ) + ->addColumn( 'two_factor_secret', 'string', [ 'limit' => 255, 'null' => true ] ) + ->addColumn( 'remember_token', 'string', [ 'limit' => 255, 'null' => true ] ) + ->addColumn( 'failed_login_attempts', 'integer', [ 'default' => 0 ] ) + ->addColumn( 'locked_until', 'timestamp', [ 'null' => true ] ) + ->addColumn( 'last_login_at', 'timestamp', [ 'null' => true ] ) + ->addColumn( 'timezone', 'string', [ 'limit' => 50, 'default' => 'UTC' ] ) + ->addColumn( 'created_at', 'timestamp', [ 'default' => 'CURRENT_TIMESTAMP' ] ) + ->addColumn( 'updated_at', 'timestamp', [ 'default' => 'CURRENT_TIMESTAMP', 'update' => 'CURRENT_TIMESTAMP' ] ) + ->addIndex( [ 'username' ], [ 'unique' => true ] ) + ->addIndex( [ 'email' ], [ 'unique' => true ] ) + ->addIndex( [ 'remember_token' ] ) + ->addIndex( [ 'status' ] ) + ->create(); + + // Create password_reset_tokens table + $tokensTable = $this->table( 'password_reset_tokens' ); + + $tokensTable->addColumn( 'email', 'string', [ 'limit' => 255 ] ) + ->addColumn( 'token', 'string', [ 'limit' => 64 ] ) + ->addColumn( 'created_at', 'timestamp', [ 'default' => 'CURRENT_TIMESTAMP' ] ) + ->addColumn( 'expires_at', 'timestamp', [ 'null' => false ] ) + ->addIndex( [ 'email' ] ) + ->addIndex( [ 'token' ] ) + ->addIndex( [ 'expires_at' ] ) + ->create(); + } +} diff --git a/resources/database/migrate/20250112000000_create_email_verification_tokens_table.php b/resources/database/migrate/20250112000000_create_email_verification_tokens_table.php new file mode 100644 index 0000000..2f10e3e --- /dev/null +++ b/resources/database/migrate/20250112000000_create_email_verification_tokens_table.php @@ -0,0 +1,27 @@ +table( 'email_verification_tokens' ); + + $table->addColumn( 'user_id', 'integer', [ 'null' => false ] ) + ->addColumn( 'token', 'string', [ 'limit' => 64 ] ) + ->addColumn( 'created_at', 'timestamp', [ 'default' => 'CURRENT_TIMESTAMP' ] ) + ->addColumn( 'expires_at', 'timestamp', [ 'null' => false ] ) + ->addIndex( [ 'user_id' ] ) + ->addIndex( [ 'token' ] ) + ->addIndex( [ 'expires_at' ] ) + ->addForeignKey( 'user_id', 'users', 'id', [ 'delete' => 'CASCADE', 'update' => 'NO_ACTION' ] ) + ->create(); + } +} diff --git a/resources/views/emails/email-verification.php b/resources/views/emails/email-verification.php new file mode 100644 index 0000000..d568efe --- /dev/null +++ b/resources/views/emails/email-verification.php @@ -0,0 +1,183 @@ + + + + + + Verify Your Email - <?= htmlspecialchars($SiteName) ?> + + + + + + diff --git a/resources/views/home/index.php b/resources/views/home/index.php index 70b6349..b827741 100644 --- a/resources/views/home/index.php +++ b/resources/views/home/index.php @@ -11,6 +11,9 @@
Blog + + Sign Up +
@@ -34,7 +37,7 @@

Admin Portal

-

Manage users and blog posts in an easy to use back end.

+

Manage users and blog posts in an easy-to-use back end.

diff --git a/resources/views/layouts/admin.php b/resources/views/layouts/admin.php index ace0a8c..7a24717 100644 --- a/resources/views/layouts/admin.php +++ b/resources/views/layouts/admin.php @@ -6,7 +6,7 @@ <?= htmlspecialchars($Title ?? 'Admin') ?> - +