-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/member portal and refactoring. #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds a full member registration and email verification system, new user-related database migrations, member controllers/views/routes, repositories and services, initializers and tests, and renames the primary configuration file from Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Controller as RegistrationController
participant Service as RegistrationService
participant UserRepo as UserRepository
participant Manager as EmailVerificationManager
participant TokenRepo as TokenRepository
participant Sender as EmailSender
User->>Controller: POST /register
Controller->>Service: register(username,email,password,confirm)
Service->>UserRepo: findByUsername / findByEmail
Service->>Service: validateRegistration(...)
Service->>UserRepo: create(user)
Service->>Manager: sendVerificationEmail(user)
Manager->>TokenRepo: deleteByUserId(user.id)
Manager->>TokenRepo: create(token)
Manager->>Sender: send(verification email)
Sender-->>Manager: success
Manager-->>Service: sent
Service-->>Controller: registration result
Controller-->>User: redirect (verify-email-sent)
sequenceDiagram
participant User
participant Controller as RegistrationController
participant Manager as EmailVerificationManager
participant TokenRepo as TokenRepository
participant UserRepo as UserRepository
User->>Controller: GET /verify-email?token=xyz
Controller->>Manager: validateToken(xyz)
Manager->>TokenRepo: findByToken(hashed)
TokenRepo-->>Manager: tokenRecord
Manager->>Manager: check isExpired()
alt valid
Manager->>UserRepo: findById(token.user_id)
Manager->>UserRepo: update(email_verified=true, status=active)
Manager->>TokenRepo: deleteByToken(hashed)
Manager-->>Controller: success
Controller-->>User: render success
else invalid/expired
Manager-->>Controller: failure
Controller-->>User: render failure
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🧬 Code graph analysis (3)src/Cms/Controllers/Member/Registration.php (5)
src/Cms/Auth/EmailVerificationManager.php (6)
resources/app/Initializers/RegistrationInitializer.php (7)
🪛 PHPMD (2.15.0)src/Cms/Controllers/Member/Registration.php66-66: Avoid unused parameters such as '$parameters'. (undefined) (UnusedFormalParameter) 107-107: Avoid unused parameters such as '$parameters'. (undefined) (UnusedFormalParameter) 128-128: Avoid unused local variables such as '$user'. (undefined) (UnusedLocalVariable) 162-162: Avoid unused parameters such as '$parameters'. (undefined) (UnusedFormalParameter) 183-183: Avoid unused parameters such as '$parameters'. (undefined) (UnusedFormalParameter) 268-268: Avoid unused parameters such as '$parameters'. (undefined) (UnusedFormalParameter) resources/app/Initializers/RegistrationInitializer.php29-29: Avoid unused parameters such as '$argv'. (undefined) (UnusedFormalParameter) 🔇 Additional comments (8)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (2)
resources/views/layouts/auth.php (1)
9-9: Verify that $theme is validated to prevent CSS injection.Same concern as in
resources/views/layouts/default.phpLine 22: ensure$themeis validated against an allowlist before use in URLs.resources/views/layouts/member.php (1)
12-12: Verify that $theme is validated to prevent CSS injection.Same concern as in
resources/views/layouts/default.phpLine 22: ensure$themeis validated against an allowlist before use in URLs.
🧹 Nitpick comments (14)
resources/database/migrate/20250111000000_create_users_table.php (1)
41-48: Enforce uniqueness on reset tokensPassword reset lookups typically address records by token; allowing duplicates risks ambiguous matches. Please make the token index unique so each token maps to exactly one row.
- ->addIndex( [ 'token' ] ) + ->addIndex( [ 'token' ], [ 'unique' => true ] )tests/Cms/Cli/Commands/Install/InstallCommandTest.php (1)
109-119: Consider adding functional tests for copyMigrations.The current test only verifies the method exists and is private, but doesn't test the actual copying behavior. Consider adding tests for:
- Successful migration file copying
- Handling of missing source directory
- Skipping existing destination files
- Error handling when copy fails
Example test structure using vfsStream:
public function testCopyMigrationsCopiesToCorrectLocation(): void { // Set up virtual filesystem with source migrations $root = vfsStream::setup('test'); $componentPath = vfsStream::url('test/component/resources/database/migrate'); $projectPath = vfsStream::url('test/project'); // Create source migration file mkdir($componentPath, 0755, true); file_put_contents($componentPath . '/001_create_users.php', '<?php // migration'); // Execute copyMigrations via reflection // Assert file copied to project/db/migrate/ // Assert appropriate messages logged }resources/views/layouts/admin.php (2)
9-9: Consider adding null coalescing for defensive coding.While the
ViewDataInitializershould provide the$themevariable, adding a null coalescing operator would make the code more defensive against initialization issues.Apply this diff:
- <link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.7/dist/<?= htmlspecialchars($theme) ?>/bootstrap.min.css" rel="stylesheet"> + <link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.7/dist/<?= htmlspecialchars($theme ?? 'sandstone') ?>/bootstrap.min.css" rel="stylesheet">
22-22: Consider adding null coalescing for consistency.Similar to Line 6 which uses
$Title ?? 'Admin', consider adding a fallback for$siteName.Apply this diff:
- <?= htmlspecialchars($siteName) ?> Admin + <?= htmlspecialchars($siteName ?? 'Neuron CMS') ?> Adminsrc/Cms/Controllers/Home.php (1)
28-35: Consider using an interface or type hint instead of method_exists.While the defensive checks are safe, using
method_existson a service suggests an unstable contract. Consider defining an interface withisRegistrationEnabled()or using type hints to eliminate the runtime check.Example refactor:
- // Check if registration is enabled - $registrationEnabled = false; - $registrationService = \Neuron\Patterns\Registry::getInstance()->get( 'RegistrationService' ); - - if( $registrationService && method_exists( $registrationService, 'isRegistrationEnabled' ) ) - { - $registrationEnabled = $registrationService->isRegistrationEnabled(); - } + // Check if registration is enabled + $registrationEnabled = false; + $registrationService = \Neuron\Patterns\Registry::getInstance()->get( 'RegistrationService' ); + + if( $registrationService instanceof \Neuron\Cms\Services\Member\RegistrationService ) + { + $registrationEnabled = $registrationService->isRegistrationEnabled(); + }resources/views/layouts/member.php (1)
37-37: Consider extracting the inline SVG fallback to a helper function.The
onerrorattribute contains a very long inline data URL SVG (300+ characters), making the template harder to read and maintain. Consider extracting this to a helper function likedefault_avatar_svg()or storing it as a constant.Example refactor in
src/Cms/View/helpers.php:function default_avatar_data_url(): string { return "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2232%22 height=%2232%22 fill=%22%23ffffff%22 viewBox=%220 0 16 16%22><circle cx=%228%22 cy=%228%22 r=%228%22 fill=%22%236c757d%22/><path d=%22M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10z%22 fill=%22%23ffffff%22/></svg>"; }Then use in template:
<img src="<?= gravatar_url($currentUser->getEmail(), 32) ?>" class="rounded-circle" width="32" height="32" alt="<?= htmlspecialchars($currentUser->getUsername()) ?>" onerror="this.onerror=null; this.src='<?= default_avatar_data_url() ?>';">resources/config/neuron.yaml.example (1)
16-17: Consider documenting cache behavior for development environments.The example shows
enabled: truewhich is appropriate for production. However, developers copying this file might benefit from a comment noting that cache should typically be disabled during development for immediate feedback on view changes.Apply this diff to add a helpful comment:
cache: - enabled: true # Enable view caching + enabled: true # Enable view caching (set to false in development for immediate view updates) storage: file # Cache storage typeresources/views/member/registration/register.php (1)
15-81: LGTM! Consider adding server-side validation notes.The registration form is well-structured with proper CSRF protection, HTML5 validation, and good UX. The client-side validation attributes (pattern, minlength, required) provide immediate feedback to users.
A few observations:
- The password helper text on Line 58 mentions "uppercase, lowercase, and numbers" but only
minlength="8"is enforced client-side. This is acceptable since server-side validation should handle the full password requirements.- Consider adding
maxlengthattributes to prevent excessively long inputs that could cause issues during processing or storage.Optionally add maxlength constraints:
<input type="text" id="username" name="username" class="form-control" placeholder="Choose a username" required autofocus pattern="[a-zA-Z0-9_]+" minlength="3" maxlength="50" + title="3-50 characters, letters, numbers, and underscores only" ><input type="email" id="email" name="email" class="form-control" placeholder="your.email@example.com" required + maxlength="255" >tests/Cms/Models/EmailVerificationTokenTest.php (1)
25-41: Consider edge case in expiration time calculation test.The expiration time calculation test works correctly for the 30-minute case, but the calculation on lines 37-38 only accounts for hours and minutes:
$minutesDiff = ( $diff->h * 60 ) + $diff->i;This could produce incorrect results if the expiration spans multiple days (e.g., if someone configures expiration > 1440 minutes). While this is unlikely given typical use cases (60 minutes default), consider making the test more robust.
Apply this diff to handle multi-day differences:
// Calculate the difference in minutes $diff = $createdAt->diff( $expiresAt ); - $minutesDiff = ( $diff->h * 60 ) + $diff->i; + $minutesDiff = ( $diff->days * 24 * 60 ) + ( $diff->h * 60 ) + $diff->i; $this->assertEquals( $expirationMinutes, $minutesDiff );src/Cms/Controllers/Member/Dashboard.php (1)
43-45: Consider extracting CSRF token generation to a helper method.The CSRF token generation pattern (instantiating
CsrfTokenManager, callinggetToken(), and storing in Registry) is repeated across multiple controllers (Dashboard and Profile). Consider extracting this to a protected method in theContentbase class to reduce duplication.For example, add to the
Contentbase class:/** * Generate and store CSRF token in Registry */ protected function ensureCsrfToken(): string { $csrfManager = new CsrfTokenManager( $this->getSessionManager() ); $token = $csrfManager->getToken(); Registry::getInstance()->set( 'Auth.CsrfToken', $token ); return $token; }Then simplify the usage:
- // Generate CSRF token and store in Registry - $csrfManager = new CsrfTokenManager( $this->getSessionManager() ); - Registry::getInstance()->set( 'Auth.CsrfToken', $csrfManager->getToken() ); + $this->ensureCsrfToken();src/Cms/Controllers/Member/Profile.php (1)
87-134: Consider adding password strength validation.The
update()method correctly handles the profile update flow, but when a new password is provided, it only checks confirmation matching. Consider validating the new password strength usingPasswordHasher::meetsRequirements()before calling the updater service.Apply this diff to add password strength validation:
// Validate password change if requested if( !empty( $newPassword ) ) { // Verify current password if( empty( $currentPassword ) || !$this->_hasher->verify( $currentPassword, $user->getPasswordHash() ) ) { $this->redirect( 'member_profile', [], ['error', 'Current password is incorrect'] ); } // Validate new password matches confirmation if( $newPassword !== $confirmPassword ) { $this->redirect( 'member_profile', [], ['error', 'New passwords do not match'] ); } + + // Validate password strength + if( !$this->_hasher->meetsRequirements( $newPassword ) ) + { + $errors = $this->_hasher->getValidationErrors( $newPassword ); + $this->redirect( 'member_profile', [], ['error', implode( ', ', $errors )] ); + } }tests/Cms/Repositories/DatabaseEmailVerificationTokenRepositoryTest.php (1)
44-79: Enable SQLite foreign-key enforcement in tests.Without
PRAGMA foreign_keys = ON, SQLite quietly ignores theON DELETE CASCADEin your schema, so these tests won’t surface regressions where referential cleanup breaks. Flip the pragma right after you grab the PDO handle to keep the test environment aligned with production.Apply this diff:
$property->setAccessible( true ); $this->pdo = $property->getValue( $this->repository ); + + // Ensure SQLite enforces foreign key constraints like production + $this->pdo->exec( 'PRAGMA foreign_keys = ON' );src/Cms/Services/Member/RegistrationService.php (1)
121-129: Log the verification mail failure before swallowing itWe swallow the exception from
sendVerificationEmail()and the comment says “Log error” but no logging happens, so support can’t see when verification delivery breaks. Please log (e.g.,Log::error(...)) before returning so we retain failure visibility while still keeping registration non-fatal.catch( Exception $e ) { - // Log error but don't fail registration - // User can request resend later + // Log error but don't fail registration. User can request resend later. + Log::error( sprintf( + 'Failed to send verification email for user %s: %s', + $user->getEmail(), + $e->getMessage() + ) ); }tests/Cms/Auth/EmailVerificationManagerTest.php (1)
180-187: Update expectation to match guarded status changeOnce
verifyEmail()stops forcing every account to ACTIVE, this callback should assert that we only flip status when it wasSTATUS_INACTIVE(and otherwise preserve the prior status). Please tweak the callback assertion after adjusting the implementation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (53)
examples/config/config.yaml(0 hunks)examples/config/neuron.yaml(1 hunks)readme.md(5 hunks)resources/app/Initializers/RegistrationInitializer.php(1 hunks)resources/app/Initializers/ViewDataInitializer.php(1 hunks)resources/config/auth.yaml(1 hunks)resources/config/database.yaml.example(1 hunks)resources/config/maintenance.yaml.example(1 hunks)resources/config/neuron.yaml(1 hunks)resources/config/neuron.yaml.example(1 hunks)resources/config/routes.yaml(1 hunks)resources/database/migrate/20250111000000_create_users_table.php(1 hunks)resources/database/migrate/20250112000000_create_email_verification_tokens_table.php(1 hunks)resources/views/emails/email-verification.php(1 hunks)resources/views/home/index.php(1 hunks)resources/views/layouts/admin.php(2 hunks)resources/views/layouts/auth.php(2 hunks)resources/views/layouts/default.php(4 hunks)resources/views/layouts/member.php(1 hunks)resources/views/member/dashboard/index.php(1 hunks)resources/views/member/profile/edit.php(1 hunks)resources/views/member/registration/email-verified.php(1 hunks)resources/views/member/registration/register.php(1 hunks)resources/views/member/registration/registration-disabled.php(1 hunks)resources/views/member/registration/verify-email-sent.php(1 hunks)specs/authentication-implementation-plan.md(4 hunks)src/Cms/Auth/EmailVerificationManager.php(1 hunks)src/Cms/Auth/Filters/MemberAuthenticationFilter.php(1 hunks)src/Cms/Cli/Commands/Install/InstallCommand.php(7 hunks)src/Cms/Cli/Commands/Maintenance/EnableCommand.php(1 hunks)src/Cms/Cli/Commands/Queue/InstallCommand.php(3 hunks)src/Cms/Controllers/Home.php(1 hunks)src/Cms/Controllers/Member/Dashboard.php(1 hunks)src/Cms/Controllers/Member/Profile.php(1 hunks)src/Cms/Controllers/Member/Registration.php(1 hunks)src/Cms/Dtos/RegisterUserDto.yaml(1 hunks)src/Cms/Maintenance/README.md(2 hunks)src/Cms/Models/EmailVerificationToken.php(1 hunks)src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php(1 hunks)src/Cms/Repositories/IEmailVerificationTokenRepository.php(1 hunks)src/Cms/Services/Member/RegistrationService.php(1 hunks)tests/BootstrapTest.php(3 hunks)tests/Cms/Auth/EmailVerificationManagerTest.php(1 hunks)tests/Cms/Auth/MemberAuthenticationFilterTest.php(1 hunks)tests/Cms/BlogControllerTest.php(1 hunks)tests/Cms/Cli/Commands/Install/InstallCommandTest.php(1 hunks)tests/Cms/Cli/Commands/User/CreateCommandTest.php(1 hunks)tests/Cms/Maintenance/MaintenanceConfigTest.php(5 hunks)tests/Cms/Models/EmailVerificationTokenTest.php(1 hunks)tests/Cms/Repositories/DatabaseEmailVerificationTokenRepositoryTest.php(1 hunks)tests/Cms/Services/RegistrationServiceTest.php(1 hunks)tests/bootstrap.php(1 hunks)versionlog.md(1 hunks)
💤 Files with no reviewable changes (1)
- examples/config/config.yaml
🧰 Additional context used
🧬 Code graph analysis (28)
resources/views/member/registration/verify-email-sent.php (2)
src/Cms/View/helpers.php (1)
route_path(70-79)src/Cms/Auth/helpers.php (1)
csrf_field(122-126)
resources/views/member/dashboard/index.php (3)
src/Cms/View/helpers.php (1)
route_path(70-79)src/Cms/Models/User.php (3)
getUsername(78-81)isEmailVerified(203-206)getLastLoginAt(379-382)src/Cms/Models/EmailVerificationToken.php (1)
getCreatedAt(91-94)
src/Cms/Controllers/Member/Registration.php (5)
src/Cms/Controllers/Content.php (3)
Content(57-279)getSessionManager(219-227)redirect(237-247)src/Cms/Auth/CsrfTokenManager.php (2)
CsrfTokenManager(13-69)validate(49-60)src/Cms/Auth/EmailVerificationManager.php (4)
EmailVerificationManager(21-243)__construct(39-52)verifyEmail(123-160)resendVerification(169-186)src/Cms/Services/Member/RegistrationService.php (4)
RegistrationService(21-214)__construct(38-51)isRegistrationEnabled(58-61)register(73-139)src/Cms/Auth/SessionManager.php (3)
get(108-112)set(99-103)getFlash(144-150)
resources/views/member/registration/email-verified.php (1)
src/Cms/View/helpers.php (1)
route_path(70-79)
resources/app/Initializers/RegistrationInitializer.php (6)
src/Cms/Auth/EmailVerificationManager.php (2)
EmailVerificationManager(21-243)setTokenExpirationMinutes(57-61)src/Cms/Auth/Filters/MemberAuthenticationFilter.php (1)
MemberAuthenticationFilter(19-105)src/Cms/Auth/PasswordHasher.php (1)
PasswordHasher(13-200)src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (1)
DatabaseEmailVerificationTokenRepository(19-135)src/Cms/Repositories/DatabaseUserRepository.php (1)
DatabaseUserRepository(19-263)src/Cms/Services/Member/RegistrationService.php (1)
RegistrationService(21-214)
src/Cms/Controllers/Member/Profile.php (6)
src/Cms/Controllers/Content.php (3)
Content(57-279)getSessionManager(219-227)redirect(237-247)src/Cms/Repositories/DatabaseUserRepository.php (1)
DatabaseUserRepository(19-263)src/Cms/Auth/PasswordHasher.php (1)
PasswordHasher(13-200)src/Cms/Auth/CsrfTokenManager.php (1)
CsrfTokenManager(13-69)src/Cms/Auth/SessionManager.php (3)
get(108-112)set(99-103)getFlash(144-150)src/Cms/Models/User.php (3)
getPasswordHash(112-115)getUsername(78-81)getRole(129-132)
resources/database/migrate/20250112000000_create_email_verification_tokens_table.php (3)
resources/database/migrate/20250111000000_create_users_table.php (1)
change(13-49)src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (1)
create(37-54)src/Cms/Repositories/IEmailVerificationTokenRepository.php (1)
create(22-22)
resources/app/Initializers/ViewDataInitializer.php (1)
resources/app/Initializers/RegistrationInitializer.php (1)
run(29-127)
resources/views/home/index.php (1)
src/Cms/View/helpers.php (1)
route_path(70-79)
tests/Cms/Cli/Commands/Install/InstallCommandTest.php (1)
src/Cms/Cli/Commands/Install/InstallCommand.php (1)
InstallCommand(18-1021)
tests/Cms/Models/EmailVerificationTokenTest.php (1)
src/Cms/Models/EmailVerificationToken.php (14)
EmailVerificationToken(14-178)getUserId(57-60)getToken(74-77)getCreatedAt(91-94)getExpiresAt(108-111)setId(48-52)getId(40-43)setUserId(65-69)setToken(82-86)setCreatedAt(99-103)setExpiresAt(116-120)isExpired(125-128)toArray(168-177)fromArray(133-163)
src/Cms/Repositories/IEmailVerificationTokenRepository.php (2)
src/Cms/Models/EmailVerificationToken.php (1)
EmailVerificationToken(14-178)src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (6)
create(37-54)findByToken(59-67)findByUserId(72-85)deleteByUserId(90-96)deleteByToken(101-107)deleteExpired(112-120)
tests/Cms/Auth/MemberAuthenticationFilterTest.php (3)
src/Cms/Auth/AuthManager.php (1)
AuthManager(17-321)src/Cms/Auth/Filters/MemberAuthenticationFilter.php (4)
MemberAuthenticationFilter(19-105)setLoginUrl(82-86)setVerifyEmailUrl(91-95)setRequireEmailVerification(100-104)src/Cms/Models/User.php (2)
setEmailVerified(211-215)isEmailVerified(203-206)
src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (3)
src/Cms/Database/ConnectionFactory.php (2)
ConnectionFactory(17-88)createFromSettings(26-36)src/Cms/Models/EmailVerificationToken.php (8)
EmailVerificationToken(14-178)__construct(29-35)getUserId(57-60)getToken(74-77)getCreatedAt(91-94)getExpiresAt(108-111)setId(48-52)fromArray(133-163)src/Cms/Repositories/IEmailVerificationTokenRepository.php (6)
create(22-22)findByToken(30-30)findByUserId(38-38)deleteByUserId(46-46)deleteByToken(54-54)deleteExpired(61-61)
src/Cms/Controllers/Home.php (2)
src/Cms/Services/Member/RegistrationService.php (1)
isRegistrationEnabled(58-61)src/Cms/Controllers/Content.php (3)
getTitle(138-141)getName(120-123)getDescription(156-159)
tests/Cms/Auth/EmailVerificationManagerTest.php (3)
src/Cms/Auth/EmailVerificationManager.php (7)
EmailVerificationManager(21-243)setTokenExpirationMinutes(57-61)sendVerificationEmail(72-94)validateToken(102-114)verifyEmail(123-160)resendVerification(169-186)cleanupExpiredTokens(193-196)src/Cms/Models/EmailVerificationToken.php (4)
EmailVerificationToken(14-178)setId(48-52)getId(40-43)getUserId(57-60)src/Cms/Models/User.php (3)
setUsername(86-90)setEmailVerified(211-215)isEmailVerified(203-206)
resources/views/member/registration/registration-disabled.php (1)
src/Cms/View/helpers.php (1)
route_path(70-79)
src/Cms/Auth/Filters/MemberAuthenticationFilter.php (3)
src/Cms/Auth/AuthManager.php (1)
AuthManager(17-321)src/Cms/Models/User.php (2)
isEmailVerified(203-206)getRole(129-132)src/Cms/Auth/SessionManager.php (1)
set(99-103)
src/Cms/Services/Member/RegistrationService.php (4)
src/Cms/Auth/EmailVerificationManager.php (3)
EmailVerificationManager(21-243)__construct(39-52)sendVerificationEmail(72-94)src/Cms/Auth/PasswordHasher.php (4)
PasswordHasher(13-200)hash(24-30)meetsRequirements(53-86)getValidationErrors(91-121)src/Cms/Events/UserCreatedEvent.php (1)
UserCreatedEvent(13-23)src/Cms/Models/User.php (4)
setUsername(86-90)setPasswordHash(120-124)setRole(137-141)setEmailVerified(211-215)
resources/views/member/registration/register.php (2)
src/Cms/View/helpers.php (1)
route_path(70-79)src/Cms/Auth/helpers.php (1)
csrf_field(122-126)
tests/Cms/Repositories/DatabaseEmailVerificationTokenRepositoryTest.php (3)
src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (7)
DatabaseEmailVerificationTokenRepository(19-135)create(37-54)findByToken(59-67)findByUserId(72-85)deleteByUserId(90-96)deleteByToken(101-107)deleteExpired(112-120)src/Cms/Models/EmailVerificationToken.php (10)
EmailVerificationToken(14-178)getId(40-43)getUserId(57-60)getToken(74-77)setUserId(65-69)setToken(82-86)setCreatedAt(99-103)setExpiresAt(116-120)getCreatedAt(91-94)getExpiresAt(108-111)src/Cms/Repositories/IEmailVerificationTokenRepository.php (6)
create(22-22)findByToken(30-30)findByUserId(38-38)deleteByUserId(46-46)deleteByToken(54-54)deleteExpired(61-61)
src/Cms/Auth/EmailVerificationManager.php (5)
src/Cms/Models/EmailVerificationToken.php (5)
EmailVerificationToken(14-178)__construct(29-35)getId(40-43)isExpired(125-128)getUserId(57-60)src/Cms/Services/Email/Sender.php (5)
Sender(17-271)to(40-44)subject(67-71)template(105-132)send(137-197)src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (6)
__construct(29-32)deleteByUserId(90-96)create(37-54)findByToken(59-67)deleteByToken(101-107)deleteExpired(112-120)src/Cms/Repositories/IEmailVerificationTokenRepository.php (5)
deleteByUserId(46-46)create(22-22)findByToken(30-30)deleteByToken(54-54)deleteExpired(61-61)src/Cms/Models/User.php (3)
isEmailVerified(203-206)setEmailVerified(211-215)getUsername(78-81)
resources/views/layouts/member.php (3)
src/Cms/View/helpers.php (2)
route_path(70-79)gravatar_url(23-27)src/Cms/Models/User.php (1)
getUsername(78-81)src/Cms/Auth/helpers.php (1)
csrf_field(122-126)
resources/views/member/profile/edit.php (3)
src/Cms/View/helpers.php (1)
route_path(70-79)src/Cms/Auth/helpers.php (1)
csrf_field(122-126)src/Cms/Models/User.php (2)
getUsername(78-81)getTimezone(396-399)
src/Cms/Models/EmailVerificationToken.php (2)
src/Cms/Auth/EmailVerificationManager.php (1)
__construct(39-52)src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (1)
__construct(29-32)
src/Cms/Controllers/Member/Dashboard.php (4)
src/Cms/Controllers/Content.php (2)
Content(57-279)getSessionManager(219-227)src/Cms/Auth/CsrfTokenManager.php (1)
CsrfTokenManager(13-69)src/Cms/Auth/SessionManager.php (2)
get(108-112)set(99-103)src/Cms/Models/User.php (1)
getUsername(78-81)
tests/Cms/Services/RegistrationServiceTest.php (4)
src/Cms/Auth/EmailVerificationManager.php (1)
EmailVerificationManager(21-243)src/Cms/Auth/PasswordHasher.php (1)
PasswordHasher(13-200)src/Cms/Services/Member/RegistrationService.php (3)
RegistrationService(21-214)isRegistrationEnabled(58-61)register(73-139)src/Cms/Models/User.php (3)
getUsername(78-81)getRole(129-132)isEmailVerified(203-206)
resources/database/migrate/20250111000000_create_users_table.php (2)
resources/database/migrate/20250112000000_create_email_verification_tokens_table.php (1)
change(13-26)src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (1)
create(37-54)
🪛 PHPMD (2.15.0)
resources/app/Initializers/RegistrationInitializer.php
29-29: Avoid unused parameters such as '$argv'. (undefined)
(UnusedFormalParameter)
src/Cms/Controllers/Member/Profile.php
48-48: Avoid unused parameters such as '$parameters'. (undefined)
(UnusedFormalParameter)
87-87: Avoid unused parameters such as '$parameters'. (undefined)
(UnusedFormalParameter)
resources/app/Initializers/ViewDataInitializer.php
37-37: Avoid unused parameters such as '$argv'. (undefined)
(UnusedFormalParameter)
tests/Cms/Auth/MemberAuthenticationFilterTest.php
129-129: Avoid unused local variables such as '$filter'. (undefined)
(UnusedLocalVariable)
src/Cms/Controllers/Member/Dashboard.php
33-33: Avoid unused parameters such as '$parameters'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (53)
tests/Cms/Cli/Commands/User/CreateCommandTest.php (1)
28-31: Config fixture rename looks goodThe virtual config now targets neuron.yaml, keeping the test aligned with the new default filename.
resources/config/database.yaml.example (1)
4-4: Documentation note updated appropriatelyThe copy instruction now references neuron.yaml, matching the new configuration layout.
resources/config/maintenance.yaml.example (1)
4-4: Maintenance config guidance stays in syncThe rename instruction now points maintainers to neuron.yaml, matching the rest of the docs.
src/Cms/Cli/Commands/Queue/InstallCommand.php (1)
96-110: Queue install messaging aligned with neuron.yamlAll user-facing strings and config lookups now reference config/neuron.yaml, keeping the command in step with the renamed configuration file.
Also applies to: 170-175, 291-330
src/Cms/Dtos/RegisterUserDto.yaml (1)
1-28: Confirm password confirmation enforcementI don’t see a validator ensuring
password_confirmationmatchespassword. Please confirm this equality check exists elsewhere in the registration workflow or add a cross-field validator so mismatched passwords are rejected.resources/views/member/profile/edit.php (1)
1-114: Well-structured profile formThe view covers success/error feedback, escapes user data, includes CSRF protection, and wires all expected fields for profile and password updates—looks great.
readme.md (1)
71-104: Docs stay in lockstep with neuron.yaml renameThe installer checklist, project structure, and configuration sections now point to neuron.yaml and mention the new initializer, keeping the README accurate.
Also applies to: 160-172, 270-282
resources/app/Initializers/ViewDataInitializer.php (1)
37-73: LGTM! Well-designed initializer with sensible defaults.The lazy evaluation pattern using closures is appropriate for dynamic values like
currentYearandisAuthenticated. All shared data entries have sensible fallback values, ensuring views won't break if Registry entries are missing.Note: The static analysis warning about unused
$argvis a false positive—it's required by theIRunnableinterface signature.src/Cms/Cli/Commands/Install/InstallCommand.php (4)
167-169: LGTM! Member directories added appropriately.The new member-related view directories (
/resources/views/member,/resources/views/member/dashboard,/resources/views/member/profile) align with the PR's member portal functionality.
220-220: LGTM! Member views included in publishing.The
memberdirectory is correctly added to the view directories list for publishing.
688-688: LGTM! Config file renamed to neuron.yaml.The configuration file rename from
config.yamltoneuron.yamlis consistently applied throughout the installer.Also applies to: 731-731
783-840: LGTM! Well-implemented migration copying logic.The new
copyMigrations()method properly:
- Creates the migrations directory if needed
- Validates the source directory exists
- Skips existing files to avoid overwrites
- Provides clear feedback messages
- Handles errors gracefully
This is a cleaner approach than generating migrations from templates.
src/Cms/Cli/Commands/Maintenance/EnableCommand.php (1)
171-171: LGTM! Config file updated to neuron.yaml.The configuration file reference is correctly updated, consistent with the project-wide rename.
versionlog.md (1)
5-6: LGTM! Changelog entries accurately reflect the changes.The new entries clearly document the timezone support and configuration file rename.
tests/BootstrapTest.php (1)
47-48: LGTM! Test updated for neuron.yaml configuration.The test file references are correctly updated to use
neuron.yamlwhile maintaining the same test coverage and logic.Also applies to: 64-65, 84-84
tests/bootstrap.php (1)
11-20: LGTM! Good test isolation with ViewDataProvider.Initializing the
ViewDataProviderin the test bootstrap ensures tests have predictable, isolated view data. This mirrors the productionViewDataInitializerbehavior and prevents tests from depending on external configuration.tests/Cms/BlogControllerTest.php (1)
71-78: LGTM! ViewDataProvider initialization aligns with view requirements.The test setup properly initializes shared view data that templates expect, ensuring tests accurately reflect the runtime environment.
resources/config/routes.yaml (1)
254-304: LGTM! Member routes are well-structured.The new member-related routes follow RESTful conventions with appropriate HTTP methods and filters. Public registration/verification routes are correctly unprotected, while dashboard and profile routes are properly protected with the
memberfilter.resources/views/layouts/default.php (1)
22-22: No theme validation required — theme is safely constrained to static configuration values.The
$themevariable originates from a YAML configuration source (Neuron\Data\Setting\Source\Yaml), not user input. The configuration file explicitly defines valid theme values (admin: vapor,subscriber: sandstone,guest: sandstone), and no user-editable theme setting exists. Since the theme is limited to these predefined, legitimate Bootswatch theme names from static configuration, CSS injection is not possible. Thehtmlspecialchars()call is appropriate defensive encoding for the template context.Likely an incorrect or invalid review comment.
resources/views/member/registration/registration-disabled.php (1)
1-26: LGTM!This is a clean, static informational view. The navigation links properly use the
route_pathhelper, and there are no security concerns with this implementation.resources/config/neuron.yaml.example (1)
50-78: Secure defaults for member registration.The member configuration uses secure defaults:
- Default role is 'subscriber' (not 'admin')
- Email verification is required
- Token expiration of 60 minutes is reasonable
These settings follow security best practices for user registration systems.
resources/views/member/dashboard/index.php (1)
1-64: LGTM! Proper output escaping and null handling.The dashboard view demonstrates good security practices:
- All user data is properly escaped with
htmlspecialchars()(lines 7, 35-36)- Null checks are implemented for optional date fields (lines 56, 58)
- Framework helpers (
route_path) are used consistentlyexamples/config/neuron.yaml (1)
38-57: LGTM! Secure member configuration defaults.The member registration configuration follows security best practices:
- Default role is 'subscriber' (line 45)
- Email verification is required (line 49)
- Reasonable token expiration of 60 minutes (line 57)
resources/views/home/index.php (2)
12-17: LGTM! Conditional Sign Up button with proper configuration check.The conditional rendering of the Sign Up button based on
$RegistrationEnabledproperly integrates with the member registration configuration. This allows the feature to be easily enabled/disabled via config.
23-40: Content updates align with new member portal features.The updated card titles and descriptions (Blog, Member Portal, Admin Portal) accurately reflect the new functionality introduced in this PR.
resources/config/neuron.yaml (1)
49-69: LGTM! Secure member registration configuration.The member configuration block implements security best practices:
- Registration can be toggled via
registration_enabledflag- Default role is 'subscriber' (not privileged)
- Email verification is required before access
- Token expiration of 60 minutes prevents indefinite token validity
specs/authentication-implementation-plan.md (1)
620-621: LGTM! Documentation updated to reflect config file rename.The references to the configuration file have been properly updated from
config.yamltoneuron.yamlthroughout the authentication implementation plan, maintaining consistency with the config file rename in this PR.Also applies to: 947-947, 1169-1169, 1577-1578
resources/config/auth.yaml (1)
42-52: LGTM! Well-documented configuration.The member portal configuration is well-structured with clear, descriptive comments and sensible default values. The 60-minute token expiration and 60-second resend throttle provide a good balance between security and user experience.
tests/Cms/Models/EmailVerificationTokenTest.php (1)
11-136: Excellent test coverage!The test suite comprehensively covers all aspects of the EmailVerificationToken model, including constructor initialization, timestamp handling, expiration logic, and serialization. The inclusion of partial data testing (lines 124-136) is particularly good for ensuring robustness.
resources/database/migrate/20250112000000_create_email_verification_tokens_table.php (1)
13-26: Well-structured migration with proper foreign key constraint.The foreign key with
CASCADEdelete is appropriate here—when a user is deleted, their verification tokens should be automatically removed. The indexes onuser_id,token, andexpires_atsupport efficient lookups and cleanup operations.resources/views/emails/email-verification.php (1)
1-183: Excellent email template with proper security measures!The email verification template is well-crafted with:
- Proper XSS prevention through
htmlspecialchars()on all dynamic content- Inline CSS for maximum email client compatibility
- Responsive design with mobile-friendly styling
- Clear call-to-action with both button and copyable link
- Professional layout and branding
The template effectively communicates urgency with the expiration notice and provides fallback options for users who have trouble with the button.
resources/views/member/registration/email-verified.php (1)
1-42: LGTM! Clear and user-friendly verification result page.The view effectively handles both success and failure states with:
- Appropriate visual feedback using Bootstrap Icons
- Properly escaped messages with sensible fallbacks
- Clear action buttons for each state
- Good UX with contextual next steps
src/Cms/Controllers/Member/Dashboard.php (1)
33-60: LGTM! Well-structured dashboard controller.The dashboard controller properly:
- Retrieves the authenticated user with appropriate error handling
- Generates and stores a CSRF token for form protection
- Prepares comprehensive view data
- Renders the appropriate view
The static analysis warning about the unused
$parameterscan be safely ignored—it's required by the routing framework's method signature convention.src/Cms/Controllers/Member/Profile.php (2)
48-79: LGTM! Profile edit form properly secured.The
edit()method correctly:
- Validates authenticated user presence
- Generates CSRF token for form protection
- Retrieves timezone data for the form
- Handles flash messages for user feedback
The static analysis warning about unused
$parameterscan be ignored—it's required by the routing framework convention.
103-116: Password validation logic is secure and correct.The password change validation properly:
- Verifies the current password before allowing changes (prevents unauthorized password changes if session is compromised)
- Ensures new password matches confirmation
- Only requires current password when changing password
The flow correctly prevents password changes without proper authentication.
tests/Cms/Auth/MemberAuthenticationFilterTest.php (1)
69-160: Solid coverage of the happy pathNice job covering the verified-user path and gating the tricky exit() flows. This gives confidence around Registry wiring.
src/Cms/Models/EmailVerificationToken.php (1)
29-176: Token entity looks cleanConstructor defaults, expiry math, and the array mappers all check out.
src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (8)
19-32: LGTM!The constructor properly delegates connection creation to
ConnectionFactory, which handles configuration validation and adapter support.
37-54: LGTM!The
createmethod correctly uses prepared statements, formats timestamps appropriately, and assigns the generated ID to the token before returning.
59-67: LGTM!The
findByTokenmethod correctly uses a prepared statement and appropriately returns null when no token is found.
72-85: LGTM!The
findByUserIdmethod correctly retrieves the most recent token usingORDER BY created_at DESCand handles the case when no token exists.
90-96: LGTM!The
deleteByUserIdmethod correctly deletes all tokens for a user and returns the count of affected rows.
101-107: LGTM!The
deleteByTokenmethod correctly deletes a specific token and returns a boolean indicating whether a token was deleted.
112-120: LGTM!The
deleteExpiredmethod correctly identifies and removes tokens whereexpires_atis earlier than the current time.
125-134: LGTM!The
mapRowToTokenmethod appropriately delegates row-to-object mapping toEmailVerificationToken::fromArray(), maintaining separation of concerns.tests/Cms/Services/RegistrationServiceTest.php (8)
24-49: LGTM!The
setUpmethod properly initializes dependencies with appropriate mocking. Using a realPasswordHasherandMemory-based settings provides good test isolation while maintaining realistic behavior.
51-79: LGTM!The constructor and registration toggle tests are straightforward and correctly validate the service's ability to check registration status from settings.
81-129: LGTM!The valid registration test comprehensively verifies the happy path, including proper user status (inactive), email verification flag (false), default role assignment, and verification email trigger.
131-150: LGTM!The disabled registration test correctly verifies that registration attempts are rejected with an appropriate error message when the feature is disabled.
152-202: LGTM!The username validation tests comprehensively cover all validation rules: empty, length constraints (3-50 characters), character restrictions (alphanumeric + underscore), and uniqueness checks.
204-234: LGTM!The email validation tests properly verify format validation and uniqueness checks, with appropriate mock setup for the existing email scenario.
236-249: LGTM!The password validation tests correctly verify password matching and strength requirements, leveraging the real
PasswordHasherfor authentic validation behavior.
251-299: LGTM!The email verification bypass test correctly validates that when verification is not required, users are created as active with verified email status, and no verification email is sent.
resources/database/migrate/20250112000000_create_email_verification_tokens_table.php
Show resolved
Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ation_tokens_table.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…o feature/member-portal
Adds member signup and portal functionality. Much refactoring and cleanup.
Summary by CodeRabbit
New Features
Documentation
Chores