# core-tenant TODO Comprehensive task list for improving the multi-tenancy package. Items are prioritised by impact and urgency. ## Legend - **P1** - Critical/Security (must fix immediately) - **P2** - High (affects production quality) - **P3** - Medium (should address soon) - **P4** - Low (quality of life improvements) - **P5** - Nice-to-have (future enhancements) - **P6** - Backlog (ideas for later consideration) --- ## P1 - Critical / Security ### BUG-001: Missing `namespace_id` columns — namespace entitlements crash at runtime **Status:** Open (discovered 2026-02-20, issue #2 phase-0 assessment) **Files:** `Migrations/0001_01_01_000000_create_tenant_tables.php`, `Models/UsageRecord.php`, `Models/Boost.php`, `Services/EntitlementService.php` The `entitlement_usage_records` and `entitlement_boosts` tables are missing a `namespace_id` column. The `UsageRecord` and `Boost` models declare `namespace_id` as `$fillable`, and `EntitlementService` writes `namespace_id` in `recordNamespaceUsage()` and `provisionNamespaceBoost()`. Any call to either method will throw a database error at runtime. **Acceptance Criteria:** - Create migration adding `namespace_id` (nullable FK → namespaces, nullOnDelete) to both tables - Add indexes: `(namespace_id, feature_code, recorded_at)` on usage records; `(namespace_id, feature_code, status)` on boosts - Verify namespace-level entitlement tests pass after migration --- ### SEC-001: Add rate limiting to EntitlementApiController **Status:** Fixed (2026-01-29) **File:** `Controllers/EntitlementApiController.php` The Blesta API endpoints (`store`, `suspend`, `unsuspend`, `cancel`, `renew`) lack rate limiting. A compromised API key could be used to mass-provision or cancel packages. **Resolution:** - Added `#[RateLimit(limit: 60, window: 60, key: 'entitlement-api')]` attribute to controller class - Documented recommended route configuration with `api.rate` and `throttle:60,1` middleware - Rate limiting at 60 requests/minute per API key when routes are registered --- ### SEC-002: Validate API authentication on EntitlementApiController routes **Status:** Fixed (2026-01-29) **File:** `Routes/api.php`, `Controllers/EntitlementApiController.php` The Blesta API controller routes are not visible in `api.php` - they may be registered elsewhere or missing authentication. Verify all Blesta API endpoints require proper API key authentication. **Resolution:** - Added comprehensive PHPDoc to controller documenting required authentication - Documented required middleware: `api.auth:entitlements.write`, `api.rate`, `throttle:60,1` - Routes are currently commented out in core-commerce/routes/api.php but controller is ready - When enabled, routes require API key with `entitlements.write` scope --- ### SEC-003: Encrypt 2FA secrets at rest **Status:** Fixed (Jan 2026, commit a35cbc9) **File:** `Concerns/TwoFactorAuthenticatable.php`, `Migrations/0001_01_01_000000_create_tenant_tables.php` The `user_two_factor_auth.secret` column stores TOTP secrets. While marked as `text`, these should be encrypted at rest using Laravel's `encrypted:string` cast. **Acceptance Criteria:** - Add `'secret_key' => 'encrypted'` cast to UserTwoFactorAuth model - Create migration to encrypt existing secrets - Verify decryption works correctly in TotpService --- ### SEC-004: Audit workspace invitation token security **Status:** Fixed (Jan 2026, commit a35cbc9) **File:** `Models/WorkspaceInvitation.php` Invitation tokens are 64-character random strings, which is good. However: - Tokens should be hashed when stored (store hash, compare with hash_equals) - Add brute-force protection for invitation acceptance endpoint - Consider shorter expiry for high-privilege roles (owner/admin) **Acceptance Criteria:** - Store hashed tokens instead of plaintext - Add rate limiting to invitation acceptance - Add configurable expiry per role type --- ### SEC-005: Add CSRF protection to webhook test endpoint **Status:** Fixed (2026-01-29) **File:** `Controllers/Api/EntitlementWebhookController.php`, `Services/EntitlementWebhookService.php` The `test` endpoint triggers an outbound HTTP request. Ensure it cannot be abused as a server-side request forgery (SSRF) vector. **Resolution:** - Added `PreventsSSRF` trait to `EntitlementWebhookService` - Created `InvalidWebhookUrlException` for SSRF validation failures - All webhook operations (register, update, test, retry) now validate URLs: - Blocks localhost and loopback addresses (127.0.0.0/8, ::1) - Blocks private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) - Blocks link-local addresses and reserved ranges - Blocks local domains (.local, .localhost, .internal) - Requires HTTPS for all webhooks - Validates DNS resolution to prevent rebinding attacks - Added `SafeWebhookUrl` validation rule to controller store/update - Added timeout (10s) and connect timeout (5s) limits --- ### SEC-006: Validate workspace_id in RequireWorkspaceContext middleware **Status:** Fixed (2026-01-29) **File:** `Middleware/RequireWorkspaceContext.php` The middleware accepts workspace_id from multiple sources (header, query, input) without validating the authenticated user's access in all code paths. **Resolution:** - Changed default behaviour to ALWAYS validate user access (breaking change) - Added `isValidWorkspaceId()` check to validate workspace ID is positive integer - Added `logWorkspaceAccessAttempt()` for security monitoring - Logs denied/invalid attempts at warning level, granted at debug level (debug mode only) - To skip validation (NOT RECOMMENDED), pass `skip_validation` parameter - Logs include: workspace_id, user_id, IP, user agent, URL, source of workspace context --- ## P2 - High Priority ### DX-001: Add strict_types declaration to all PHP files **Status:** Fixed (2026-01-29) **Files:** Multiple files missing declaration Several files were missing `declare(strict_types=1);`: - `Models/Workspace.php` - `Models/User.php` - `Services/EntitlementService.php` **Resolution:** - Added `declare(strict_types=1);` to all three files - Declaration placed immediately after ` workspace -> user tier) - Added examples for common use cases (checking entitlements, recording usage, provisioning) - Documented caching behaviour and invalidation triggers --- ### TEST-001: Add tests for namespace-level entitlements **Status:** Fixed (2026-01-29) **File:** `tests/Feature/EntitlementServiceTest.php` The test file covers workspace-level entitlements but not namespace-level (`canForNamespace`, `recordNamespaceUsage`, etc.). **Resolution:** - Added comprehensive test suite for namespace-level entitlements - Tests `canForNamespace()` with user-owned and workspace-owned namespaces - Tests entitlement cascade (namespace -> workspace -> user tier) with various scenarios - Tests `provisionNamespacePackage()` including package replacement, expiry, and metadata - Tests `provisionNamespaceBoost()` including stacking, unlimited boosts, and expiry - Tests `recordNamespaceUsage()` with metadata and workspace context - Tests `getNamespaceUsageSummary()` with usage percentages and near-limit detection - Tests `invalidateNamespaceCache()` for both limits and usage - Tests multiple namespaces with different entitlements and usage tracking - Tests boost stacking behaviour including unlimited override --- ### TEST-002: Add integration tests for EntitlementApiController **Status:** Fixed (2026-01-29) **File:** `tests/Feature/EntitlementApiTest.php` Need HTTP-level integration tests for the API endpoints, including authentication, validation, and error cases. **Acceptance Criteria:** - Test all CRUD operations via HTTP - Test validation error responses - Test authentication failures - Test rate limiting (once implemented) **Resolution:** - Added comprehensive integration tests for EntitlementApiController - Cross-App Entitlement API tests: - `GET /api/v1/entitlements/check` - authentication, validation (email, feature, quantity), 404 responses, entitlement checks - `POST /api/v1/entitlements/usage` - authentication, validation, recording usage with metadata - `GET /api/v1/entitlements/summary` - authentication, workspace summary with packages, features, boosts - Blesta Provisioning API tests: - `POST /api/provisioning/entitlements` (store) - authentication, validation (email, name, product_code, dates), user creation, workspace creation, package provisioning - `GET /api/provisioning/entitlements/{id}` (show) - authentication, 404 handling, entitlement details with ISO8601 dates - `POST /api/provisioning/entitlements/{id}/suspend` - authentication, 404 handling, suspension with reason, log entry creation - `POST /api/provisioning/entitlements/{id}/unsuspend` - authentication, 404 handling, reactivation, access restoration - `POST /api/provisioning/entitlements/{id}/cancel` - authentication, 404 handling, cancellation with reason, access denial - `POST /api/provisioning/entitlements/{id}/renew` - authentication, validation, date updates, log entry creation - Error response format tests for consistency across endpoints - Rate limiting tests verifying the `#[RateLimit]` attribute configuration (60 requests/minute) - All tests use Pest syntax with `declare(strict_types=1)` --- ### PERF-001: Optimise EntitlementService cache invalidation **Status:** Fixed (2026-01-29) **File:** `Services/EntitlementService.php` The `invalidateCache()` method iterates all features and clears each key individually. This is O(n) where n = feature count. **Acceptance Criteria:** - Use cache tags when available (Redis) - Implement version-based cache busting - Benchmark before/after with 100+ features **Resolution:** - Added cache tag support for O(1) invalidation when using Redis/Memcached: - Workspace-scoped tags: `entitlement:ws:{id}` - Namespace-scoped tags: `entitlement:ns:{id}` - Type-specific tags: `entitlement:limits`, `entitlement:usage` - Added granular invalidation methods: - `invalidateUsageCache()` - invalidates only usage data for a specific feature - `invalidateLimitCache()` - invalidates only limit data - `invalidateNamespaceUsageCache()` - namespace-scoped usage invalidation - Updated `recordUsage()` and `recordNamespaceUsage()` to use granular invalidation - Falls back to O(n) iteration for non-taggable cache drivers (file, database) - Created `EntitlementCacheInvalidated` event for event-driven cache management - Event includes reason constants for audit/debugging: - `REASON_USAGE_RECORDED`, `REASON_PACKAGE_PROVISIONED`, `REASON_PACKAGE_SUSPENDED` - `REASON_PACKAGE_REACTIVATED`, `REASON_PACKAGE_REVOKED`, `REASON_BOOST_PROVISIONED` - `REASON_BOOST_EXPIRED`, `REASON_MANUAL` - Added `supportsCacheTags()` helper to detect taggable stores - `provisionBoost()` now invalidates only the affected feature's cache - `expireCycleBoundBoosts()` collects affected features and invalidates granularly --- ### PERF-002: Add database indexes for common queries **Status:** Fixed (2026-01-29) **File:** `Migrations/2026_01_29_000000_add_performance_indexes.php` Missing indexes identified: - `users.tier` (for tier-based queries) - `namespaces.slug` (currently only unique in combination) - `entitlement_usage_records.user_id` **Resolution:** - Created migration `2026_01_29_000000_add_performance_indexes.php` adding: - `users.tier` - for tier-based queries (getTier(), isPaid(), etc.) - `namespaces.slug` - for slug lookups independent of owner - `entitlement_usage_records.user_id` - foreign key index - `workspaces.is_active` - for active() scope queries - `workspaces.type` - for type filtering - `workspaces.domain` - for domain lookups - `user_workspace.team_id` - foreign key from teams migration - `entitlement_logs.user_id` - foreign key index - All indexes use explicit names for reliable rollback --- ### CODE-001: Extract WorkspaceScope to separate file **Status:** Open **File:** `Scopes/WorkspaceScope.php` The WorkspaceScope class exists but is referenced in BelongsToWorkspace trait without actually being applied as a global scope. Clarify the architecture. **Acceptance Criteria:** - Document when WorkspaceScope vs BelongsToWorkspace should be used - Consider applying WorkspaceScope as a proper global scope - Update CLAUDE.md with guidance --- ### CODE-002: Consolidate User model relationships **Status:** Open **File:** `Models/User.php` The User model has many undefined relationships (Page, Project, Domain, Pixel, etc.) that reference classes not in this package. These should either be: 1. Moved to the consuming application 2. Made conditional on class existence **Acceptance Criteria:** - Audit all relationships for undefined classes - Add `class_exists()` guards or move to app layer - Document which relationships are package-native vs app-specific --- ### CODE-003: Remove hardcoded domain in EntitlementApiController **Status:** Open **File:** `Controllers/EntitlementApiController.php`, Line 80 The workspace creation uses hardcoded domain `'hub.host.uk.com'`. This should be configurable. **Acceptance Criteria:** - Move to config value - Add sensible default - Document in CLAUDE.md --- ## P3 - Medium Priority ### DX-005: Add `declare(strict_types=1)` to remaining model/service files **Status:** Open (discovered 2026-02-20, issue #2 phase-0 assessment) **Files:** `Models/AccountDeletionRequest.php`, `Models/Boost.php`, `Models/EntitlementLog.php`, `Models/Feature.php`, `Models/Package.php`, `Models/UsageRecord.php`, `Models/WaitlistEntry.php`, `Models/WorkspacePackage.php`, `Services/EntitlementResult.php` Nine files are still missing `declare(strict_types=1)` despite it being a documented coding standard (DX-001 only fixed three files). **Acceptance Criteria:** - Add `declare(strict_types=1);` immediately after `80% mutation score on critical code --- ### CODE-006: Extract constants from WorkspaceMember **Status:** Open **File:** `Models/WorkspaceMember.php` Role constants should be in an enum for type safety. **Acceptance Criteria:** - Create WorkspaceMemberRole enum - Update model to use enum - Update all role comparisons --- ### CODE-007: Add configurable invitation expiry **Status:** Open **File:** `Models/Workspace.php`, Line 654 The `invite()` method has hardcoded 7-day expiry. Make configurable. **Acceptance Criteria:** - Add config key `tenant.invitation_expiry_days` - Document configuration option --- ### FEAT-003: Add workspace transfer ownership **Status:** Open Allow workspace owners to transfer ownership to another member. **Acceptance Criteria:** - Add `transferOwnership()` method to WorkspaceManager - Require confirmation from new owner - Log ownership transfer in audit log --- ### FEAT-004: Add bulk invitation support **Status:** Open Allow inviting multiple users at once (CSV upload or multi-email input). **Acceptance Criteria:** - Add `inviteMany()` method - Support CSV email import - Handle duplicates gracefully --- ## P5 - Nice to Have ### DX-007: Add OpenAPI/Swagger documentation **Status:** Open Generate API documentation from route definitions. **Acceptance Criteria:** - Add scramble or l5-swagger - Document all API endpoints - Include authentication requirements --- ### FEAT-005: Add workspace activity log **Status:** Open Track all significant workspace actions for audit purposes. **Acceptance Criteria:** - Log member additions/removals - Log permission changes - Log package/boost changes - Provide query interface --- ### FEAT-006: Add usage forecasting **Status:** Open Predict when a workspace will hit limits based on usage trends. **Acceptance Criteria:** - Track daily usage aggregates - Implement simple linear projection - Show "estimated days until limit" in dashboard --- ### FEAT-007: Add webhook event filtering **Status:** Open Allow webhooks to filter events by additional criteria (e.g., specific features only). **Acceptance Criteria:** - Add filter configuration to webhook - Support feature code patterns - Support threshold filtering for limit events --- ## P6 - Backlog / Ideas ### IDEA-001: GraphQL API for entitlements Consider adding GraphQL endpoint for more flexible entitlement queries. ### IDEA-002: Real-time usage updates WebSocket support for live usage updates in dashboard. ### IDEA-003: Entitlement simulation mode Allow testing "what if I upgrade" scenarios without actual changes. ### IDEA-004: Multi-region support Support for workspace data residency requirements. ### IDEA-005: Workspace templates Pre-configured workspace setups for different use cases. --- ## Completed _Move items here when done with completion date._