diff --git a/app/Http/Controllers/Auth/MagicLinkController.php b/app/Http/Controllers/Auth/MagicLinkController.php index 1caba8c..0ad4f9b 100755 --- a/app/Http/Controllers/Auth/MagicLinkController.php +++ b/app/Http/Controllers/Auth/MagicLinkController.php @@ -51,7 +51,9 @@ class MagicLinkController extends Controller // Queue the magic link email Mail::to($email)->queue(new MagicLoginLink($loginUrl, $token->plain_code, 15)); - return back()->with('status', 'Check your email for a login link and code!'); + return redirect()->route('verify-code') + ->with('status', 'Check your email for your login code!') + ->with('email', $email); } catch (ValidationException $e) { throw $e; } diff --git a/app/Models/MagicLoginToken.php b/app/Models/MagicLoginToken.php index d7af1a2..992a984 100755 --- a/app/Models/MagicLoginToken.php +++ b/app/Models/MagicLoginToken.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; class MagicLoginToken extends Model { @@ -48,13 +48,13 @@ class MagicLoginToken extends Model public static function generate(string $email, ?string $ip = null, ?string $ua = null): self { $emailHash = User::hashEmail($email); - $wordToken = self::generateWordToken(); + $token = Str::random(64); $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT); return self::create([ 'email_hash' => $emailHash, - 'token_hash' => Hash::make($wordToken), - 'plain_token' => $wordToken, + 'token_hash' => Hash::make($token), + 'plain_token' => $token, 'code_hash' => Hash::make($code), 'plain_code' => $code, 'expires_at' => now()->addMinutes(15), @@ -63,27 +63,6 @@ class MagicLoginToken extends Model ]); } - /** - * Generate a unique 4-word token from the word list. - */ - public static function generateWordToken(): string - { - $words = explode("\n", trim(Storage::get('words.txt'))); - - do { - $selectedWords = []; - for ($i = 0; $i < 4; $i++) { - $selectedWords[] = $words[array_rand($words)]; - } - $token = implode('-', $selectedWords); - - // Check for uniqueness in database - $exists = self::where('plain_token', $token)->exists(); - } while ($exists); - - return $token; - } - /** * Check if the token is valid (not expired and not used). */ diff --git a/docker/supervisor/supervisord.conf b/docker/supervisor/supervisord.conf index 9e0a822..f2465ad 100755 --- a/docker/supervisor/supervisord.conf +++ b/docker/supervisor/supervisord.conf @@ -1,9 +1,19 @@ +[unix_http_server] +file=/var/run/supervisor.sock +chmod=0770 + [supervisord] nodaemon=true logfile=/dev/stdout logfile_maxbytes=0 pidfile=/tmp/supervisord.pid +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock + [program:php-fpm] command=/usr/local/sbin/php-fpm -F autostart=true @@ -26,6 +36,8 @@ stderr_logfile_maxbytes=0 command=php /var/www/html/artisan queue:work --sleep=3 --tries=3 --max-time=3600 autostart=true autorestart=true +startsecs=0 +startretries=10 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr diff --git a/resources/views/auth/verify-code.blade.php b/resources/views/auth/verify-code.blade.php index 50c827a..5c76344 100755 --- a/resources/views/auth/verify-code.blade.php +++ b/resources/views/auth/verify-code.blade.php @@ -38,7 +38,7 @@ type="email" id="email" name="email" - value="{{ old('email') }}" + value="{{ old('email', session('email')) }}" required autofocus class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors @error('email') border-red-500 @enderror" diff --git a/start.sh b/start.sh deleted file mode 100755 index 0c363b7..0000000 --- a/start.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash -# Helper script to start Laravel containers with Podman - -# Use flatpak-spawn to run podman commands on the host -PODMAN="flatpak-spawn --host podman" - -echo "Creating network if it doesn't exist..." -$PODMAN network exists laravel || $PODMAN network create laravel - -echo "Building application image..." -$PODMAN build -t laravel-app . - -echo "Starting MySQL database..." -$PODMAN run -d \ - --name laravel-db \ - --network laravel \ - --replace \ - -e MYSQL_DATABASE=laravel \ - -e MYSQL_ROOT_PASSWORD=root \ - -e MYSQL_USER=laravel \ - -e MYSQL_PASSWORD=secret \ - -p 3306:3306 \ - -v laravel-dbdata:/var/lib/mysql \ - docker.io/library/mysql:8.0 - -echo "Starting Redis..." -$PODMAN run -d \ - --name laravel-redis \ - --network laravel \ - --replace \ - -p 6379:6379 \ - docker.io/library/redis:alpine - -echo "Waiting for database to be ready..." -sleep 10 - -echo "Starting Laravel application..." -$PODMAN run -d \ - --name laravel-app \ - --network laravel \ - --replace \ - -v "$(pwd)":/var/www/html:z \ - -p 8080:80 \ - laravel-app - -echo "" -echo "✓ All containers started successfully!" -echo "" -echo "Access your Laravel application at: http://localhost:8080" -echo "" -echo "Useful commands:" -echo " View logs: flatpak-spawn --host podman logs -f laravel-app" -echo " Run artisan: flatpak-spawn --host podman exec laravel-app php artisan [command]" -echo " Run migrations: flatpak-spawn --host podman exec laravel-app php artisan migrate" -echo " Stop containers: ./stop.sh" -echo "" diff --git a/stop.sh b/stop.sh deleted file mode 100755 index bae7bce..0000000 --- a/stop.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -# Helper script to stop Laravel containers - -PODMAN="flatpak-spawn --host podman" - -echo "Stopping containers..." -$PODMAN stop laravel-app laravel-db laravel-redis 2>/dev/null -echo "Removing containers..." -$PODMAN rm laravel-app laravel-db laravel-redis 2>/dev/null -echo "✓ Containers stopped and removed" -echo "" -echo "Note: Database data is preserved in the 'laravel-dbdata' volume" -echo "To remove the volume and delete all data: flatpak-spawn --host podman volume rm laravel-dbdata" diff --git a/tests/Feature/Auth/MagicLinkAuthTest.php b/tests/Feature/Auth/MagicLinkAuthTest.php index b53803d..3c662bf 100755 --- a/tests/Feature/Auth/MagicLinkAuthTest.php +++ b/tests/Feature/Auth/MagicLinkAuthTest.php @@ -40,8 +40,8 @@ class MagicLinkAuthTest extends TestCase 'email' => 'test@example.com', ]); - $response->assertRedirect(); - $response->assertSessionHas('status', 'Check your email for a login link and code!'); + $response->assertRedirect(route('verify-code')); + $response->assertSessionHas('status', 'Check your email for your login code!'); Mail::assertQueued(MagicLoginLink::class, function ($mail) { return $mail->hasTo('test@example.com'); @@ -71,7 +71,7 @@ class MagicLinkAuthTest extends TestCase ]); } - public function test_valid_4_word_token_logs_user_in(): void + public function test_valid_token_logs_user_in(): void { $user = User::create([ 'email_hash' => User::hashEmail('test@example.com'), diff --git a/tests/Unit/MagicLoginTokenTest.php b/tests/Unit/MagicLoginTokenTest.php index 794b9c8..7b591ab 100755 --- a/tests/Unit/MagicLoginTokenTest.php +++ b/tests/Unit/MagicLoginTokenTest.php @@ -6,51 +6,13 @@ use App\Models\MagicLoginToken; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; use Tests\TestCase; class MagicLoginTokenTest extends TestCase { use RefreshDatabase; - public function test_word_token_generation_creates_exactly_4_words(): void - { - $token = MagicLoginToken::generateWordToken(); - - $words = explode('-', $token); - - $this->assertCount(4, $words, 'Token should contain exactly 4 words'); - } - - public function test_word_token_is_hyphen_separated(): void - { - $token = MagicLoginToken::generateWordToken(); - - $this->assertMatchesRegularExpression( - '/^[a-z]+-[a-z]+-[a-z]+-[a-z]+$/', - $token, - 'Token should match pattern: word-word-word-word' - ); - } - - public function test_words_are_from_word_list_file(): void - { - $wordList = explode("\n", trim(Storage::get('words.txt'))); - $wordList = array_map('trim', $wordList); - $wordList = array_filter($wordList); - - $token = MagicLoginToken::generateWordToken(); - $words = explode('-', $token); - - foreach ($words as $word) { - $this->assertContains( - $word, - $wordList, - "Word '{$word}' should be from the word list file" - ); - } - } - public function test_token_expiry_validation_works(): void { $token = MagicLoginToken::generate('test@example.com', '127.0.0.1', 'TestAgent'); @@ -125,7 +87,7 @@ class MagicLoginTokenTest extends TestCase ); $this->assertFalse( - $token->verifyToken('wrong-token-here-bad'), + $token->verifyToken(Str::random(64)), 'Incorrect token should not verify' ); } @@ -270,7 +232,7 @@ class MagicLoginTokenTest extends TestCase $this->assertEquals($token1->email_hash, $token2->email_hash); } - public function test_word_tokens_are_unique(): void + public function test_tokens_are_unique(): void { $generatedTokens = []; @@ -301,24 +263,4 @@ class MagicLoginTokenTest extends TestCase $this->assertInstanceOf(\Illuminate\Support\Carbon::class, $token->used_at); } - public function test_4_word_token_format_validation(): void - { - $token = MagicLoginToken::generate('test@example.com', '127.0.0.1', 'TestAgent'); - - $plainToken = $token->plain_token; - - $this->assertMatchesRegularExpression( - '/^[a-z]+-[a-z]+-[a-z]+-[a-z]+$/', - $plainToken, - 'Token should be 4 words separated by hyphens' - ); - - $words = explode('-', $plainToken); - $this->assertCount(4, $words, 'Token should contain exactly 4 words'); - - foreach ($words as $word) { - $this->assertNotEmpty($word, 'Each word should not be empty'); - $this->assertMatchesRegularExpression('/^[a-z]+$/', $word, 'Each word should contain only lowercase letters'); - } - } }