diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 76f6a6e..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "composer" - directory: "/" - schedule: - interval: "weekly" - day: "monday" - labels: - - "type:dependencies" - - "priority:low" - commit-message: - prefix: "deps(composer):" - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - day: "monday" - labels: - - "type:dependencies" - - "priority:low" - commit-message: - prefix: "deps(npm):" - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - day: "monday" - labels: - - "type:dependencies" - - "priority:low" - commit-message: - prefix: "deps(actions):" - diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 5ef77a8..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: CodeQL - -on: - push: - branches: [dev, main] - pull_request: - branches: [dev, main] - schedule: - - cron: "0 6 * * 1" - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: javascript - - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:javascript" - diff --git a/docs/packages/agentic/api-keys.md b/docs/packages/agentic/api-keys.md deleted file mode 100644 index cb96e57..0000000 --- a/docs/packages/agentic/api-keys.md +++ /dev/null @@ -1,319 +0,0 @@ ---- -title: API Keys -description: Guide to Agent API key management -updated: 2026-01-29 ---- - -# API Key Management - -Agent API keys provide authenticated access to the MCP tools and agentic services. This guide covers key creation, permissions, and security. - -## Key Structure - -API keys follow the format: `ak_` + 32 random alphanumeric characters. - -Example: `ak_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6` - -The key is only displayed once at creation. Store it securely. - -## Creating Keys - -### Via Admin Panel - -1. Navigate to Workspace Settings > API Keys -2. Click "Create New Key" -3. Enter a descriptive name -4. Select permissions -5. Set expiration (optional) -6. Click Create -7. Copy the displayed key immediately - -### Programmatically - -```php -use Core\Mod\Agentic\Services\AgentApiKeyService; - -$service = app(AgentApiKeyService::class); - -$key = $service->create( - workspace: $workspace, - name: 'My Agent Key', - permissions: [ - AgentApiKey::PERM_PLANS_READ, - AgentApiKey::PERM_PLANS_WRITE, - AgentApiKey::PERM_SESSIONS_WRITE, - ], - rateLimit: 100, - expiresAt: now()->addYear() -); - -// Only available once -$plainKey = $key->plainTextKey; -``` - -## Permissions - -### Available Permissions - -| Permission | Constant | Description | -|------------|----------|-------------| -| `plans.read` | `PERM_PLANS_READ` | List and view plans | -| `plans.write` | `PERM_PLANS_WRITE` | Create, update, archive plans | -| `phases.write` | `PERM_PHASES_WRITE` | Update phases, manage tasks | -| `sessions.read` | `PERM_SESSIONS_READ` | List and view sessions | -| `sessions.write` | `PERM_SESSIONS_WRITE` | Start, update, end sessions | -| `tools.read` | `PERM_TOOLS_READ` | View tool analytics | -| `templates.read` | `PERM_TEMPLATES_READ` | List and view templates | -| `templates.instantiate` | `PERM_TEMPLATES_INSTANTIATE` | Create plans from templates | -| `notify:read` | `PERM_NOTIFY_READ` | List push campaigns | -| `notify:write` | `PERM_NOTIFY_WRITE` | Create/update campaigns | -| `notify:send` | `PERM_NOTIFY_SEND` | Send notifications | - -### Permission Checking - -```php -// Single permission -$key->hasPermission('plans.write'); - -// Any of several -$key->hasAnyPermission(['plans.read', 'sessions.read']); - -// All required -$key->hasAllPermissions(['plans.write', 'phases.write']); -``` - -### Updating Permissions - -```php -$service->updatePermissions($key, [ - AgentApiKey::PERM_PLANS_READ, - AgentApiKey::PERM_SESSIONS_READ, -]); -``` - -## Rate Limiting - -### Configuration - -Each key has a configurable rate limit (requests per minute): - -```php -$key = $service->create( - workspace: $workspace, - name: 'Limited Key', - permissions: [...], - rateLimit: 50 // 50 requests/minute -); - -// Update later -$service->updateRateLimit($key, 100); -``` - -### Checking Status - -```php -$status = $service->getRateLimitStatus($key); -// Returns: -// [ -// 'limit' => 100, -// 'remaining' => 85, -// 'reset_in_seconds' => 45, -// 'used' => 15 -// ] -``` - -### Response Headers - -Rate limit info is included in API responses: - -``` -X-RateLimit-Limit: 100 -X-RateLimit-Remaining: 85 -X-RateLimit-Reset: 45 -``` - -When rate limited (HTTP 429): -``` -Retry-After: 45 -``` - -## IP Restrictions - -Keys can be restricted to specific IP addresses or ranges. - -### Enabling Restrictions - -```php -// Enable with whitelist -$service->enableIpRestrictions($key, [ - '192.168.1.0/24', // CIDR range - '10.0.0.5', // Single IPv4 - '2001:db8::1', // Single IPv6 - '2001:db8::/32', // IPv6 CIDR -]); - -// Disable restrictions -$service->disableIpRestrictions($key); -``` - -### Managing Whitelist - -```php -// Add single entry -$key->addToIpWhitelist('192.168.2.0/24'); - -// Remove entry -$key->removeFromIpWhitelist('192.168.1.0/24'); - -// Replace entire list -$key->updateIpWhitelist([ - '10.0.0.0/8', - '172.16.0.0/12', -]); -``` - -### Parsing Input - -For user-entered whitelists: - -```php -$result = $service->parseIpWhitelistInput(" - 192.168.1.1 - 192.168.2.0/24 - # This is a comment - invalid-ip -"); - -// Result: -// [ -// 'entries' => ['192.168.1.1', '192.168.2.0/24'], -// 'errors' => ['invalid-ip: Invalid IP address'] -// ] -``` - -## Key Lifecycle - -### Expiration - -```php -// Set expiration on create -$key = $service->create( - ... - expiresAt: now()->addMonths(6) -); - -// Extend expiration -$service->extendExpiry($key, now()->addYear()); - -// Remove expiration (never expires) -$service->removeExpiry($key); -``` - -### Revocation - -```php -// Immediately revoke -$service->revoke($key); - -// Check status -$key->isRevoked(); // true -$key->isActive(); // false -``` - -### Status Helpers - -```php -$key->isActive(); // Not revoked, not expired -$key->isRevoked(); // Has been revoked -$key->isExpired(); // Past expiration date -$key->getStatusLabel(); // "Active", "Revoked", or "Expired" -``` - -## Authentication - -### Making Requests - -Include the API key as a Bearer token: - -```bash -curl -H "Authorization: Bearer ak_your_key_here" \ - https://mcp.host.uk.com/api/agent/plans -``` - -### Authentication Flow - -1. Middleware extracts Bearer token -2. Key looked up by SHA-256 hash -3. Status checked (revoked, expired) -4. IP validated if restrictions enabled -5. Permissions checked against required scopes -6. Rate limit checked and incremented -7. Usage recorded (count, timestamp, IP) - -### Error Responses - -| HTTP Code | Error | Description | -|-----------|-------|-------------| -| 401 | `unauthorised` | Missing or invalid key | -| 401 | `key_revoked` | Key has been revoked | -| 401 | `key_expired` | Key has expired | -| 403 | `ip_not_allowed` | Request IP not whitelisted | -| 403 | `permission_denied` | Missing required permission | -| 429 | `rate_limited` | Rate limit exceeded | - -## Usage Tracking - -Each key tracks: -- `call_count` - Total lifetime calls -- `last_used_at` - Timestamp of last use -- `last_used_ip` - IP of last request - -Access via: -```php -$key->call_count; -$key->getLastUsedForHumans(); // "2 hours ago" -``` - -## Best Practices - -1. **Use descriptive names** - "Production Agent" not "Key 1" -2. **Minimal permissions** - Only grant needed scopes -3. **Set expiration** - Rotate keys periodically -4. **Enable IP restrictions** - When agents run from known IPs -5. **Monitor usage** - Review call patterns regularly -6. **Revoke promptly** - If key may be compromised -7. **Separate environments** - Different keys for dev/staging/prod - -## Example: Complete Setup - -```php -use Core\Mod\Agentic\Services\AgentApiKeyService; -use Core\Mod\Agentic\Models\AgentApiKey; - -$service = app(AgentApiKeyService::class); - -// Create a production key -$key = $service->create( - workspace: $workspace, - name: 'Production Agent - Claude', - permissions: [ - AgentApiKey::PERM_PLANS_READ, - AgentApiKey::PERM_PLANS_WRITE, - AgentApiKey::PERM_PHASES_WRITE, - AgentApiKey::PERM_SESSIONS_WRITE, - AgentApiKey::PERM_TEMPLATES_READ, - AgentApiKey::PERM_TEMPLATES_INSTANTIATE, - ], - rateLimit: 200, - expiresAt: now()->addYear() -); - -// Restrict to known IPs -$service->enableIpRestrictions($key, [ - '203.0.113.0/24', // Office network - '198.51.100.50', // CI/CD server -]); - -// Store the key securely -$plainKey = $key->plainTextKey; // Only chance to get this! -``` diff --git a/docs/packages/agentic/architecture.md b/docs/packages/agentic/architecture.md deleted file mode 100644 index e393fed..0000000 --- a/docs/packages/agentic/architecture.md +++ /dev/null @@ -1,322 +0,0 @@ ---- -title: Architecture -description: Technical architecture of the core-agentic package -updated: 2026-01-29 ---- - -# Architecture - -The `core-agentic` package provides AI agent orchestration infrastructure for the Host UK platform. It enables multi-agent collaboration, persistent task tracking, and unified access to multiple AI providers. - -## Overview - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ MCP Protocol Layer │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ Plan │ │ Phase │ │ Session │ │ State │ ... tools │ -│ │ Tools │ │ Tools │ │ Tools │ │ Tools │ │ -│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ -└───────┼────────────┼────────────┼────────────┼──────────────────┘ - │ │ │ │ -┌───────┴────────────┴────────────┴────────────┴──────────────────┐ -│ AgentToolRegistry │ -│ - Tool registration and discovery │ -│ - Permission checking (API key scopes) │ -│ - Dependency validation │ -│ - Circuit breaker integration │ -└──────────────────────────────────────────────────────────────────┘ - │ -┌───────┴──────────────────────────────────────────────────────────┐ -│ Core Services │ -│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ -│ │ AgenticManager │ │ AgentApiKey │ │ PlanTemplate │ │ -│ │ (AI Providers) │ │ Service │ │ Service │ │ -│ └────────────────┘ └────────────────┘ └────────────────┘ │ -│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ -│ │ IpRestriction │ │ Content │ │ AgentSession │ │ -│ │ Service │ │ Service │ │ Service │ │ -│ └────────────────┘ └────────────────┘ └────────────────┘ │ -└──────────────────────────────────────────────────────────────────┘ - │ -┌───────┴──────────────────────────────────────────────────────────┐ -│ Data Layer │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ -│ │ AgentPlan │ │ AgentPhase │ │ AgentSession│ │ AgentApiKey ││ -│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘│ -│ ┌─────────────┐ ┌─────────────┐ │ -│ │ Workspace │ │ Task │ │ -│ │ State │ │ │ │ -│ └─────────────┘ └─────────────┘ │ -└──────────────────────────────────────────────────────────────────┘ -``` - -## Core Concepts - -### Agent Plans - -Plans represent structured work with phases, tasks, and progress tracking. They persist across agent sessions, enabling handoff between different AI models or instances. - -``` -AgentPlan -├── slug (unique identifier) -├── title -├── status (draft → active → completed/archived) -├── current_phase -└── phases[] (AgentPhase) - ├── name - ├── tasks[] - │ ├── name - │ └── status - ├── dependencies[] - └── checkpoints[] -``` - -**Lifecycle:** -1. Created via MCP tool or template -2. Activated to begin work -3. Phases started/completed in order -4. Plan auto-completes when all phases done -5. Archived for historical reference - -### Agent Sessions - -Sessions track individual work periods. They enable context recovery when an agent's context window resets or when handing off to another agent. - -``` -AgentSession -├── session_id (prefixed unique ID) -├── agent_type (opus/sonnet/haiku) -├── status (active/paused/completed/failed) -├── work_log[] (chronological actions) -├── artifacts[] (files created/modified) -├── context_summary (current state) -└── handoff_notes (for next agent) -``` - -**Handoff Flow:** -1. Session logs work as it progresses -2. Before context ends, agent calls `session_handoff` -3. Handoff notes capture summary, next steps, blockers -4. Next agent calls `session_resume` to continue -5. Resume session inherits context from previous - -### Workspace State - -Key-value state storage shared between sessions and plans. Enables agents to persist decisions, configurations, and intermediate results. - -``` -WorkspaceState -├── key (namespaced identifier) -├── value (any JSON-serialisable data) -├── type (json/markdown/code/reference) -└── category (for organisation) -``` - -## MCP Tool Architecture - -All MCP tools extend the `AgentTool` base class which provides: - -### Input Validation - -```php -protected function requireString(array $args, string $key, ?int $maxLength = null): string -protected function optionalInt(array $args, string $key, ?int $default = null): ?int -protected function requireEnum(array $args, string $key, array $allowed): string -``` - -### Circuit Breaker Protection - -```php -return $this->withCircuitBreaker('agentic', function () { - // Database operations that could fail - return AgentPlan::where('slug', $slug)->first(); -}, fn () => $this->error('Service unavailable', 'circuit_open')); -``` - -### Dependency Declaration - -```php -public function dependencies(): array -{ - return [ - ToolDependency::contextExists('workspace_id', 'Workspace required'), - ToolDependency::toolCalled('session_start', 'Start session first'), - ]; -} -``` - -### Tool Categories - -| Category | Tools | Purpose | -|----------|-------|---------| -| `plan` | plan_create, plan_get, plan_list, plan_update_status, plan_archive | Work plan management | -| `phase` | phase_get, phase_update_status, phase_add_checkpoint | Phase operations | -| `session` | session_start, session_end, session_log, session_handoff, session_resume, session_replay | Session tracking | -| `state` | state_get, state_set, state_list | Persistent state | -| `task` | task_update, task_toggle | Task completion | -| `template` | template_list, template_preview, template_create_plan | Plan templates | -| `content` | content_generate, content_batch_generate, content_brief_create | Content generation | - -## AI Provider Abstraction - -The `AgenticManager` provides unified access to multiple AI providers: - -```php -$ai = app(AgenticManager::class); - -// Use specific provider -$response = $ai->claude()->generate($system, $user); -$response = $ai->gemini()->generate($system, $user); -$response = $ai->openai()->generate($system, $user); - -// Use by name (for configuration-driven selection) -$response = $ai->provider('gemini')->generate($system, $user); -``` - -### Provider Interface - -All providers implement `AgenticProviderInterface`: - -```php -interface AgenticProviderInterface -{ - public function generate(string $systemPrompt, string $userPrompt, array $config = []): AgenticResponse; - public function stream(string $systemPrompt, string $userPrompt, array $config = []): Generator; - public function name(): string; - public function defaultModel(): string; - public function isAvailable(): bool; -} -``` - -### Response Object - -```php -class AgenticResponse -{ - public string $content; - public string $model; - public int $inputTokens; - public int $outputTokens; - public int $durationMs; - public ?string $stopReason; - public array $raw; - - public function estimateCost(): float; -} -``` - -## Authentication - -### API Key Flow - -``` -Request → AgentApiAuth Middleware → AgentApiKeyService::authenticate() - │ - ├── Validate key (SHA-256 hash lookup) - ├── Check revoked/expired - ├── Validate IP whitelist - ├── Check permissions - ├── Check rate limit - └── Record usage -``` - -### Permission Model - -```php -// Permission constants -AgentApiKey::PERM_PLANS_READ // 'plans.read' -AgentApiKey::PERM_PLANS_WRITE // 'plans.write' -AgentApiKey::PERM_SESSIONS_WRITE // 'sessions.write' -// etc. - -// Check permissions -$key->hasPermission('plans.write'); -$key->hasAllPermissions(['plans.read', 'sessions.write']); -``` - -### IP Restrictions - -API keys can optionally restrict access by IP: - -- Individual IPv4/IPv6 addresses -- CIDR notation (e.g., `192.168.1.0/24`) -- Mixed whitelist - -## Event-Driven Boot - -The module uses the Core framework's event-driven lazy loading: - -```php -class Boot extends ServiceProvider -{ - public static array $listens = [ - AdminPanelBooting::class => 'onAdminPanel', - ConsoleBooting::class => 'onConsole', - McpToolsRegistering::class => 'onMcpTools', - ]; -} -``` - -This ensures: -- Views only loaded when admin panel boots -- Commands only registered when console boots -- MCP tools only registered when MCP module initialises - -## Multi-Tenancy - -All data is workspace-scoped via the `BelongsToWorkspace` trait: - -- Queries auto-scoped to current workspace -- Creates auto-assign workspace_id -- Cross-tenant queries throw `MissingWorkspaceContextException` - -## File Structure - -``` -core-agentic/ -├── Boot.php # Service provider with event handlers -├── config.php # Module configuration -├── Migrations/ # Database schema -├── Models/ # Eloquent models -│ ├── AgentPlan.php -│ ├── AgentPhase.php -│ ├── AgentSession.php -│ ├── AgentApiKey.php -│ └── WorkspaceState.php -├── Services/ # Business logic -│ ├── AgenticManager.php # AI provider orchestration -│ ├── AgentApiKeyService.php # API key management -│ ├── IpRestrictionService.php -│ ├── PlanTemplateService.php -│ ├── ContentService.php -│ ├── ClaudeService.php -│ ├── GeminiService.php -│ └── OpenAIService.php -├── Mcp/ -│ ├── Tools/Agent/ # MCP tool implementations -│ │ ├── AgentTool.php # Base class -│ │ ├── Plan/ -│ │ ├── Phase/ -│ │ ├── Session/ -│ │ ├── State/ -│ │ └── ... -│ ├── Prompts/ # MCP prompt definitions -│ └── Servers/ # MCP server configurations -├── Middleware/ -│ └── AgentApiAuth.php # API authentication -├── Controllers/ -│ └── ForAgentsController.php # Agent discovery endpoint -├── View/ -│ ├── Blade/admin/ # Admin panel views -│ └── Modal/Admin/ # Livewire components -├── Jobs/ # Queue jobs -├── Console/Commands/ # Artisan commands -└── Tests/ # Pest test suites -``` - -## Dependencies - -- `host-uk/core` - Event system, base classes -- `host-uk/core-tenant` - Workspace, BelongsToWorkspace trait -- `host-uk/core-mcp` - MCP infrastructure, CircuitBreaker diff --git a/docs/packages/agentic/mcp-tools.md b/docs/packages/agentic/mcp-tools.md deleted file mode 100644 index da12266..0000000 --- a/docs/packages/agentic/mcp-tools.md +++ /dev/null @@ -1,670 +0,0 @@ ---- -title: MCP Tools Reference -description: Complete reference for core-agentic MCP tools -updated: 2026-01-29 ---- - -# MCP Tools Reference - -This document provides a complete reference for all MCP tools in the `core-agentic` package. - -## Overview - -Tools are organised into categories: - -| Category | Description | Tools Count | -|----------|-------------|-------------| -| plan | Work plan management | 5 | -| phase | Phase operations | 3 | -| session | Session tracking | 8 | -| state | Persistent state | 3 | -| task | Task completion | 2 | -| template | Plan templates | 3 | -| content | Content generation | 6 | - -## Plan Tools - -### plan_create - -Create a new work plan with phases and tasks. - -**Scopes:** `write` - -**Input:** -```json -{ - "title": "string (required)", - "slug": "string (optional, auto-generated)", - "description": "string (optional)", - "context": "object (optional)", - "phases": [ - { - "name": "string", - "description": "string", - "tasks": ["string"] - } - ] -} -``` - -**Output:** -```json -{ - "success": true, - "plan": { - "slug": "my-plan-abc123", - "title": "My Plan", - "status": "draft", - "phases": 3 - } -} -``` - -**Dependencies:** workspace_id in context - ---- - -### plan_get - -Get a plan by slug with full details. - -**Scopes:** `read` - -**Input:** -```json -{ - "slug": "string (required)" -} -``` - -**Output:** -```json -{ - "success": true, - "plan": { - "slug": "my-plan", - "title": "My Plan", - "status": "active", - "progress": { - "total": 5, - "completed": 2, - "percentage": 40 - }, - "phases": [...] - } -} -``` - ---- - -### plan_list - -List plans with optional filtering. - -**Scopes:** `read` - -**Input:** -```json -{ - "status": "string (optional: draft|active|completed|archived)", - "limit": "integer (optional, default 20)" -} -``` - -**Output:** -```json -{ - "success": true, - "plans": [ - { - "slug": "plan-1", - "title": "Plan One", - "status": "active" - } - ], - "count": 1 -} -``` - ---- - -### plan_update_status - -Update a plan's status. - -**Scopes:** `write` - -**Input:** -```json -{ - "slug": "string (required)", - "status": "string (required: draft|active|completed|archived)" -} -``` - ---- - -### plan_archive - -Archive a plan with optional reason. - -**Scopes:** `write` - -**Input:** -```json -{ - "slug": "string (required)", - "reason": "string (optional)" -} -``` - -## Phase Tools - -### phase_get - -Get phase details by plan slug and phase order. - -**Scopes:** `read` - -**Input:** -```json -{ - "plan_slug": "string (required)", - "phase_order": "integer (required)" -} -``` - ---- - -### phase_update_status - -Update a phase's status. - -**Scopes:** `write` - -**Input:** -```json -{ - "plan_slug": "string (required)", - "phase_order": "integer (required)", - "status": "string (required: pending|in_progress|completed|blocked|skipped)", - "reason": "string (optional, for blocked/skipped)" -} -``` - ---- - -### phase_add_checkpoint - -Add a checkpoint note to a phase. - -**Scopes:** `write` - -**Input:** -```json -{ - "plan_slug": "string (required)", - "phase_order": "integer (required)", - "note": "string (required)", - "context": "object (optional)" -} -``` - -## Session Tools - -### session_start - -Start a new agent session. - -**Scopes:** `write` - -**Input:** -```json -{ - "plan_slug": "string (optional)", - "agent_type": "string (required: opus|sonnet|haiku)", - "context": "object (optional)" -} -``` - -**Output:** -```json -{ - "success": true, - "session": { - "session_id": "ses_abc123xyz", - "agent_type": "opus", - "plan": "my-plan", - "status": "active" - } -} -``` - ---- - -### session_end - -End a session with status and summary. - -**Scopes:** `write` - -**Input:** -```json -{ - "session_id": "string (required)", - "status": "string (required: completed|failed)", - "summary": "string (optional)" -} -``` - ---- - -### session_log - -Add a work log entry to an active session. - -**Scopes:** `write` - -**Input:** -```json -{ - "session_id": "string (required)", - "message": "string (required)", - "type": "string (optional: info|warning|error|success|checkpoint)", - "data": "object (optional)" -} -``` - ---- - -### session_handoff - -Prepare session for handoff to another agent. - -**Scopes:** `write` - -**Input:** -```json -{ - "session_id": "string (required)", - "summary": "string (required)", - "next_steps": ["string"], - "blockers": ["string"], - "context_for_next": "object (optional)" -} -``` - ---- - -### session_resume - -Resume a paused session. - -**Scopes:** `write` - -**Input:** -```json -{ - "session_id": "string (required)" -} -``` - -**Output:** -```json -{ - "success": true, - "session": {...}, - "handoff_context": { - "summary": "Previous work summary", - "next_steps": ["Continue with..."], - "blockers": [] - } -} -``` - ---- - -### session_replay - -Get replay context for a session. - -**Scopes:** `read` - -**Input:** -```json -{ - "session_id": "string (required)" -} -``` - -**Output:** -```json -{ - "success": true, - "replay_context": { - "session_id": "ses_abc123", - "progress_summary": {...}, - "last_checkpoint": {...}, - "decisions": [...], - "errors": [...] - } -} -``` - ---- - -### session_continue - -Create a new session that continues from a previous one. - -**Scopes:** `write` - -**Input:** -```json -{ - "session_id": "string (required)", - "agent_type": "string (optional)" -} -``` - ---- - -### session_artifact - -Add an artifact (file) to a session. - -**Scopes:** `write` - -**Input:** -```json -{ - "session_id": "string (required)", - "path": "string (required)", - "action": "string (required: created|modified|deleted)", - "metadata": "object (optional)" -} -``` - ---- - -### session_list - -List sessions with optional filtering. - -**Scopes:** `read` - -**Input:** -```json -{ - "plan_slug": "string (optional)", - "status": "string (optional)", - "limit": "integer (optional)" -} -``` - -## State Tools - -### state_set - -Set a workspace state value. - -**Scopes:** `write` - -**Input:** -```json -{ - "plan_slug": "string (required)", - "key": "string (required)", - "value": "any (required)", - "category": "string (optional)" -} -``` - ---- - -### state_get - -Get a workspace state value. - -**Scopes:** `read` - -**Input:** -```json -{ - "plan_slug": "string (required)", - "key": "string (required)" -} -``` - ---- - -### state_list - -List all state for a plan. - -**Scopes:** `read` - -**Input:** -```json -{ - "plan_slug": "string (required)", - "category": "string (optional)" -} -``` - -## Task Tools - -### task_update - -Update a task within a phase. - -**Scopes:** `write` - -**Input:** -```json -{ - "plan_slug": "string (required)", - "phase_order": "integer (required)", - "task_identifier": "string|integer (required)", - "status": "string (optional: pending|completed)", - "notes": "string (optional)" -} -``` - ---- - -### task_toggle - -Toggle a task's completion status. - -**Scopes:** `write` - -**Input:** -```json -{ - "plan_slug": "string (required)", - "phase_order": "integer (required)", - "task_identifier": "string|integer (required)" -} -``` - -## Template Tools - -### template_list - -List available plan templates. - -**Scopes:** `read` - -**Output:** -```json -{ - "success": true, - "templates": [ - { - "slug": "feature-development", - "name": "Feature Development", - "description": "Standard feature workflow", - "phases_count": 5, - "variables": [ - { - "name": "FEATURE_NAME", - "required": true - } - ] - } - ] -} -``` - ---- - -### template_preview - -Preview a template with variable substitution. - -**Scopes:** `read` - -**Input:** -```json -{ - "slug": "string (required)", - "variables": { - "FEATURE_NAME": "Authentication" - } -} -``` - ---- - -### template_create_plan - -Create a plan from a template. - -**Scopes:** `write` - -**Input:** -```json -{ - "template_slug": "string (required)", - "variables": "object (required)", - "title": "string (optional, overrides template)", - "activate": "boolean (optional, default false)" -} -``` - -## Content Tools - -### content_generate - -Generate content using AI. - -**Scopes:** `write` - -**Input:** -```json -{ - "prompt": "string (required)", - "provider": "string (optional: claude|gemini|openai)", - "config": { - "temperature": 0.7, - "max_tokens": 4000 - } -} -``` - ---- - -### content_batch_generate - -Generate content for a batch specification. - -**Scopes:** `write` - -**Input:** -```json -{ - "batch_id": "string (required)", - "provider": "string (optional)", - "dry_run": "boolean (optional)" -} -``` - ---- - -### content_brief_create - -Create a content brief for later generation. - -**Scopes:** `write` - ---- - -### content_brief_get - -Get a content brief. - -**Scopes:** `read` - ---- - -### content_brief_list - -List content briefs. - -**Scopes:** `read` - ---- - -### content_status - -Get batch generation status. - -**Scopes:** `read` - ---- - -### content_usage_stats - -Get AI usage statistics. - -**Scopes:** `read` - ---- - -### content_from_plan - -Generate content based on plan context. - -**Scopes:** `write` - -## Error Responses - -All tools return errors in this format: - -```json -{ - "error": "Error message", - "code": "error_code" -} -``` - -Common error codes: -- `validation_error` - Invalid input -- `not_found` - Resource not found -- `permission_denied` - Insufficient permissions -- `rate_limited` - Rate limit exceeded -- `service_unavailable` - Circuit breaker open - -## Circuit Breaker - -Tools use circuit breaker protection for database calls. When the circuit opens: - -```json -{ - "error": "Agentic service temporarily unavailable", - "code": "service_unavailable" -} -``` - -The circuit resets after successful health checks. diff --git a/docs/packages/agentic/security.md b/docs/packages/agentic/security.md deleted file mode 100644 index d5bf2ef..0000000 --- a/docs/packages/agentic/security.md +++ /dev/null @@ -1,279 +0,0 @@ ---- -title: Security -description: Security considerations and audit notes for core-agentic -updated: 2026-01-29 ---- - -# Security Considerations - -This document outlines security considerations, known issues, and recommendations for the `core-agentic` package. - -## Authentication - -### API Key Security - -**Current Implementation:** -- Keys generated with `ak_` prefix + 32 random characters -- Stored as SHA-256 hash (no salt) -- Key only visible once at creation time -- Supports expiration dates -- Supports revocation - -**Known Issues:** - -1. **No salt in hash (SEC-001)** - - Risk: Rainbow table attacks possible against common key formats - - Mitigation: Keys are high-entropy (32 random chars), reducing practical risk - - Recommendation: Migrate to Argon2id with salt - -2. **Key prefix visible in hash display** - - The `getMaskedKey()` method shows first 6 chars of the hash, not the original key - - This is safe but potentially confusing for users - -**Recommendations:** -- Consider key rotation reminders -- Add key compromise detection (unusual usage patterns) -- Implement key versioning for smooth rotation - -### IP Whitelisting - -**Implementation:** -- Per-key IP restriction toggle -- Supports IPv4 and IPv6 -- Supports CIDR notation -- Logged when requests blocked - -**Validation:** -- Uses `filter_var()` with `FILTER_VALIDATE_IP` -- CIDR prefix validated against IP version limits (0-32 for IPv4, 0-128 for IPv6) -- Normalises IPs for consistent comparison - -**Edge Cases Handled:** -- Empty whitelist with restrictions enabled = deny all -- Invalid IPs/CIDRs rejected during configuration -- IP version mismatch (IPv4 vs IPv6) handled correctly - -## Authorisation - -### Multi-Tenancy - -**Workspace Scoping:** -- All models use `BelongsToWorkspace` trait -- Queries automatically scoped to current workspace context -- Missing workspace throws `MissingWorkspaceContextException` - -**Known Issues:** - -1. **StateSet tool lacks workspace validation (SEC-003)** - - Risk: Plan lookup by slug without workspace constraint - - Impact: Could allow cross-tenant state manipulation if slugs collide - - Fix: Add workspace_id check to plan query - -2. **Some tools have soft dependency on workspace** - - SessionStart marks workspace as optional if plan_slug provided - - Could theoretically allow workspace inference attacks - -### Permission Model - -**Scopes:** -- `plans.read` - List and view plans -- `plans.write` - Create, update, archive plans -- `phases.write` - Update phase status, manage tasks -- `sessions.read` - List and view sessions -- `sessions.write` - Start, update, complete sessions -- `tools.read` - View tool analytics -- `templates.read` - List and view templates -- `templates.instantiate` - Create plans from templates - -**Tool Scope Enforcement:** -- Each tool declares required scopes -- `AgentToolRegistry::execute()` validates scopes before execution -- Missing scope throws `RuntimeException` - -## Rate Limiting - -### Current Implementation - -**Global Rate Limiting:** -- ForAgentsController: 60 requests/minute per IP -- Configured via `RateLimiter::for('agentic-api')` - -**Per-Key Rate Limiting:** -- Configurable per API key (default: 100/minute) -- Uses cache-based counter with 60-second TTL -- Atomic increment via `Cache::add()` + `Cache::increment()` - -**Known Issues:** - -1. **No per-tool rate limiting (SEC-004)** - - Risk: Single key can call expensive tools unlimited times - - Impact: Resource exhaustion, cost overrun - - Fix: Add tool-specific rate limits - -2. **Rate limit counter not distributed** - - Multiple app servers may have separate counters - - Fix: Ensure Redis cache driver in production - -### Response Headers - -Rate limit status exposed via headers: -- `X-RateLimit-Limit` - Maximum requests allowed -- `X-RateLimit-Remaining` - Requests remaining in window -- `X-RateLimit-Reset` - Seconds until reset -- `Retry-After` - When rate limited - -## Input Validation - -### MCP Tool Inputs - -**Validation Helpers:** -- `requireString()` - Type + optional length validation -- `requireInt()` - Type + optional min/max validation -- `requireEnum()` - Value from allowed set -- `requireArray()` - Type validation - -**Known Issues:** - -1. **Template variable injection (VAL-001)** - - JSON escaping added but character validation missing - - Risk: Specially crafted variables could affect template behaviour - - Recommendation: Add explicit character whitelist - -2. **SQL orderByRaw pattern (SEC-002)** - - TaskCommand uses raw SQL for FIELD() ordering - - Currently safe (hardcoded values) but fragile pattern - - Recommendation: Use parameterised approach - -### Content Validation - -ContentService validates generated content: -- Minimum word count (600 words) -- UK English spelling checks -- Banned word detection -- Structure validation (headings required) - -## Data Protection - -### Sensitive Data Handling - -**API Keys:** -- Plaintext only available once (at creation) -- Hash stored, never logged -- Excluded from model serialisation via `$hidden` - -**Session Data:** -- Work logs may contain sensitive context -- Artifacts track file paths (not contents) -- Context summaries could contain user data - -**Recommendations:** -- Add data retention policies for sessions -- Consider encrypting context_summary field -- Audit work_log for sensitive data patterns - -### Logging - -**Current Logging:** -- IP restriction blocks logged with key metadata -- No API key plaintext ever logged -- No sensitive context logged - -**Recommendations:** -- Add audit logging for permission changes -- Log key creation/revocation events -- Consider structured logging for SIEM integration - -## Transport Security - -**Requirements:** -- All endpoints should be HTTPS-only -- MCP portal at mcp.host.uk.com -- API endpoints under /api/agent/* - -**Headers Set:** -- `X-Client-IP` - For debugging/audit -- Rate limit headers - -**Recommendations:** -- Add HSTS headers -- Consider mTLS for high-security deployments - -## Dependency Security - -### External API Calls - -AI provider services make external API calls: -- Anthropic API (Claude) -- Google AI API (Gemini) -- OpenAI API - -**Security Measures:** -- API keys from environment variables only -- HTTPS connections -- 300-second timeout -- Retry with exponential backoff - -**Recommendations:** -- Consider API key vault integration -- Add certificate pinning for provider endpoints -- Monitor for API key exposure in responses - -### Internal Dependencies - -The package depends on: -- `host-uk/core` - Event system -- `host-uk/core-tenant` - Workspace scoping -- `host-uk/core-mcp` - MCP infrastructure - -All are internal packages with shared security posture. - -## Audit Checklist - -### Pre-Production - -- [ ] All SEC-* issues in TODO.md addressed -- [ ] API key hashing upgraded to Argon2id -- [ ] StateSet workspace scoping fixed -- [ ] Per-tool rate limiting implemented -- [ ] Test coverage for auth/permission logic - -### Regular Audits - -- [ ] Review API key usage patterns -- [ ] Check for expired but not revoked keys -- [ ] Audit workspace scope bypass attempts -- [ ] Review rate limit effectiveness -- [ ] Check for unusual tool call patterns - -### Incident Response - -1. **Compromised API Key** - - Immediately revoke via `$key->revoke()` - - Check usage history in database - - Notify affected workspace owner - - Review all actions taken with key - -2. **Cross-Tenant Access** - - Disable affected workspace - - Audit all data access - - Review workspace scoping logic - - Implement additional checks - -## Security Contacts - -For security issues: -- Create private issue in repository -- Email security@host.uk.com -- Do not disclose publicly until patched - -## Changelog - -**2026-01-29** -- Initial security documentation -- Documented known issues SEC-001 through SEC-004 -- Added audit checklist - -**2026-01-21** -- Rate limiting functional (was stub) -- Admin routes now require Hades role -- ForAgentsController rate limited diff --git a/docs/packages/analytics/api.md b/docs/packages/analytics/api.md deleted file mode 100644 index aa0435f..0000000 --- a/docs/packages/analytics/api.md +++ /dev/null @@ -1,868 +0,0 @@ ---- -title: API Reference -description: REST API documentation for core-analytics -updated: 2026-01-29 ---- - -# API Reference - -This document describes the REST API for the core-analytics package. - -## Base URL - -All authenticated endpoints are prefixed with `/analytics`. - -## Authentication - -### Public Endpoints - -Tracking endpoints do not require authentication but need a valid `pixel_key`: - -``` -POST /analytics/track/{pixelKey} -POST /analytics/track -GET /analytics/pixel?key={pixelKey} -POST /analytics/heartbeat -POST /analytics/leave -POST /analytics/event -``` - -### Authenticated Endpoints - -Management endpoints require authentication via the Core PHP Framework auth system: - -``` -Authorization: Bearer {token} -``` - -## Rate Limiting - -Public tracking endpoints: 10,000 requests/minute (shared pool) - -## Tracking Endpoints - -### Track Pageview (POST) - -Track a pageview or event. - -**Endpoint:** `POST /analytics/track/{pixelKey}` - -**Response:** 1x1 transparent GIF (always returns success to avoid exposing errors) - ---- - -### Track Pageview (JSON) - -Track with full JSON payload. - -**Endpoint:** `POST /analytics/track` - -**Request Body:** -```json -{ - "key": "uuid-pixel-key", - "type": "pageview", - "visitor_id": "visitor-uuid", - "session_id": "session-uuid", - "path": "/page/path", - "title": "Page Title", - "referrer": "https://referrer.com", - "utm_source": "google", - "utm_medium": "cpc", - "utm_campaign": "summer-sale", - "screen_width": 1920, - "screen_height": 1080, - "language": "en-GB" -} -``` - -**Response:** -```json -{ - "ok": true, - "event_id": 12345, - "visitor_id": "visitor-uuid", - "session_id": "session-uuid" -} -``` - ---- - -### Track Pixel (GET) - -Lightweight tracking for noscript fallback. - -**Endpoint:** `GET /analytics/pixel?key={pixelKey}&p={path}&t={title}&r={referrer}` - -**Response:** 1x1 transparent GIF - ---- - -### Heartbeat - -Update time on page and scroll depth. - -**Endpoint:** `POST /analytics/heartbeat` - -**Request Body:** -```json -{ - "event_id": 12345, - "time_on_page": 120, - "scroll_depth": 75, - "session_id": "session-uuid" -} -``` - -**Response:** -```json -{ - "ok": true -} -``` - ---- - -### Leave - -End session on page unload (sendBeacon). - -**Endpoint:** `POST /analytics/leave` - -**Request Body:** -```json -{ - "session_id": "session-uuid" -} -``` - ---- - -### Custom Event - -Track a custom event. - -**Endpoint:** `POST /analytics/event` - -**Request Body:** -```json -{ - "key": "uuid-pixel-key", - "name": "button_click", - "visitor_id": "visitor-uuid", - "session_id": "session-uuid", - "properties": { - "button_id": "signup", - "variant": "blue" - } -} -``` - ---- - -## Website Management - -### List Websites - -**Endpoint:** `GET /analytics/websites` - -**Response:** -```json -{ - "data": [ - { - "id": 1, - "name": "My Website", - "host": "example.com", - "pixel_key": "uuid", - "tracking_enabled": true, - "is_enabled": true, - "created_at": "2026-01-01T00:00:00Z" - } - ] -} -``` - ---- - -### Create Website - -**Endpoint:** `POST /analytics/websites` - -**Request Body:** -```json -{ - "name": "My Website", - "host": "example.com", - "tracking_type": "lightweight", - "channel_type": "website" -} -``` - ---- - -### Get Website - -**Endpoint:** `GET /analytics/websites/{id}` - ---- - -### Update Website - -**Endpoint:** `PUT /analytics/websites/{id}` - ---- - -### Delete Website - -**Endpoint:** `DELETE /analytics/websites/{id}` - ---- - -## Statistics - -### Get Website Stats - -**Endpoint:** `GET /analytics/websites/{id}/stats` - -**Query Parameters:** -- `start_date` - ISO 8601 date (default: 7 days ago) -- `end_date` - ISO 8601 date (default: now) -- `timezone` - Timezone string (default: UTC) - -**Response:** -```json -{ - "total_pageviews": 12543, - "unique_visitors": 3421, - "bounce_rate": 42.5, - "avg_session_duration": 185, - "period": { - "start": "2026-01-22T00:00:00Z", - "end": "2026-01-29T00:00:00Z" - } -} -``` - ---- - -### Get Time Series - -**Endpoint:** `GET /analytics/websites/{id}/timeseries` - -**Query Parameters:** -- `metric` - `pageviews`, `visitors`, `sessions` -- `start_date` - ISO 8601 date -- `end_date` - ISO 8601 date -- `interval` - `day`, `week`, `month` - -**Response:** -```json -{ - "2026-01-22": 1543, - "2026-01-23": 1621, - "2026-01-24": 1489 -} -``` - ---- - -### Get Real-time Stats - -**Endpoint:** `GET /analytics/websites/{id}/realtime` - -**Response:** -```json -{ - "active_visitors": 23, - "active_pages": [ - {"path": "/", "viewers": 12}, - {"path": "/pricing", "viewers": 8} - ], - "locations": [ - {"country_code": "GB", "visitors": 15}, - {"country_code": "US", "visitors": 8} - ], - "timestamp": "2026-01-29T12:00:00Z" -} -``` - ---- - -## Goals - -### List Goals - -**Endpoint:** `GET /analytics/websites/{id}/goals` - ---- - -### Create Goal - -**Endpoint:** `POST /analytics/websites/{id}/goals` - -**Request Body:** -```json -{ - "name": "Signup Completion", - "type": "pageview", - "match_type": "equals", - "match_value": "/signup/complete" -} -``` - -**Goal Types:** -- `pageview` - URL match -- `event` - Custom event match -- `duration` - Session duration threshold -- `pages_per_session` - Pageview count threshold - -**Match Types (for pageview):** -- `equals` - Exact match -- `contains` - Substring match -- `starts_with` - Prefix match -- `ends_with` - Suffix match -- `regex` - Regular expression - ---- - -### Get Goal Conversions - -**Endpoint:** `GET /analytics/websites/{id}/goals/{goalId}/conversions` - -**Query Parameters:** -- `start_date` -- `end_date` -- `limit` - ---- - -### Get Goal Stats - -**Endpoint:** `GET /analytics/websites/{id}/goals/{goalId}/conversions/stats` - -**Response:** -```json -{ - "total_conversions": 342, - "conversion_rate": 4.2, - "total_value": 15230.50, - "average_value": 44.53 -} -``` - ---- - -## Funnels - -### List Funnels - -**Endpoint:** `GET /analytics/websites/{id}/funnels` - ---- - -### Create Funnel - -**Endpoint:** `POST /analytics/websites/{id}/funnels` - -**Request Body:** -```json -{ - "name": "Checkout Funnel", - "is_strict": false, - "window_hours": 24 -} -``` - ---- - -### Get Funnel Analysis - -**Endpoint:** `GET /analytics/websites/{id}/funnels/{funnelId}/analysis` - -**Query Parameters:** -- `start_date` -- `end_date` - -**Response:** -```json -{ - "funnel_id": 1, - "funnel_name": "Checkout Funnel", - "summary": { - "total_entrants": 1000, - "completed": 120, - "completion_rate": 12.0, - "avg_completion_time": 540, - "total_steps": 4 - }, - "steps": [ - { - "step_id": 1, - "name": "Add to Cart", - "visitors": 1000, - "conversion_rate": 100.0, - "drop_off": 0, - "drop_off_rate": 0 - }, - { - "step_id": 2, - "name": "View Cart", - "visitors": 650, - "conversion_rate": 65.0, - "drop_off": 350, - "drop_off_rate": 35.0 - } - ] -} -``` - ---- - -### Add Funnel Step - -**Endpoint:** `POST /analytics/websites/{id}/funnels/{funnelId}/steps` - -**Request Body:** -```json -{ - "name": "Add to Cart", - "match_type": "pageview", - "match_value": "/cart/add", - "is_optional": false -} -``` - ---- - -## A/B Experiments - -### List Experiments - -**Endpoint:** `GET /analytics/websites/{id}/experiments` - ---- - -### Create Experiment - -**Endpoint:** `POST /analytics/websites/{id}/experiments` - -**Request Body:** -```json -{ - "name": "Button Colour Test", - "goal_type": "pageview", - "goal_value": "/signup/complete", - "traffic_percentage": 100 -} -``` - ---- - -### Start Experiment - -**Endpoint:** `POST /analytics/websites/{id}/experiments/{experimentId}/start` - ---- - -### Pause Experiment - -**Endpoint:** `POST /analytics/websites/{id}/experiments/{experimentId}/pause` - ---- - -### Stop Experiment - -**Endpoint:** `POST /analytics/websites/{id}/experiments/{experimentId}/stop` - ---- - -### Get Results - -**Endpoint:** `GET /analytics/websites/{id}/experiments/{experimentId}/results` - -**Response:** -```json -{ - "experiment": { - "id": 1, - "name": "Button Colour Test", - "status": "running" - }, - "metrics": { - "total_visitors": 5000, - "total_conversions": 250, - "overall_conversion_rate": 5.0 - }, - "analysis": { - "is_significant": true, - "confidence": 95.5, - "p_value": 0.045, - "winner": 2, - "recommendation": "\"Blue Button\" is the winner with 95% confidence (+12% lift)", - "results": { - "1": { - "name": "Control", - "visitors": 2500, - "conversions": 100, - "conversion_rate": 4.0, - "is_control": true - }, - "2": { - "name": "Blue Button", - "visitors": 2500, - "conversions": 150, - "conversion_rate": 6.0, - "lift": 50.0, - "is_significant": true - } - } - } -} -``` - ---- - -### Add Variant - -**Endpoint:** `POST /analytics/websites/{id}/experiments/{experimentId}/variants` - -**Request Body:** -```json -{ - "name": "Blue Button", - "is_control": false, - "weight": 50, - "config": { - "button_color": "#0066cc" - } -} -``` - ---- - -### Get Variant (Public) - -For client-side variant assignment. - -**Endpoint:** `GET /analytics/experiment/variant` - -**Query Parameters:** -- `experiment_id` -- `visitor_id` - -**Response:** -```json -{ - "variant_id": 2, - "variant_name": "Blue Button", - "config": { - "button_color": "#0066cc" - } -} -``` - ---- - -## Heatmaps - -### List Heatmaps - -**Endpoint:** `GET /analytics/websites/{id}/heatmaps` - ---- - -### Create Heatmap - -**Endpoint:** `POST /analytics/websites/{id}/heatmaps` - -**Request Body:** -```json -{ - "name": "Homepage Clicks", - "url_pattern": "/", - "type": "click" -} -``` - -**Heatmap Types:** -- `click` - Click positions -- `move` - Mouse movement -- `scroll` - Scroll depth - ---- - -### Get Heatmap Data - -**Endpoint:** `GET /analytics/websites/{id}/heatmaps/{heatmapId}/data` - -**Response:** -```json -{ - "heatmap_id": 1, - "data": [ - {"x": 500, "y": 300, "count": 45}, - {"x": 750, "y": 450, "count": 32} - ], - "viewport": { - "width": 1920, - "height": 1080 - } -} -``` - ---- - -## Session Replays - -### List Replays - -**Endpoint:** `GET /analytics/websites/{id}/replays` - -**Query Parameters:** -- `limit` -- `device_type` -- `country_code` - ---- - -### Get Replay - -**Endpoint:** `GET /analytics/websites/{id}/replays/{replayId}` - ---- - -### Get Playback Data - -**Endpoint:** `GET /analytics/websites/{id}/replays/{replayId}/playback` - -**Response:** rrweb-compatible event array - ---- - -### Delete Replay - -**Endpoint:** `DELETE /analytics/websites/{id}/replays/{replayId}` - ---- - -## Bot Detection - -### Get Bot Stats - -**Endpoint:** `GET /analytics/websites/{id}/bots/stats` - -**Response:** -```json -{ - "period": { - "from": "2026-01-01", - "to": "2026-01-29" - }, - "totals": { - "total_requests": 50000, - "blocked_requests": 2500, - "allowed_requests": 47500, - "block_rate": 5.0 - }, - "bot_types": { - "crawler": 1500, - "scraper": 800, - "headless": 200 - }, - "top_bots": { - "Googlebot": 800, - "Bingbot": 400, - "Ahrefs": 300 - } -} -``` - ---- - -### List Detections - -**Endpoint:** `GET /analytics/websites/{id}/bots/detections` - ---- - -### List Rules - -**Endpoint:** `GET /analytics/websites/{id}/bots/rules` - ---- - -### Create Rule - -**Endpoint:** `POST /analytics/websites/{id}/bots/rules` - -**Request Body:** -```json -{ - "rule_type": "whitelist", - "match_type": "ip", - "match_value": "192.168.1.100", - "description": "Office IP" -} -``` - -**Rule Types:** -- `whitelist` - Always allow -- `blacklist` - Always block - -**Match Types:** -- `ip` - Exact IP match -- `ip_range` - CIDR range (e.g., `192.168.1.0/24`) -- `user_agent` - Substring match in User-Agent - ---- - -## GDPR - -### Export Visitor Data - -**Endpoint:** `GET /analytics/gdpr/export/{visitorHash}` - -**Response:** JSON file download with all visitor data - ---- - -### Delete Visitor Data - -**Endpoint:** `DELETE /analytics/gdpr/visitor/{visitorHash}` - -**Response:** -```json -{ - "deleted_counts": { - "events": 152, - "sessions": 12, - "pageviews": 145, - "conversions": 3, - "visitor": 1 - } -} -``` - ---- - -### Anonymise Visitor - -**Endpoint:** `POST /analytics/gdpr/anonymise/{visitorHash}` - -Preserves aggregate data while removing PII. - ---- - -### Record Consent (Public) - -**Endpoint:** `POST /analytics/gdpr/consent` - -**Request Body:** -```json -{ - "visitor_id": "visitor-uuid", - "pixel_key": "uuid-pixel-key" -} -``` - ---- - -### Withdraw Consent (Public) - -**Endpoint:** `DELETE /analytics/gdpr/consent` - ---- - -### Check Consent Status (Public) - -**Endpoint:** `GET /analytics/gdpr/consent/status?visitor_id={id}&pixel_key={key}` - ---- - -## Email Reports - -### List Reports - -**Endpoint:** `GET /analytics/websites/{id}/email-reports` - ---- - -### Create Report - -**Endpoint:** `POST /analytics/websites/{id}/email-reports` - -**Request Body:** -```json -{ - "name": "Weekly Summary", - "frequency": "weekly", - "recipients": ["user@example.com"], - "metrics": ["pageviews", "visitors", "bounce_rate"] -} -``` - -**Frequencies:** -- `daily` -- `weekly` -- `monthly` - ---- - -### Preview Report - -**Endpoint:** `POST /analytics/websites/{id}/email-reports/{reportId}/preview` - ---- - -### Send Report Now - -**Endpoint:** `POST /analytics/websites/{id}/email-reports/{reportId}/send` - ---- - -## Error Responses - -### Standard Error Format - -```json -{ - "ok": false, - "error": "Error message", - "code": "ERROR_CODE" -} -``` - -### Common Error Codes - -| HTTP Status | Code | Description | -|-------------|------|-------------| -| 400 | `VALIDATION_ERROR` | Invalid request parameters | -| 401 | `UNAUTHENTICATED` | Missing or invalid authentication | -| 403 | `FORBIDDEN` | Insufficient permissions | -| 404 | `NOT_FOUND` | Resource not found | -| 429 | `RATE_LIMITED` | Too many requests | -| 500 | `SERVER_ERROR` | Internal server error | - ---- - -## SDK Integration - -### JavaScript Tracker - -```html - -``` - -### Server-Side Tracking - -```php -use Core\Mod\Analytics\Services\AnalyticsTrackingService; - -$tracking = app(AnalyticsTrackingService::class); -$tracking->track($website, [ - 'type' => 'pageview', - 'path' => '/api/endpoint', - 'visitor_id' => $request->header('X-Visitor-ID'), -], $request); -``` diff --git a/docs/packages/analytics/architecture.md b/docs/packages/analytics/architecture.md deleted file mode 100644 index 2779f74..0000000 --- a/docs/packages/analytics/architecture.md +++ /dev/null @@ -1,419 +0,0 @@ ---- -title: Architecture -description: Technical architecture of core-analytics -updated: 2026-01-29 ---- - -# Architecture - -This document describes the technical architecture of the core-analytics package, a privacy-focused website analytics module for the Core PHP Framework. - -## Overview - -core-analytics is a Laravel package providing website analytics with: - -- Privacy-first design (IP anonymisation, DNT respect, GDPR compliance) -- Real-time visitor tracking via Redis -- Session replays and heatmaps -- A/B testing with statistical significance -- Funnel analysis -- Bot detection and filtering -- Multi-tenant workspace isolation - -## Package Structure - -``` -core-analytics/ -├── Boot.php # Service provider, event registration -├── config.php # Configuration defaults -├── Controllers/ -│ ├── PixelController.php # Public tracking endpoints -│ └── Api/ # Authenticated API controllers -├── Services/ -│ ├── AnalyticsService.php # Core stats/aggregation -│ ├── AnalyticsTrackingService.php # Event tracking -│ ├── BotDetectionService.php # Bot scoring -│ ├── FunnelService.php # Funnel analysis -│ ├── GdprService.php # Privacy compliance -│ ├── GeoIpService.php # Geolocation -│ ├── RealtimeAnalyticsService.php # Redis-based realtime -│ ├── SessionReplayStorageService.php -│ └── AnalyticsExperimentService.php # A/B testing -├── Jobs/ -│ ├── ProcessTrackingEvent.php # Main event processor -│ ├── ProcessPageview.php -│ ├── ProcessHeatmapEvent.php -│ └── ProcessSessionReplay.php -├── Models/ # Eloquent models -├── Migrations/ # Database migrations -├── Console/Commands/ # Artisan commands -├── Mcp/Tools/ # MCP tool handlers -├── View/ # Blade/Livewire components -└── routes/ # Route definitions -``` - -## Event-Driven Module Loading - -The package follows the Core PHP Framework event-driven pattern: - -```php -class Boot extends ServiceProvider -{ - public static array $listens = [ - AdminPanelBooting::class => 'onAdminPanel', - WebRoutesRegistering::class => 'onWebRoutes', - ApiRoutesRegistering::class => 'onApiRoutes', - ConsoleBooting::class => 'onConsole', - ]; -} -``` - -Handlers are only instantiated when their events fire, enabling lazy loading. - -## Data Flow - -### Tracking Flow - -``` -┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ JS Tracker │────>│ PixelController │────>│ ProcessTracking │ -│ (browser) │ │ (validation) │ │ Event (queue) │ -└─────────────────┘ └──────────────────┘ └────────┬────────┘ - │ - ┌─────────────────────────────────┼─────────────────────────────────┐ - │ │ │ - v v v - ┌───────────────┐ ┌──────────────────┐ ┌─────────────────┐ - │ Bot Check │ │ Entitlement │ │ GeoIP │ - │ (scoring) │ │ Check │ │ Lookup │ - └───────┬───────┘ └────────┬─────────┘ └────────┬────────┘ - │ │ │ - └───────────────────────────────┼──────────────────────────────────┘ - │ - v - ┌──────────────────┐ - │ Database Write │ - │ (visitor, │ - │ session, │ - │ event) │ - └────────┬─────────┘ - │ - ┌──────────────────────────────┼──────────────────────────────────┐ - │ │ │ - v v v - ┌───────────────┐ ┌──────────────────┐ ┌─────────────────┐ - │ Goal Check │ │ Cache Invalidate│ │ Realtime │ - │ (conversion) │ │ (stats cache) │ │ Broadcast │ - └───────────────┘ └──────────────────┘ └─────────────────┘ -``` - -### Statistics Query Flow - -``` -┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ Dashboard │────>│ Stats Controller│────>│ AnalyticsService│ -│ (request) │ │ (auth, scope) │ │ (query/cache) │ -└─────────────────┘ └──────────────────┘ └────────┬────────┘ - │ - v - ┌──────────────────┐ - │ Cache Check │ - │ (5 min TTL) │ - └────────┬─────────┘ - │ - ┌───────────────┴───────────────┐ - │ │ - v v - ┌───────────────┐ ┌───────────────┐ - │ Cache Hit │ │ Cache Miss │ - │ (return) │ │ (query DB) │ - └───────────────┘ └───────┬───────┘ - │ - v - ┌───────────────┐ - │ Store Cache │ - │ (return) │ - └───────────────┘ -``` - -## Database Schema - -### Core Tables - -| Table | Purpose | Volume | -|-------|---------|--------| -| `analytics_websites` | Website/property configuration | Low | -| `analytics_visitors` | Unique visitor records | High | -| `analytics_sessions` | Session aggregation | High | -| `analytics_events` | Individual events (pageviews, clicks) | Very High | -| `analytics_pageviews` | Denormalised pageview data | Very High | -| `analytics_goals` | Goal definitions | Low | -| `analytics_goal_conversions` | Goal conversion records | Medium | -| `analytics_daily_stats` | Pre-aggregated daily statistics | Low | - -### Feature Tables - -| Table | Purpose | -|-------|---------| -| `analytics_heatmaps` | Heatmap configuration | -| `analytics_heatmap_events` | Click/scroll/move coordinates | -| `analytics_session_replays` | Session replay metadata | -| `analytics_funnels` | Funnel definitions | -| `analytics_funnel_steps` | Funnel step configuration | -| `analytics_funnel_conversions` | Funnel progress tracking | -| `analytics_experiments` | A/B test configuration | -| `analytics_variants` | Experiment variant definitions | -| `analytics_experiment_visitors` | Variant assignments | -| `analytics_bot_detections` | Bot detection logs | -| `analytics_bot_rules` | Custom whitelist/blacklist | -| `analytics_bot_ip_cache` | IP reputation cache | -| `analytics_email_reports` | Scheduled report config | -| `analytics_email_report_logs` | Report send history | - -### Indexes - -Key indexes for query performance: - -```sql --- Website + date range queries -INDEX (website_id, created_at) -INDEX (website_id, started_at) -INDEX (website_id, last_seen_at) - --- Visitor/session lookups -UNIQUE (website_id, visitor_uuid) -UNIQUE (website_id, session_uuid) -INDEX (visitor_id) -INDEX (session_id) - --- Path analysis -INDEX (website_id, path, created_at) -``` - -## Multi-Tenancy - -All models use the `BelongsToWorkspace` trait from core-tenant: - -```php -class AnalyticsWebsite extends Model -{ - use BelongsToWorkspace; - // ... -} -``` - -This provides: -- Automatic `workspace_id` assignment on create -- Global scope filtering to current workspace -- `MissingWorkspaceContextException` safety - -## Caching Strategy - -### Cache Keys - -``` -analytics:stats:{website_id}:{start}:{end} -analytics:timeseries:{website_id}:{metric}:{start}:{end}:{interval} -analytics:top_pages:{website_id}:{start}:{end}:{limit} -analytics:traffic_sources:{website_id}:{start}:{end} -analytics:geo:{website_id}:{start}:{end} -analytics_website:{pixel_key} # Website lookup cache (1 hour) -analytics_config:{pixel_key} # Config cache (5 minutes) -bot_rules:{website_id} # Bot rules cache (5 minutes) -``` - -### Invalidation - -Cache invalidation occurs on: -- New tracking event (debounced, selective invalidation) -- Manual invalidation via `AnalyticsService::invalidateCache()` -- TTL expiration (5 minutes for stats) - -## Queue Architecture - -### Queues - -| Queue | Purpose | Workers | -|-------|---------|---------| -| `analytics-tracking` | High-priority tracking events | 2-4 | -| `analytics` | Heatmaps, replays, cleanup | 1-2 | -| `default` | Email reports, low-priority | 1 | - -### Job Configuration - -```php -class ProcessTrackingEvent implements ShouldQueue -{ - public int $tries = 3; - public int $timeout = 30; -} -``` - -## Real-Time Analytics - -Uses Redis sorted sets for 5-minute sliding window: - -``` -analytics:realtime:visitors:{website_id} # Sorted set: visitor_id -> timestamp -analytics:realtime:visitor_page:{website_id}:{id} # String: current path -analytics:realtime:visitor_country:{website_id}:{id} -``` - -### Broadcast Throttling - -Updates are throttled to 2-second intervals to prevent flooding WebSocket channels during high traffic. - -## Bot Detection - -### Scoring Algorithm - -Signals are weighted (sum = 100): - -| Signal | Weight | Description | -|--------|--------|-------------| -| User-Agent | 35% | Bot patterns, headless browsers, HTTP libraries | -| Headers | 20% | Missing Accept/Accept-Language, automation indicators | -| IP Reputation | 15% | Datacenter IPs, known crawler ranges | -| Behaviour | 20% | JS indicators, screen dimensions, timing | -| Custom Rules | 10% | Whitelist/blacklist matches | - -### Thresholds - -- `threshold` (50): Score >= threshold = classified as bot -- `block_threshold` (70): Score >= threshold = blocked from tracking -- `min_log_score` (30): Minimum score to log detection - -### Legitimate Crawlers - -Known search engine IPs (Google, Bing, etc.) receive a 30-point score reduction and are logged but not blocked. - -## A/B Testing - -### Variant Assignment - -Uses deterministic hashing for consistent assignment: - -```php -$hash = abs(crc32($visitorId . $experimentId)) % 100; -``` - -This ensures the same visitor always gets the same variant, even across sessions. - -### Statistical Significance - -Uses two-proportion z-test: - -1. Calculate conversion rates (p1, p2) -2. Calculate pooled proportion -3. Calculate standard error -4. Compute z-score and p-value -5. Compare against confidence level (default 95%) - -Minimum sample size per variant: 100 (configurable). - -## Data Retention - -Tier-based retention: - -| Tier | Days | -|------|------| -| Free | 30 | -| Pro | 90 | -| Business | 365 | -| Enterprise | 3650 | - -Cleanup via `analytics:cleanup` command: -1. Aggregate data into `analytics_daily_stats` -2. Delete raw events/sessions/pageviews -3. Clean orphaned visitors - -## Privacy Features - -### IP Anonymisation - -Last octet zeroed by default: -``` -192.168.1.123 -> 192.168.1.0 -``` - -### Do Not Track - -Respects DNT header when `analytics.privacy.respect_dnt` is enabled. - -### GDPR Compliance - -- `GdprService::exportVisitorData()` - Full data export -- `GdprService::deleteVisitorData()` - Complete deletion -- `GdprService::anonymiseVisitor()` - Preserve aggregates, remove PII -- Consent tracking per-visitor - -## External Dependencies - -### Required - -- `host-uk/core` - Core PHP Framework -- Redis - Real-time analytics, caching -- Queue worker - Event processing - -### Optional - -- MaxMind GeoLite2 - IP geolocation (falls back to CDN headers) -- S3/compatible - Session replay storage (falls back to local) - -## Configuration - -Key configuration options in `config.php`: - -```php -return [ - 'session_replay' => [ - 'disk' => 'local', // or 's3' - 'expiry_days' => 90, - 'max_size' => 10 * 1024 * 1024, - ], - 'bot_detection' => [ - 'enabled' => true, - 'threshold' => 50, - 'block_threshold' => 70, - ], - 'privacy' => [ - 'anonymise_ip' => true, - 'respect_dnt' => true, - ], - 'retention' => [ - 'tiers' => [ - 'free' => 30, - 'pro' => 90, - 'business' => 365, - 'enterprise' => 3650, - ], - ], -]; -``` - -## Scaling Considerations - -### High Volume Sites - -For sites with >1M daily pageviews: - -1. **Separate analytics database** - Isolate from application DB -2. **Read replicas** - Route stats queries to replicas -3. **Redis Cluster** - Scale real-time tracking -4. **Dedicated queue workers** - Scale event processing -5. **ClickHouse** - Consider columnar storage for aggregations - -### Recommended Worker Configuration - -``` -# Low traffic (<100k/day) -php artisan queue:work --queue=analytics-tracking,analytics - -# Medium traffic (100k-1M/day) -php artisan queue:work --queue=analytics-tracking --workers=2 -php artisan queue:work --queue=analytics - -# High traffic (>1M/day) -php artisan queue:work --queue=analytics-tracking --workers=4 -php artisan queue:work --queue=analytics --workers=2 -``` diff --git a/docs/packages/analytics/security.md b/docs/packages/analytics/security.md deleted file mode 100644 index d7f59b0..0000000 --- a/docs/packages/analytics/security.md +++ /dev/null @@ -1,461 +0,0 @@ ---- -title: Security -description: Security considerations and audit notes for core-analytics -updated: 2026-01-29 ---- - -# Security - -This document outlines security considerations, implemented mitigations, and audit notes for the core-analytics package. - -## Threat Model - -### Actors - -1. **Anonymous attackers** - Attempting to exploit public tracking endpoints -2. **Authenticated users** - Potentially abusing legitimate access -3. **Tracked visitors** - Privacy concerns, data exposure -4. **Malicious websites** - Cross-site attacks via tracking pixel - -### Assets - -1. Analytics data (pageviews, sessions, conversions) -2. Visitor PII (IP addresses, geolocation, device info) -3. Session replay recordings -4. Workspace/tenant data isolation - -## Implemented Mitigations - -### Input Validation - -#### Tracking Endpoints - -All tracking endpoints validate input: - -```php -$validated = $request->validate([ - 'key' => 'required|uuid', - 'type' => 'sometimes|string|in:pageview,click,scroll,form,goal,custom', - 'path' => 'required|string|max:512', - 'title' => 'sometimes|string|max:256', - // ... more fields -]); -``` - -**Audit note:** Consider adding Form Request classes for better organisation. - -#### Regex Pattern Safety - -Goal patterns support regex matching with ReDoS protection: - -```php -protected function safeRegexMatch(string $pattern, string $subject): bool -{ - // Limit subject length - if (strlen($subject) > 2048) { - $subject = substr($subject, 0, 2048); - } - - // Validate pattern syntax - if (!$this->isValidRegexPattern($pattern)) { - return false; - } - - // Set lower backtrack limit - ini_set('pcre.backtrack_limit', '10000'); - // ... -} -``` - -**Audit note:** Consider pattern complexity analysis at storage time, not just execution. - -### Rate Limiting - -Public tracking endpoints are rate-limited: - -```php -Route::middleware('throttle:10000,1')->prefix('analytics')->group(function () { - // 10,000 requests per minute -}); -``` - -**Audit note:** Rate limiting is per-IP, not per-pixel-key. Shared pool could be exhausted by targeting one key. Consider per-key quotas. - -### Bot Detection - -Multi-signal bot detection prevents analytics pollution: - -| Signal | Detection Method | -|--------|------------------| -| User-Agent | Pattern matching for known bots, headless browsers | -| HTTP Headers | Missing Accept/Accept-Language headers | -| IP Reputation | Datacenter IP ranges, cached bot scores | -| Behaviour | Invalid screen dimensions, fast request timing | - -**Audit note:** Bot detection relies on IP caching. NAT/VPN users could be incorrectly flagged. Consider composite scoring. - -### Multi-Tenant Isolation - -All models use `BelongsToWorkspace` trait: - -```php -class AnalyticsWebsite extends Model -{ - use BelongsToWorkspace; - // Automatically scoped to current workspace -} -``` - -This provides: -- Automatic `workspace_id` assignment on create -- Global scope filtering queries to current workspace -- Exception thrown if no workspace context - -**Audit note:** Verify all API endpoints properly set workspace context before queries. - -### IP Anonymisation - -IP addresses are anonymised before storage by default: - -```php -'ip' => PrivacyHelper::anonymiseIp($request->ip()), -``` - -Zeroes the last octet: `192.168.1.123` -> `192.168.1.0` - -Controlled by `analytics.privacy.anonymise_ip` config option. - -### Pixel Key Security - -Pixel keys are UUIDs generated at website creation: - -```php -protected static function booted(): void -{ - static::creating(function (AnalyticsWebsite $website) { - if (empty($website->pixel_key)) { - $website->pixel_key = Str::uuid(); - } - }); -} -``` - -Keys are: -- Unique across all websites -- Cannot be user-specified -- Cached for lookup efficiency - -**Audit note:** Keys are not rotatable. Consider adding key rotation capability. - -### CORS Configuration - -Tracking endpoints allow cross-origin requests (required for tracking pixels): - -```php -protected function corsHeaders(): array -{ - return [ - 'Access-Control-Allow-Origin' => '*', - 'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS', - 'Access-Control-Allow-Headers' => 'Content-Type, X-Requested-With', - 'Access-Control-Max-Age' => '86400', - ]; -} -``` - -**Audit note:** `*` origin is acceptable for tracking endpoints. Authenticated API endpoints should have stricter CORS. - -## GDPR Compliance - -### Data Export - -Full data export for GDPR requests: - -```php -public function exportVisitorData(AnalyticsVisitor $visitor): array -{ - return [ - 'visitor' => [...], - 'sessions' => $this->exportSessions($visitor), - 'events' => $this->exportEvents($visitor), - 'pageviews' => $this->exportPageviews($visitor), - 'conversions' => $this->exportConversions($visitor), - 'heatmap_events' => $this->exportHeatmapEvents($visitor), - 'session_replays' => $this->exportSessionReplays($visitor), - ]; -} -``` - -**Audit note:** Verify `AnalyticsFunnelConversion` and `AnalyticsExperimentVisitor` are included. - -### Data Deletion - -Complete data deletion: - -```php -public function deleteVisitorData(AnalyticsVisitor $visitor): array -{ - DB::transaction(function () use ($visitor, &$counts) { - $counts['events'] = AnalyticsEvent::where('visitor_id', $visitor->id)->delete(); - $counts['pageviews'] = Pageview::where('visitor_id', $visitor->visitor_uuid)->delete(); - // ... all related data - $counts['visitor'] = $visitor->delete() ? 1 : 0; - }); -} -``` - -### Anonymisation - -Alternative to deletion that preserves aggregates: - -```php -public function anonymiseVisitor(AnalyticsVisitor $visitor): AnalyticsVisitor -{ - $visitor->update([ - 'ip' => null, - 'country_code' => null, - 'city_name' => null, - 'region' => null, - 'browser_language' => null, - 'custom_parameters' => null, - 'is_anonymised' => true, - 'anonymised_at' => now(), - ]); - // Also anonymises related records -} -``` - -### Consent Tracking - -Per-visitor consent tracking: - -```php -public function recordConsent(AnalyticsVisitor $visitor, ?string $ip = null): AnalyticsVisitor -{ - $visitor->update([ - 'consent_given' => true, - 'consent_given_at' => now(), - 'consent_ip' => $ip, - ]); -} -``` - -Tracking can be configured to require consent: - -```php -if ($website->require_consent && !$visitor->hasConsent()) { - return null; // Don't track -} -``` - -## Session Replay Security - -### Storage - -Replays are stored compressed on configured disk (S3 recommended for production): - -```php -$compressedData = gzencode($jsonData, 9); -Storage::disk($this->disk)->put($storagePath, $compressedData); -``` - -### Sensitive Data - -**Warning:** Session replays may capture sensitive form data. - -Mitigations: -1. Client-side SDK should mask password fields, credit cards -2. Max size limit (10MB default) prevents large data exfiltration -3. Expiry (90 days default) limits data retention - -**Audit note:** Consider server-side PII detection and redaction. - -### Access Control - -Replay playback requires authentication and workspace ownership: - -```php -Route::middleware(['auth', 'api'])->group(function () { - Route::get('/websites/{website}/replays/{replay}/playback', ...); -}); -``` - -## A/B Testing Security - -### Variant Assignment - -Deterministic assignment prevents manipulation: - -```php -$hash = abs(crc32($visitorId . $experimentId)) % 100; -``` - -Users cannot choose their variant. - -### Statistical Integrity - -- Minimum sample sizes enforced before declaring winners -- Statistical significance uses standard z-test -- Results are read-only once experiment is complete - -## API Security - -### Authentication - -All management endpoints require authentication: - -```php -Route::middleware(['auth', 'api'])->prefix('analytics')->group(function () { - // Websites, goals, experiments, etc. -}); -``` - -### Authorisation - -Workspace scoping provides implicit authorisation. Users can only access resources in their workspace. - -**Audit note:** Consider explicit policy classes for finer-grained control. - -## Data Retention - -### Automatic Cleanup - -```bash -php artisan analytics:cleanup -``` - -Tier-based retention: - -| Tier | Days | -|------|------| -| Free | 30 | -| Pro | 90 | -| Business | 365 | -| Enterprise | 3650 | - -### Aggregation Before Deletion - -Data is aggregated into `analytics_daily_stats` before raw data deletion, preserving historical trends. - -## Known Vulnerabilities / Limitations - -### Rate Limit Bypass - -**Issue:** Rate limiting uses shared pool across all pixel keys. - -**Impact:** Medium - Attacker could exhaust quota for legitimate users. - -**Mitigation:** Consider per-pixel-key rate limiting. - -### Bot IP Cache Poisoning - -**Issue:** IP scores are cached for 24 hours. NAT/VPN users share IPs. - -**Impact:** Low - False positives for legitimate users behind bot-flagged IPs. - -**Mitigation:** Consider composite scoring (IP + fingerprint) and score decay. - -### Session Replay PII - -**Issue:** Replays may contain sensitive data if client SDK doesn't mask inputs. - -**Impact:** Medium - PII exposure in replays. - -**Mitigation:** Document client-side requirements; consider server-side sanitisation. - -## Security Headers - -The package doesn't set security headers (handled by framework/proxy). Recommended: - -``` -X-Content-Type-Options: nosniff -X-Frame-Options: DENY -Content-Security-Policy: default-src 'self' -``` - -## Logging - -Security-relevant events are logged: - -```php -Log::info('GDPR: Visitor data deleted', [...]); -Log::info('GDPR: Consent recorded', [...]); -Log::warning('ProcessTrackingEvent: Missing website_id', [...]); -``` - -**Audit note:** Consider structured security event logging for SIEM integration. - -## Dependency Security - -### Direct Dependencies - -- `host-uk/core` - Internal, audited -- `laravel/*` - Well-maintained, security updates - -### GeoIP Database - -MaxMind GeoLite2 database should be updated regularly: - -```bash -# Recommended: weekly cron job -wget -O storage/app/geoip/GeoLite2-City.mmdb https://... -``` - -## Recommendations - -### High Priority - -1. Implement per-pixel-key rate limiting -2. Add Form Request classes for input validation -3. Server-side session replay PII detection -4. Complete GDPR export (funnel/experiment data) - -### Medium Priority - -1. Add policy classes for explicit authorisation -2. Pixel key rotation capability -3. Composite bot scoring (IP + fingerprint) -4. Structured security event logging - -### Low Priority - -1. IP reputation decay -2. Anomaly detection for abuse patterns -3. CSP header configuration guide - -## Audit Checklist - -- [ ] All models use BelongsToWorkspace trait -- [ ] API endpoints set workspace context -- [ ] Input validation on all endpoints -- [ ] Rate limiting active -- [ ] GDPR export complete -- [ ] Session replay PII handling documented -- [ ] Bot detection thresholds tuned -- [ ] Cleanup job scheduled -- [ ] GeoIP database current -- [ ] Dependencies updated - -## Incident Response - -### Data Breach - -1. Identify affected workspaces -2. Export affected visitor data -3. Notify workspace owners -4. Delete or anonymise exposed data -5. Rotate affected pixel keys - -### Bot Attack - -1. Review `analytics_bot_detections` for patterns -2. Add custom blacklist rules -3. Adjust thresholds if needed -4. Consider IP-based blocking at CDN level - -### Quota Abuse - -1. Identify abusive pixel key -2. Check workspace entitlements -3. Disable tracking for affected website -4. Contact workspace owner diff --git a/docs/packages/analytics/testing.md b/docs/packages/analytics/testing.md deleted file mode 100644 index bc37900..0000000 --- a/docs/packages/analytics/testing.md +++ /dev/null @@ -1,435 +0,0 @@ ---- -title: Testing -description: Test coverage and testing guide for core-analytics -updated: 2026-01-29 ---- - -# Testing - -This document describes the testing strategy, coverage, and guidelines for the core-analytics package. - -## Test Structure - -``` -tests/ -├── Feature/ -│ ├── Admin/ # Admin panel tests -│ ├── Api/ # API endpoint tests -│ ├── Integration/ # End-to-end flow tests -│ ├── Mcp/ # MCP tool tests -│ ├── AnalyticsServiceTest.php -│ ├── AnalyticsTrackingServiceTest.php -│ ├── BotDetectionServiceTest.php -│ ├── ExperimentTest.php -│ ├── FunnelTest.php -│ ├── GdprTest.php -│ ├── GoalApiTest.php -│ ├── GoalTest.php -│ ├── HeatmapTest.php -│ ├── SessionReplayTest.php -│ └── ... -├── Unit/ -│ └── UserAgentParserTest.php -├── UseCase/ -│ ├── CreateWebsiteBasic.php -│ └── CreateWebsiteEnhanced.php -└── TestCase.php -``` - -## Running Tests - -```bash -# Run all tests -composer test - -# Run with coverage -composer test -- --coverage - -# Run specific test file -./vendor/bin/pest tests/Feature/BotDetectionServiceTest.php - -# Run specific test -./vendor/bin/pest --filter="test_detects_known_bot_user_agents" - -# Run tests in parallel -./vendor/bin/pest --parallel -``` - -## Test Database - -Tests use SQLite in-memory database with `RefreshDatabase` trait: - -```php -use Illuminate\Foundation\Testing\RefreshDatabase; - -class BotDetectionServiceTest extends TestCase -{ - use RefreshDatabase; -} -``` - -## Coverage Summary - -### Services - -| Service | Coverage | Notes | -|---------|----------|-------| -| AnalyticsService | Good | Stats generation, time series | -| AnalyticsTrackingService | Good | Event tracking, session management | -| BotDetectionService | Good | Detection, caching, rules | -| FunnelService | Partial | Analysis covered, step matching needs more | -| GdprService | Good | Export, delete, anonymise | -| GeoIpService | Partial | CDN headers covered, MaxMind mocked | -| RealtimeAnalyticsService | Partial | Redis operations, needs integration test | -| SessionReplayStorageService | Partial | Store/retrieve, cleanup needs more | -| AnalyticsExperimentService | Good | Variant assignment, significance | - -### Controllers - -| Controller | Coverage | Notes | -|------------|----------|-------| -| PixelController | Partial | Basic tracking, needs edge cases | -| AnalyticsWebsiteController | Basic | CRUD operations | -| AnalyticsStatsController | Basic | Endpoint tests | -| GoalController | Good | CRUD and conversions | -| ExperimentController | Good | Full lifecycle | -| FunnelController | Partial | Analysis endpoint | -| GdprController | Partial | Export tested | -| BotDetectionController | Basic | Stats endpoint | - -### Models - -| Model | Coverage | Notes | -|-------|----------|-------| -| AnalyticsWebsite | Good | Relationships, scopes | -| AnalyticsVisitor | Partial | Basic tests | -| AnalyticsSession | Partial | Basic tests | -| AnalyticsEvent | Partial | Basic tests | -| Goal | Good | Matching logic, conversions | -| AnalyticsFunnel | Partial | Basic tests | -| AnalyticsExperiment | Good | Lifecycle, variants | -| BotDetection | Good | Logging tests | -| BotRule | Good | Matching tests | - -## Testing Patterns - -### Service Tests - -```php -describe('Analytics Service', function () { - beforeEach(function () { - Cache::flush(); - $this->service = new AnalyticsService; - $this->website = Website::factory()->create(); - }); - - it('generates basic statistics for a website', function () { - AnalyticsEvent::factory()->count(10)->create([ - 'website_id' => $this->website->id, - 'type' => 'pageview', - ]); - - $stats = $this->service->generateStats($this->website); - - expect($stats)->toHaveKeys([ - 'total_pageviews', - 'unique_visitors', - 'bounce_rate', - ]); - }); -}); -``` - -### Bot Detection Tests - -```php -public function test_detects_known_bot_user_agents(): void -{ - $botUserAgents = [ - 'Googlebot/2.1 (+http://www.google.com/bot.html)', - 'curl/7.79.1', - 'HeadlessChrome/120.0.6099.109', - ]; - - foreach ($botUserAgents as $ua) { - $request = $this->createRequest($ua); - $result = $this->service->analyse($request); - - $this->assertTrue( - $result['is_bot'] || $result['score'] >= 30, - "Failed to detect bot: {$ua}" - ); - } -} -``` - -### API Tests - -```php -it('returns website statistics', function () { - $website = AnalyticsWebsite::factory()->create(); - - $this->actingAs($user) - ->getJson("/analytics/websites/{$website->id}/stats") - ->assertOk() - ->assertJsonStructure([ - 'total_pageviews', - 'unique_visitors', - 'bounce_rate', - ]); -}); -``` - -### Mock Request Helper - -```php -protected function createRequest(string $userAgent, array $headers = []): Request -{ - $request = Request::create('/', 'GET'); - $request->headers->set('User-Agent', $userAgent); - - foreach ($headers as $name => $value) { - $request->headers->set($name, $value); - } - - $request->server->set('REMOTE_ADDR', '127.0.0.1'); - - return $request; -} -``` - -## Missing Test Coverage - -### High Priority - -1. **Full tracking flow integration test** - - Pixel hit -> Queue job -> Database write -> Cache invalidation - - Goal conversion flow - - Experiment variant assignment - -2. **Rate limiting tests** - - Verify rate limits are enforced - - Test different throttle scenarios - -3. **Multi-tenant isolation tests** - - Verify workspace scoping - - Cross-workspace data leak prevention - -### Medium Priority - -1. **Bot detection edge cases** - - Privacy browsers (Brave, Tor) - - Corporate proxies - - VPN users - -2. **Session replay tests** - - Large replay handling - - Compression/decompression - - Expiry cleanup - -3. **Real-time analytics tests** - - Redis sorted set operations - - Broadcast throttling - - Cleanup of stale data - -### Low Priority - -1. **Email report tests** - - Report generation - - Scheduling - - Preview functionality - -2. **Heatmap aggregation tests** - - Large dataset aggregation - - Viewport normalisation - -## Test Data Factories - -### AnalyticsWebsiteFactory - -```php -AnalyticsWebsite::factory()->create([ - 'name' => 'Test Site', - 'host' => 'test.example.com', - 'tracking_enabled' => true, -]); -``` - -### AnalyticsEventFactory - -```php -AnalyticsEvent::factory()->count(100)->create([ - 'website_id' => $website->id, - 'type' => 'pageview', - 'created_at' => now()->subDays(rand(1, 30)), -]); -``` - -### AnalyticsSessionFactory - -```php -AnalyticsSession::factory()->create([ - 'website_id' => $website->id, - 'is_bounce' => false, - 'duration' => 300, - 'pageviews' => 5, -]); -``` - -## Mocking External Services - -### Redis - -```php -use Illuminate\Support\Facades\Redis; - -Redis::shouldReceive('zadd')->once(); -Redis::shouldReceive('zrangebyscore')->andReturn(['visitor-1', 'visitor-2']); -``` - -### MaxMind GeoIP - -```php -$this->mock(GeoIpService::class, function ($mock) { - $mock->shouldReceive('lookup') - ->andReturn([ - 'country_code' => 'GB', - 'city_name' => 'London', - ]); -}); -``` - -### Queue Jobs - -```php -use Illuminate\Support\Facades\Queue; - -Queue::fake(); - -// Perform action that dispatches job - -Queue::assertPushed(ProcessTrackingEvent::class); -``` - -## Performance Testing - -For high-volume testing: - -```php -it('handles high volume of events efficiently', function () { - $website = AnalyticsWebsite::factory()->create(); - - // Create 10,000 events - AnalyticsEvent::factory() - ->count(10000) - ->create(['website_id' => $website->id]); - - $start = microtime(true); - $stats = $this->service->generateStats($website); - $duration = microtime(true) - $start; - - expect($duration)->toBeLessThan(1.0); // Under 1 second -}); -``` - -## Test Environment Configuration - -### phpunit.xml - -```xml - - - - - - - - -``` - -### Test-Specific Config - -```php -config(['analytics.bot_detection.enabled' => true]); -config(['analytics.bot_detection.threshold' => 50]); -``` - -## CI Integration - -Tests run on GitHub Actions: - -```yaml -- name: Run Tests - run: composer test -- --coverage-clover coverage.xml - -- name: Upload Coverage - uses: codecov/codecov-action@v3 -``` - -## Writing New Tests - -### Guidelines - -1. Use Pest syntax for new tests -2. Use descriptive test names -3. Test one thing per test -4. Use factories for test data -5. Clean up after tests (RefreshDatabase handles this) -6. Mock external services -7. Test edge cases and error conditions - -### Example Test Structure - -```php -describe('Feature Name', function () { - beforeEach(function () { - // Setup - }); - - describe('scenario A', function () { - it('does expected behaviour', function () { - // Arrange - $input = [...]; - - // Act - $result = $this->service->method($input); - - // Assert - expect($result)->toBe($expected); - }); - - it('handles edge case', function () { - // ... - }); - - it('throws exception for invalid input', function () { - expect(fn() => $this->service->method(null)) - ->toThrow(InvalidArgumentException::class); - }); - }); -}); -``` - -## Debugging Tests - -### Dump and Die - -```php -dd($result); // Dump and die -dump($result); // Dump and continue -``` - -### Database Queries - -```php -\DB::enableQueryLog(); -// ... code ... -dd(\DB::getQueryLog()); -``` - -### Test Output - -```bash -./vendor/bin/pest --verbose -./vendor/bin/pest --debug -``` diff --git a/docs/packages/bio/architecture.md b/docs/packages/bio/architecture.md deleted file mode 100644 index f453093..0000000 --- a/docs/packages/bio/architecture.md +++ /dev/null @@ -1,396 +0,0 @@ ---- -title: Architecture -description: Technical architecture of the core-bio package -updated: 2026-01-29 ---- - -# core-bio Architecture - -This document describes the technical architecture of the `core-bio` package, which provides link-in-bio, short link, and static page functionality for the Host UK platform. - -## Overview - -The `core-bio` package is a Laravel package that integrates with the Core PHP Framework's event-driven module system. It provides: - -- **Biolink Pages** - Block-based link-in-bio pages with 60+ block types -- **Short Links** - URL shortening with redirect tracking -- **Static Pages** - Custom HTML/CSS/JS pages with XSS sanitisation -- **vCards** - Downloadable contact cards -- **Event Pages** - Calendar event links with iCal generation -- **File Links** - Secure file downloads with tracking - -## Module Registration - -The package uses event-driven registration via `Boot.php`: - -```php -public static array $listens = [ - ClientRoutesRegistering::class => 'onClientRoutes', - WebRoutesRegistering::class => 'onWebRoutes', - ApiRoutesRegistering::class => 'onApiRoutes', -]; -``` - -This lazy-loading pattern means the module is only instantiated when its events fire. - -## Directory Structure - -``` -core-bio/ -├── Actions/ # Single-purpose business logic (CreateBiolink, UpdateBiolink, DeleteBiolink) -├── Console/ -│ └── Commands/ # Artisan commands (aggregation, cleanup, domain verification) -├── Controllers/ -│ ├── Api/ # REST API controllers (PageController, BlockController, etc.) -│ └── Web/ # Web controllers (public rendering, redirects, submissions) -├── Effects/ -│ ├── Background/ # Background effect implementations (snow, leaves, stars, etc.) -│ ├── Block/ # Block-level effects -│ └── Catalog.php # Effect registry -├── Exceptions/ # Custom exceptions (AI service errors) -├── Jobs/ # Queue jobs (click tracking, notifications) -├── Lang/ # Translation files (en_GB) -├── Mail/ # Mailable classes (BioReport) -├── Mcp/ -│ └── Tools/ # MCP AI agent tools -├── Middleware/ # HTTP middleware (domain resolution, targeting, password) -├── Migrations/ # Database migrations -├── Models/ # Eloquent models -├── Notifications/ # Laravel notifications -├── Policies/ # Authorisation policies -├── Requests/ # Form request validation -├── Services/ # Business logic services -├── View/ -│ ├── Blade/ # Blade templates -│ │ ├── admin/ # Admin panel views -│ │ ├── components/ # Reusable components -│ │ └── emails/ # Email templates -│ └── Modal/ -│ └── Admin/ # Livewire admin components -├── routes/ # Route definitions (web.php, api.php, console.php) -├── Boot.php # Service provider and event handlers -├── config.php # Package configuration (merged as 'webpage') -└── device-frames.php # Device frame configuration for previews -``` - -## Core Models - -### Page (Biolink) - -The central model representing all page types. Uses single-table inheritance via `type` column. - -```php -// Types: 'biolink', 'link' (short link), 'static', 'vcard', 'event', 'file' -$biolink = Page::create([ - 'workspace_id' => $workspace->id, - 'user_id' => $user->id, - 'type' => 'biolink', - 'url' => 'mypage', - 'settings' => [...], -]); -``` - -**Key relationships:** -- `workspace()` - Multi-tenant isolation via `BelongsToWorkspace` trait -- `blocks()` - HasMany Block for biolink pages -- `theme()` - BelongsTo Theme for styling -- `domain()` - BelongsTo Domain for custom domains -- `project()` - BelongsTo Project for organisation -- `pixels()` - BelongsToMany Pixel for tracking -- `revisions()` - HasMany BioRevision for undo functionality -- `subPages()` - HasMany self-referential for nested pages - -### Block - -Represents a content block within a biolink page. Supports 60+ block types across categories: - -- **Standard**: link, heading, paragraph, avatar, image, socials -- **Embeds**: youtube, spotify, tiktok, vimeo, etc. -- **Advanced**: map, email_collector, faq, countdown, etc. -- **Payments**: paypal, donation, product, service - -```php -$block = Block::create([ - 'biolink_id' => $biolink->id, - 'type' => 'link', - 'region' => 'content', // HLCRF region - 'order' => 1, - 'settings' => [ - 'url' => 'https://example.com', - 'text' => 'Visit Example', - ], -]); -``` - -**A/B Testing Fields:** -- `ab_test_id` - UUID grouping variants -- `is_control` - Whether this is the control variant -- `traffic_split` - Percentage of traffic for this variant -- `is_winner` - Declared winner after test - -### Click / ClickStat - -Two-tier analytics storage: - -- **Click** - Individual click records with full attribution (IP hash, country, device, referrer, UTM) -- **ClickStat** - Pre-aggregated daily statistics for fast queries - -Clicks are tracked asynchronously via `TrackBioLinkClick` job to avoid blocking page loads. - -## HLCRF Layout System - -The package supports multi-region layouts (Header, Left, Content, Right, Footer) with per-breakpoint configuration: - -```php -// Layout presets from config -'layout_presets' => [ - 'bio' => ['phone' => 'C', 'tablet' => 'C', 'desktop' => 'C'], - 'landing' => ['phone' => 'C', 'tablet' => 'HCF', 'desktop' => 'HCF'], - 'blog' => ['phone' => 'C', 'tablet' => 'HCF', 'desktop' => 'HCRF'], - 'docs' => ['phone' => 'C', 'tablet' => 'HCF', 'desktop' => 'HLCF'], - 'portfolio' => ['phone' => 'C', 'tablet' => 'HCF', 'desktop' => 'HLCRF'], -], -``` - -Blocks specify their region and per-region ordering: - -```php -$block->region = Block::REGION_HEADER; // 'header', 'left', 'content', 'right', 'footer' -$block->region_order = 1; -``` - -## Service Layer - -### AnalyticsService - -Queries click data with retention enforcement: - -```php -$service = app(AnalyticsService::class); - -// Respects workspace entitlements for data retention -$retention = $service->enforceDateRetention($start, $end, $workspace); - -// Get breakdown data -$byCountry = $service->getClicksByCountry($biolink, $start, $end); -$byDevice = $service->getClicksByDevice($biolink, $start, $end); -$byReferrer = $service->getClicksByReferrer($biolink, $start, $end); -``` - -### StaticPageSanitiser - -Security-critical service for sanitising user-provided HTML/CSS/JS: - -```php -$sanitiser = app(StaticPageSanitiser::class); - -$clean = $sanitiser->sanitiseStaticPage( - html: $userHtml, - css: $userCss, - js: $userJs -); -``` - -**Security approach:** -- HTML: HTMLPurifier with strict allowlist -- CSS: Blocklist for dangerous patterns (expression, javascript:, @import) -- JS: Blocklist for eval-like constructs (documented limitations) - -See `docs/security.md` for details. - -### DomainVerificationService - -Handles custom domain DNS verification: - -```php -$service = app(DomainVerificationService::class); - -// Verify via TXT record or CNAME -$verified = $service->verify($domain); - -// Get DNS instructions for user -$instructions = $service->getDnsInstructions($domain); -``` - -### BioPasswordRateLimiter - -Prevents brute force attacks on password-protected pages: - -```php -$limiter = app(BioPasswordRateLimiter::class); - -if ($limiter->tooManyAttempts($biolink, $request)) { - $seconds = $limiter->availableIn($biolink, $request); - // Show rate limit error -} - -// On failed attempt - increments with exponential backoff -$limiter->increment($biolink, $request); - -// On success - clear rate limit (backoff level persists) -$limiter->clear($biolink, $request); -``` - -## API Layer - -RESTful API supporting both session auth and API key auth: - -``` -GET /api/bio # List biolinks -POST /api/bio # Create biolink -GET /api/bio/{id} # Get biolink -PUT /api/bio/{id} # Update biolink -DELETE /api/bio/{id} # Delete biolink - -GET /api/bio/{id}/blocks # List blocks -POST /api/bio/{id}/blocks # Add block -PUT /api/blocks/{id} # Update block -DELETE /api/blocks/{id} # Delete block - -GET /api/bio/{id}/analytics # Summary stats -GET /api/bio/{id}/analytics/geo # Geographic breakdown -GET /api/bio/{id}/analytics/utm # UTM campaign data -``` - -API key routes mirror session routes with `api.auth` and `api.scope.enforce` middleware. - -## MCP Tools - -AI agent tools via Model Context Protocol: - -```php -// Available actions -$actions = [ - 'list', 'get', 'create', 'update', 'delete', - 'add_block', 'update_block', 'delete_block', -]; - -// Example: Create biolink -$response = $bioTools->handle(new Request([ - 'action' => 'create', - 'user_id' => $userId, - 'url' => 'my-page', - 'title' => 'My Page', - 'blocks' => [...], -])); -``` - -Additional MCP tools in separate classes: -- `BioAnalyticsTools` - Analytics queries -- `DomainTools` - Custom domain management -- `PixelTools` - Tracking pixel management -- `ProjectTools` - Project/folder management -- `QrTools` - QR code generation -- `ThemeTools` - Theme management - -## Effects System - -Extensible background effects via `Effects/Catalog.php`: - -```php -// Register effect -Catalog::registerBackgroundEffect('snow', SnowEffect::class); - -// Get effect for rendering -$effect = $page->getBackgroundEffect(); -$html = $effect?->render(); -``` - -Available effects: snow, rain, leaves, autumn_leaves, stars, bubbles, waves, lava_lamp, grid_motion. - -## Multi-Tenancy - -All data is scoped to workspaces using the `BelongsToWorkspace` trait: - -```php -class Page extends Model -{ - use BelongsToWorkspace; // Auto-scopes queries, sets workspace_id on create -} -``` - -The trait: -- Adds global scope to filter by current workspace -- Auto-assigns `workspace_id` on model creation -- Throws `MissingWorkspaceContextException` without valid context - -## Caching Strategy - -- **Domain resolution**: 1-hour cache per domain -- **Public pages**: Cached with `biopage:{domain_id}:{url}` key -- **Analytics**: No caching (queries pre-aggregated ClickStat table) -- **Themes**: System themes cached, user themes not cached - -Cache invalidation triggers: -- Page update clears page cache -- Theme update clears all biolinks using that theme -- Domain update clears domain cache - -## Queue Jobs - -| Job | Purpose | Queue | -|-----|---------|-------| -| `TrackBioLinkClick` | Record individual click with attribution | default | -| `BatchTrackClicks` | Bulk click tracking for high traffic | default | -| `SendBioLinkNotification` | Webhook/email notifications | notifications | -| `SendSubmissionNotification` | Form submission notifications | notifications | - -## Configuration - -The package configuration is merged into Laravel's config as `webpage`: - -```php -// Access config -$defaultDomain = config('webpage.default_domain'); -$blockTypes = config('webpage.block_types'); -$reservedSlugs = config('webpage.reserved_slugs'); -``` - -Key configuration areas: -- `default_domain` - Base domain for biolinks -- `allowed_domains` - Domains that can serve biolinks -- `reserved_slugs` - URLs that cannot be claimed -- `block_types` - All available block types with metadata -- `layout_presets` - HLCRF layout configurations -- `og_images` - Dynamic OG image generation settings -- `revisions` - Revision history limits - -## Database Schema - -Main tables (prefixed `biolink_`): -- `biolinks` - Pages/links -- `biolink_blocks` - Page blocks -- `biolink_themes` - Theme definitions -- `biolink_domains` - Custom domains -- `biolink_projects` - Organisation folders -- `biolink_pixels` - Tracking pixels -- `biolink_clicks` - Individual click records -- `biolink_click_stats` - Aggregated statistics -- `biolink_submissions` - Form submissions -- `biolink_notification_handlers` - Notification configs -- `biolink_pwas` - PWA configurations -- `biolink_push_*` - Push notification tables -- `biolink_templates` - Page templates -- `biolink_revisions` - Change history (separate migration) -- `biolink_edit_locks` - Collaborative editing locks - -## Testing - -Tests use Pest with Orchestra Testbench: - -```bash -# Run all tests -./vendor/bin/pest - -# Run with coverage -./vendor/bin/pest --coverage - -# Run specific test -./vendor/bin/pest --filter=PageTest -``` - -Test categories: -- **Feature tests** - Full integration tests for workflows -- **Unit tests** - Isolated service tests -- **Security tests** - XSS, CSRF, injection prevention -- **Use cases** - Example usage patterns diff --git a/docs/packages/bio/block-types.md b/docs/packages/bio/block-types.md deleted file mode 100644 index 1e7b496..0000000 --- a/docs/packages/bio/block-types.md +++ /dev/null @@ -1,746 +0,0 @@ ---- -title: Block Types Reference -description: Complete reference for all biolink block types -updated: 2026-01-29 ---- - -# Block Types Reference - -This document provides a complete reference for all available block types in the `core-bio` package. - -## Block Type Categories - -Blocks are organised into four categories: - -| Category | Description | -|----------|-------------| -| `standard` | Basic content blocks (links, text, images) | -| `embeds` | Third-party content embeds (YouTube, Spotify, etc.) | -| `advanced` | Feature-rich blocks (maps, forms, calendars) | -| `payments` | Payment and commerce blocks | - -## Tier Access - -Block types are gated by subscription tier: - -| Tier | Access | -|------|--------| -| `null` (free) | Available to all users | -| `pro` | Requires Pro plan or higher | -| `ultimate` | Requires Ultimate plan | -| `payment` | Requires payment add-on | - -## Standard Blocks - -### link -Basic clickable link button. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-link` | -| Category | standard | -| Has Statistics | Yes | -| Themable | Yes | -| Tier | Free | - -**Settings:** -- `url` (string) - Destination URL -- `text` (string) - Button text -- `icon` (string, optional) - FontAwesome icon class - -### heading -Section heading/title. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-heading` | -| Category | standard | -| Has Statistics | No | -| Themable | Yes | -| Tier | Free | - -**Settings:** -- `text` (string) - Heading text -- `level` (int) - HTML heading level (1-6) - -### paragraph -Text content block. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-paragraph` | -| Category | standard | -| Has Statistics | No | -| Themable | Yes | -| Tier | Free | - -**Settings:** -- `text` (string) - Paragraph content - -### avatar -Profile image display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-user` | -| Category | standard | -| Has Statistics | Yes | -| Themable | No | -| Tier | Free | - -**Settings:** -- `image` (string) - Image path or URL -- `size` (string) - Display size - -### image -Image display block. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-image` | -| Category | standard | -| Has Statistics | Yes | -| Themable | No | -| Tier | Free | - -**Settings:** -- `image` (string) - Image path or URL -- `alt` (string) - Alt text -- `link` (string, optional) - Click destination - -### socials -Social media icon links. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-users` | -| Category | standard | -| Has Statistics | No | -| Themable | Yes | -| Tier | Free | - -**Settings:** -- `platforms` (array) - List of platform handles -- `style` (string) - Icon display style - -### business_hours -Opening hours display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-clock` | -| Category | standard | -| Has Statistics | No | -| Themable | Yes | -| Tier | Free | - -**Settings:** -- `hours` (array) - Day/time pairs -- `timezone` (string) - Timezone identifier - -### modal_text -Expandable text content. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-book-open` | -| Category | standard | -| Has Statistics | Yes | -| Themable | Yes | -| Tier | Free | - -**Settings:** -- `title` (string) - Modal trigger text -- `content` (string) - Full content - -### header (Pro) -Full-width header section. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-theater-masks` | -| Category | standard | -| Has Statistics | Yes | -| Themable | No | -| Tier | Pro | - -### image_grid (Pro) -Multiple image grid display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-images` | -| Category | standard | -| Has Statistics | Yes | -| Themable | No | -| Tier | Pro | - -### divider (Pro) -Visual separator. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-grip-lines` | -| Category | standard | -| Has Statistics | No | -| Themable | No | -| Tier | Pro | - -### list (Pro) -Bullet/numbered list. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-list` | -| Category | standard | -| Has Statistics | No | -| Themable | No | -| Tier | Pro | - -### big_link (Ultimate) -Large featured link. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-external-link-alt` | -| Category | standard | -| Has Statistics | Yes | -| Themable | Yes | -| Tier | Ultimate | - -### audio (Ultimate) -Audio player. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-volume-up` | -| Category | standard | -| Has Statistics | No | -| Themable | No | -| Tier | Ultimate | - -### video (Ultimate) -Self-hosted video player. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-video` | -| Category | standard | -| Has Statistics | No | -| Themable | No | -| Tier | Ultimate | - -### file (Ultimate) -File download. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-file` | -| Category | standard | -| Has Statistics | Yes | -| Themable | Yes | -| Tier | Ultimate | - -### cta (Ultimate) -Call-to-action block. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-comments` | -| Category | standard | -| Has Statistics | Yes | -| Themable | Yes | -| Tier | Ultimate | - -## Embed Blocks - -### youtube (Free) -YouTube video embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.youtube.com, youtu.be | -| Tier | Free | - -**Settings:** -- `url` (string) - YouTube video URL - -### spotify (Free) -Spotify embed (track, album, playlist). - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | open.spotify.com | -| Tier | Free | - -### soundcloud (Free) -SoundCloud track embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | soundcloud.com | -| Tier | Free | - -### tiktok_video (Free) -TikTok video embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.tiktok.com | -| Tier | Free | - -### twitch (Free) -Twitch stream/video embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.twitch.tv | -| Tier | Free | - -### vimeo (Free) -Vimeo video embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | vimeo.com | -| Tier | Free | - -### applemusic (Pro) -Apple Music embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | music.apple.com | -| Tier | Pro | - -### tidal (Pro) -Tidal music embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | tidal.com | -| Tier | Pro | - -### mixcloud (Pro) -Mixcloud embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.mixcloud.com | -| Tier | Pro | - -### kick (Pro) -Kick stream embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | kick.com | -| Tier | Pro | - -### twitter_tweet (Pro) -X/Twitter tweet embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | twitter.com, x.com | -| Tier | Pro | - -### twitter_video (Pro) -X/Twitter video embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | twitter.com, x.com | -| Tier | Pro | - -### pinterest_profile (Pro) -Pinterest profile embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | pinterest.com, www.pinterest.com | -| Tier | Pro | - -### instagram_media (Pro) -Instagram post embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.instagram.com | -| Tier | Pro | - -### snapchat (Pro) -Snapchat embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.snapchat.com, snapchat.com | -| Tier | Pro | - -### tiktok_profile (Pro) -TikTok profile embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.tiktok.com | -| Tier | Pro | - -### vk_video (Pro) -VK video embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | vk.com | -| Tier | Pro | - -### typeform (Ultimate) -Typeform form embed. - -| Property | Value | -|----------|-------| -| Tier | Ultimate | - -### calendly (Ultimate) -Calendly scheduling embed. - -| Property | Value | -|----------|-------| -| Tier | Ultimate | - -### discord (Ultimate) -Discord server widget. - -| Property | Value | -|----------|-------| -| Tier | Ultimate | - -### facebook (Ultimate) -Facebook content embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.facebook.com, fb.watch | -| Tier | Ultimate | - -### reddit (Ultimate) -Reddit post embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | www.reddit.com | -| Tier | Ultimate | - -### iframe (Ultimate) -Generic iframe embed (use with caution). - -| Property | Value | -|----------|-------| -| Tier | Ultimate | - -### pdf_document (Ultimate) -PDF viewer embed. - -| Property | Value | -|----------|-------| -| Has Statistics | Yes | -| Tier | Ultimate | - -### powerpoint_presentation (Ultimate) -PowerPoint viewer. - -| Property | Value | -|----------|-------| -| Has Statistics | Yes | -| Tier | Ultimate | - -### excel_spreadsheet (Ultimate) -Excel viewer. - -| Property | Value | -|----------|-------| -| Has Statistics | Yes | -| Tier | Ultimate | - -### rumble (Ultimate) -Rumble video embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | rumble.com | -| Tier | Ultimate | - -### telegram (Ultimate) -Telegram channel/post embed. - -| Property | Value | -|----------|-------| -| Whitelisted Hosts | t.me | -| Tier | Ultimate | - -## Advanced Blocks - -### map (Free) -Interactive map display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-map` | -| Category | advanced | -| Has Statistics | Yes | -| Tier | Free | - -**Settings:** -- `address` (string) - Location address -- `latitude` (float) - Latitude coordinate -- `longitude` (float) - Longitude coordinate - -### email_collector (Free) -Email signup form. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-envelope` | -| Category | advanced | -| Has Statistics | No | -| Tier | Free | - -**Settings:** -- `placeholder` (string) - Input placeholder -- `button_text` (string) - Submit button text - -### phone_collector (Free) -Phone number collection. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-phone-square-alt` | -| Category | advanced | -| Has Statistics | No | -| Tier | Free | - -### contact_collector (Free) -Full contact form. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-address-book` | -| Category | advanced | -| Has Statistics | No | -| Tier | Free | - -### rss_feed (Pro) -RSS feed display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-rss` | -| Category | advanced | -| Tier | Pro | - -### custom_html (Pro) -Custom HTML content (sanitised). - -| Property | Value | -|----------|-------| -| Icon | `fas fa-code` | -| Category | advanced | -| Tier | Pro | - -### vcard (Pro) -Downloadable contact card. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-id-card` | -| Category | advanced | -| Has Statistics | Yes | -| Tier | Pro | - -**Settings:** See `config.vcard_fields` for all available fields. - -### alert (Pro) -Notification/announcement. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-bell` | -| Category | advanced | -| Has Statistics | Yes | -| Tier | Pro | - -### appointment_calendar (Ultimate) -Booking/scheduling widget. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-calendar` | -| Category | advanced | -| Tier | Ultimate | - -### faq (Ultimate) -Frequently asked questions. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-feather` | -| Category | advanced | -| Tier | Ultimate | - -### countdown (Ultimate) -Countdown timer. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-clock` | -| Category | advanced | -| Tier | Ultimate | - -### external_item (Ultimate) -External product/item display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-money-bill-wave` | -| Category | advanced | -| Has Statistics | Yes | -| Tier | Ultimate | - -### share (Ultimate) -Social sharing buttons. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-share-square` | -| Category | advanced | -| Has Statistics | Yes | -| Tier | Ultimate | - -### coupon (Ultimate) -Discount coupon display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-tags` | -| Category | advanced | -| Has Statistics | Yes | -| Tier | Ultimate | - -### youtube_feed (Ultimate) -YouTube channel feed. - -| Property | Value | -|----------|-------| -| Icon | `fab fa-youtube` | -| Category | advanced | -| Tier | Ultimate | - -### timeline (Ultimate) -Event timeline display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-ellipsis-v` | -| Category | advanced | -| Tier | Ultimate | - -### review (Ultimate) -Review/testimonial display. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-star` | -| Category | advanced | -| Tier | Ultimate | - -### image_slider (Ultimate) -Image carousel. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-clone` | -| Category | advanced | -| Has Statistics | Yes | -| Tier | Ultimate | - -### markdown (Ultimate) -Markdown content renderer. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-sticky-note` | -| Category | advanced | -| Tier | Ultimate | - -## Payment Blocks - -### paypal (Free) -PayPal payment button. - -| Property | Value | -|----------|-------| -| Icon | `fab fa-paypal` | -| Category | payments | -| Has Statistics | Yes | -| Tier | Free | - -### donation (Payment) -Donation collection. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-hand-holding-usd` | -| Category | payments | -| Tier | Payment add-on | - -### product (Payment) -Product purchase. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-cube` | -| Category | payments | -| Tier | Payment add-on | - -### service (Payment) -Service booking/purchase. - -| Property | Value | -|----------|-------| -| Icon | `fas fa-comments-dollar` | -| Category | payments | -| Tier | Payment add-on | - -## HLCRF Region Support - -Some blocks support placement in multiple layout regions: - -| Block Type | Allowed Regions | -|------------|-----------------| -| link | H, L, C, R, F | -| heading | H, L, C, R, F | -| socials | H, L, C, R, F | -| divider | H, L, C, R, F | - -Blocks without `allowed_regions` config default to Content (C) only. - -## Adding Custom Blocks - -To add a new block type: - -1. Add block definition to `config.php`: -```php -'my_block' => [ - 'icon' => 'fas fa-star', - 'color' => '#ff0000', - 'category' => 'advanced', - 'has_statistics' => true, - 'themable' => true, - 'tier' => 'pro', -], -``` - -2. Create Blade template at `View/Blade/blocks/my_block.blade.php` - -3. Add settings schema validation in relevant request classes - -4. Document the block type in this reference diff --git a/docs/packages/bio/security.md b/docs/packages/bio/security.md deleted file mode 100644 index e9631ac..0000000 --- a/docs/packages/bio/security.md +++ /dev/null @@ -1,438 +0,0 @@ ---- -title: Security -description: Security considerations and audit notes for core-bio -updated: 2026-01-29 ---- - -# Security Documentation - -This document covers security considerations, threat mitigations, and audit notes for the `core-bio` package. - -## Security Model Overview - -The `core-bio` package handles user-generated content including HTML, CSS, JavaScript, URLs, and file uploads. Security is enforced at multiple layers: - -1. **Input Validation** - Request validation classes -2. **Sanitisation** - Content sanitisers for XSS prevention -3. **Authorisation** - Policies and workspace isolation -4. **Rate Limiting** - Abuse prevention -5. **Output Encoding** - Blade template escaping - -## Authentication and Authorisation - -### Multi-Tenant Isolation - -All data is scoped to workspaces using the `BelongsToWorkspace` trait: - -```php -// Automatic query scoping -$biolinks = Page::all(); // Only returns workspace's biolinks - -// Automatic workspace assignment on create -$biolink = Page::create([...]); // workspace_id auto-set -``` - -Without valid workspace context, `MissingWorkspaceContextException` is thrown. - -### Policy Enforcement - -The `BioPagePolicy` defines access rules: - -| Action | Rule | -|--------|------| -| `viewAny` | Any authenticated user | -| `view` | Owner OR workspace member (read-only) | -| `create` | Has access to at least one workspace | -| `update` | Owner only | -| `delete` | Owner only | - -**Design decision:** Workspace members can view all biolinks within the workspace but only owners can modify. This enables team visibility while protecting individual content. - -### API Authentication - -Two authentication methods: -1. **Session auth** - Standard Laravel session cookies -2. **API key auth** - `Authorization: Bearer hk_xxx` header - -API routes use `api.auth` middleware with scope enforcement (`api.scope.enforce`): -- GET requests require `read` scope -- POST/PUT/PATCH require `write` scope -- DELETE requires `delete` scope - -## XSS Prevention - -### Static Page Sanitisation - -The `StaticPageSanitiser` service handles user-provided HTML/CSS/JS for static pages: - -#### HTML Sanitisation - -Uses HTMLPurifier with strict allowlist: - -**Allowed elements:** -- Structure: div, span, section, article, header, footer, main, nav, aside -- Text: h1-h6, p, br, hr, strong, em, b, i, u, small, mark, del, ins, sub, sup, code, pre, blockquote -- Lists: ul, ol, li, dl, dt, dd -- Links/Media: a, img, picture, source, video, audio, iframe (restricted) -- Tables: table, thead, tbody, tfoot, tr, th, td, caption -- Forms: form, input, textarea, button, label, select, option, fieldset, legend - -**Allowed attributes:** -- Most elements: id, class, style -- Links: href, target, rel -- Images: src, alt, width, height -- iframes: src, width, height (SafeIframe for YouTube/Vimeo only) - -**Blocked completely:** -- `