Simplifies the namespace hierarchy by removing the intermediate Mod segment. Updates all 118 files including models, services, controllers, middleware, tests, and composer.json autoload configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
7.9 KiB
Tenant Module Review
Updated: 2026-01-21 - All implementations verified complete. Rate limiting, deletion logging, query optimisation, soft deletes for WorkspacePackage, and cache invalidation optimisation implemented
Overview
The Tenant module is the core multi-tenancy system for Host Hub, handling:
- Users and Authentication: User model, 2FA, API tokens, email verification
- Workspaces: The tenant boundary - users belong to workspaces, resources are scoped to workspaces
- Entitlements: Feature access control via packages, boosts, and usage tracking
- Account Management: Account deletion with grace period, settings
- Referrals: Agent referral tracking for the Trees programme
This is a foundational module that other modules depend on heavily.
Production Readiness Score: 92/100 (was 90/100 - cache optimisation and soft deletes added 2026-01-21)
The module has solid architecture, good test coverage for core functionality, proper security patterns, and configuration externalisation. Critical namespace issues fixed in Wave 2. Cache invalidation now uses efficient version-based approach. WorkspacePackage now has soft deletes for audit history.
Critical Issues (Must Fix)
- Workspace.packages() uses wrong namespace: FIXED - Now uses
Mod\Tenant\Models\Package::class - Workspace.bioPages/bioProjects/bioDomains/bioPixels use wrong namespace: FIXED - 12 namespace replacements from
\App\Models\BioLink\*toMod\Web\Models\*across DemoTestUserSeeder.php, WorkspaceDetails.php, and WorkspaceManager.php - User boosts relationship may conflict: User has a
boosts()relationship but Boost hasworkspace_idas the primary foreign key, notuser_id. The relationship exists but the intended use case is unclear - WorkspaceService.get() bypasses user authorisation: VERIFIED SECURE - The
get()method callsgetModel()which correctly queries through$user->workspaces()relationship, ensuring only accessible workspaces are returned. The review item was outdated.
Recommended Improvements
- Add rate limiting to referral tracking: DONE -
ReferralController::track()now has rate limiting applied. - Add logging to account deletion confirmation: DONE -
ConfirmDeletionLivewire component now logs deletion actions. - Cache invalidation in EntitlementService is expensive: DONE - Refactored to use version-based cache keys.
buildCacheKey()now includes version in key (e.g.,entitlement:{id}:v{version}:limit:{code}). Invalidation simply increments the version, making all old keys stale without iteration. - WorkspaceManager.setDefault() could be optimised: DONE - Refactored to use a single optimised query.
- Add index hints for UsageRecord queries:
getTotalUsage()andgetRollingUsage()query by workspace_id + feature_code + recorded_at - ensure composite index exists - Consider soft deletes for WorkspacePackage: DONE - Added
SoftDeletestrait to WorkspacePackage model and migration2026_01_21_200000_add_soft_deletes_to_workspace_packages.phpfor thedeleted_atcolumn. - EntitlementService.getTotalLimit() cache key does not include version: DONE - Cache keys now include version via
buildCacheKey()method. All entitlement cache keys use formatentitlement:{id}:v{version}:{type}:{code}.
Missing Features (Future)
- UserStatsService has multiple TODO comments: Lines 83-93 for social accounts, scheduled posts, and storage usage tracking
- Workspace teams/roles beyond owner: Current pivot only has 'role' but no team management UI or additional role types implemented
- Entitlement webhook notifications: No webhook dispatch when limits are reached or packages change
- Usage alerts/notifications: No mechanism to notify users when approaching limits
- Billing cycle reset automation:
expireCycleBoundBoosts()exists but no scheduler entry visible - Web routes file missing: No
Routes/web.phpfile exists despite the Boot.php checking for it - Workspace invitation system: No invite flow for adding users to workspaces
Test Coverage Assessment
Well Tested (Good Coverage):
EntitlementServiceTest.php- Comprehensive coverage of can(), recordUsage(), provisionPackage/Boost, suspend/reactivate, revokePackage (600+ lines)AccountDeletionTest.php- Full lifecycle including model methods, job, and command (300+ lines)WorkspaceTenancyTest.php- Workspace isolation, scoping, relationshipsAccessTokenGuardTest.php- Token authentication, expiry, creation, revocation
Tested but Limited:
EntitlementApiTest.php- Tests API endpoints but uses non-existent package code 'social-creator' (tests may fail)AuthenticationTest.php,ProfileTest.php,SettingsTest.php- Exist but not reviewed in detail
Missing Test Coverage:
WorkspaceService- No dedicated testsWorkspaceManager- No dedicated testsUserStatsService- No testsResolveWorkspaceFromSubdomainmiddleware - No testsReferralController- No testsBelongsToWorkspacetrait - Only integration tests via WorkspaceTenancyTestTwoFactorAuthenticatableconcern - Test file exists but not reviewed- Livewire components (ConfirmDeletion, CancelDeletion, WorkspaceHome) - No dedicated tests
Security Concerns
Positive Security Patterns:
- Tokens stored as SHA-256 hashes (UserToken)
- Password re-verification required for account deletion (ConfirmDeletion)
- LIKE pattern escaping in WorkspaceService.findBySubdomain() (SQL injection prevention)
- Sensitive fields ($hidden) on User and Workspace models
- WP connector secret guarded against mass assignment
Concerns:
- WorkspaceService.get() has no authorisation: VERIFIED SECURE - Method correctly uses
$user->workspaces()relationship, not a raw query - ReferralController stores IP in cookie: IP address stored in JSON cookie - GDPR consideration, should be session-only
- No CSRF protection visible for Livewire deletion: executeDelete() is called via Livewire dispatch - verify CSRF is enforced
- Workspace.validateWebhookSignature() timing attack: Uses hash_equals which is good, but hash_hmac result should also be compared in constant time
- User tier bypass for email verification: Hades users bypass email verification - ensure this is intentional business logic
Notes
Architecture Observations
- Clean separation between WorkspaceManager (request-scoped operations) and WorkspaceService (session/persistence)
- EntitlementService is well-designed with proper caching, logging, and transaction handling
- Good use of value objects (EntitlementResult) for type safety
- Backward compatibility aliases in Boot.php for migration from old namespace
Code Quality
- Consistent use of strict types
- Good docblocks on most public methods
- Uses Laravel conventions (factories, seeders, Pest tests)
- Models have proper casts, fillable/guarded definitions
Configuration
- Proper config externalisation in
config/tenant.php - Environment variables for cache TTL and grace period
- No hardcoded secrets or credentials found
Dual Entitlement Systems
The module has two entitlement approaches that may cause confusion:
- UserTier enum (FREE/APOLLO/HADES) - Used on User model, defines features as simple array
- Package/Feature/Boost system - Used on Workspace model, full database-driven entitlements
These should be reconciled - currently User.getTier() returns enum-based limits while Workspace.can() uses the database-driven system.
Dependencies
The module has relationships to models in other modules:
Mod\Analytics\Models\*Mod\Social\Models\*Mod\Web\Models\*Mod\Trust\Models\*Mod\Notify\Models\*Mod\Commerce\Models\*Mod\Trees\Models\*Mod\Api\Models\*Mod\Content\Models\*
These cross-module dependencies are appropriate for a tenant module but ensure circular dependencies are avoided.