Skip to content
This repository has been archived by the owner on Oct 1, 2021. It is now read-only.

Commit

Permalink
feat: throttle verification mail resending in UI (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
leMaur authored Apr 21, 2021
1 parent a6508ff commit 950bb29
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 44 deletions.
47 changes: 24 additions & 23 deletions resources/lang/en/actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,28 @@
declare(strict_types=1);

return [
'disable' => 'Disable',
'enable' => 'Enable',
'export_personal_data' => 'Export Personal Data',
'understand' => 'I Understand',
'update' => 'Update',
'select_timezone' => 'Select Timezone',
'sign_in' => 'Sign In',
'sign_up' => 'Sign Up',
'reset_password' => 'Reset Password',
'enter_recovery_code' => 'Enter recovery code',
'enter_2fa_code' => 'Enter 2FA code',
'verify' => 'Verify',
'delete_account' => 'Delete Account',
'nevermind' => 'Nevermind',
'done' => 'Done',
'confirm_logout' => 'Logout Other Browser Sessions',
'cancel' => 'Cancel',
'delete' => 'Delete',
'send' => 'Send',
'skip' => 'Skip',
'download' => 'Download',
'recovery_codes' => 'Recovery Codes',
'confirm' => 'Confirm',
'disable' => 'Disable',
'enable' => 'Enable',
'export_personal_data' => 'Export Personal Data',
'understand' => 'I Understand',
'update' => 'Update',
'select_timezone' => 'Select Timezone',
'sign_in' => 'Sign In',
'sign_up' => 'Sign Up',
'reset_password' => 'Reset Password',
'enter_recovery_code' => 'Enter recovery code',
'enter_2fa_code' => 'Enter 2FA code',
'verify' => 'Verify',
'delete_account' => 'Delete Account',
'nevermind' => 'Nevermind',
'done' => 'Done',
'confirm_logout' => 'Logout Other Browser Sessions',
'cancel' => 'Cancel',
'delete' => 'Delete',
'send' => 'Send',
'skip' => 'Skip',
'download' => 'Download',
'recovery_codes' => 'Recovery Codes',
'confirm' => 'Confirm',
'resend_email_verification' => 'click here to request another',
];
3 changes: 2 additions & 1 deletion resources/lang/en/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
'verify' => [
'page_header' => 'Verify Your Email Address',
'link_description' => 'A verification link has been sent to your email address.',
'resend_verification' => '<span>Before proceeding, please check your email for a verification link.</span> <span>If you did not receive the email, <button type="submit" class="link">click here to request another</button>.</span>',
'line_1' => 'Before proceeding, please check your email for a verification link.',
'line_2' => 'If you did not receive the email,',
],
];
2 changes: 1 addition & 1 deletion resources/lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
return [
'invalid_2fa_authentication_code' => 'The provided two factor authentication code was invalid',
'export_limit_reached' => 'You can export your personal data once every 15 minutes.',
'invalid_2fa_authentication_code' => 'The provided two factor authentication code was invalid',
'subscription' => [
'duplicate' => "You're already subscribed.",
'pending' => 'You should shortly receive an email to confirm your subscription. You may need to check your spam folder if an email doesn’t arrive within a few minutes.',
],
'resend_email_verification_limit' => 'Please wait 5 minutes before requesting another e-mail. Ensure to check your spam folder.',
];
21 changes: 2 additions & 19 deletions resources/views/auth/verify-email.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,6 @@
@endsection

@section('content')
<div class="flex max-w-xl p-8 mx-auto my-6 bg-white rounded-xl">
<div class="flex flex-col w-full text-center space-y-6">
<div class="space-y-4">
<h1>@lang('fortify::auth.verify.page_header')</h1>

<p>@lang('fortify::auth.verify.link_description')</p>
</div>

<img class="mb-5 mx-12" src="/images/auth/verify-email.svg" />

<form method="POST" action="{{ route('verification.send') }}">
@csrf

<p class="text-sm text-theme-secondary-600 lg:no-wrap-span-children">
@lang('fortify::auth.verify.resend_verification')
</p>
</form>
</div>
</div>
<livewire:auth.verify-email />
@endsection

28 changes: 28 additions & 0 deletions resources/views/components/auth-verify-email.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<div class="flex max-w-xl p-8 mx-auto my-6 bg-white rounded-lg">
<div class="flex flex-col w-full text-center space-y-6">
<div class="space-y-4">
<h1>@lang('fortify::auth.verify.page_header')</h1>

<p>@lang('fortify::auth.verify.link_description')</p>
</div>

<img class="mb-5 mx-12" src="/images/auth/verify-email.svg" />

<form wire:click.prevent="resend" wire:poll>
<p class="text-sm text-theme-secondary-600 lg:no-wrap-span-children">
<span>@lang('fortify::auth.verify.line_1')</span>
<span>@lang('fortify::auth.verify.line_2')</span>

@if($this->rateLimitReached())
<span class="link" data-tippy-content="@lang('fortify::messages.resend_email_verification_limit')">
@lang('fortify::actions.resend_email_verification')
</span>
@else
<button wire:loading.attr="disabled" type="submit" class="link">
@lang('fortify::actions.resend_email_verification')
</button>
@endif
</p>
</form>
</div>
</div>
42 changes: 42 additions & 0 deletions src/Components/VerifyEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace ARKEcosystem\Fortify\Components;

use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\View\View;
use Laravel\Fortify\Http\Controllers\EmailVerificationNotificationController;
use Livewire\Component;

class VerifyEmail extends Component
{
use WithRateLimiting;

private const MAX_ATTEMPTS = 1;

private const DECAY_SECONDS = 5 * 60;

public function render(): View
{
return view('ark-fortify::components.auth-verify-email');
}

public function resend(): void
{
try {
$this->rateLimit(self::MAX_ATTEMPTS, self::DECAY_SECONDS);
} catch (TooManyRequestsException $e) {
return;
}

(new EmailVerificationNotificationController())->store(request());
}

public function rateLimitReached(): bool
{
return RateLimiter::tooManyAttempts($this->getRateLimitKey('resend'), self::MAX_ATTEMPTS);
}
}
2 changes: 2 additions & 0 deletions src/FortifyServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use ARKEcosystem\Fortify\Components\UpdateProfileInformationForm;
use ARKEcosystem\Fortify\Components\UpdateProfilePhotoForm;
use ARKEcosystem\Fortify\Components\UpdateTimezoneForm;
use ARKEcosystem\Fortify\Components\VerifyEmail;
use ARKEcosystem\Fortify\Http\Responses\FailedPasswordResetLinkRequestResponse as FortifyFailedPasswordResetLinkRequestResponse;
use ARKEcosystem\Fortify\Http\Responses\SuccessfulPasswordResetLinkRequestResponse as FortifySuccessfulPasswordResetLinkRequestResponse;
use ARKEcosystem\Fortify\Responses\FailedTwoFactorLoginResponse;
Expand Down Expand Up @@ -130,6 +131,7 @@ public function registerLivewireComponents(): void
Livewire::component('auth.register-form', RegisterForm::class);
Livewire::component('auth.reset-password-form', ResetPasswordForm::class);
Livewire::component('newsletter.footer-subscription-form', FooterEmailSubscriptionForm::class);
Livewire::component('auth.verify-email', VerifyEmail::class);
}

/**
Expand Down
27 changes: 27 additions & 0 deletions tests/Components/VerifyEmailTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

use ARKEcosystem\Fortify\Components\VerifyEmail;
use Livewire\Livewire;

use function Tests\createUserModel;

it('can resend a verification email', function (): void {
Livewire::actingAs(createUserModel())
->test(VerifyEmail::class)
->call('resend')
->assertDontSee(trans('fortify::messages.resend_email_verification_limit'));
});

it('can resend a verification email once every 5 minutes', function (): void {
$component = Livewire::actingAs(createUserModel())
->test(VerifyEmail::class)
->call('resend')
->call('resend')
->assertSee(trans('fortify::messages.resend_email_verification_limit'));

$this->travel(6)->minutes();

$component->call('$refresh')->assertDontSee(trans('fortify::messages.resend_email_verification_limit'));
});

0 comments on commit 950bb29

Please sign in to comment.