From c73c4fa8b99104fef9f467879ee1acd7357b3853 Mon Sep 17 00:00:00 2001 From: Developer Date: Sun, 7 Dec 2025 17:57:36 -0700 Subject: [PATCH 1/5] feat(auth): add artisan login command for automated dev login Implements php artisan login command that automatically creates a dev user if needed and logs in via Playwright browser automation - Artisan command that creates dev user if missing and opens browser for login - Playwright script handles automated login flow with credentials - Claude slash command for /login available for quick dev access - Useful for rapid development iteration without manual login --- .claude/commands/login.md | 25 +++++++--- app/Console/Commands/LoginCommand.php | 70 +++++++++++++++++++++++++++ scripts/login.cjs | 20 ++++++++ 3 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 app/Console/Commands/LoginCommand.php create mode 100644 scripts/login.cjs diff --git a/.claude/commands/login.md b/.claude/commands/login.md index 06e3399..8a439c6 100644 --- a/.claude/commands/login.md +++ b/.claude/commands/login.md @@ -1,9 +1,22 @@ -# Auto-Login for Local Development +--- +description: Fast login - ensures user exists and logs in via Playwright +--- -Login as the first user using Playwright MCP. +Execute these steps quickly with minimal output: -## Instructions +1. Run the login command to ensure user exists and get credentials: +```bash +php artisan login +``` -1. Navigate Playwright to: `http://chat.test/dev/login` -2. This auto-logs in as the first user (or creates one if none exist) -3. Confirm you're on the dashboard +2. Parse the output for URL, email, password. Navigate to the login URL with mcp__playwright__browser_navigate + +3. Snapshot, fill, submit in rapid succession: +- mcp__playwright__browser_snapshot to get refs +- mcp__playwright__browser_type email field with the email from step 1 +- mcp__playwright__browser_type password field with the password from step 1 +- mcp__playwright__browser_click the login button + +4. Confirm success with one final snapshot + +Report only: "Logged in as {email} - now on [URL]" diff --git a/app/Console/Commands/LoginCommand.php b/app/Console/Commands/LoginCommand.php new file mode 100644 index 0000000..07b7cf3 --- /dev/null +++ b/app/Console/Commands/LoginCommand.php @@ -0,0 +1,70 @@ +option('email'); + + if (! $this->shouldLogin()) { + $this->fixLoginRequirements($email); + } + + $url = config('app.url'); + + $this->info("Logging in as: {$email}"); + + $result = Process::path(base_path()) + ->run(['npx', 'playwright', 'test', '--', 'node', 'scripts/login.cjs', $url, $email, 'password']); + + if (! $result->successful()) { + // Try direct node execution + $result = Process::path(base_path()) + ->run(['node', 'scripts/login.cjs', $url, $email, 'password']); + } + + $this->line($result->output()); + + if ($result->failed()) { + $this->error($result->errorOutput()); + return self::FAILURE; + } + + return self::SUCCESS; + } + + protected function shouldLogin(): bool + { + $email = $this->option('email'); + + if (! User::where('email', $email)->exists()) { + return false; + } + + return true; + } + + protected function fixLoginRequirements(string $email): void + { + if (! User::where('email', $email)->exists()) { + $this->info("Creating user: {$email}"); + + User::create([ + 'name' => 'Dev User', + 'email' => $email, + 'password' => Hash::make('password'), + ]); + } + } +} diff --git a/scripts/login.cjs b/scripts/login.cjs new file mode 100644 index 0000000..6f2e98b --- /dev/null +++ b/scripts/login.cjs @@ -0,0 +1,20 @@ +const { chromium } = require('playwright'); + +const url = process.argv[2] || 'http://chat.test'; +const email = process.argv[3] || 'dev@test.com'; +const password = process.argv[4] || 'password'; + +(async () => { + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage(); + + await page.goto(url + '/login'); + await page.fill('input[type="email"], input[name="email"]', email); + await page.fill('input[type="password"], input[name="password"]', password); + await page.click('button[type="submit"]'); + + await page.waitForURL('**/dashboard**'); + + const finalUrl = page.url(); + console.log('Logged in as ' + email + ' - now on ' + finalUrl); +})(); From 4dc1045a20a97e5ad743c5dad03190a390ad76e2 Mon Sep 17 00:00:00 2001 From: Developer Date: Sun, 7 Dec 2025 17:57:40 -0700 Subject: [PATCH 2/5] chore(config): disable 2FA and set APP_URL to chat.test for development Updates development configuration to streamline login flow and align with local development domain - APP_URL updated from localhost to chat.test for proper domain handling - Two-factor authentication disabled for development convenience --- .env.example | 2 +- config/fortify.php | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 46096e0..adc7cf1 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ APP_NAME=Laravel APP_ENV=local APP_KEY= APP_DEBUG=true -APP_URL=http://localhost +APP_URL=http://chat.test APP_LOCALE=en APP_FALLBACK_LOCALE=en diff --git a/config/fortify.php b/config/fortify.php index ce67e2c..585cd5d 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -147,11 +147,12 @@ Features::registration(), Features::resetPasswords(), Features::emailVerification(), - Features::twoFactorAuthentication([ - 'confirm' => true, - 'confirmPassword' => true, + // Features::twoFactorAuthentication([ + // 'confirm' => true, + // 'confirmPassword' => true, // 'window' => 0 - ]), + // ]), ], ]; + From d87421be53c66d15c66f3bdbe7ddb4c23a29b835 Mon Sep 17 00:00:00 2001 From: Developer Date: Sun, 7 Dec 2025 18:01:37 -0700 Subject: [PATCH 3/5] chore: remove /login slash command (conflicts with built-in) --- .claude/commands/login.md | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .claude/commands/login.md diff --git a/.claude/commands/login.md b/.claude/commands/login.md deleted file mode 100644 index 8a439c6..0000000 --- a/.claude/commands/login.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -description: Fast login - ensures user exists and logs in via Playwright ---- - -Execute these steps quickly with minimal output: - -1. Run the login command to ensure user exists and get credentials: -```bash -php artisan login -``` - -2. Parse the output for URL, email, password. Navigate to the login URL with mcp__playwright__browser_navigate - -3. Snapshot, fill, submit in rapid succession: -- mcp__playwright__browser_snapshot to get refs -- mcp__playwright__browser_type email field with the email from step 1 -- mcp__playwright__browser_type password field with the password from step 1 -- mcp__playwright__browser_click the login button - -4. Confirm success with one final snapshot - -Report only: "Logged in as {email} - now on [URL]" From e71011a70ef65c7060946ae06757d83a219c0c91 Mon Sep 17 00:00:00 2001 From: Jordan Partridge Date: Mon, 8 Dec 2025 20:13:57 -0700 Subject: [PATCH 4/5] fix: restore 2FA feature and fix flaky test - Re-enable 2FA in fortify config (disabling breaks Wayfinder build) - Fix flaky test by specifying different providers for credentials --- config/fortify.php | 9 ++++----- .../Settings/ProviderCredentialControllerTest.php | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/config/fortify.php b/config/fortify.php index 585cd5d..ce67e2c 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -147,12 +147,11 @@ Features::registration(), Features::resetPasswords(), Features::emailVerification(), - // Features::twoFactorAuthentication([ - // 'confirm' => true, - // 'confirmPassword' => true, + Features::twoFactorAuthentication([ + 'confirm' => true, + 'confirmPassword' => true, // 'window' => 0 - // ]), + ]), ], ]; - diff --git a/tests/Feature/Http/Controllers/Settings/ProviderCredentialControllerTest.php b/tests/Feature/Http/Controllers/Settings/ProviderCredentialControllerTest.php index ee178a2..2c0f3f4 100644 --- a/tests/Feature/Http/Controllers/Settings/ProviderCredentialControllerTest.php +++ b/tests/Feature/Http/Controllers/Settings/ProviderCredentialControllerTest.php @@ -549,8 +549,8 @@ it('prevents toggling model when both credential and model belong to different users', function () { $user = User::factory()->create(); $other = User::factory()->create(); - $otherCredential = UserApiCredential::factory()->for($other)->create(); - $anotherCredential = UserApiCredential::factory()->for($other)->create(); + $otherCredential = UserApiCredential::factory()->for($other)->openai()->create(); + $anotherCredential = UserApiCredential::factory()->for($other)->anthropic()->create(); $model = \App\Models\AiModel::factory()->forCredential($anotherCredential)->create(['enabled' => true]); $response = $this->actingAs($user)->patch( From 7c3977dbcee74a82b916a261f61a52480882a938 Mon Sep 17 00:00:00 2001 From: Jordan Partridge Date: Mon, 8 Dec 2025 20:20:51 -0700 Subject: [PATCH 5/5] fix: resolve ESLint errors - Exclude scripts/ from ESLint (CommonJS files) - Remove unused InputError import from Providers.vue --- eslint.config.js | 2 +- resources/js/pages/settings/Providers.vue | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index ea86a49..1edc89d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,7 +7,7 @@ export default defineConfigWithVueTs( vue.configs['flat/essential'], vueTsConfigs.recommended, { - ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js', 'resources/js/components/ui/*'], + ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js', 'resources/js/components/ui/*', 'scripts/*'], }, { rules: { diff --git a/resources/js/pages/settings/Providers.vue b/resources/js/pages/settings/Providers.vue index 2e51382..ea1240a 100644 --- a/resources/js/pages/settings/Providers.vue +++ b/resources/js/pages/settings/Providers.vue @@ -31,7 +31,6 @@ import { SelectValue, } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; -import InputError from '@/components/InputError.vue'; import { Form } from '@inertiajs/vue3'; import { Trash2, Plus, Eye, EyeOff, Loader2, CheckCircle, XCircle } from 'lucide-vue-next';