(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()); } }