diff --git a/.agents/skills/configure-nightwatch/SKILL.md b/.agents/skills/configure-nightwatch/SKILL.md new file mode 100644 index 0000000..1fc580b --- /dev/null +++ b/.agents/skills/configure-nightwatch/SKILL.md @@ -0,0 +1,404 @@ +--- +name: configure-nightwatch +description: Configures Laravel Nightwatch data collection, sampling rates, filtering rules, and redaction policies. Use when setting up Nightwatch, managing data volume, protecting sensitive data (PII), or optimizing event collection for production workloads. +license: MIT +metadata: + author: laravel +--- + +# Nightwatch Configuration Guide + +This skill helps configure Laravel Nightwatch data collection to balance observability, performance, and privacy. Covers sampling strategies, filtering rules, and redaction methods across all event types. + +## Documentation Reference + +The [Nightwatch Documentation](https://nightwatch.laravel.com/docs) is the definitive and up-to-date source of information for all Nightwatch configuration options. This skill provides practical guidance and common patterns, but always consult the official documentation as the primary source of truth for specific details, environment variables, and API behavior. The documentation includes comprehensive coverage of: + +- [Filtering and Configuration](https://nightwatch.laravel.com/docs/filtering) - Core concepts for sampling, filtering, and redaction +- Individual event type pages with specific configuration options: + - [Requests](https://nightwatch.laravel.com/docs/requests) - Request sampling, header handling, payload capture + - [Commands](https://nightwatch.laravel.com/docs/commands) - Command sampling and redaction + - [Queries](https://nightwatch.laravel.com/docs/queries) - Query filtering and redaction + - [Cache](https://nightwatch.laravel.com/docs/cache) - Cache event filtering by key or pattern + - [Jobs](https://nightwatch.laravel.com/docs/jobs) - Job filtering and sampling decoupling + - [Mail](https://nightwatch.laravel.com/docs/mail) - Mail event filtering + - [Notifications](https://nightwatch.laravel.com/docs/notifications) - Notification filtering by channel + - [Exceptions](https://nightwatch.laravel.com/docs/exceptions) - Exception sampling and throttling + - [Outgoing Requests](https://nightwatch.laravel.com/docs/outgoing-requests) - HTTP request filtering +- [reference.md](reference.md) - Quick lookup table by event type, production presets, and verification checklist + +## Data Collection Flow + +Nightwatch processes events through three stages: + +1. **Sampling** - Controls which entry points are captured (requests, commands, scheduled tasks) +2. **Filtering** - Excludes specific events after sampling (queries, cache, mail, etc.) +3. **Redaction** - Modifies captured data to remove/obfuscate sensitive information + +``` +Request/Command/Scheduled Task + | + v + [Sampling?] ----NO----> Drop entire trace + | YES + v + Events generated + | + v + [Filtering?] ----YES---> Drop specific event + | NO + v + [Redaction] ----------> Store modified data +``` + +--- + +## Sampling Configuration + +Sampling determines which entry points (requests, commands, scheduled tasks) trigger full trace collection. When an entry point is sampled, all related events are captured. + +### Global Sample Rates + +Configure via environment variables: + +```bash + +# Default: 100% sampling (all requests/commands captured) + +NIGHTWATCH_REQUEST_SAMPLE_RATE=0.1 # Recommended: 10% of requests + +NIGHTWATCH_COMMAND_SAMPLE_RATE=1.0 # Capture all commands + +NIGHTWATCH_EXCEPTION_SAMPLE_RATE=1.0 # Always capture exceptions + +``` + +**Recommendation**: Start with `0.1` (10%) for requests in production, adjust based on volume and needs. + +### Route-Based Sampling + +Apply different rates to specific routes using the `Sample` middleware: + +```php routes/web.php +use Illuminate\Support\Facades\Route; +use Laravel\Nightwatch\Http\Middleware\Sample; + +// Sample admin routes at 100% +Route::middleware(Sample::rate(1.0))->prefix('admin')->group(function () { + // All admin routes sampled fully +}); + +// Sample API routes at 5% +Route::middleware(Sample::rate(0.05))->prefix('api')->group(function () { + // API routes sampled sparingly +}); + +// Always sample critical endpoints +Route::post('/checkout', [CheckoutController::class, 'process']) + ->middleware(Sample::always()); + +// Never sample health checks +Route::get('/health', [HealthController::class, 'check']) + ->middleware(Sample::never()); +``` + +### Unmatched Route Sampling + +Handle 404/bot traffic with reduced sampling: + +```php routes/web.php +Route::fallback(fn () => abort(404)) + ->middleware(Sample::rate(0.01)); // 1% sampling for unmatched routes +``` + +### Dynamic Sampling + +Sample based on runtime conditions (user role, request attributes): + +```php app/Http/Middleware/SampleAdminRequests.php +use Closure; +use Illuminate\Http\Request; +use Laravel\Nightwatch\Facades\Nightwatch; + +class SampleAdminRequests +{ + public function handle(Request $request, Closure $next) + { + if ($request->user()?->isAdmin()) { + Nightwatch::sample(); // Always sample admin requests + } + return $next($request); + } +} +``` + +### Command Sampling + +Exclude specific commands from sampling: + +```php AppServiceProvider.php +use Illuminate\Console\Events\CommandStarting; +use Illuminate\Support\Facades\Event; +use Laravel\Nightwatch\Facades\Nightwatch; + +public function boot(): void +{ + Event::listen(function (CommandStarting $event) { + if (in_array($event->command, ['schedule:finish', 'horizon:snapshot'])) { + Nightwatch::dontSample(); + } + }); +} +``` + +### Vendor Commands + +Nightwatch automatically ignores framework/internal commands. Opt-in to capture them: + +```php +Nightwatch::captureDefaultVendorCommands(); +``` + +--- + +## Filtering Configuration + +Filtering excludes specific events from collection after sampling. Use filtering to reduce noise and quota usage. + +### Database Queries + +**Filter all queries** (disable query collection): + +```bash +NIGHTWATCH_IGNORE_QUERIES=true +``` + +**Filter specific queries** by SQL pattern: + +```php AppServiceProvider.php +use Laravel\Nightwatch\Facades\Nightwatch; +use Laravel\Nightwatch\Records\Query; + +public function boot(): void +{ + // Filter job table queries (PostgreSQL) + Nightwatch::rejectQueries(function (Query $query) { + return str_contains($query->sql, 'into "jobs"'); + }); + + // Filter cache table queries (MySQL) + Nightwatch::rejectQueries(function (Query $query) { + return str_contains($query->sql, 'from `cache`') + || str_contains($query->sql, 'into `cache`'); + }); +} +``` + +### Cache Events + +**Filter all cache events**: + +```bash +NIGHTWATCH_IGNORE_CACHE_EVENTS=true +``` + +**Filter by cache key patterns**: + +```php +Nightwatch::rejectCacheKeys([ + 'my-app:users', // Exact match + '/^my-app:posts:/', // Regex: starts with my-app:posts: + '/^[a-zA-Z0-9]{40}$/', // Regex: session IDs +]); +``` + +**Filter with callback**: + +```php +use Laravel\Nightwatch\Records\CacheEvent; + +Nightwatch::rejectCacheEvents(function (CacheEvent $cacheEvent) { + return str_starts_with($cacheEvent->key, 'temp:'); +}); +``` + +### Mail Events + +**Filter all mail**: + +```bash +NIGHTWATCH_IGNORE_MAIL=true +``` + +**Filter specific mail**: + +```php +use Laravel\Nightwatch\Records\Mail; + +Nightwatch::rejectMail(function (Mail $mail) { + return str_contains($mail->subject, 'Newsletter'); +}); +``` + +### Notification Events + +**Filter all notifications**: + +```bash +NIGHTWATCH_IGNORE_NOTIFICATIONS=true +``` + +**Filter by channel**: + +```php +use Laravel\Nightwatch\Records\Notification; + +Nightwatch::rejectNotifications(function (Notification $notification) { + return $notification->channel === 'database'; +}); +``` + +### Outgoing HTTP Requests + +**Filter all outgoing requests**: + +```bash +NIGHTWATCH_IGNORE_OUTGOING_REQUESTS=true +``` + +**Filter by URL**: + +```php +use Laravel\Nightwatch\Records\OutgoingRequest; + +Nightwatch::rejectOutgoingRequests(function (OutgoingRequest $request) { + return str_contains($request->url, 'analytics.example.com'); +}); +``` + +### Queued Jobs + +**Filter specific jobs**: + +```php +use Laravel\Nightwatch\Records\QueuedJob; + +Nightwatch::rejectQueuedJobs(function (QueuedJob $job) { + return $job->name === 'App\Jobs\LowPriorityJob'; +}); +``` + +### Decoupling Job Sampling + +Sample jobs independently from parent contexts: + +```php +use Illuminate\Support\Facades\Queue; + +public function boot(): void +{ + Queue::before(fn () => Nightwatch::sample(rate: 0.5)); +} +``` + +--- + +## Redaction Configuration + +Redaction modifies captured data to remove or obfuscate sensitive information. Unlike filtering, redaction keeps the event but sanitizes its content. + +### Request Redaction + +**Redact sensitive headers** (automatically redacts: Authorization, Cookie, X-XSRF-TOKEN): + +```bash + +# Customize redacted headers + +NIGHTWATCH_REDACT_HEADERS=Authorization,Cookie,Proxy-Authorization,X-API-Key +``` + +**Redact request payloads** (disabled by default): + +```bash + +# Enable payload capture + +NIGHTWATCH_CAPTURE_REQUEST_PAYLOAD=true + +# Customize redacted fields + +NIGHTWATCH_REDACT_PAYLOAD_FIELDS=password,password_confirmation,ssn,credit_card +``` + +**Programmatic redaction**: + +```php +use Laravel\Nightwatch\Facades\Nightwatch; +use Laravel\Nightwatch\Records\Request; + +Nightwatch::redactRequests(function (Request $request) { + $request->url = str_replace('secret', '***', $request->url); + $request->ip = preg_replace('/\d+$/', '***', $request->ip); +}); +``` + +### Query Redaction + +```php +use Laravel\Nightwatch\Records\Query; + +Nightwatch::redactQueries(function (Query $query) { + $query->sql = str_replace('secret_token', '***', $query->sql); +}); +``` + +### Cache Redaction + +```php +use Laravel\Nightwatch\Records\CacheEvent; + +Nightwatch::redactCacheEvents(function (CacheEvent $cacheEvent) { + $cacheEvent->key = str_replace('user:', 'user:***:', $cacheEvent->key); +}); +``` + +### Command Redaction + +```php +use Laravel\Nightwatch\Records\Command; + +Nightwatch::redactCommands(function (Command $command) { + $command->command = preg_replace('/--password=\S+/', '--password=***', $command->command); +}); +``` + +### Exception Redaction + +```php +use Laravel\Nightwatch\Records\Exception; + +Nightwatch::redactExceptions(function (Exception $exception) { + $exception->message = str_replace('secret', '***', $exception->message); +}); +``` + +### Mail Redaction + +```php +use Laravel\Nightwatch\Records\Mail; + +Nightwatch::redactMail(function (Mail $mail) { + $mail->subject = str_replace('Invoice #', 'Invoice ***', $mail->subject); +}); +``` + +### Outgoing Request Redaction + +```php +use Laravel\Nightwatch\Records\OutgoingRequest; + +Nightwatch::redactOutgoingRequests(function (OutgoingRequest $outgoingRequest) { + $outgoingRequest->url = preg_replace('/api_key=\w+/', 'api_key=***', $outgoingRequest->url); +}); +``` diff --git a/.agents/skills/configure-nightwatch/reference.md b/.agents/skills/configure-nightwatch/reference.md new file mode 100644 index 0000000..b071d2f --- /dev/null +++ b/.agents/skills/configure-nightwatch/reference.md @@ -0,0 +1,108 @@ +# Nightwatch Configuration Reference + +## Configuration Summary by Event Type + +| Event Type | Sampling | Filtering | Redaction | +| --------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------- | +| **Requests** | `NIGHTWATCH_REQUEST_SAMPLE_RATE`, Route middleware | Not applicable | Headers, payload, URL, IP | +| **Commands** | `NIGHTWATCH_COMMAND_SAMPLE_RATE`, Event listener | Not applicable | Command arguments | +| **Queries** | Parent context | `rejectQueries()`, `NIGHTWATCH_IGNORE_QUERIES` | SQL statement | +| **Cache** | Parent context | `rejectCacheKeys()`, `rejectCacheEvents()`, `NIGHTWATCH_IGNORE_CACHE_EVENTS` | Cache key | +| **Jobs** | Parent context, Queue::before | `rejectQueuedJobs()` | Not applicable | +| **Mail** | Parent context | `rejectMail()`, `NIGHTWATCH_IGNORE_MAIL` | Subject | +| **Notifications** | Parent context | `rejectNotifications()`, `NIGHTWATCH_IGNORE_NOTIFICATIONS` | Not applicable | +| **Outgoing Requests** | Parent context | `rejectOutgoingRequests()`, `NIGHTWATCH_IGNORE_OUTGOING_REQUESTS` | URL | +| **Exceptions** | `NIGHTWATCH_EXCEPTION_SAMPLE_RATE` | Not applicable | Exception message | + +--- + +## Production Recommendations + +### High-Traffic Applications + +```bash + +# Conservative sampling + +NIGHTWATCH_REQUEST_SAMPLE_RATE=0.01 # 1% of requests + +NIGHTWATCH_COMMAND_SAMPLE_RATE=0.1 # 10% of commands + +NIGHTWATCH_EXCEPTION_SAMPLE_RATE=1.0 # Always capture exceptions + +# Filter noisy events + +NIGHTWATCH_IGNORE_CACHE_EVENTS=true +NIGHTWATCH_IGNORE_QUERIES=true # Or filter specific queries programmatically + +``` + +### Privacy-Conscious Applications + +```bash + +# Disable sensitive data collection + +NIGHTWATCH_CAPTURE_REQUEST_PAYLOAD=false +NIGHTWATCH_REDACT_HEADERS=Authorization,Cookie,Proxy-Authorization,X-XSRF-TOKEN + +# Or use redaction in AppServiceProvider + +``` + +### Balanced Configuration (Recommended Start) + +```bash + +# Sample rates + +NIGHTWATCH_REQUEST_SAMPLE_RATE=0.1 +NIGHTWATCH_COMMAND_SAMPLE_RATE=1.0 +NIGHTWATCH_EXCEPTION_SAMPLE_RATE=1.0 + +# Filter obvious noise programmatically + +# Redact PII as needed + +``` + +--- + +## Verification Checklist + +After configuration: + +- [ ] Sampling rates appropriate for traffic volume +- [ ] Noisy events filtered (cache, certain queries) +- [ ] Sensitive data redacted (PII, tokens, credentials) +- [ ] Exceptions always captured for debugging +- [ ] Test in development with `NIGHTWATCH_REQUEST_SAMPLE_RATE=1.0` +- [ ] Monitor event quota usage in Nightwatch dashboard + +--- + +## Common Patterns + +### Filter Health Checks + Reduce Sampling + +```php +Route::get('/health', fn() => ['status' => 'ok']) + ->middleware(Sample::never()); +``` + +### Exclude Internal/Vendor Queries + +```php +Nightwatch::rejectQueries(fn($q) => + str_contains($q->sql, 'telescope') || + str_contains($q->sql, 'pulse') +); +``` + +### Protect User Data in Cache Keys + +```php +Nightwatch::redactCacheEvents(fn($e) => + $e->key = preg_replace('/user:\d+/', 'user:***', $e->key) +); +``` diff --git a/.agents/skills/echo-development/SKILL.md b/.agents/skills/echo-development/SKILL.md new file mode 100644 index 0000000..9b8264b --- /dev/null +++ b/.agents/skills/echo-development/SKILL.md @@ -0,0 +1,214 @@ +--- +name: echo-development +description: "Develops real-time broadcasting with Laravel Echo. Activates when setting up broadcasting (Reverb, Pusher, Ably); creating ShouldBroadcast events; defining broadcast channels (public, private, presence, encrypted); authorizing channels; configuring Echo; listening for events; implementing client events (whisper); setting up model broadcasting; broadcasting notifications; or when the user mentions broadcasting, Echo, WebSockets, real-time events, Reverb, or presence channels." +license: MIT +metadata: + author: laravel +--- + +# Laravel Broadcasting & Echo + +## When to Apply + +Activate this skill when: + +- Installing or configuring Laravel broadcasting (Reverb, Pusher, Ably) +- Creating events that implement `ShouldBroadcast` +- Defining broadcast channels and authorization +- Setting up Laravel Echo on the client side +- Listening for broadcast events, notifications, or model events +- Implementing client-to-client events (whisper) +- Working with presence channels for user awareness + +## Documentation + +Use `search-docs` for detailed broadcasting patterns and documentation. + +## Basic Usage + +### Installing Broadcasting + +```bash +php artisan install:broadcasting +``` + +Use flags for specific drivers: `--reverb`, `--pusher`, `--ably`. This creates `config/broadcasting.php` and `routes/channels.php`. + +### Creating a Broadcast Event + +```bash +php artisan make:event OrderShipped +``` + + +```php +namespace App\Events; + +use App\Models\Order; +use Illuminate\Broadcasting\InteractsWithSockets; +use Illuminate\Broadcasting\PrivateChannel; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Queue\SerializesModels; + +class OrderShipped implements ShouldBroadcast +{ + use InteractsWithSockets, SerializesModels; + + public function __construct(public Order $order) {} + + public function broadcastOn(): array + { + return [new PrivateChannel('orders.'.$this->order->id)]; + } +} +``` + +Dispatch the event: + + +```php +use App\Events\OrderShipped; + +OrderShipped::dispatch($order); +``` + +### Authorizing Channels + +Define authorization in `routes/channels.php`: + + +```php +use App\Models\Order; +use App\Models\User; + +Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) { + return $user->id === Order::findOrNew($orderId)->user_id; +}); +``` + +Create a channel class for complex authorization: + +```bash +php artisan make:channel OrderChannel +``` + +List all registered channels: + +```bash +php artisan channel:list +``` + +### Client-Side Setup + +Install Echo and Pusher JS: + +```bash +npm install --save-dev laravel-echo pusher-js +``` + + +```javascript +import Echo from 'laravel-echo'; +import Pusher from 'pusher-js'; +window.Pusher = Pusher; + +window.Echo = new Echo({ + broadcaster: 'reverb', + key: import.meta.env.VITE_REVERB_APP_KEY, + wsHost: import.meta.env.VITE_REVERB_HOST, + wsPort: import.meta.env.VITE_REVERB_PORT ?? 80, + wssPort: import.meta.env.VITE_REVERB_PORT ?? 443, + forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + enabledTransports: ['ws', 'wss'], +}); +``` + +### Listening for Events + + +```javascript +Echo.private(`orders.${orderId}`) + .listen('OrderShipmentStatusUpdated', (e) => { + console.log(e.order); + }); +``` + +### Running Required Processes + +```bash +php artisan queue:work # Required for ShouldBroadcast events + +php artisan reverb:start # Required for Reverb driver + +``` + +## What's Possible + +Use `search-docs` to find detailed code examples and configuration for each of these: + +### Channel Types + +- Public (`new Channel`) — no auth, anyone can subscribe. Use for app-wide announcements, public feeds, or status pages. +- Private (`new PrivateChannel`) — requires authorization. Use for user-specific data like orders, messages, or account updates. +- Presence (`new PresenceChannel`) — authorized + tracks who's online. Use for chat rooms, collaborative editing, "who's viewing this" features, or typing indicators. +- EncryptedPrivate — end-to-end encryption, Pusher/Reverb only. Use when payload must be hidden from the broadcast server (e.g., sensitive financial data or private messages). +- Drivers: `reverb` (self-hosted WebSocket server), `pusher` (managed service), `ably` (managed service), `log` (writes to Laravel log, use for debugging), `null` (no-op, use for testing) + +### Event Customization + +- `broadcastAs()` — custom event name (client must use dot prefix: `.listen('.custom.name')`). Use when you want stable API names decoupled from PHP class names, or shorter event names for the frontend. +- `broadcastWith()` — control exact payload. Use to avoid leaking sensitive model attributes, slim down large payloads, or add computed data not on the model. +- `broadcastWhen()` — conditional broadcasting. Use to skip broadcasting when changes are trivial (e.g., only broadcast order updates above a threshold, or skip unchanged fields). +- `broadcastQueue()` / `$queue` — route to specific queue. Use to isolate real-time broadcasts from slow background jobs so they're processed faster. +- `$connection` — set queue connection per event. Use when broadcasts should go through a faster queue backend like Redis while other jobs use the database driver. + +### Broadcasting Interfaces + +- `ShouldBroadcast` — queue the broadcast (default). Use for most events to avoid blocking the HTTP response. +- `ShouldBroadcastNow` — broadcast synchronously, skip queue. Use during development or for time-critical events where queue latency is unacceptable. +- `ShouldDispatchAfterCommit` — wait for DB transaction commit. Use when the event references newly created records that listeners need to query (prevents race conditions). +- `ShouldRescue` — auto-catch broadcast exceptions. Use to prevent broadcast failures (e.g., WebSocket server down) from disrupting the user's HTTP request. +- `InteractsWithSockets` — required for `toOthers()`. Use on any event where you want to exclude the sender (optimistic UI updates). +- `InteractsWithBroadcasting` — override driver per event via `broadcastVia()`. Use in multi-driver setups (e.g., some events via Reverb, others via Pusher). + +### Broadcasting Helpers + +- `broadcast(new Event)->toOthers()` — exclude current user's socket. Use when the client already updates optimistically from the API response to avoid duplicate updates. +- `broadcast(new Event)->via('pusher')` — override connection. Use to route specific events through a different broadcast driver than the default. +- `Broadcast::on()`, `Broadcast::private()`, `Broadcast::presence()` — anonymous broadcasting without event classes. Chain `.as('name')->with($data)->send()` or `.sendNow()`. Use for simple one-off broadcasts where creating a full event class is overkill (e.g., quick status updates, simple notifications). + +### Channel Authorization + +- Closure-based in `routes/channels.php` — use for simple authorization logic (e.g., checking ownership). +- Model binding: `Broadcast::channel('orders.{order}', fn (User $user, Order $order) => ...)` — use when authorization depends on the model instance (auto-resolves from route parameter). +- Channel classes via `php artisan make:channel` — use for complex authorization logic that benefits from dependency injection or reusable logic across channels. +- Multiple guards: `['guards' => ['web', 'admin']]` — use when the channel should be accessible by users authenticated via different guards (e.g., both regular users and admins). + +### Model Broadcasting + +- `BroadcastsEvents` trait auto-broadcasts created/updated/deleted/trashed/restored. Use to automatically keep clients in sync with Eloquent model changes without writing individual events. +- Channel convention: `App.Models.Post.{id}` — clients subscribe to model-specific channels. +- `broadcastAs($event)` and `broadcastWith($event)` for per-action customization. Use to send different payloads for create vs update, or suppress certain event types. +- `newBroadcastableEvent($event)` for event instance customization (e.g., `->dontBroadcastToCurrentUser()`). Use when you need to modify the underlying event object before it's dispatched. + +### Client-Side Features + +- Client events: `whisper()` / `listenForWhisper()` — peer-to-peer without server roundtrip (private/presence channels only). Use for typing indicators, cursor positions, or any ephemeral state that doesn't need server persistence. +- Presence channels: `Echo.join()` with `here()`, `joining()`, `leaving()`, `error()` callbacks. Use for showing online users, "X is viewing this document" features, or live participant counts. +- Notification broadcasting: `.notification()` on user's private channel. Use to show real-time notifications (toast, badge counts) pushed from Laravel's notification system. +- Connection management: `Echo.connectionStatus()`, `Echo.leaveAllChannels()`, `Echo.disconnect()`. Use to show connection indicators, clean up on logout, or handle offline/reconnect scenarios. +- Custom namespace: `new Echo({ namespace: 'App.Other.Namespace' })`. Use when your events live outside the default `App\Events` namespace. + +## Common Pitfalls + +- Queue worker must be running for `ShouldBroadcast` events. Use `ShouldBroadcastNow` during development. +- `BROADCAST_CONNECTION` not `BROADCAST_DRIVER`: Laravel 11+ renamed this env key. +- `toOthers()` requires `InteractsWithSockets` trait AND `X-Socket-ID` header. Echo auto-adds this to global Axios. For `fetch`, manually send `Echo.socketId()`. +- CORS: When frontend/backend are on different origins, add `broadcasting/auth` to `config/cors.php` paths and set `supports_credentials` to `true`. +- Missing `VITE_` prefix: Client-side env vars must start with `VITE_`. +- `channels.php` not loaded: Verify it's included in `withRouting()` in `bootstrap/app.php`. +- Reverb is long-running: Code changes require `php artisan reverb:restart`. +- Presence channel auth must return an array of user data (`['id' => $user->id, 'name' => $user->name]`), not `true`. Returning `true` silently fails. +- Dot prefix rule: When using `broadcastAs()`, client must prefix with `.` (e.g., `.listen('.custom.name')`). Without the dot, Echo looks for `App\Events\custom.name` which silently fails. +- Reverb host separation: `REVERB_SERVER_HOST`/`REVERB_SERVER_PORT` (internal bind) vs `REVERB_HOST`/`REVERB_PORT` (public address) vs `VITE_REVERB_HOST`/`VITE_REVERB_PORT` (client JS). +- Sanctum SPA auth: Ensure `/broadcasting/auth` uses `auth:sanctum` middleware and CSRF tokens are sent with `withCredentials: true`. diff --git a/.agents/skills/fortify-development/SKILL.md b/.agents/skills/fortify-development/SKILL.md new file mode 100644 index 0000000..2c4e84f --- /dev/null +++ b/.agents/skills/fortify-development/SKILL.md @@ -0,0 +1,151 @@ +--- +name: fortify-development +description: 'ACTIVATE when the user works on authentication in Laravel. This includes login, registration, password reset, email verification, two-factor authentication (2FA/TOTP/QR codes/recovery codes), passkeys, profile updates, password confirmation, or any auth-related routes and controllers. Activate when the user mentions Fortify, auth, authentication, login, register, signup, forgot password, verify email, 2FA, passkeys, WebAuthn, or references app/Actions/Fortify/, CreateNewUser, UpdateUserProfileInformation, FortifyServiceProvider, config/fortify.php, or auth guards. Fortify is the frontend-agnostic authentication backend for Laravel that registers all auth routes and controllers. Also activate when building SPA or headless authentication, customizing login redirects, overriding response contracts like LoginResponse, or configuring login throttling. Do NOT activate for Laravel Passport (OAuth2 API tokens), Socialite (OAuth social login), or non-auth Laravel features.' +license: MIT +metadata: + author: laravel +--- + +# Laravel Fortify Development + +Fortify is a headless authentication backend that provides authentication routes and controllers for Laravel applications. + +## Documentation + +Use `search-docs` for detailed Laravel Fortify patterns and documentation. + +## Usage + +- **Routes**: Use `list-routes` with `only_vendor: true` and `action: "Fortify"` to see all registered endpoints +- **Actions**: Check `app/Actions/Fortify/` for customizable business logic (user creation, password validation, etc.) +- **Config**: See `config/fortify.php` for all options including features, guards, rate limiters, and username field +- **Contracts**: Look in `Laravel\Fortify\Contracts\` for overridable response classes (`LoginResponse`, `LogoutResponse`, etc.) +- **Views**: All view callbacks are set in `FortifyServiceProvider::boot()` using `Fortify::loginView()`, `Fortify::registerView()`, etc. + +## Available Features + +Enable in `config/fortify.php` features array: + +- `Features::registration()` - User registration +- `Features::resetPasswords()` - Password reset via email +- `Features::emailVerification()` - Requires User to implement `MustVerifyEmail` +- `Features::updateProfileInformation()` - Profile updates +- `Features::updatePasswords()` - Password changes +- `Features::twoFactorAuthentication()` - 2FA with QR codes and recovery codes +- `Features::passkeys()` - Passwordless authentication with WebAuthn passkeys + +> Use `search-docs` for feature configuration options and customization patterns. + +## Setup Workflows + +### Two-Factor Authentication Setup + +``` +- [ ] Add TwoFactorAuthenticatable trait to User model +- [ ] Enable feature in config/fortify.php +- [ ] If the `*_add_two_factor_columns_to_users_table.php` migration is missing, publish via `php artisan vendor:publish --tag=fortify-migrations` and migrate +- [ ] Set up view callbacks in FortifyServiceProvider +- [ ] Create 2FA management UI +- [ ] Test QR code and recovery codes +``` + +> Use `search-docs` for TOTP implementation and recovery code handling patterns. + +### Passkeys Setup + +``` +- [ ] Add PasskeyAuthenticatable trait to User model and implement PasskeyUser +- [ ] Enable passkeys feature in config/fortify.php +- [ ] If the passkeys table migration is missing, publish via `php artisan vendor:publish --tag=fortify-migrations` and migrate +- [ ] Configure passkeys relying_party_id, allowed_origins, user_handle_secret, and timeout if defaults are not suitable +- [ ] Build UI with @laravel/passkeys for registration, login, confirmation, and deletion +``` + +> Use `search-docs` for passkey configuration options. For `@laravel/passkeys` frontend usage, refer to the package's README on npm. + +### Email Verification Setup + +``` +- [ ] Enable emailVerification feature in config +- [ ] Implement MustVerifyEmail interface on User model +- [ ] Set up verifyEmailView callback +- [ ] Add verified middleware to protected routes +- [ ] Test verification email flow +``` + +> Use `search-docs` for MustVerifyEmail implementation patterns. + +### Password Reset Setup + +``` +- [ ] Enable resetPasswords feature in config +- [ ] Set up requestPasswordResetLinkView callback +- [ ] Set up resetPasswordView callback +- [ ] Define password.reset named route (if views disabled) +- [ ] Test reset email and link flow +``` + +> Use `search-docs` for custom password reset flow patterns. + +### SPA Authentication Setup + +``` +- [ ] Set 'views' => false in config/fortify.php +- [ ] Install and configure Laravel Sanctum for session-based SPA authentication +- [ ] Use the 'web' guard in config/fortify.php (required for session-based authentication) +- [ ] Set up CSRF token handling +- [ ] Test XHR authentication flows +``` + +> Use `search-docs` for integration and SPA authentication patterns. + +#### Two-Factor Authentication in SPA Mode + +When `views` is set to `false`, Fortify returns JSON responses instead of redirects. + +If a user attempts to log in and two-factor authentication is enabled, the login request will return a JSON response indicating that a two-factor challenge is required: + +```json +{ + "two_factor": true +} +``` + +## Best Practices + +### Custom Authentication Logic + +Override authentication behavior using `Fortify::authenticateUsing()` for custom user retrieval or `Fortify::authenticateThrough()` to customize the authentication pipeline. Override response contracts in `AppServiceProvider` for custom redirects. + +### Registration Customization + +Modify `app/Actions/Fortify/CreateNewUser.php` to customize user creation logic, validation rules, and additional fields. + +### Rate Limiting + +Configure via `fortify.limiters.login` in config. Default configuration throttles by username + IP combination. + +## Key Endpoints + +| Feature | Method | Endpoint | +|------------------------|----------|---------------------------------------------| +| Login | POST | `/login` | +| Logout | POST | `/logout` | +| Register | POST | `/register` | +| Password Reset Request | POST | `/forgot-password` | +| Password Reset | POST | `/reset-password` | +| Email Verify Notice | GET | `/email/verify` | +| Resend Verification | POST | `/email/verification-notification` | +| Password Confirm | POST | `/user/confirm-password` | +| Enable 2FA | POST | `/user/two-factor-authentication` | +| Confirm 2FA | POST | `/user/confirmed-two-factor-authentication` | +| 2FA Challenge | POST | `/two-factor-challenge` | +| Get QR Code | GET | `/user/two-factor-qr-code` | +| Recovery Codes | GET/POST | `/user/two-factor-recovery-codes` | +| Passkey Login Options | GET | `/passkeys/login/options` | +| Passkey Login | POST | `/passkeys/login` | +| Passkey Confirm Options| GET | `/passkeys/confirm/options` | +| Passkey Confirm | POST | `/passkeys/confirm` | +| Passkey Options | GET | `/user/passkeys/options` | +| Register Passkey | POST | `/user/passkeys` | +| Delete Passkey | DELETE | `/user/passkeys/{passkey}` | diff --git a/.agents/skills/inertia-vue-development/SKILL.md b/.agents/skills/inertia-vue-development/SKILL.md new file mode 100644 index 0000000..2813e8c --- /dev/null +++ b/.agents/skills/inertia-vue-development/SKILL.md @@ -0,0 +1,575 @@ +--- +name: inertia-vue-development +description: "Develops Inertia.js v3 Vue client-side applications. Activates when creating Vue pages, forms, or navigation; using ,