diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..a1e6547 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,361 @@ +--- +title: Architecture +description: Technical architecture of core-template - the starter template for Core PHP Framework applications +updated: 2026-01-29 +--- + +# Architecture + +core-template is the official starter template for building applications with the Core PHP Framework. It provides a pre-configured Laravel 12 application with the modular monolith architecture, Livewire 3, and Flux UI integration. + +## Overview + +``` +core-template/ +├── app/ +│ ├── Http/Controllers/ # Traditional controllers (rarely used) +│ ├── Models/ # Application-wide Eloquent models +│ ├── Mod/ # Feature modules (your code goes here) +│ └── Providers/ # Service providers +├── bootstrap/ +│ ├── app.php # Application bootstrap with Core providers +│ └── providers.php # Additional providers +├── config/ +│ └── core.php # Core framework configuration +├── public/ +│ └── index.php # Web entry point +├── resources/ +│ ├── css/app.css # Tailwind entry point +│ ├── js/app.js # JavaScript entry point +│ └── views/ # Global Blade views +├── routes/ +│ ├── web.php # Fallback web routes +│ ├── api.php # Fallback API routes +│ └── console.php # Console command routes +└── tests/ + ├── Feature/ # HTTP/Livewire feature tests + └── Unit/ # Unit tests +``` + +## Bootstrap Process + +The application bootstrap (`bootstrap/app.php`) registers Core PHP Framework providers: + +```php +return Application::configure(basePath: dirname(__DIR__)) + ->withProviders([ + \Core\LifecycleEventProvider::class, // Event system + \Core\Website\Boot::class, // Website components + \Core\Front\Boot::class, // Frontend (Livewire, Flux) + \Core\Mod\Boot::class, // Module discovery + ]) + ->withRouting(...) + ->withMiddleware(function (Middleware $middleware) { + \Core\Front\Boot::middleware($middleware); + }) + ->create(); +``` + +### Provider Loading Order + +1. **LifecycleEventProvider** - Sets up the event-driven architecture +2. **Website\Boot** - Registers website-level functionality +3. **Front\Boot** - Configures Livewire and frontend middleware +4. **Mod\Boot** - Discovers and loads modules from configured paths + +## Module System + +Modules are self-contained feature packages that register via lifecycle events. This is the core architectural pattern of the framework. + +### Module Paths + +Configured in `config/core.php`: + +```php +'module_paths' => [ + app_path('Core'), // Local framework overrides (EUPL-1.2) + app_path('Mod'), // Your application modules + app_path('Website'), // Website-specific code +], +``` + +### Module Structure + +Each module lives in `app/Mod/{ModuleName}/` with a `Boot.php` entry point: + +``` +app/Mod/Blog/ +├── Boot.php # Event listeners (required) +├── Models/ +│ └── Post.php # Eloquent models +├── Routes/ +│ ├── web.php # Web routes +│ └── api.php # API routes +├── Views/ +│ └── posts/ +│ └── index.blade.php +├── Livewire/ +│ └── PostListPage.php # Livewire components +├── Migrations/ +│ └── 2025_01_01_create_posts_table.php +└── Tests/ + └── PostTest.php +``` + +### Boot.php Pattern + +The `Boot.php` class declares which lifecycle events it responds to: + +```php + 'onWebRoutes', + ApiRoutesRegistering::class => 'onApiRoutes', + AdminPanelBooting::class => ['onAdminPanel', 10], // With priority + ConsoleBooting::class => 'onConsole', + ]; + + public function onWebRoutes(WebRoutesRegistering $event): void + { + // Register routes + $event->routes(fn() => require __DIR__.'/Routes/web.php'); + + // Register view namespace (accessed as 'blog::view.name') + $event->views('blog', __DIR__.'/Views'); + } + + public function onApiRoutes(ApiRoutesRegistering $event): void + { + $event->routes(fn() => require __DIR__.'/Routes/api.php'); + } + + public function onAdminPanel(AdminPanelBooting $event): void + { + // Register admin navigation + $event->navigation('Blog', 'blog.admin.index', 'newspaper'); + + // Register admin resources + $event->resource('posts', PostResource::class); + } + + public function onConsole(ConsoleBooting $event): void + { + // Register artisan commands + $event->commands([ + ImportPostsCommand::class, + ]); + } +} +``` + +### Lifecycle Events + +| Event | When Fired | Common Uses | +|-------|------------|-------------| +| `WebRoutesRegistering` | Web routes loading | Public routes, views | +| `ApiRoutesRegistering` | API routes loading | REST endpoints | +| `AdminPanelBooting` | Admin panel setup | Navigation, resources | +| `ClientRoutesRegistering` | Authenticated SaaS routes | Dashboard, settings | +| `ConsoleBooting` | Artisan bootstrapping | Commands, schedules | +| `McpToolsRegistering` | MCP server setup | AI agent tools | + +### Lazy Loading + +Modules are discovered at boot time, but their `Boot` classes are only instantiated when the events they listen to are fired. This means: + +- Console commands don't load web routes +- API requests don't load admin panel code +- Unused modules have minimal overhead + +## Dependency Packages + +The template depends on four Core PHP Framework packages: + +| Package | Namespace | Purpose | +|---------|-----------|---------| +| `host-uk/core` | `Core\` | Foundation: events, modules, lifecycle | +| `host-uk/core-admin` | `Core\Admin\` | Admin panel, Livewire modals, Flux UI | +| `host-uk/core-api` | `Core\Api\` | REST API, scopes, rate limiting, webhooks | +| `host-uk/core-mcp` | `Core\Mcp\` | Model Context Protocol for AI agents | + +These are loaded as Composer dependencies and provide the framework infrastructure. + +## Frontend Stack + +### Livewire 3 + +Livewire components live within modules: + +```php +// app/Mod/Blog/Livewire/PostListPage.php + Post::latest()->paginate(10), + ]); + } +} +``` + +### Flux UI + +Flux Pro components are the standard UI library. Example usage: + +```blade + + Edit Post + + + Save + +``` + +### Asset Pipeline + +Vite handles asset compilation: + +- `resources/css/app.css` - Tailwind CSS entry point +- `resources/js/app.js` - JavaScript entry point +- Module assets are not automatically included; import them in the main files or use `@vite` directive + +## Configuration + +### Core Framework (`config/core.php`) + +```php +return [ + // Paths to scan for modules + 'module_paths' => [ + app_path('Core'), + app_path('Mod'), + app_path('Website'), + ], + + // Service configuration + 'services' => [ + 'cache_discovery' => env('CORE_CACHE_DISCOVERY', true), + ], + + // CDN configuration + 'cdn' => [ + 'enabled' => env('CDN_ENABLED', false), + 'driver' => env('CDN_DRIVER', 'bunny'), + ], +]; +``` + +### Environment Variables + +Key Core-specific environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `CORE_CACHE_DISCOVERY` | `true` | Cache module discovery for performance | +| `CDN_ENABLED` | `false` | Enable CDN for static assets | +| `CDN_DRIVER` | `bunny` | CDN provider (bunny, cloudflare, etc.) | +| `FLUX_LICENSE_KEY` | - | Flux Pro license key (optional) | + +## Testing + +Tests use Pest PHP and follow Laravel conventions: + +```php +// tests/Feature/BlogTest.php +count(3)->create(); + + $this->get('/blog') + ->assertOk() + ->assertSee($posts->first()->title); +}); + +it('requires authentication to create posts', function () { + $this->post('/blog', ['title' => 'Test']) + ->assertRedirect('/login'); +}); +``` + +### Test Organisation + +- **Feature tests** - HTTP requests, Livewire components, integration tests +- **Unit tests** - Services, utilities, isolated logic +- **Module tests** - Can live within the module directory (`app/Mod/Blog/Tests/`) + +## Routing + +### Route Registration + +Routes are registered via module events, not the traditional `routes/` directory: + +```php +// app/Mod/Blog/Routes/web.php +name('blog.')->group(function () { + Route::get('/', PostListPage::class)->name('index'); + Route::get('/{post:slug}', PostShowPage::class)->name('show'); +}); +``` + +The `routes/web.php` and `routes/api.php` files are fallbacks for routes that don't belong to any module. + +### View Namespacing + +Module views are namespaced: + +```blade +{{-- Accessing blog module views --}} +@include('blog::partials.header') + +{{-- In a Livewire component --}} +return view('blog::posts.index', [...]); +``` + +## Namespace Conventions + +| Path | Namespace | License | +|------|-----------|---------| +| `app/Core/` | `Core\` (local) | EUPL-1.2 | +| `app/Mod/` | `App\Mod\` | Your choice | +| `app/Website/` | `App\Website\` | Your choice | +| `vendor/host-uk/core/` | `Core\` | EUPL-1.2 | + +The `app/Core/` directory is for local overrides of framework classes. Any class you place here will take precedence over the vendor package. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..8fab065 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,354 @@ +--- +title: Getting Started +description: Quick start guide for creating a new Core PHP Framework application +updated: 2026-01-29 +--- + +# Getting Started + +This guide walks you through creating your first application with Core PHP Framework using the core-template. + +## Prerequisites + +- PHP 8.2 or higher +- Composer 2.x +- Node.js 18+ and npm +- SQLite (default) or MySQL/PostgreSQL + +## Installation + +### 1. Clone the Template + +```bash +git clone https://github.com/host-uk/core-template.git my-project +cd my-project +``` + +Or use Composer create-project (once published): + +```bash +composer create-project host-uk/core-template my-project +``` + +### 2. Install Dependencies + +```bash +# PHP dependencies +composer install + +# JavaScript dependencies +npm install +``` + +### 3. Configure Environment + +```bash +# Copy environment file +cp .env.example .env + +# Generate application key +php artisan key:generate + +# Create SQLite database +touch database/database.sqlite + +# Run migrations +php artisan migrate +``` + +### 4. Start Development Server + +```bash +# In one terminal - PHP server +php artisan serve + +# In another terminal - Vite dev server +npm run dev +``` + +Visit http://localhost:8000 to see your application. + +## Creating Your First Module + +The Core PHP Framework uses a modular architecture. Features are organised as self-contained modules. + +### Using the Artisan Command + +```bash +# Create a full-featured module +php artisan make:mod Blog --all + +# Or select specific features +php artisan make:mod Blog --web --api +``` + +### Manual Creation + +1. Create the module directory: + +```bash +mkdir -p app/Mod/Blog/{Models,Routes,Views,Livewire,Migrations,Tests} +``` + +2. Create `app/Mod/Blog/Boot.php`: + +```php + 'onWebRoutes', + ]; + + public function onWebRoutes(WebRoutesRegistering $event): void + { + $event->routes(fn() => require __DIR__.'/Routes/web.php'); + $event->views('blog', __DIR__.'/Views'); + } +} +``` + +3. Create `app/Mod/Blog/Routes/web.php`: + +```php +name('blog.index'); +``` + +4. Create `app/Mod/Blog/Views/index.blade.php`: + +```blade + + + + + Blog + + +

Welcome to the Blog

+ + +``` + +Visit http://localhost:8000/blog to see your module in action. + +## Adding a Model + +Create `app/Mod/Blog/Models/Post.php`: + +```php + 'datetime', + ]; + } +} +``` + +Create a migration in `app/Mod/Blog/Migrations/`: + +```php +id(); + $table->string('title'); + $table->string('slug')->unique(); + $table->text('content'); + $table->timestamp('published_at')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('posts'); + } +}; +``` + +Run the migration: + +```bash +php artisan migrate +``` + +## Adding a Livewire Component + +Create `app/Mod/Blog/Livewire/PostListPage.php`: + +```php + Post::latest()->paginate(10), + ]); + } +} +``` + +Update `app/Mod/Blog/Routes/web.php`: + +```php +name('blog.index'); +``` + +Create `app/Mod/Blog/Views/posts/index.blade.php`: + +```blade +
+

Blog Posts

+ + @forelse ($posts as $post) +
+

{{ $post->title }}

+

{{ Str::limit($post->content, 200) }}

+
+ @empty +

No posts yet.

+ @endforelse + + {{ $posts->links() }} +
+``` + +## Writing Tests + +Create `app/Mod/Blog/Tests/PostTest.php`: + +```php +get('/blog') + ->assertOk() + ->assertSee('Blog Posts'); +}); + +it('shows posts on the index page', function () { + $post = Post::create([ + 'title' => 'Test Post', + 'slug' => 'test-post', + 'content' => 'This is a test post.', + ]); + + $this->get('/blog') + ->assertOk() + ->assertSee('Test Post'); +}); +``` + +Run tests: + +```bash +vendor/bin/pest +``` + +## Code Formatting + +Before committing, run Laravel Pint: + +```bash +# Format changed files only +vendor/bin/pint --dirty + +# Format all files +vendor/bin/pint +``` + +## Next Steps + +- Read the [Architecture documentation](architecture.md) to understand the module system +- Review [Security considerations](security.md) before deploying +- Explore the [Core PHP Framework documentation](https://github.com/host-uk/core-php) +- Add the Admin Panel with `host-uk/core-admin` +- Build an API with `host-uk/core-api` + +## Common Commands + +```bash +# Development +php artisan serve # Start PHP server +npm run dev # Start Vite with HMR +npm run build # Build for production + +# Modules +php artisan make:mod Name # Create a new module +php artisan make:mod Name --all # With all features + +# Database +php artisan migrate # Run migrations +php artisan migrate:fresh # Reset and re-run migrations +php artisan db:seed # Run seeders + +# Testing +vendor/bin/pest # Run all tests +vendor/bin/pest --filter=Name # Run specific test + +# Code Quality +vendor/bin/pint # Format code +vendor/bin/pint --test # Check formatting without changes +``` diff --git a/docs/modules.md b/docs/modules.md new file mode 100644 index 0000000..a3449ef --- /dev/null +++ b/docs/modules.md @@ -0,0 +1,427 @@ +--- +title: Modules +description: Creating and organising modules in Core PHP Framework applications +updated: 2026-01-29 +--- + +# Modules + +Modules are the building blocks of Core PHP Framework applications. Each module is a self-contained feature that registers itself via lifecycle events. + +## Module Structure + +A typical module follows this structure: + +``` +app/Mod/Blog/ +├── Boot.php # Entry point - event listeners +├── Models/ +│ └── Post.php # Eloquent models +├── Routes/ +│ ├── web.php # Public routes +│ └── api.php # API routes +├── Views/ +│ ├── index.blade.php +│ └── posts/ +│ └── show.blade.php +├── Livewire/ +│ ├── PostListPage.php +│ └── PostShowPage.php +├── Actions/ +│ └── CreatePost.php # Business logic +├── Services/ +│ └── PostService.php +├── Migrations/ +│ └── 2025_01_01_create_posts_table.php +└── Tests/ + ├── PostTest.php + └── CreatePostTest.php +``` + +## The Boot Class + +Every module requires a `Boot.php` file that declares its event listeners: + +```php + 'onWebRoutes', + ApiRoutesRegistering::class => 'onApiRoutes', + AdminPanelBooting::class => ['onAdminPanel', 10], // With priority + ConsoleBooting::class => 'onConsole', + ]; + + public function onWebRoutes(WebRoutesRegistering $event): void + { + // Register routes + $event->routes(fn() => require __DIR__.'/Routes/web.php'); + + // Register view namespace (accessed as 'blog::view.name') + $event->views('blog', __DIR__.'/Views'); + } + + public function onApiRoutes(ApiRoutesRegistering $event): void + { + $event->routes(fn() => require __DIR__.'/Routes/api.php'); + } + + public function onAdminPanel(AdminPanelBooting $event): void + { + // Register navigation item + $event->navigation('Blog', 'blog.admin.index', 'newspaper'); + + // Register admin resources + $event->resource('posts', PostResource::class); + } + + public function onConsole(ConsoleBooting $event): void + { + // Register artisan commands + $event->commands([ + ImportPostsCommand::class, + PublishScheduledPostsCommand::class, + ]); + + // Register scheduled tasks + $event->schedule(function ($schedule) { + $schedule->command('blog:publish-scheduled')->hourly(); + }); + } +} +``` + +## Lifecycle Events + +### WebRoutesRegistering + +Fired when web routes are being registered. Use for public-facing routes. + +```php +public function onWebRoutes(WebRoutesRegistering $event): void +{ + // Register route file + $event->routes(fn() => require __DIR__.'/Routes/web.php'); + + // Register view namespace + $event->views('blog', __DIR__.'/Views'); + + // Register Blade components + $event->components('blog', __DIR__.'/Views/Components'); + + // Register middleware + $event->middleware('blog.auth', BlogAuthMiddleware::class); +} +``` + +### ApiRoutesRegistering + +Fired when API routes are being registered. Routes are automatically prefixed with `/api`. + +```php +public function onApiRoutes(ApiRoutesRegistering $event): void +{ + $event->routes(fn() => require __DIR__.'/Routes/api.php'); + + // Register API resources + $event->resource('posts', PostApiResource::class); +} +``` + +### AdminPanelBooting + +Fired when the admin panel is being set up (requires `core-admin` package). + +```php +public function onAdminPanel(AdminPanelBooting $event): void +{ + // Navigation item with icon + $event->navigation('Blog', 'blog.admin.index', 'newspaper'); + + // Navigation group with sub-items + $event->navigationGroup('Blog', [ + ['Posts', 'blog.admin.posts', 'file-text'], + ['Categories', 'blog.admin.categories', 'folder'], + ['Tags', 'blog.admin.tags', 'tag'], + ], 'newspaper'); + + // Register admin resource + $event->resource('posts', PostResource::class); + + // Register widget for dashboard + $event->widget(RecentPostsWidget::class); +} +``` + +### ClientRoutesRegistering + +Fired for authenticated SaaS routes (dashboard, settings, etc.). + +```php +public function onClientRoutes(ClientRoutesRegistering $event): void +{ + $event->routes(fn() => require __DIR__.'/Routes/client.php'); +} +``` + +### ConsoleBooting + +Fired when Artisan is bootstrapping. + +```php +public function onConsole(ConsoleBooting $event): void +{ + // Register commands + $event->commands([ + ImportPostsCommand::class, + ]); + + // Register scheduled tasks + $event->schedule(function ($schedule) { + $schedule->command('blog:publish-scheduled') + ->hourly() + ->withoutOverlapping(); + }); +} +``` + +### McpToolsRegistering + +Fired when the MCP server is being set up (requires `core-mcp` package). + +```php +public function onMcpTools(McpToolsRegistering $event): void +{ + $event->tool('create_post', CreatePostTool::class); + $event->tool('list_posts', ListPostsTool::class); +} +``` + +## Event Priorities + +You can specify a priority for event listeners. Higher numbers execute first: + +```php +public static array $listens = [ + AdminPanelBooting::class => ['onAdminPanel', 100], // High priority + WebRoutesRegistering::class => ['onWebRoutes', 10], // Normal priority +]; +``` + +Priorities are useful when: +- Your module needs to register before/after other modules +- You need to override routes from other modules +- You need to modify admin navigation order + +## View Namespacing + +Views are namespaced by the identifier you provide: + +```php +$event->views('blog', __DIR__.'/Views'); +``` + +Access views using the namespace prefix: + +```blade +{{-- In controllers/components --}} +return view('blog::posts.index'); + +{{-- In Blade templates --}} +@include('blog::partials.sidebar') +@extends('blog::layouts.main') +``` + +## Route Files + +### Web Routes (`Routes/web.php`) + +```php +name('blog.')->group(function () { + Route::get('/', PostListPage::class)->name('index'); + Route::get('/{post:slug}', PostShowPage::class)->name('show'); +}); + +// With middleware +Route::middleware(['auth'])->prefix('blog')->name('blog.')->group(function () { + Route::get('/my-posts', MyPostsPage::class)->name('my-posts'); +}); +``` + +### API Routes (`Routes/api.php`) + +```php +name('api.blog.')->group(function () { + Route::apiResource('posts', PostController::class); +}); +``` + +## Actions Pattern + +For complex business logic, use the Actions pattern: + +```php + $data['title'], + 'slug' => Str::slug($data['title']), + 'content' => $data['content'], + 'published_at' => $data['publish_now'] ? now() : null, + ]); + } +} +``` + +Usage: + +```php +$post = CreatePost::run([ + 'title' => 'My Post', + 'content' => 'Content here...', + 'publish_now' => true, +]); +``` + +## Module Discovery + +Modules are discovered automatically from paths configured in `config/core.php`: + +```php +'module_paths' => [ + app_path('Core'), // Framework overrides + app_path('Mod'), // Application modules + app_path('Website'), // Website-specific modules +], +``` + +### Caching + +In production, module discovery is cached. Clear the cache when adding new modules: + +```bash +php artisan cache:clear +``` + +Or disable caching during development: + +```env +CORE_CACHE_DISCOVERY=false +``` + +## Creating Modules with Artisan + +The `make:mod` command scaffolds a new module: + +```bash +# Full module with all features +php artisan make:mod Blog --all + +# Web routes only +php artisan make:mod Blog --web + +# API routes only +php artisan make:mod Blog --api + +# Admin panel integration +php artisan make:mod Blog --admin + +# Combination +php artisan make:mod Blog --web --api --admin +``` + +## Module Dependencies + +If your module depends on another module, check for its presence: + +```php +public function onWebRoutes(WebRoutesRegistering $event): void +{ + // Check if core-tenant is available + if (!class_exists(\Core\Tenant\Models\Workspace::class)) { + return; + } + + $event->routes(fn() => require __DIR__.'/Routes/web.php'); +} +``` + +## Best Practices + +### Keep Modules Focused + +Each module should represent a single feature or domain: + +- `Blog` - Blog posts, categories, tags +- `Shop` - Products, orders, cart +- `Newsletter` - Subscribers, campaigns + +### Use Clear Naming + +- Module name: PascalCase singular (`Blog`, not `Blogs`) +- Namespace: `App\Mod\{ModuleName}` +- View namespace: lowercase (`blog::`, `shop::`) + +### Isolate Dependencies + +Keep inter-module dependencies minimal. If modules need to communicate: + +1. Use events (preferred) +2. Use interfaces and dependency injection +3. Use shared services in `app/Services/` + +### Test Modules in Isolation + +Write tests that don't depend on other modules being present: + +```php +it('creates a blog post', function () { + $post = Post::create([ + 'title' => 'Test', + 'slug' => 'test', + 'content' => 'Content', + ]); + + expect($post)->toBeInstanceOf(Post::class); + expect($post->title)->toBe('Test'); +}); +``` diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 0000000..02494ea --- /dev/null +++ b/docs/security.md @@ -0,0 +1,266 @@ +--- +title: Security +description: Security considerations and audit notes for core-template +updated: 2026-01-29 +--- + +# Security + +This document covers security considerations for applications built with core-template. It includes both framework-provided protections and recommendations for hardening your application. + +## Built-in Protections + +### CSRF Protection + +Laravel's CSRF protection is enabled by default for all web routes. The template includes axios configuration that automatically attaches the CSRF token to AJAX requests: + +```javascript +// resources/js/bootstrap.js +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +``` + +For forms, use the `@csrf` Blade directive: + +```blade +
+ @csrf + +
+``` + +### XSS Protection + +Blade's `{{ }}` syntax automatically escapes output. Use `{!! !!}` only for trusted HTML content. + +### SQL Injection + +Eloquent ORM and Query Builder use parameterised queries by default. Avoid raw queries where possible: + +```php +// Safe - parameterised +User::where('email', $email)->first(); + +// Dangerous - raw SQL +DB::select("SELECT * FROM users WHERE email = '$email'"); // Don't do this +``` + +### Mass Assignment + +Models should define `$fillable` or `$guarded` properties to prevent mass assignment vulnerabilities: + +```php +class Post extends Model +{ + protected $fillable = ['title', 'content', 'slug']; +} +``` + +### Password Hashing + +The template configures bcrypt with 12 rounds by default (`BCRYPT_ROUNDS=12` in `.env.example`). This is appropriate for production use. + +## Recommendations + +### Security Headers + +Add security headers via middleware. Create `app/Http/Middleware/SecurityHeaders.php`: + +```php +headers->set('X-Frame-Options', 'SAMEORIGIN'); + $response->headers->set('X-Content-Type-Options', 'nosniff'); + $response->headers->set('X-XSS-Protection', '1; mode=block'); + $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); + + // Content Security Policy (adjust as needed) + $response->headers->set( + 'Content-Security-Policy', + "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" + ); + + return $response; + } +} +``` + +Register in `bootstrap/app.php`: + +```php +->withMiddleware(function (Middleware $middleware) { + \Core\Front\Boot::middleware($middleware); + $middleware->web(append: [ + \App\Http\Middleware\SecurityHeaders::class, + ]); +}) +``` + +### Session Security + +For production environments, update these settings in `.env`: + +```env +SESSION_SECURE_COOKIE=true # Only send cookies over HTTPS +SESSION_ENCRYPT=true # Encrypt session data +SESSION_HTTP_ONLY=true # Prevent JavaScript access to session cookie +SESSION_SAME_SITE=strict # Strict same-site policy +``` + +### HTTPS Enforcement + +Force HTTPS in production by adding to `AppServiceProvider`: + +```php +public function boot(): void +{ + if ($this->app->environment('production')) { + URL::forceScheme('https'); + } +} +``` + +### Rate Limiting + +The default welcome route has no rate limiting. For production, add throttle middleware: + +```php +Route::middleware('throttle:60,1')->group(function () { + Route::get('/', function () { + return view('welcome'); + }); +}); +``` + +Configure custom rate limiters in `AppServiceProvider`: + +```php +use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Support\Facades\RateLimiter; + +public function boot(): void +{ + RateLimiter::for('api', function (Request $request) { + return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); + }); + + RateLimiter::for('login', function (Request $request) { + return Limit::perMinute(5)->by($request->ip()); + }); +} +``` + +### APP_KEY Management + +The `APP_KEY` is critical for encryption. Never: + +- Commit it to version control +- Share it between environments +- Use predictable values + +Rotate the key only when necessary, understanding that: + +- Existing encrypted data becomes unreadable +- Active sessions are invalidated +- Signed URLs become invalid + +### Debug Mode + +Ensure `APP_DEBUG=false` in production. Debug mode exposes: + +- Stack traces with file paths +- Environment variables +- Database queries + +### Database Credentials + +Never commit database credentials. Use environment variables: + +```env +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=myapp +DB_USERNAME=${DB_USERNAME} +DB_PASSWORD=${DB_PASSWORD} +``` + +## Audit Checklist + +### Before Deployment + +- [ ] `APP_DEBUG=false` +- [ ] `APP_ENV=production` +- [ ] `APP_KEY` is set and unique +- [ ] Session cookies are secure (`SESSION_SECURE_COOKIE=true`) +- [ ] HTTPS is enforced +- [ ] Security headers are configured +- [ ] Rate limiting is in place for sensitive endpoints +- [ ] `.env` is not accessible via web (check with `curl https://yoursite.com/.env`) +- [ ] Storage directory is not web-accessible +- [ ] Error pages don't leak sensitive information +- [ ] Database credentials are environment-specific +- [ ] Third-party API keys are not exposed in client-side code + +### Authentication (if using core-tenant) + +- [ ] Password reset tokens expire appropriately +- [ ] Login attempts are rate limited +- [ ] Account lockout is configured after failed attempts +- [ ] Two-factor authentication is available for sensitive accounts +- [ ] Session regeneration on login +- [ ] Session invalidation on logout + +### API Security (if using core-api) + +- [ ] API keys are properly scoped +- [ ] Rate limiting per API key +- [ ] Webhook signatures are verified +- [ ] CORS is configured appropriately +- [ ] Sensitive endpoints require authentication + +## Dependencies + +Keep dependencies updated to receive security patches: + +```bash +# Check for outdated packages +composer outdated + +# Update dependencies +composer update + +# Check for known vulnerabilities +composer audit +``` + +The template includes Dependabot configuration (`.github/dependabot.yml`) to automate security updates. + +## Reporting Security Issues + +If you discover a security vulnerability in the Core PHP Framework: + +1. **Do not** create a public GitHub issue +2. Email security concerns to the maintainers directly +3. Include detailed steps to reproduce +4. Allow reasonable time for a fix before public disclosure + +## Resources + +- [Laravel Security Documentation](https://laravel.com/docs/security) +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [PHP Security Best Practices](https://www.php.net/manual/en/security.php) +- [Snyk Security Advisories](https://security.snyk.io/)