# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

OG Shortlink is an OpenGraph-enabled URL shortener with shimlink protection, multi-domain support, and comprehensive security features. Built for cPanel shared hosting environments with strict security requirements.

**Key Features:**
- Multi-domain shortlink support with ownership
- OpenGraph metadata scraping and customization
- Shimlink guard (prevents redirect chains from social media redirectors)
- WAF (Web Application Firewall) with attack pattern detection
- DDoS protection, rate limiting, bot detection
- Abuse detection with pattern recognition
- CSRF protection with enhanced validation
- CSP nonce support for inline scripts

## Architecture

### Entry Points
- `index.php` - Main application entry (dashboard, shortlink creation)
- `redirect.php` - Shortlink redirect handler with OpenGraph metadata injection
- `install.php` - Web-based installation wizard for cPanel
- Root utility scripts: `check-env.php`, `check-requirements.php`, `clear-cache.php`, `generate-password-hash.php`

### Bootstrap Flow
1. `index.php` / `redirect.php` set up custom error handlers with error ID tracking
2. Generate CSP nonce (`CSP_NONCE` constant)
3. `Bootstrap::boot()` loads environment, sets timezone, configures secure session
4. `Headers::send()` applies security headers (CSP with nonce, X-Content-Type-Options, etc.)
5. Manual autoloader in `app/src/Core/Autoloader.php` (PSR-4 for `App\` namespace)

### Core Security Layers (Applied in Order)
1. **WAF** (`App\Security\Waf`) - Pattern-based attack detection (SQL injection, XSS, path traversal, command injection)
2. **Rate Limiter** (`App\Security\RateLimiter`) - Per-IP and per-session request throttling
3. **Bot Detector** (`App\Security\BotDetector`) - User-Agent analysis and behavior patterns
4. **Shimlink Guard** (`App\Security\ShimlinkGuard`) - Blocks social media redirectors (t.co, l.facebook.com, bit.ly, etc.)
5. **Security Guard** (`App\Security\SecurityGuard`) - SSRF prevention, IP validation, private range blocking
6. **URL Guard** (`App\Security\UrlGuard`) - Malicious URL pattern detection
7. **Abuse Detector** (`App\Security\AbuseDetector`) - Rapid creation, duplicate URL, pattern-based abuse detection
8. **CSRF** (`App\Security\Csrf` / `CsrfEnhanced`) - Token generation/validation with double-submit cookie pattern

### Service Layer Pattern
Services orchestrate business logic and coordinate repositories/security:
- `ShortlinkService` - Create/manage shortlinks with all security validations, supports random domain per shortlink
- `DomainService` - Manage shortlink domains and ownership
- `UserService` - User authentication, password hashing, role management
- `UrlCleaner` - Remove tracking parameters, normalize URLs

### Repository Pattern
All database access through repositories (interface-based):
- `ShortlinkRepository` - CRUD for shortlinks table
- `DomainRepository` - Domain management
- `UserRepository` - User authentication data
- `SettingsRepository` - Application settings (key-value store)
- `DefaultImageRepository` - Default OG images

Database connection: `PdoFactory::fromEnv()` creates PDO with `PDO::ATTR_EMULATE_PREPARES=false`, prepared statements only.

### Key Domain Objects
- `User`, `UserRole`, `UserStatus` - User entity with role/status enums
- `Slugger` - Generate random slugs with collision detection
- `OgScraper` - Fetch OpenGraph metadata from destination URLs
- `OpenGraphBuilder` - Build OG meta tags for HTML injection
- `ShortlinkCache` - Caching layer for shortlink lookups (file-based)

### Configuration
- `.env` file in `app/.env` (loaded by `Env::loadOptional()`)
- Config files in `app/config/`: `waf.php`, `performance.php`, `shared-hosting.php`
- WAF supports IP whitelisting, blocked IPs, excluded paths

## Database

### Schema
Tables: `users`, `domains`, `shortlinks`, `settings`, `default_images`, `migrations`

**Migrations:**
- Fresh install: Use `app/migrations/complete_installation.sql`
- CLI runner: `php app/bin/migrate.php [migrate|rollback|status]`
- `Migrator` class tracks migrations in `migrations` table, supports transactions

### Installation
Run `install.php` in browser or import `complete_installation.sql` directly:
```bash
mysql -u user -p database < app/migrations/complete_installation.sql
```

## Development Commands

### Database Migrations
```bash
# Run pending migrations
php app/bin/migrate.php migrate

# Check migration status
php app/bin/migrate.php status

# Rollback last migration
php app/bin/migrate.php rollback

# Rollback N migrations
php app/bin/migrate.php rollback --steps=3
```

### Utility Scripts (CLI)
```bash
# Validate deployment (checks DB, permissions, security)
php app/bin/verify-deployment.php

# Monitor security events
php app/bin/monitor-security.php

# Validate OpenGraph data integrity
php app/bin/validate-og.php

# Verify refactoring (code quality checks)
php app/bin/verify-refactoring.php

# Precache shortlinks
php app/bin/precache.php
```

### Web-Accessible Utilities
- `check-env.php` - Verify environment configuration
- `check-requirements.php` - Check PHP extensions and server requirements
- `clear-cache.php` - Clear application cache
- `generate-password-hash.php` - Generate bcrypt password hashes

## Security Requirements

### Mandatory Patterns
- **No shell execution**: `exec`, `shell_exec`, `system`, `passthru`, `popen`, `proc_open`, `eval` are forbidden
- **Prepared statements only**: All SQL via PDO prepared statements with `PDO::ATTR_EMULATE_PREPARES=false`
- **Input validation**: Use `InputValidator` for all user input
- **Output escaping**: Context-aware escaping (HTML, attribute, URL, JS)
- **CSRF tokens**: Required for all state-changing operations
- **CSP nonce**: Use `CSP_NONCE` constant for inline scripts

### Security Headers (Applied by `Headers::send()`)
- `Content-Security-Policy` with nonce support
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: no-referrer`
- `Permissions-Policy` (restrictive)
- `X-Frame-Options: DENY`
- HSTS when HTTPS detected

### Error Handling
- Custom error handler logs with unique error ID (`[og-shortlink][{errorId}][severity]`)
- Shutdown function catches fatal errors
- Exception handler returns generic "Server error. Error ID: {id}" to users
- All exceptions extend base Exception or use custom: `ValidationException`, `NotFoundException`, `AuthenticationException`, `RateLimitException`

## Important Constraints

### Shared Hosting Environment
- **No root access**: No system packages, no daemon installs
- **No Composer in production**: Use manual autoloader (`Autoloader::create()`)
- **File-based caching**: No Redis/Memcached assumed
- **cPanel integration**: `CpanelClient` and `CloudflareClient` for domain management
- **MySQL/MariaDB only**: Via PDO with MySQLi extension

### Code Standards
- PHP 8.2+ (composer.json specifies `^8.2` but enforce 8.3 per global rules)
- `declare(strict_types=1);` on all files
- PSR-4 autoloading (`App\` namespace → `app/src/`)
- No arrow functions (`fn`) - use anonymous functions
- `Throwable $e` in catch blocks
- Cyclomatic complexity < 10 per method

### File Structure
```
/                          # Web root (index.php, redirect.php, install.php)
├── app/
│   ├── src/               # PSR-4 App\ namespace
│   │   ├── Bootstrap.php
│   │   ├── Core/          # Autoloader, Logger, ErrorHandler, Cache
│   │   ├── Config/        # Env, ConfigLoader
│   │   ├── Database/      # PdoFactory, Migrator
│   │   ├── Security/      # WAF, CSRF, Guards, RateLimiter, etc.
│   │   ├── Service/       # Business logic services
│   │   ├── Shortlink/     # Shortlink domain models
│   │   ├── User/          # User domain models
│   │   ├── Domain/        # Domain management
│   │   ├── Social/        # OG scraping
│   │   ├── Exception/     # Custom exceptions
│   │   └── ...
│   ├── bin/               # CLI scripts (migrate.php, verify-*.php)
│   ├── config/            # Config files (waf.php, etc.)
│   ├── migrations/        # SQL migration files
│   ├── templates/         # PHP templates (layout.php, dashboard.php, login.php)
│   ├── .env               # Environment configuration (not in repo)
│   └── composer.json      # Dependencies (dev only, no vendor in prod)
├── assets/                # CSS, JS
└── img/                   # Images
```

## Testing & Verification

Before deployment, run:
```bash
php app/bin/verify-deployment.php
```

This checks:
- Database connectivity
- Required tables and columns
- File permissions (cache, logs)
- Security configuration
- PHP extensions

## Features

### Random Domain per Shortlink
Fitur ini memungkinkan setiap shortlink dalam satu batch menggunakan domain yang berbeda secara acak atau merata (round-robin).

**Cara kerja:**
1. User memilih "Random Domain" dari dropdown di dashboard
2. Frontend mengirim flag `use_random_domains: true` ke backend
3. Backend mengambil semua active domains yang accessible oleh user (berdasarkan role dan ownership)
4. Service memvalidasi semua domains, kemudian untuk setiap shortlink dalam batch:
   - Memilih domain berdasarkan distribution mode (random atau round-robin)
   - Generate slug unik untuk domain tersebut
   - Create shortlink dengan domain yang berbeda
5. Log statistik distribusi domain ke error_log

**Distribution Modes:**
- **Random**: Setiap shortlink mendapat domain secara acak (default)
- **Round-robin**: Distribusi merata, domain dipilih secara bergiliran

**Settings:**
- `random_domain_distribution_mode` (string): `'random'` atau `'round-robin'` (default: `'random'`)
- `random_domain_strict_limit` (bool): Enable stricter rate limiting untuk random domain mode (default: `true`)
- `random_domain_rate_limit` (int): Max requests per 5 minutes untuk random domain mode (default: `10`)

**Rate Limiting:**
- Standard mode: 20 requests per 5 minutes
- Random domain mode (strict): 10 requests per 5 minutes (configurable)
- Menggunakan key berbeda: `create_shortlinks_random_domain` vs `create_shortlinks`

**Logging:**
Setiap batch creation dengan random domain akan log statistik ke error_log:
```
[og-shortlink][RANDOM_DOMAIN_STATS][RANDOM] Total: 10 | Distribution: domain1.com:3(30%), domain2.com:4(40%), domain3.com:3(30%)
```

**API Request Example:**
```json
{
  "destination_url": "https://example.com",
  "count": 5,
  "use_random_domains": true,
  "domain_id": 0,
  "use_shim": true
}
```

**Implementation:**
- `ShortlinkService::createMultiple()` - Accepts `string|array $domainHost`, implements distribution logic with statistics tracking
- `SettingsRepository` - Stores distribution mode and rate limit settings
- `index.php` (line ~671-700) - Strict rate limiting for random domain mode
- `index.php` (line ~719-765) - Handles random domain mode detection and domain collection
- `assets/js/app.js` (line ~290-299) - Frontend flag handling

## Common Tasks

### Add New Security Pattern
1. Update pattern arrays in `app/src/Security/Waf.php`
2. Configuration in `app/config/waf.php` for exclusions/whitelists
3. Pattern detection in `Waf::detect()` method

### Add New Domain
1. Insert into `domains` table via `DomainRepository`
2. Domain ownership tracked via `user_id` column
3. Verify with `DomainService::verifyOwnership()`

### Add New Migration
1. Create `00X_description.sql` in `app/migrations/`
2. Run `php app/bin/migrate.php migrate`
3. For reversible migrations, create `00X_description.rollback.sql`

### Create New Repository
1. Define interface extending `RepositoryInterface`
2. Implement concrete class with PDO dependency
3. Use prepared statements exclusively
4. Handle exceptions, convert PDOException to domain exceptions

### Debug Security Blocks
1. Check error logs for `[og-shortlink][{errorId}]` entries
2. Review WAF patterns in `app/config/waf.php`
3. Add IP to `whitelisted_ips` for testing
4. Use `excluded_paths` for legitimate endpoints with false positives

### Monitor Random Domain Usage
1. Check error logs for `[og-shortlink][RANDOM_DOMAIN_STATS]` entries
2. Analyze distribution patterns:
   - `RANDOM` mode: Should show varied distribution
   - `ROUND-ROBIN` mode: Should show even distribution (equal percentages)
3. Monitor rate limit violations: `[RateLimiter]` entries with `create_shortlinks_random_domain` key
4. Example log analysis:
   ```bash
   # Count random domain usage
   grep "RANDOM_DOMAIN_STATS" error_log | wc -l

   # View distribution patterns
   grep "RANDOM_DOMAIN_STATS" error_log | tail -20

   # Check rate limit violations
   grep "create_shortlinks_random_domain" error_log | grep "BLOCKED"
   ```

### Configure Random Domain Settings
Settings can be modified via admin dashboard or directly in database:
```sql
-- Set distribution mode to round-robin
INSERT INTO app_settings (k, v) VALUES ('random_domain_distribution_mode', 'round-robin')
ON DUPLICATE KEY UPDATE v = 'round-robin';

-- Adjust rate limit (default: 10 requests per 5 minutes)
INSERT INTO app_settings (k, v) VALUES ('random_domain_rate_limit', '15')
ON DUPLICATE KEY UPDATE v = '15';

-- Disable strict rate limiting
INSERT INTO app_settings (k, v) VALUES ('random_domain_strict_limit', '0')
ON DUPLICATE KEY UPDATE v = '0';
```
