Removing 4 word bollocks and fixing rootless supervisor for podman
This commit is contained in:
parent
a22db4ee0f
commit
82ed2e3ce2
8 changed files with 26 additions and 160 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
56
start.sh
56
start.sh
|
|
@ -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 ""
|
||||
13
stop.sh
13
stop.sh
|
|
@ -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"
|
||||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue