test borked

This commit is contained in:
Dan Baker 2026-02-22 17:49:23 +00:00
parent 49b528a66b
commit 2418edccfd
29 changed files with 2036 additions and 121 deletions

View file

@ -0,0 +1,117 @@
<?php
namespace App\Services;
use App\Models\MagicLoginToken;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
class MagicLinkAuthService
{
/**
* Send a magic link to the given email address.
*
* @throws ValidationException
*/
public function sendMagicLink(string $email, ?string $ip, ?string $ua): MagicLoginToken
{
$key = 'magic-link:' . hash('sha256', strtolower(trim($email)));
if (RateLimiter::tooManyAttempts($key, 5)) {
$seconds = RateLimiter::availableIn($key);
throw ValidationException::withMessages([
'email' => [
"Too many magic link requests. Please try again in {$seconds} seconds.",
],
]);
}
RateLimiter::hit($key, 3600);
$user = User::findByEmail($email);
if (!$user) {
$user = User::create([
'email_hash' => User::hashEmail($email),
]);
}
return MagicLoginToken::generate($email, $ip, $ua);
}
/**
* Verify a magic link token and log the user in.
*/
public function verifyMagicLink(string $token): bool
{
$magicToken = MagicLoginToken::valid()
->where('plain_token', $token)
->first();
if (!$magicToken) {
return false;
}
$user = User::where('email_hash', $magicToken->email_hash)->first();
if (!$user) {
return false;
}
$magicToken->markAsUsed();
Auth::login($user, true);
return true;
}
/**
* Verify a magic code and log the user in.
*/
public function verifyCode(string $email, string $code): bool
{
$emailHash = User::hashEmail($email);
$key = 'magic-code:' . $emailHash;
if (RateLimiter::tooManyAttempts($key, 5)) {
return false;
}
$magicToken = MagicLoginToken::valid()
->where('email_hash', $emailHash)
->latest()
->first();
if (!$magicToken || !$magicToken->verifyCode($code)) {
RateLimiter::hit($key, 300);
return false;
}
$user = User::where('email_hash', $emailHash)->first();
if (!$user) {
return false;
}
$magicToken->markAsUsed();
RateLimiter::clear($key);
Auth::login($user, true);
return true;
}
/**
* Clean up expired and used tokens older than 7 days.
*/
public function cleanupExpiredTokens(): int
{
return MagicLoginToken::expiredOrUsed()
->where('created_at', '<', now()->subDays(7))
->delete();
}
}