Files
SkinbaseNova/app/Http/Controllers/Auth/RegisteredUserController.php

179 lines
5.7 KiB
PHP

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Mail\RegistrationVerificationMail;
use App\Models\User;
use App\Services\Security\RecaptchaVerifier;
use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Illuminate\View\View;
class RegisteredUserController extends Controller
{
public function __construct(
private readonly RecaptchaVerifier $recaptchaVerifier
)
{
}
/**
* Display the registration view.
*/
public function create(Request $request): View
{
return view('auth.register', [
'prefillEmail' => (string) $request->query('email', ''),
]);
}
public function notice(Request $request): View
{
$email = (string) session('registration_email', '');
$remaining = $email === '' ? 0 : $this->resendRemainingSeconds($email);
return view('auth.register-notice', [
'email' => $email,
'resendSeconds' => $remaining,
]);
}
/**
* Handle an incoming registration request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'website' => ['nullable', 'max:0'],
]);
if ($this->recaptchaVerifier->isEnabled()) {
$request->validate([
'g-recaptcha-response' => ['required', 'string'],
]);
$verified = $this->recaptchaVerifier->verify(
(string) $request->input('g-recaptcha-response', ''),
$request->ip()
);
if (! $verified) {
return back()
->withInput($request->except('website'))
->withErrors(['captcha' => 'reCAPTCHA verification failed. Please try again.']);
}
}
$user = User::create([
'username' => null,
'name' => Str::before((string) $validated['email'], '@'),
'email' => $validated['email'],
'password' => Hash::make(Str::random(64)),
'is_active' => false,
'onboarding_step' => 'email',
'username_changed_at' => now(),
]);
$token = Str::random(64);
DB::table('user_verification_tokens')->insert([
'user_id' => $user->id,
'token' => $token,
'expires_at' => now()->addDay(),
'created_at' => now(),
'updated_at' => now(),
]);
Mail::to($user->email)->queue(new RegistrationVerificationMail($token));
$cooldown = $this->resendCooldownSeconds();
$this->setResendCooldown((string) $validated['email'], $cooldown);
return redirect(route('register.notice', absolute: false))
->with('status', 'Verification email sent. Please check your inbox.')
->with('registration_email', (string) $validated['email']);
}
public function resendVerification(Request $request): RedirectResponse
{
$validated = $request->validate([
'email' => ['required', 'string', 'lowercase', 'email', 'max:255'],
]);
$email = (string) $validated['email'];
$remaining = $this->resendRemainingSeconds($email);
if ($remaining > 0) {
return back()
->with('registration_email', $email)
->withErrors(['email' => "Please wait {$remaining} seconds before resending."]);
}
$user = User::query()
->where('email', $email)
->whereNull('email_verified_at')
->where('onboarding_step', 'email')
->first();
if (! $user) {
return back()
->with('registration_email', $email)
->withErrors(['email' => 'No pending verification found for this email.']);
}
DB::table('user_verification_tokens')->where('user_id', $user->id)->delete();
$token = Str::random(64);
DB::table('user_verification_tokens')->insert([
'user_id' => $user->id,
'token' => $token,
'expires_at' => now()->addDay(),
'created_at' => now(),
'updated_at' => now(),
]);
Mail::to($user->email)->queue(new RegistrationVerificationMail($token));
$cooldown = $this->resendCooldownSeconds();
$this->setResendCooldown($email, $cooldown);
return redirect(route('register.notice', absolute: false))
->with('registration_email', $email)
->with('status', 'Verification email resent. Please check your inbox.');
}
private function resendCooldownSeconds(): int
{
return max(5, (int) config('antispam.register.resend_cooldown_seconds', 60));
}
private function resendCooldownCacheKey(string $email): string
{
return 'register:resend:cooldown:' . sha1(strtolower(trim($email)));
}
private function setResendCooldown(string $email, int $seconds): void
{
$until = CarbonImmutable::now()->addSeconds($seconds)->timestamp;
Cache::put($this->resendCooldownCacheKey($email), $until, $seconds + 5);
}
private function resendRemainingSeconds(string $email): int
{
$until = (int) Cache::get($this->resendCooldownCacheKey($email), 0);
if ($until <= 0) {
return 0;
}
return max(0, $until - time());
}
}