From 981b22501c5f9df996e6d93e73f4e08b221bc18f Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sat, 5 Aug 2023 23:19:14 +0200 Subject: [PATCH 01/12] WIP --- .../bladebones/stubs/defaults/routes/web.stub | 4 + ...countRecoveryChallengeController.bladetmpl | 13 +- packages/core/lang/en/auth.php | 1 + .../AccountRecoveryChallengeController.php | 114 +++++++------ .../SubmitAccountRecoveryChallengeTests.php | 151 ++++++++++-------- .../ViewAccountRecoveryChallengePageTests.php | 30 ++-- 6 files changed, 173 insertions(+), 140 deletions(-) diff --git a/packages/bladebones/stubs/defaults/routes/web.stub b/packages/bladebones/stubs/defaults/routes/web.stub index 5599790..4b03bb5 100644 --- a/packages/bladebones/stubs/defaults/routes/web.stub +++ b/packages/bladebones/stubs/defaults/routes/web.stub @@ -46,6 +46,10 @@ Route::middleware('guest')->group(function () { Route::get('/auth/recover-account/{token}', [AccountRecoveryChallengeController::class, 'create'])->name('recover-account.challenge'); Route::post('/auth/recover-account/{token}', [AccountRecoveryChallengeController::class, 'store']); + Route::middleware([])->group(function () { + Route::get('/auth/reset', ['foo', 'bar'])->name('recover-account.reset'); + }); + Route::get('/auth/register', [RegisterController::class, 'create'])->name('register'); Route::post('/auth/register', [RegisterController::class, 'store']); Route::delete('/auth/register', [RegisterController::class, 'destroy']); diff --git a/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl b/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl index 2598b83..8b549ff 100644 --- a/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl +++ b/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl @@ -13,9 +13,9 @@ class AccountRecoveryChallengeController extends BaseController /** * Handle an incoming request to view the account recovery challenge page. * - * {!! '@' !!}see static::sendAccountRecoveredResponse() * {!! '@' !!}see static::sendChallengePageResponse() * {!! '@' !!}see static::sendInvalidRecoveryLinkResponse() + * {!! '@' !!}see static::sendRecoveryModeEnabledResponse() */ public function create(Request $request, string $token): RedirectResponse|View { @@ -52,7 +52,7 @@ class AccountRecoveryChallengeController extends BaseController */ protected function sendInvalidRecoveryLinkResponse(Request $request): void { - abort(403, 'The given email and recovery token combination are invalid.'); + abort(403, __('laravel-auth::auth.recovery.invalid')); } /** @@ -68,14 +68,11 @@ class AccountRecoveryChallengeController extends BaseController } /** - * Sends a response indicating that the user's account has been recovered. - * - * Typically, you'd want this response to redirect the user to their account's security settings page, - * where they can adjust whatever is causing them to be unable to authenticate using normal means. + * Sends a response indicating that recovery mode has been enabled for the given user. */ - protected function sendAccountRecoveredResponse(Request $request, Authenticatable $user): RedirectResponse + protected function sendRecoveryModeEnabledResponse(Request $request, Authenticatable $user): RedirectResponse { - return redirect()->route('auth.settings'); + return redirect()->route('recover-account.reset'); } /** diff --git a/packages/core/lang/en/auth.php b/packages/core/lang/en/auth.php index 6ff78b6..e5b2959 100644 --- a/packages/core/lang/en/auth.php +++ b/packages/core/lang/en/auth.php @@ -33,6 +33,7 @@ ], 'recovery' => [ 'sent' => 'If the provided email address is associated with an account, you will receive a recovery link shortly.', + 'invalid' => 'The provided email and recovery token combination are invalid.', 'throttle' => 'Too many recovery requests. Please try again in :seconds seconds.', ], 'verification' => [ diff --git a/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php b/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php index 59a0d59..f2aadab 100644 --- a/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php +++ b/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php @@ -6,21 +6,21 @@ use ClaudioDekker\LaravelAuth\Events\AccountRecoveryFailed; use ClaudioDekker\LaravelAuth\Events\Mixins\EmitsLockoutEvent; use ClaudioDekker\LaravelAuth\Http\Concerns\InteractsWithRateLimiting; -use ClaudioDekker\LaravelAuth\Http\Mixins\EnablesSudoMode; use ClaudioDekker\LaravelAuth\LaravelAuth; use ClaudioDekker\LaravelAuth\RecoveryCodeManager; +use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Password; +use Illuminate\Support\Str; use Illuminate\Support\Timebox; abstract class AccountRecoveryChallengeController { use EmitsLockoutEvent; - use EnablesSudoMode; use InteractsWithRateLimiting; /** @@ -45,26 +45,25 @@ abstract protected function sendInvalidRecoveryLinkResponse(Request $request); abstract protected function sendInvalidRecoveryCodeResponse(Request $request); /** - * Sends a response indicating that the user's account has been recovered. - * - * Typically, you'd want this response to redirect the user to their account's security settings page, - * where they can adjust whatever is causing them to be unable to authenticate using normal means. + * Sends a response indicating that recovery mode has been enabled for the given user. * * @return mixed */ - abstract protected function sendAccountRecoveredResponse(Request $request, Authenticatable $user); + abstract protected function sendRecoveryModeEnabledResponse(Request $request, Authenticatable $user); /** * Handle an incoming request to view the account recovery challenge page. * - * @see static::sendAccountRecoveredResponse() + * @return mixed + * * @see static::sendChallengePageResponse() * @see static::sendInvalidRecoveryLinkResponse() - * - * @return mixed + * @see static::sendRecoveryModeEnabledResponse() */ public function create(Request $request, string $token) { + $this->incrementRateLimitingCounter($request); + return App::make(Timebox::class)->call(function (Timebox $timebox) use ($request, $token) { if (! $user = $this->resolveUser($request)) { return $this->sendInvalidRecoveryLinkResponse($request); @@ -78,8 +77,10 @@ public function create(Request $request, string $token) if (! $this->hasRecoveryCodes($request, $user)) { $this->invalidateRecoveryLink($request, $user); + $this->enableRecoveryMode($request, $user); + $this->resetRateLimitingCounter($request); - return $this->handleAccountRecoveredResponse($request, $user); + return $this->sendRecoveryModeEnabledResponse($request, $user); } return $this->sendChallengePageResponse($request, $token); @@ -89,12 +90,12 @@ public function create(Request $request, string $token) /** * Handle an incoming account recovery challenge response. * - * @see static::sendRateLimitedResponse() - * @see static::sendAccountRecoveredResponse() + * @return mixed + * + * @see static::sendRecoveryModeEnabledResponse() * @see static::sendInvalidRecoveryCodeResponse() * @see static::sendInvalidRecoveryLinkResponse() - * - * @return mixed + * @see static::sendRateLimitedResponse() */ public function store(Request $request, string $token) { @@ -116,11 +117,12 @@ public function store(Request $request, string $token) } if (! $this->hasRecoveryCodes($request, $user)) { - $this->resetRateLimitingCounter($request); $this->invalidateRecoveryLink($request, $user); + $this->enableRecoveryMode($request, $user); + $this->resetRateLimitingCounter($request); $timebox->returnEarly(); - return $this->handleAccountRecoveredResponse($request, $user); + return $this->sendRecoveryModeEnabledResponse($request, $user); } if (! $this->hasValidRecoveryCode($request, $user)) { @@ -129,12 +131,13 @@ public function store(Request $request, string $token) return $this->sendInvalidRecoveryCodeResponse($request); } - $this->resetRateLimitingCounter($request); $this->invalidateRecoveryCode($request, $user); $this->invalidateRecoveryLink($request, $user); + $this->enableRecoveryMode($request, $user); + $this->resetRateLimitingCounter($request); $timebox->returnEarly(); - return $this->handleAccountRecoveredResponse($request, $user); + return $this->sendRecoveryModeEnabledResponse($request, $user); }, 300 * 1000); } @@ -159,28 +162,15 @@ protected function resolveUser(Request $request): ?Authenticatable ->first(); } - /** - * Handles the situation where the user has successfully recovered their account. - * - * @return mixed - */ - protected function handleAccountRecoveredResponse(Request $request, Authenticatable $user) - { - $this->authenticate($request, $user); - $this->enableSudoMode($request); - $this->emitAccountRecoveredEvent($request, $user); - - return $this->sendAccountRecoveredResponse($request, $user); - } - - /** - * Authenticate the user into the application. - */ - protected function authenticate(Request $request, Authenticatable $user): void - { - Auth::login($user); - } - + // + // /** + // * Authenticate the user into the application. + // */ + // protected function authenticate(Request $request, Authenticatable $user): void + // { + // Auth::login($user); + // } + // /** * Determine whether the user has recovery codes. */ @@ -216,14 +206,14 @@ protected function invalidateRecoveryLink(Request $request, Authenticatable $use { Password::getRepository()->delete($user); } - - /** - * Emits an event indicating that the user's account has been recovered. - */ - protected function emitAccountRecoveredEvent(Request $request, Authenticatable $user): void - { - Event::dispatch(new AccountRecovered($request, $user)); - } + // + // /** + // * Emits an event indicating that the user's account has been recovered. + // */ + // protected function emitAccountRecoveredEvent(Request $request, Authenticatable $user): void + // { + // Event::dispatch(new AccountRecovered($request, $user)); + // } /** * Emits an event indicating that an account recovery attempt has failed. @@ -237,4 +227,30 @@ protected function emitAccountRecoveryFailedEvent(Request $request, Authenticata { Event::dispatch(new AccountRecoveryFailed($request, $user)); } + + /** + * Enables recovery mode for the given user. + */ + protected function enableRecoveryMode(Request $request, Authenticatable $user): void + { + $request->session()->put('auth.recovery_mode.user_id', $user->getAuthIdentifier()); + $request->session()->put('auth.recovery_mode.enabled_at', now()); + } + + /** + * Determine the rate limits that apply to the request. + */ + protected function rateLimits(Request $request): array + { + $limits = [ + Limit::perMinute(250), + Limit::perMinute(5)->by('ip::'.$request->ip()), + ]; + + if ($request->has('email')) { + $limits[] = Limit::perMinute(5)->by('email::'.Str::transliterate(Str::lower($request->input('email')))); + } + + return $limits; + } } diff --git a/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitAccountRecoveryChallengeTests.php b/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitAccountRecoveryChallengeTests.php index 7eb14ab..58b0884 100644 --- a/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitAccountRecoveryChallengeTests.php +++ b/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitAccountRecoveryChallengeTests.php @@ -3,10 +3,7 @@ namespace ClaudioDekker\LaravelAuth\Testing\Partials\Challenges\Recovery; use App\Providers\RouteServiceProvider; -use ClaudioDekker\LaravelAuth\Events\AccountRecovered; use ClaudioDekker\LaravelAuth\Events\AccountRecoveryFailed; -use ClaudioDekker\LaravelAuth\Events\SudoModeEnabled; -use ClaudioDekker\LaravelAuth\Http\Middleware\EnsureSudoMode; use Illuminate\Auth\Events\Lockout; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Event; @@ -17,10 +14,9 @@ trait SubmitAccountRecoveryChallengeTests { /** @test */ - public function the_user_account_can_be_recovered(): void + public function the_user_can_begin_to_recover_the_account(): void { - Carbon::setTestNow(now()); - Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); + Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); $repository = Password::getRepository(); $token = $repository->create($user); @@ -28,29 +24,25 @@ public function the_user_account_can_be_recovered(): void $this->expectTimeboxWithEarlyReturn(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $user->getEmailForPasswordReset(), + 'email' => $email = $user->getEmailForPasswordReset(), 'code' => 'PIPIM-7LTUT', ]); - $response->assertRedirect(route('auth.settings')); - $response->assertSessionMissing(EnsureSudoMode::REQUIRED_AT_KEY); - $response->assertSessionHas(EnsureSudoMode::CONFIRMED_AT_KEY, now()->unix()); - $this->assertFullyAuthenticatedAs($response, $user); + $this->assertGuest(); + $response->assertRedirect(route('recover-account.reset')); + $response->assertSessionHas('auth.recovery_mode.user_id', $user->id); $this->assertFalse($repository->exists($user, $token)); $this->assertSame(['H4PFK-ENVZV', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P'], $user->fresh()->recovery_codes); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); - Event::assertDispatched(AccountRecovered::class, fn ($event) => $event->user->is($user) && $event->request === request()); - Event::assertNotDispatched(AccountRecoveryFailed::class); - Event::assertNotDispatched(SudoModeEnabled::class); - Carbon::setTestNow(); + $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); + Event::assertNothingDispatched(); } /** @test */ public function the_account_recovery_challenge_code_verification_request_accepts_any_code_when_the_users_recovery_codes_have_been_cleared(): void { - Carbon::setTestNow(now()); - Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); + Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => null]); $repository = Password::getRepository(); $token = $repository->create($user); @@ -58,27 +50,23 @@ public function the_account_recovery_challenge_code_verification_request_accepts $this->expectTimeboxWithEarlyReturn(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $user->getEmailForPasswordReset(), + 'email' => $email = $user->getEmailForPasswordReset(), 'code' => 'INVLD-CODES', ]); - $response->assertRedirect(route('auth.settings')); - $response->assertSessionMissing(EnsureSudoMode::REQUIRED_AT_KEY); - $response->assertSessionHas(EnsureSudoMode::CONFIRMED_AT_KEY, now()->unix()); - $this->assertFullyAuthenticatedAs($response, $user); + $this->assertGuest(); + $response->assertRedirect(route('recover-account.reset')); + $response->assertSessionHas('auth.recovery_mode.user_id', $user->id); $this->assertFalse($repository->exists($user, $token)); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); - Event::assertDispatched(AccountRecovered::class, fn ($event) => $event->user->is($user) && $event->request === request()); - Event::assertNotDispatched(AccountRecoveryFailed::class); - Event::assertNotDispatched(SudoModeEnabled::class); - Carbon::setTestNow(); + $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); + Event::assertNothingDispatched(); } /** @test */ - public function the_user_account_cannot_be_recovered_when_authenticated(): void + public function the_user_cannot_begin_to_recover_the_account_when_already_authenticated(): void { - Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); $userA = $this->generateUser(['id' => 1, 'email' => 'claudio@ubient.net', 'recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); $userB = $this->generateUser(['id' => 2, 'email' => 'another@example.com', 'recovery_codes' => $codes, $this->usernameField() => $this->anotherUsername()]); $repository = Password::getRepository(); @@ -91,44 +79,44 @@ public function the_user_account_cannot_be_recovered_when_authenticated(): void ]); $response->assertRedirect(RouteServiceProvider::HOME); - $response->assertSessionMissing(EnsureSudoMode::REQUIRED_AT_KEY); - $response->assertSessionMissing(EnsureSudoMode::CONFIRMED_AT_KEY); + $response->assertSessionMissing('auth.recovery_mode.user_id'); + $this->assertAuthenticatedAs($userA); $this->assertTrue($repository->exists($userB, $token)); $this->assertSame($codes, $userB->fresh()->recovery_codes); - Event::assertNothingDispatched(); } /** @test */ - public function the_user_account_cannot_be_recovered_when_the_provided_email_does_not_resolve_to_an_existing_user(): void + public function the_user_cannot_begin_to_recover_the_account_when_the_provided_email_does_not_resolve_to_an_existing_user(): void { - Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); + Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); $repository = Password::getRepository(); $token = $repository->create($user); + $this->assertTrue($repository->exists($user, $token)); $this->expectTimebox(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => 'nonexistent-user@example.com', + 'email' => $email = 'nonexistent-user@example.com', 'code' => 'PIPIM-7LTUT', ]); + $this->assertGuest(); $response->assertForbidden(); + $response->assertSessionMissing('auth.recovery_mode.user_id'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame('The given email and recovery token combination are invalid.', $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); $this->assertTrue($repository->exists($user, $token)); $this->assertSame($codes, $user->fresh()->recovery_codes); - $this->assertGuest(); - $response->assertSessionMissing(EnsureSudoMode::REQUIRED_AT_KEY); - $response->assertSessionMissing(EnsureSudoMode::CONFIRMED_AT_KEY); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); + $this->assertSame(1, $this->getRateLimitAttempts('email::'.$email)); Event::assertNothingDispatched(); } /** @test */ - public function the_user_account_cannot_be_recovered_when_the_recovery_token_does_not_belong_to_the_user_that_is_being_recovered(): void + public function the_user_cannot_begin_to_recover_the_account_when_the_recovery_token_does_not_belong_to_the_user_that_is_being_recovered(): void { - Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); + Event::fake([Lockout::class, AccountRecoveryFailed::class]); $userA = $this->generateUser(['id' => 1, 'email' => 'claudio@ubient.net', 'recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); $userB = $this->generateUser(['id' => 2, 'email' => 'another@example.com', 'recovery_codes' => $codes, $this->usernameField() => $this->anotherUsername()]); $repository = Password::getRepository(); @@ -136,54 +124,51 @@ public function the_user_account_cannot_be_recovered_when_the_recovery_token_doe $this->expectTimebox(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $userB->getEmailForPasswordReset(), + 'email' => $email = $userB->getEmailForPasswordReset(), 'code' => 'PIPIM-7LTUT', ]); + $this->assertGuest(); $response->assertForbidden(); + $response->assertSessionMissing('auth.recovery_mode.user_id'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame('The given email and recovery token combination are invalid.', $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); $this->assertTrue($repository->exists($userA, $token)); $this->assertSame($codes, $userA->fresh()->recovery_codes); - $this->assertGuest(); - $response->assertSessionMissing(EnsureSudoMode::REQUIRED_AT_KEY); - $response->assertSessionMissing(EnsureSudoMode::CONFIRMED_AT_KEY); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); + $this->assertSame(1, $this->getRateLimitAttempts('email::'.$email)); Event::assertNothingDispatched(); } /** @test */ - public function the_user_account_cannot_be_recovered_when_the_recovery_token_does_not_exist(): void + public function the_user_cannot_begin_to_recover_the_account_when_the_recovery_token_does_not_exist(): void { - Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); + Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); - $repository = Password::getRepository(); - $token = $repository->create($user); $this->expectTimebox(); $response = $this->post(route('recover-account.challenge', ['token' => 'invalid-token']), [ - 'email' => $user->getEmailForPasswordReset(), + 'email' => $email = $user->getEmailForPasswordReset(), 'code' => 'PIPIM-7LTUT', ]); + $this->assertGuest(); $response->assertForbidden(); + $response->assertSessionMissing('auth.recovery_mode.user_id'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame('The given email and recovery token combination are invalid.', $response->exception->getMessage()); - $this->assertTrue($repository->exists($user, $token)); + $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); $this->assertSame($codes, $user->fresh()->recovery_codes); - $this->assertGuest(); - $response->assertSessionMissing(EnsureSudoMode::REQUIRED_AT_KEY); - $response->assertSessionMissing(EnsureSudoMode::CONFIRMED_AT_KEY); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); + $this->assertSame(1, $this->getRateLimitAttempts('email::'.$email)); Event::assertNothingDispatched(); } /** @test */ - public function the_user_account_cannot_be_recovered_when_an_invalid_recovery_code_is_provided(): void + public function the_user_cannot_begin_to_recover_the_account_when_an_invalid_recovery_code_is_provided(): void { - Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); + Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); $repository = Password::getRepository(); $token = $repository->create($user); @@ -191,22 +176,21 @@ public function the_user_account_cannot_be_recovered_when_an_invalid_recovery_co $this->expectTimebox(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $user->getEmailForPasswordReset(), + 'email' => $email = $user->getEmailForPasswordReset(), 'code' => 'INV4L-1DCD3', ]); + $this->assertGuest(); + $response->assertSessionMissing('auth.recovery_mode.user_id'); $this->assertInstanceOf(ValidationException::class, $response->exception); $this->assertSame(['code' => [__('laravel-auth::auth.challenge.recovery')]], $response->exception->errors()); $this->assertTrue($repository->exists($user, $token)); $this->assertSame($codes, $user->fresh()->recovery_codes); - $this->assertGuest(); - $response->assertSessionMissing(EnsureSudoMode::REQUIRED_AT_KEY); - $response->assertSessionMissing(EnsureSudoMode::CONFIRMED_AT_KEY); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); - Event::assertDispatched(AccountRecoveryFailed::class, fn ($event) => $event->user->is($user) && $event->request === request()); - Event::assertNotDispatched(AccountRecovered::class); - Event::assertNotDispatched(SudoModeEnabled::class); + $this->assertSame(1, $this->getRateLimitAttempts('email::'.$email)); + Event::assertDispatched(AccountRecoveryFailed::class, fn (AccountRecoveryFailed $event) => $event->user->is($user)); + Event::assertNotDispatched(Lockout::class); } /** @test */ @@ -214,8 +198,10 @@ public function account_recovery_challenge_requests_are_rate_limited_after_too_m { Carbon::setTestNow(now()); Event::fake([Lockout::class]); - $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT']]); - $token = Password::getRepository()->create($user); + $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); + $repository = Password::getRepository(); + $token = $repository->create($user); + $this->assertTrue($repository->exists($user, $token)); $this->hitRateLimiter(250, ''); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ @@ -226,6 +212,7 @@ public function account_recovery_challenge_requests_are_rate_limited_after_too_m $this->assertInstanceOf(ValidationException::class, $response->exception); $this->assertSame(['code' => [__('laravel-auth::auth.challenge.throttle', ['seconds' => 60])]], $response->exception->errors()); $this->assertSame($codes, $user->fresh()->recovery_codes); + $this->assertTrue($repository->exists($user, $token)); Event::assertDispatched(Lockout::class, fn (Lockout $event) => $event->request === request()); Carbon::setTestNow(); } @@ -235,8 +222,10 @@ public function account_recovery_challenge_requests_are_rate_limited_after_too_m { Carbon::setTestNow(now()); Event::fake([Lockout::class]); - $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT']]); - $token = Password::getRepository()->create($user); + $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); + $repository = Password::getRepository(); + $token = $repository->create($user); + $this->assertTrue($repository->exists($user, $token)); $this->hitRateLimiter(5, 'ip::127.0.0.1'); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ @@ -247,6 +236,32 @@ public function account_recovery_challenge_requests_are_rate_limited_after_too_m $this->assertInstanceOf(ValidationException::class, $response->exception); $this->assertSame(['code' => [__('laravel-auth::auth.challenge.throttle', ['seconds' => 60])]], $response->exception->errors()); $this->assertSame($codes, $user->fresh()->recovery_codes); + $this->assertTrue($repository->exists($user, $token)); + Event::assertDispatched(Lockout::class, fn (Lockout $event) => $event->request === request()); + Carbon::setTestNow(); + } + + /** @test */ + public function account_recovery_challenge_requests_are_rate_limited_after_too_many_failed_requests_for_one_email_address(): void + { + Carbon::setTestNow(now()); + Event::fake([Lockout::class]); + $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); + $repository = Password::getRepository(); + $token = $repository->create($user); + $this->assertTrue($repository->exists($user, $token)); + $email = $user->getEmailForPasswordReset(); + $this->hitRateLimiter(5, 'email::'.$email); + + $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ + 'email' => $email, + 'code' => 'PIPIM-7LTUT', + ]); + + $this->assertInstanceOf(ValidationException::class, $response->exception); + $this->assertSame(['code' => [__('laravel-auth::auth.challenge.throttle', ['seconds' => 60])]], $response->exception->errors()); + $this->assertSame($codes, $user->fresh()->recovery_codes); + $this->assertTrue($repository->exists($user, $token)); Event::assertDispatched(Lockout::class, fn (Lockout $event) => $event->request === request()); Carbon::setTestNow(); } diff --git a/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php b/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php index 4bbbae3..3f2d897 100644 --- a/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php +++ b/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php @@ -6,7 +6,6 @@ use ClaudioDekker\LaravelAuth\Events\AccountRecovered; use ClaudioDekker\LaravelAuth\Events\AccountRecoveryFailed; use ClaudioDekker\LaravelAuth\Events\SudoModeEnabled; -use ClaudioDekker\LaravelAuth\Http\Middleware\EnsureSudoMode; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Password; @@ -32,7 +31,6 @@ public function the_account_recovery_challenge_page_can_be_viewed(): void /** @test */ public function the_account_recovery_challenge_page_is_skipped_when_the_user_does_not_have_any_recovery_codes(): void { - Carbon::setTestNow(now()); Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); $user = $this->generateUser(['recovery_codes' => null]); $repository = Password::getRepository(); @@ -42,18 +40,16 @@ public function the_account_recovery_challenge_page_is_skipped_when_the_user_doe $response = $this->get(route('recover-account.challenge', [ 'token' => $token, - 'email' => $user->getEmailForPasswordReset(), + 'email' => $email = $user->getEmailForPasswordReset(), ])); - $response->assertRedirect(route('auth.settings')); - $response->assertSessionMissing(EnsureSudoMode::REQUIRED_AT_KEY); - $response->assertSessionHas(EnsureSudoMode::CONFIRMED_AT_KEY, now()->unix()); - $this->assertFullyAuthenticatedAs($response, $user); + $this->assertGuest(); + $response->assertRedirect(route('recover-account.reset')); + $response->assertSessionHas('auth.recovery_mode.user_id', $user->id); $this->assertFalse($repository->exists($user, $token)); - Event::assertDispatched(AccountRecovered::class, fn ($event) => $event->user->is($user) && $event->request === request()); - Event::assertNotDispatched(AccountRecoveryFailed::class); - Event::assertNotDispatched(SudoModeEnabled::class); - Carbon::setTestNow(); + $this->assertSame(1, $this->getRateLimitAttempts('')); + $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); + $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); } /** @test */ @@ -80,8 +76,9 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_pr ])); $response->assertForbidden(); + $response->assertSessionMissing('auth.recovery_mode.user_id'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame('The given email and recovery token combination are invalid.', $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); } /** @test */ @@ -98,8 +95,9 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re ])); $response->assertForbidden(); + $response->assertSessionMissing('auth.recovery_mode.user_id'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame('The given email and recovery token combination are invalid.', $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); } /** @test */ @@ -114,8 +112,9 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re ])); $response->assertForbidden(); + $response->assertSessionMissing('auth.recovery_mode.user_id'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame('The given email and recovery token combination are invalid.', $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); } /** @test */ @@ -133,8 +132,9 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re ])); $response->assertForbidden(); + $response->assertSessionMissing('auth.recovery_mode.user_id'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame('The given email and recovery token combination are invalid.', $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); Carbon::setTestNow(); } } From 47f285b4ac208cd094f60670e2607b5aebe45fd4 Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sat, 5 Aug 2023 23:26:00 +0200 Subject: [PATCH 02/12] WIP --- ...countRecoveryChallengeController.bladetmpl | 6 ++--- .../AccountRecoveryChallengeController.php | 27 +++---------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl b/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl index 8b549ff..dab2945 100644 --- a/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl +++ b/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl @@ -13,8 +13,8 @@ class AccountRecoveryChallengeController extends BaseController /** * Handle an incoming request to view the account recovery challenge page. * - * {!! '@' !!}see static::sendChallengePageResponse() * {!! '@' !!}see static::sendInvalidRecoveryLinkResponse() + * {!! '@' !!}see static::sendChallengePageResponse() * {!! '@' !!}see static::sendRecoveryModeEnabledResponse() */ public function create(Request $request, string $token): RedirectResponse|View @@ -26,9 +26,9 @@ class AccountRecoveryChallengeController extends BaseController * Handle an incoming account recovery challenge response. * * {!! '@' !!}see static::sendRateLimitedResponse() - * {!! '@' !!}see static::sendAccountRecoveredResponse() - * {!! '@' !!}see static::sendInvalidRecoveryCodeResponse() * {!! '@' !!}see static::sendInvalidRecoveryLinkResponse() + * {!! '@' !!}see static::sendInvalidRecoveryCodeResponse() + * {!! '@' !!}see static::sendRecoveryModeEnabledResponse() */ public function store(Request $request, string $token): RedirectResponse { diff --git a/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php b/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php index f2aadab..cef15f4 100644 --- a/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php +++ b/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php @@ -2,7 +2,6 @@ namespace ClaudioDekker\LaravelAuth\Http\Controllers\Challenges; -use ClaudioDekker\LaravelAuth\Events\AccountRecovered; use ClaudioDekker\LaravelAuth\Events\AccountRecoveryFailed; use ClaudioDekker\LaravelAuth\Events\Mixins\EmitsLockoutEvent; use ClaudioDekker\LaravelAuth\Http\Concerns\InteractsWithRateLimiting; @@ -12,7 +11,6 @@ use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Password; use Illuminate\Support\Str; @@ -56,8 +54,8 @@ abstract protected function sendRecoveryModeEnabledResponse(Request $request, Au * * @return mixed * - * @see static::sendChallengePageResponse() * @see static::sendInvalidRecoveryLinkResponse() + * @see static::sendChallengePageResponse() * @see static::sendRecoveryModeEnabledResponse() */ public function create(Request $request, string $token) @@ -92,10 +90,10 @@ public function create(Request $request, string $token) * * @return mixed * - * @see static::sendRecoveryModeEnabledResponse() - * @see static::sendInvalidRecoveryCodeResponse() - * @see static::sendInvalidRecoveryLinkResponse() * @see static::sendRateLimitedResponse() + * @see static::sendInvalidRecoveryLinkResponse() + * @see static::sendInvalidRecoveryCodeResponse() + * @see static::sendRecoveryModeEnabledResponse() */ public function store(Request $request, string $token) { @@ -162,15 +160,6 @@ protected function resolveUser(Request $request): ?Authenticatable ->first(); } - // - // /** - // * Authenticate the user into the application. - // */ - // protected function authenticate(Request $request, Authenticatable $user): void - // { - // Auth::login($user); - // } - // /** * Determine whether the user has recovery codes. */ @@ -206,14 +195,6 @@ protected function invalidateRecoveryLink(Request $request, Authenticatable $use { Password::getRepository()->delete($user); } - // - // /** - // * Emits an event indicating that the user's account has been recovered. - // */ - // protected function emitAccountRecoveredEvent(Request $request, Authenticatable $user): void - // { - // Event::dispatch(new AccountRecovered($request, $user)); - // } /** * Emits an event indicating that an account recovery attempt has failed. From 82ef62f9df254992c6969e83b8e8f8f7a560d9dd Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 00:07:29 +0200 Subject: [PATCH 03/12] WIP --- .../Recovery/SubmitAccountRecoveryChallengeTests.php | 11 +++++++++++ .../ViewAccountRecoveryChallengePageTests.php | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitAccountRecoveryChallengeTests.php b/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitAccountRecoveryChallengeTests.php index 58b0884..5b780eb 100644 --- a/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitAccountRecoveryChallengeTests.php +++ b/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitAccountRecoveryChallengeTests.php @@ -16,6 +16,7 @@ trait SubmitAccountRecoveryChallengeTests /** @test */ public function the_user_can_begin_to_recover_the_account(): void { + Carbon::setTestNow(now()); Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); $repository = Password::getRepository(); @@ -31,17 +32,20 @@ public function the_user_can_begin_to_recover_the_account(): void $this->assertGuest(); $response->assertRedirect(route('recover-account.reset')); $response->assertSessionHas('auth.recovery_mode.user_id', $user->id); + $response->assertSessionHas('auth.recovery_mode.enabled_at', now()); $this->assertFalse($repository->exists($user, $token)); $this->assertSame(['H4PFK-ENVZV', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P'], $user->fresh()->recovery_codes); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); Event::assertNothingDispatched(); + Carbon::setTestNow(); } /** @test */ public function the_account_recovery_challenge_code_verification_request_accepts_any_code_when_the_users_recovery_codes_have_been_cleared(): void { + Carbon::setTestNow(now()); Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => null]); $repository = Password::getRepository(); @@ -57,11 +61,13 @@ public function the_account_recovery_challenge_code_verification_request_accepts $this->assertGuest(); $response->assertRedirect(route('recover-account.reset')); $response->assertSessionHas('auth.recovery_mode.user_id', $user->id); + $response->assertSessionHas('auth.recovery_mode.enabled_at', now()); $this->assertFalse($repository->exists($user, $token)); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); Event::assertNothingDispatched(); + Carbon::setTestNow(); } /** @test */ @@ -80,6 +86,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_already_authen $response->assertRedirect(RouteServiceProvider::HOME); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertAuthenticatedAs($userA); $this->assertTrue($repository->exists($userB, $token)); $this->assertSame($codes, $userB->fresh()->recovery_codes); @@ -103,6 +110,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_provided_e $this->assertGuest(); $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); $this->assertTrue($repository->exists($user, $token)); @@ -131,6 +139,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_recovery_t $this->assertGuest(); $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); $this->assertTrue($repository->exists($userA, $token)); @@ -156,6 +165,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_recovery_t $this->assertGuest(); $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); $this->assertSame($codes, $user->fresh()->recovery_codes); @@ -182,6 +192,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_an_invalid_rec $this->assertGuest(); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(ValidationException::class, $response->exception); $this->assertSame(['code' => [__('laravel-auth::auth.challenge.recovery')]], $response->exception->errors()); $this->assertTrue($repository->exists($user, $token)); diff --git a/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php b/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php index 3f2d897..f102638 100644 --- a/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php +++ b/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php @@ -31,6 +31,7 @@ public function the_account_recovery_challenge_page_can_be_viewed(): void /** @test */ public function the_account_recovery_challenge_page_is_skipped_when_the_user_does_not_have_any_recovery_codes(): void { + Carbon::setTestNow(now()); Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); $user = $this->generateUser(['recovery_codes' => null]); $repository = Password::getRepository(); @@ -46,10 +47,12 @@ public function the_account_recovery_challenge_page_is_skipped_when_the_user_doe $this->assertGuest(); $response->assertRedirect(route('recover-account.reset')); $response->assertSessionHas('auth.recovery_mode.user_id', $user->id); + $response->assertSessionHas('auth.recovery_mode.enabled_at', now()); $this->assertFalse($repository->exists($user, $token)); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); + Carbon::setTestNow(); } /** @test */ @@ -77,6 +80,7 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_pr $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); } @@ -96,6 +100,7 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); } @@ -113,6 +118,7 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); } @@ -133,6 +139,7 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); + $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); Carbon::setTestNow(); From 41cd7e51ed62e282eda852d420e808bd2f73d30b Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 00:12:40 +0200 Subject: [PATCH 04/12] WIP --- .../Recovery/ViewAccountRecoveryChallengePageTests.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php b/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php index f102638..a7ba2c1 100644 --- a/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php +++ b/packages/core/src/Testing/Partials/Challenges/Recovery/ViewAccountRecoveryChallengePageTests.php @@ -3,9 +3,8 @@ namespace ClaudioDekker\LaravelAuth\Testing\Partials\Challenges\Recovery; use App\Providers\RouteServiceProvider; -use ClaudioDekker\LaravelAuth\Events\AccountRecovered; use ClaudioDekker\LaravelAuth\Events\AccountRecoveryFailed; -use ClaudioDekker\LaravelAuth\Events\SudoModeEnabled; +use Illuminate\Auth\Events\Lockout; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Password; @@ -32,7 +31,7 @@ public function the_account_recovery_challenge_page_can_be_viewed(): void public function the_account_recovery_challenge_page_is_skipped_when_the_user_does_not_have_any_recovery_codes(): void { Carbon::setTestNow(now()); - Event::fake([AccountRecovered::class, AccountRecoveryFailed::class, SudoModeEnabled::class]); + Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => null]); $repository = Password::getRepository(); $token = $repository->create($user); @@ -52,6 +51,7 @@ public function the_account_recovery_challenge_page_is_skipped_when_the_user_doe $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); + Event::assertNothingDispatched(); Carbon::setTestNow(); } From f66be1eae80b73cf538c6544df83b5eeb95072ab Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 17:17:45 +0200 Subject: [PATCH 05/12] WIP --- .../bladebones/src/Testing/BladeViewTests.php | 8 +-- ...sts.php => RecoveryChallengeViewTests.php} | 2 +- ...Tests.php => RecoveryRequestViewTests.php} | 2 +- .../bladebones/stubs/defaults/routes/web.stub | 12 ++-- ... => RecoveryChallengeController.bladetmpl} | 4 +- ...pl => RecoveryRequestController.bladetmpl} | 4 +- .../Tests/AuthenticationTest.bladetmpl | 8 +-- .../Feature/Console/GenerateCommandTest.php | 72 +++++++++---------- packages/core/src/Console/GenerateCommand.php | 4 +- ...er.php => RecoveryChallengeController.php} | 2 +- ...ller.php => RecoveryRequestController.php} | 2 +- .../Testing/AccountRecoveryChallengeTests.php | 12 ---- .../Testing/AccountRecoveryRequestTests.php | 12 ---- ...s.php => SubmitRecoveryChallengeTests.php} | 2 +- ...php => ViewRecoveryChallengePageTests.php} | 2 +- ...sts.php => SubmitRecoveryRequestTests.php} | 2 +- ...s.php => ViewRecoveryRequestPageTests.php} | 2 +- .../src/Testing/RecoveryChallengeTests.php | 12 ++++ .../core/src/Testing/RecoveryRequestTests.php | 12 ++++ 19 files changed, 88 insertions(+), 88 deletions(-) rename packages/bladebones/src/Testing/Partials/{AccountRecoveryChallengeViewTests.php => RecoveryChallengeViewTests.php} (95%) rename packages/bladebones/src/Testing/Partials/{AccountRecoveryRequestViewTests.php => RecoveryRequestViewTests.php} (88%) rename packages/bladebones/templates/Controllers/Challenges/{AccountRecoveryChallengeController.bladetmpl => RecoveryChallengeController.bladetmpl} (94%) rename packages/bladebones/templates/Controllers/{AccountRecoveryRequestController.bladetmpl => RecoveryRequestController.bladetmpl} (95%) rename packages/core/src/Http/Controllers/Challenges/{AccountRecoveryChallengeController.php => RecoveryChallengeController.php} (99%) rename packages/core/src/Http/Controllers/{AccountRecoveryRequestController.php => RecoveryRequestController.php} (99%) delete mode 100644 packages/core/src/Testing/AccountRecoveryChallengeTests.php delete mode 100644 packages/core/src/Testing/AccountRecoveryRequestTests.php rename packages/core/src/Testing/Partials/Challenges/Recovery/{SubmitAccountRecoveryChallengeTests.php => SubmitRecoveryChallengeTests.php} (99%) rename packages/core/src/Testing/Partials/Challenges/Recovery/{ViewAccountRecoveryChallengePageTests.php => ViewRecoveryChallengePageTests.php} (99%) rename packages/core/src/Testing/Partials/{SubmitAccountRecoveryRequestTests.php => SubmitRecoveryRequestTests.php} (99%) rename packages/core/src/Testing/Partials/{ViewAccountRecoveryRequestPageTests.php => ViewRecoveryRequestPageTests.php} (93%) create mode 100644 packages/core/src/Testing/RecoveryChallengeTests.php create mode 100644 packages/core/src/Testing/RecoveryRequestTests.php diff --git a/packages/bladebones/src/Testing/BladeViewTests.php b/packages/bladebones/src/Testing/BladeViewTests.php index 5b10663..d92a077 100644 --- a/packages/bladebones/src/Testing/BladeViewTests.php +++ b/packages/bladebones/src/Testing/BladeViewTests.php @@ -2,8 +2,8 @@ namespace ClaudioDekker\LaravelAuthBladebones\Testing; -use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\AccountRecoveryChallengeViewTests; -use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\AccountRecoveryRequestViewTests; +use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RecoveryChallengeViewTests; +use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RecoveryRequestViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\CredentialsOverviewViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\LoginViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\MultiFactorChallengeViewTests; @@ -16,10 +16,10 @@ trait BladeViewTests { use RegisterViewTests; use LoginViewTests; - use AccountRecoveryRequestViewTests; + use RecoveryRequestViewTests; // Challenges - use AccountRecoveryChallengeViewTests; + use RecoveryChallengeViewTests; use MultiFactorChallengeViewTests; use SudoModeChallengeViewTests; diff --git a/packages/bladebones/src/Testing/Partials/AccountRecoveryChallengeViewTests.php b/packages/bladebones/src/Testing/Partials/RecoveryChallengeViewTests.php similarity index 95% rename from packages/bladebones/src/Testing/Partials/AccountRecoveryChallengeViewTests.php rename to packages/bladebones/src/Testing/Partials/RecoveryChallengeViewTests.php index 8e1313e..6788530 100644 --- a/packages/bladebones/src/Testing/Partials/AccountRecoveryChallengeViewTests.php +++ b/packages/bladebones/src/Testing/Partials/RecoveryChallengeViewTests.php @@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Password; -trait AccountRecoveryChallengeViewTests +trait RecoveryChallengeViewTests { /** @test */ public function the_account_recovery_challenge_page_uses_blade_views(): void diff --git a/packages/bladebones/src/Testing/Partials/AccountRecoveryRequestViewTests.php b/packages/bladebones/src/Testing/Partials/RecoveryRequestViewTests.php similarity index 88% rename from packages/bladebones/src/Testing/Partials/AccountRecoveryRequestViewTests.php rename to packages/bladebones/src/Testing/Partials/RecoveryRequestViewTests.php index bfffe03..77d1ea5 100644 --- a/packages/bladebones/src/Testing/Partials/AccountRecoveryRequestViewTests.php +++ b/packages/bladebones/src/Testing/Partials/RecoveryRequestViewTests.php @@ -2,7 +2,7 @@ namespace ClaudioDekker\LaravelAuthBladebones\Testing\Partials; -trait AccountRecoveryRequestViewTests +trait RecoveryRequestViewTests { /** @test */ public function the_account_recovery_request_page_uses_blade_views(): void diff --git a/packages/bladebones/stubs/defaults/routes/web.stub b/packages/bladebones/stubs/defaults/routes/web.stub index 4b03bb5..1e9bb80 100644 --- a/packages/bladebones/stubs/defaults/routes/web.stub +++ b/packages/bladebones/stubs/defaults/routes/web.stub @@ -1,7 +1,7 @@ group(function () { Route::post('/auth/login/challenge', [MultiFactorChallengeController::class, 'store']); }); - Route::get('/auth/recover-account', [AccountRecoveryRequestController::class, 'create'])->name('recover-account'); - Route::post('/auth/recover-account', [AccountRecoveryRequestController::class, 'store']); - Route::get('/auth/recover-account/{token}', [AccountRecoveryChallengeController::class, 'create'])->name('recover-account.challenge'); - Route::post('/auth/recover-account/{token}', [AccountRecoveryChallengeController::class, 'store']); + Route::get('/auth/recover-account', [RecoveryRequestController::class, 'create'])->name('recover-account'); + Route::post('/auth/recover-account', [RecoveryRequestController::class, 'store']); + Route::get('/auth/recover-account/{token}', [RecoveryChallengeController::class, 'create'])->name('recover-account.challenge'); + Route::post('/auth/recover-account/{token}', [RecoveryChallengeController::class, 'store']); Route::middleware([])->group(function () { Route::get('/auth/reset', ['foo', 'bar'])->name('recover-account.reset'); diff --git a/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl b/packages/bladebones/templates/Controllers/Challenges/RecoveryChallengeController.bladetmpl similarity index 94% rename from packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl rename to packages/bladebones/templates/Controllers/Challenges/RecoveryChallengeController.bladetmpl index dab2945..19bc27b 100644 --- a/packages/bladebones/templates/Controllers/Challenges/AccountRecoveryChallengeController.bladetmpl +++ b/packages/bladebones/templates/Controllers/Challenges/RecoveryChallengeController.bladetmpl @@ -1,14 +1,14 @@ namespace App\Http\Controllers\Auth\Challenges; -use ClaudioDekker\LaravelAuth\Http\Controllers\Challenges\AccountRecoveryChallengeController as BaseController; +use ClaudioDekker\LaravelAuth\Http\Controllers\Challenges\RecoveryChallengeController as BaseController; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; -class AccountRecoveryChallengeController extends BaseController +class RecoveryChallengeController extends BaseController { /** * Handle an incoming request to view the account recovery challenge page. diff --git a/packages/bladebones/templates/Controllers/AccountRecoveryRequestController.bladetmpl b/packages/bladebones/templates/Controllers/RecoveryRequestController.bladetmpl similarity index 95% rename from packages/bladebones/templates/Controllers/AccountRecoveryRequestController.bladetmpl rename to packages/bladebones/templates/Controllers/RecoveryRequestController.bladetmpl index 1d90467..0c76b48 100644 --- a/packages/bladebones/templates/Controllers/AccountRecoveryRequestController.bladetmpl +++ b/packages/bladebones/templates/Controllers/RecoveryRequestController.bladetmpl @@ -15,7 +15,7 @@ namespace App\Http\Controllers\Auth; -use ClaudioDekker\LaravelAuth\Http\Controllers\AccountRecoveryRequestController as BaseController; +use ClaudioDekker\LaravelAuth\Http\Controllers\RecoveryRequestController as BaseController; @if (count($traits) > 0) @foreach($traits as $trait) use {{ $trait }}; @@ -27,7 +27,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; -class AccountRecoveryRequestController extends BaseController +class RecoveryRequestController extends BaseController { @php $uses = []; diff --git a/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl b/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl index 3772131..e77ffe6 100644 --- a/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl +++ b/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl @@ -4,8 +4,8 @@ @endphp namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryRequestTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\{{ $verificationTrait }}; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\{{ $flavorTrait }}; @@ -48,13 +48,13 @@ class AuthenticationTest extends TestCase @endforeach // Basic Auth - use AccountRecoveryRequestTests; + use RecoveryRequestTests; use RegistrationTests; use LoginTests; use LogoutTests; // Challenges - use AccountRecoveryChallengeTests; + use RecoveryChallengeTests; use MultiFactorChallengeTests; use SudoModeChallengeTests; diff --git a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php index d4bb5e9..7e769da 100644 --- a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php +++ b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php @@ -56,8 +56,8 @@ public function it_accepts_all_default_options_without_prompting_when_passing_th $this->assertMockShouldReceiveController('Settings/RegisterPublicKeyCredentialController'); $this->assertMockShouldReceiveController('Settings/RegisterTotpCredentialController'); $this->assertMockShouldReceiveController('VerifyEmailController'); - $this->assertMockShouldReceiveController('AccountRecoveryRequestController'); - $this->assertMockShouldReceiveController('Challenges/AccountRecoveryChallengeController'); + $this->assertMockShouldReceiveController('RecoveryRequestController'); + $this->assertMockShouldReceiveController('Challenges/RecoveryChallengeController'); $this->assertMockShouldReceiveController('Challenges/MultiFactorChallengeController'); $this->assertMockShouldReceiveController('Challenges/SudoModeChallengeController'); $this->assertMockShouldReceiveController('LoginController'); @@ -73,8 +73,8 @@ public function it_accepts_all_default_options_without_prompting_when_passing_th namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryRequestTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -103,13 +103,13 @@ class AuthenticationTest extends TestCase use RegisterWithVerificationEmailTests; // Basic Auth - use AccountRecoveryRequestTests; + use RecoveryRequestTests; use RegistrationTests; use LoginTests; use LogoutTests; // Challenges - use AccountRecoveryChallengeTests; + use RecoveryChallengeTests; use MultiFactorChallengeTests; use SudoModeChallengeTests; @@ -161,8 +161,8 @@ public function it_asks_whether_you_want_to_send_verification_emails_on_registra $this->assertMockShouldReceiveController('Settings/RegisterPublicKeyCredentialController'); $this->assertMockShouldReceiveController('Settings/RegisterTotpCredentialController'); $this->assertMockShouldReceiveController('VerifyEmailController'); - $this->assertMockShouldReceiveController('AccountRecoveryRequestController'); - $this->assertMockShouldReceiveController('Challenges/AccountRecoveryChallengeController'); + $this->assertMockShouldReceiveController('RecoveryRequestController'); + $this->assertMockShouldReceiveController('Challenges/RecoveryChallengeController'); $this->assertMockShouldReceiveController('Challenges/MultiFactorChallengeController'); $this->assertMockShouldReceiveController('Challenges/SudoModeChallengeController'); $this->assertMockShouldReceiveController('LoginController'); @@ -178,8 +178,8 @@ public function it_asks_whether_you_want_to_send_verification_emails_on_registra namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryRequestTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -206,13 +206,13 @@ class AuthenticationTest extends TestCase use RegisterWithVerificationEmailTests; // Basic Auth - use AccountRecoveryRequestTests; + use RecoveryRequestTests; use RegistrationTests; use LoginTests; use LogoutTests; // Challenges - use AccountRecoveryChallengeTests; + use RecoveryChallengeTests; use MultiFactorChallengeTests; use SudoModeChallengeTests; @@ -254,8 +254,8 @@ public function it_does_not_send_verification_emails_on_registration_when_you_an $this->assertMockShouldReceiveController('Settings/RegisterPublicKeyCredentialController'); $this->assertMockShouldReceiveController('Settings/RegisterTotpCredentialController'); $this->assertMockShouldReceiveController('VerifyEmailController'); - $this->assertMockShouldReceiveController('AccountRecoveryRequestController'); - $this->assertMockShouldReceiveController('Challenges/AccountRecoveryChallengeController'); + $this->assertMockShouldReceiveController('RecoveryRequestController'); + $this->assertMockShouldReceiveController('Challenges/RecoveryChallengeController'); $this->assertMockShouldReceiveController('Challenges/MultiFactorChallengeController'); $this->assertMockShouldReceiveController('Challenges/SudoModeChallengeController'); $this->assertMockShouldReceiveController('LoginController'); @@ -271,8 +271,8 @@ public function it_does_not_send_verification_emails_on_registration_when_you_an namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryRequestTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithoutVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -299,13 +299,13 @@ class AuthenticationTest extends TestCase use RegisterWithoutVerificationEmailTests; // Basic Auth - use AccountRecoveryRequestTests; + use RecoveryRequestTests; use RegistrationTests; use LoginTests; use LogoutTests; // Challenges - use AccountRecoveryChallengeTests; + use RecoveryChallengeTests; use MultiFactorChallengeTests; use SudoModeChallengeTests; @@ -346,8 +346,8 @@ public function it_does_not_send_verification_emails_on_registration_when_the_re $this->assertMockShouldReceiveController('Settings/RegisterPublicKeyCredentialController'); $this->assertMockShouldReceiveController('Settings/RegisterTotpCredentialController'); $this->assertMockShouldReceiveController('VerifyEmailController'); - $this->assertMockShouldReceiveController('AccountRecoveryRequestController'); - $this->assertMockShouldReceiveController('Challenges/AccountRecoveryChallengeController'); + $this->assertMockShouldReceiveController('RecoveryRequestController'); + $this->assertMockShouldReceiveController('Challenges/RecoveryChallengeController'); $this->assertMockShouldReceiveController('Challenges/MultiFactorChallengeController'); $this->assertMockShouldReceiveController('Challenges/SudoModeChallengeController'); $this->assertMockShouldReceiveController('LoginController'); @@ -363,8 +363,8 @@ public function it_does_not_send_verification_emails_on_registration_when_the_re namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryRequestTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithoutVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -393,13 +393,13 @@ class AuthenticationTest extends TestCase use RegisterWithoutVerificationEmailTests; // Basic Auth - use AccountRecoveryRequestTests; + use RecoveryRequestTests; use RegistrationTests; use LoginTests; use LogoutTests; // Challenges - use AccountRecoveryChallengeTests; + use RecoveryChallengeTests; use MultiFactorChallengeTests; use SudoModeChallengeTests; @@ -439,8 +439,8 @@ public function it_does_not_create_views_and_view_tests_when_the_without_views_f $this->assertMockShouldReceiveController('Settings/RegisterPublicKeyCredentialController'); $this->assertMockShouldReceiveController('Settings/RegisterTotpCredentialController'); $this->assertMockShouldReceiveController('VerifyEmailController'); - $this->assertMockShouldReceiveController('AccountRecoveryRequestController'); - $this->assertMockShouldReceiveController('Challenges/AccountRecoveryChallengeController'); + $this->assertMockShouldReceiveController('RecoveryRequestController'); + $this->assertMockShouldReceiveController('Challenges/RecoveryChallengeController'); $this->assertMockShouldReceiveController('Challenges/MultiFactorChallengeController'); $this->assertMockShouldReceiveController('Challenges/SudoModeChallengeController'); $this->assertMockShouldReceiveController('LoginController'); @@ -456,8 +456,8 @@ public function it_does_not_create_views_and_view_tests_when_the_without_views_f namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryRequestTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -484,13 +484,13 @@ class AuthenticationTest extends TestCase use RegisterWithVerificationEmailTests; // Basic Auth - use AccountRecoveryRequestTests; + use RecoveryRequestTests; use RegistrationTests; use LoginTests; use LogoutTests; // Challenges - use AccountRecoveryChallengeTests; + use RecoveryChallengeTests; use MultiFactorChallengeTests; use SudoModeChallengeTests; @@ -542,8 +542,8 @@ public function it_asks_what_flavor_of_user_accounts_should_be_used(): void $this->assertMockShouldReceiveController('Settings/RegisterPublicKeyCredentialController'); $this->assertMockShouldReceiveController('Settings/RegisterTotpCredentialController'); $this->assertMockShouldReceiveController('VerifyEmailController'); - $this->assertMockShouldReceiveController('AccountRecoveryRequestController'); - $this->assertMockShouldReceiveController('Challenges/AccountRecoveryChallengeController'); + $this->assertMockShouldReceiveController('RecoveryRequestController'); + $this->assertMockShouldReceiveController('Challenges/RecoveryChallengeController'); $this->assertMockShouldReceiveController('Challenges/MultiFactorChallengeController'); $this->assertMockShouldReceiveController('Challenges/SudoModeChallengeController'); $this->assertMockShouldReceiveController('LoginController', function ($contents) { @@ -564,8 +564,8 @@ public function it_asks_what_flavor_of_user_accounts_should_be_used(): void namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\AccountRecoveryRequestTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithoutVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\UsernameBased; @@ -592,13 +592,13 @@ class AuthenticationTest extends TestCase use UsernameBased; // Basic Auth - use AccountRecoveryRequestTests; + use RecoveryRequestTests; use RegistrationTests; use LoginTests; use LogoutTests; // Challenges - use AccountRecoveryChallengeTests; + use RecoveryChallengeTests; use MultiFactorChallengeTests; use SudoModeChallengeTests; diff --git a/packages/core/src/Console/GenerateCommand.php b/packages/core/src/Console/GenerateCommand.php index df42f64..9d3d283 100644 --- a/packages/core/src/Console/GenerateCommand.php +++ b/packages/core/src/Console/GenerateCommand.php @@ -115,8 +115,8 @@ protected function installTests(): void */ protected function installControllers(): void { - $this->generate('Controllers.AccountRecoveryRequestController', app_path('Http/Controllers/Auth/AccountRecoveryRequestController.php')); - $this->generate('Controllers.Challenges.AccountRecoveryChallengeController', app_path('Http/Controllers/Auth/Challenges/AccountRecoveryChallengeController.php')); + $this->generate('Controllers.RecoveryRequestController', app_path('Http/Controllers/Auth/RecoveryRequestController.php')); + $this->generate('Controllers.Challenges.RecoveryChallengeController', app_path('Http/Controllers/Auth/Challenges/RecoveryChallengeController.php')); $this->generate('Controllers.Challenges.MultiFactorChallengeController', app_path('Http/Controllers/Auth/Challenges/MultiFactorChallengeController.php')); $this->generate('Controllers.Challenges.SudoModeChallengeController', app_path('Http/Controllers/Auth/Challenges/SudoModeChallengeController.php')); $this->generate('Controllers.LoginController', app_path('Http/Controllers/Auth/LoginController.php')); diff --git a/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php b/packages/core/src/Http/Controllers/Challenges/RecoveryChallengeController.php similarity index 99% rename from packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php rename to packages/core/src/Http/Controllers/Challenges/RecoveryChallengeController.php index cef15f4..1f558a7 100644 --- a/packages/core/src/Http/Controllers/Challenges/AccountRecoveryChallengeController.php +++ b/packages/core/src/Http/Controllers/Challenges/RecoveryChallengeController.php @@ -16,7 +16,7 @@ use Illuminate\Support\Str; use Illuminate\Support\Timebox; -abstract class AccountRecoveryChallengeController +abstract class RecoveryChallengeController { use EmitsLockoutEvent; use InteractsWithRateLimiting; diff --git a/packages/core/src/Http/Controllers/AccountRecoveryRequestController.php b/packages/core/src/Http/Controllers/RecoveryRequestController.php similarity index 99% rename from packages/core/src/Http/Controllers/AccountRecoveryRequestController.php rename to packages/core/src/Http/Controllers/RecoveryRequestController.php index f13c6ff..c6a059a 100644 --- a/packages/core/src/Http/Controllers/AccountRecoveryRequestController.php +++ b/packages/core/src/Http/Controllers/RecoveryRequestController.php @@ -13,7 +13,7 @@ use Illuminate\Support\Facades\Password; use Illuminate\Support\Timebox; -abstract class AccountRecoveryRequestController +abstract class RecoveryRequestController { use InteractsWithRateLimiting; use EmitsLockoutEvent; diff --git a/packages/core/src/Testing/AccountRecoveryChallengeTests.php b/packages/core/src/Testing/AccountRecoveryChallengeTests.php deleted file mode 100644 index 9370b99..0000000 --- a/packages/core/src/Testing/AccountRecoveryChallengeTests.php +++ /dev/null @@ -1,12 +0,0 @@ - Date: Sun, 6 Aug 2023 15:53:11 +0000 Subject: [PATCH 06/12] Fix styling --- packages/bladebones/src/Testing/BladeViewTests.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bladebones/src/Testing/BladeViewTests.php b/packages/bladebones/src/Testing/BladeViewTests.php index d92a077..71fa7cb 100644 --- a/packages/bladebones/src/Testing/BladeViewTests.php +++ b/packages/bladebones/src/Testing/BladeViewTests.php @@ -2,11 +2,11 @@ namespace ClaudioDekker\LaravelAuthBladebones\Testing; -use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RecoveryChallengeViewTests; -use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RecoveryRequestViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\CredentialsOverviewViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\LoginViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\MultiFactorChallengeViewTests; +use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RecoveryChallengeViewTests; +use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RecoveryRequestViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RegisterPublicKeyCredentialViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RegisterTotpCredentialViewTests; use ClaudioDekker\LaravelAuthBladebones\Testing\Partials\RegisterViewTests; From a16ab2d5441f00c53f7995787a7ece1bc34f4340 Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 18:19:59 +0200 Subject: [PATCH 07/12] WIP --- packages/bladebones/src/Testing/BladeViewTests.php | 2 +- .../templates/Tests/AuthenticationTest.bladetmpl | 2 +- .../tests/Feature/Console/GenerateCommandTest.php | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/bladebones/src/Testing/BladeViewTests.php b/packages/bladebones/src/Testing/BladeViewTests.php index 71fa7cb..b34be72 100644 --- a/packages/bladebones/src/Testing/BladeViewTests.php +++ b/packages/bladebones/src/Testing/BladeViewTests.php @@ -19,8 +19,8 @@ trait BladeViewTests use RecoveryRequestViewTests; // Challenges - use RecoveryChallengeViewTests; use MultiFactorChallengeViewTests; + use RecoveryChallengeViewTests; use SudoModeChallengeViewTests; // Settings diff --git a/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl b/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl index e77ffe6..aa43bda 100644 --- a/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl +++ b/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl @@ -54,8 +54,8 @@ class AuthenticationTest extends TestCase use LogoutTests; // Challenges - use RecoveryChallengeTests; use MultiFactorChallengeTests; + use RecoveryChallengeTests; use SudoModeChallengeTests; // Settings diff --git a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php index 7e769da..bf0be79 100644 --- a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php +++ b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php @@ -109,7 +109,6 @@ class AuthenticationTest extends TestCase use LogoutTests; // Challenges - use RecoveryChallengeTests; use MultiFactorChallengeTests; use SudoModeChallengeTests; @@ -212,8 +211,8 @@ class AuthenticationTest extends TestCase use LogoutTests; // Challenges - use RecoveryChallengeTests; use MultiFactorChallengeTests; + use RecoveryChallengeTests; use SudoModeChallengeTests; // Settings @@ -305,8 +304,8 @@ class AuthenticationTest extends TestCase use LogoutTests; // Challenges - use RecoveryChallengeTests; use MultiFactorChallengeTests; + use RecoveryChallengeTests; use SudoModeChallengeTests; // Settings @@ -399,8 +398,8 @@ class AuthenticationTest extends TestCase use LogoutTests; // Challenges - use RecoveryChallengeTests; use MultiFactorChallengeTests; + use RecoveryChallengeTests; use SudoModeChallengeTests; // Settings @@ -490,8 +489,8 @@ class AuthenticationTest extends TestCase use LogoutTests; // Challenges - use RecoveryChallengeTests; use MultiFactorChallengeTests; + use RecoveryChallengeTests; use SudoModeChallengeTests; // Settings @@ -598,8 +597,8 @@ class AuthenticationTest extends TestCase use LogoutTests; // Challenges - use RecoveryChallengeTests; use MultiFactorChallengeTests; + use RecoveryChallengeTests; use SudoModeChallengeTests; // Settings From 688c49e3427a2461c4d716da1518ba67316943ac Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 18:22:57 +0200 Subject: [PATCH 08/12] WIP --- packages/bladebones/stubs/defaults/routes/web.stub | 6 +++--- .../bladebones/templates/Tests/AuthenticationTest.bladetmpl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bladebones/stubs/defaults/routes/web.stub b/packages/bladebones/stubs/defaults/routes/web.stub index 1e9bb80..6dd2c3d 100644 --- a/packages/bladebones/stubs/defaults/routes/web.stub +++ b/packages/bladebones/stubs/defaults/routes/web.stub @@ -1,10 +1,10 @@ group(function () { }); Route::middleware('auth')->group(function () { - Route::get('/home', fn () => view('home')); + Route::get('/home', fn() => view('home')); Route::delete('/logout', [LoginController::class, 'destroy'])->name('logout'); diff --git a/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl b/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl index aa43bda..5675829 100644 --- a/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl +++ b/packages/bladebones/templates/Tests/AuthenticationTest.bladetmpl @@ -4,8 +4,6 @@ @endphp namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\{{ $verificationTrait }}; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\{{ $flavorTrait }}; @@ -13,6 +11,8 @@ use ClaudioDekker\LaravelAuth\Testing\GenerateRecoveryCodesTests; use ClaudioDekker\LaravelAuth\Testing\LoginTests; use ClaudioDekker\LaravelAuth\Testing\LogoutTests; use ClaudioDekker\LaravelAuth\Testing\MultiFactorChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\RegisterPublicKeyCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegisterTotpCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegistrationTests; From 7a79c270f766b054cb78c7e66e1fddfdfdb6a707 Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 18:25:05 +0200 Subject: [PATCH 09/12] WIP --- .../Feature/Console/GenerateCommandTest.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php index bf0be79..f1913ea 100644 --- a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php +++ b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php @@ -73,8 +73,6 @@ public function it_accepts_all_default_options_without_prompting_when_passing_th namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -82,6 +80,8 @@ public function it_accepts_all_default_options_without_prompting_when_passing_th use ClaudioDekker\LaravelAuth\Testing\LoginTests; use ClaudioDekker\LaravelAuth\Testing\LogoutTests; use ClaudioDekker\LaravelAuth\Testing\MultiFactorChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\RegisterPublicKeyCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegisterTotpCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegistrationTests; @@ -103,8 +103,8 @@ class AuthenticationTest extends TestCase use RegisterWithVerificationEmailTests; // Basic Auth - use RecoveryRequestTests; use RegistrationTests; + use RecoveryRequestTests; use LoginTests; use LogoutTests; @@ -177,8 +177,6 @@ public function it_asks_whether_you_want_to_send_verification_emails_on_registra namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -186,6 +184,8 @@ public function it_asks_whether_you_want_to_send_verification_emails_on_registra use ClaudioDekker\LaravelAuth\Testing\LoginTests; use ClaudioDekker\LaravelAuth\Testing\LogoutTests; use ClaudioDekker\LaravelAuth\Testing\MultiFactorChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\RegisterPublicKeyCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegisterTotpCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegistrationTests; @@ -270,8 +270,6 @@ public function it_does_not_send_verification_emails_on_registration_when_you_an namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithoutVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -279,6 +277,8 @@ public function it_does_not_send_verification_emails_on_registration_when_you_an use ClaudioDekker\LaravelAuth\Testing\LoginTests; use ClaudioDekker\LaravelAuth\Testing\LogoutTests; use ClaudioDekker\LaravelAuth\Testing\MultiFactorChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\RegisterPublicKeyCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegisterTotpCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegistrationTests; @@ -362,8 +362,6 @@ public function it_does_not_send_verification_emails_on_registration_when_the_re namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithoutVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -371,6 +369,8 @@ public function it_does_not_send_verification_emails_on_registration_when_the_re use ClaudioDekker\LaravelAuth\Testing\LoginTests; use ClaudioDekker\LaravelAuth\Testing\LogoutTests; use ClaudioDekker\LaravelAuth\Testing\MultiFactorChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\RegisterPublicKeyCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegisterTotpCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegistrationTests; @@ -455,8 +455,6 @@ public function it_does_not_create_views_and_view_tests_when_the_without_views_f namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\EmailBased; @@ -464,6 +462,8 @@ public function it_does_not_create_views_and_view_tests_when_the_without_views_f use ClaudioDekker\LaravelAuth\Testing\LoginTests; use ClaudioDekker\LaravelAuth\Testing\LogoutTests; use ClaudioDekker\LaravelAuth\Testing\MultiFactorChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\RegisterPublicKeyCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegisterTotpCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegistrationTests; @@ -563,8 +563,6 @@ public function it_asks_what_flavor_of_user_accounts_should_be_used(): void namespace Tests\Feature; -use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; -use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerification\RegisterWithoutVerificationEmailTests; use ClaudioDekker\LaravelAuth\Testing\EmailVerificationTests; use ClaudioDekker\LaravelAuth\Testing\Flavors\UsernameBased; @@ -572,6 +570,8 @@ public function it_asks_what_flavor_of_user_accounts_should_be_used(): void use ClaudioDekker\LaravelAuth\Testing\LoginTests; use ClaudioDekker\LaravelAuth\Testing\LogoutTests; use ClaudioDekker\LaravelAuth\Testing\MultiFactorChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryChallengeTests; +use ClaudioDekker\LaravelAuth\Testing\RecoveryRequestTests; use ClaudioDekker\LaravelAuth\Testing\RegisterPublicKeyCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegisterTotpCredentialTests; use ClaudioDekker\LaravelAuth\Testing\RegistrationTests; From 7de0d22e5d457a5eff1d9ccd36886049de9ab8d3 Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 18:26:39 +0200 Subject: [PATCH 10/12] WIP --- .../bladebones/tests/Feature/Console/GenerateCommandTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php index f1913ea..04e8fa1 100644 --- a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php +++ b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php @@ -103,13 +103,14 @@ class AuthenticationTest extends TestCase use RegisterWithVerificationEmailTests; // Basic Auth - use RegistrationTests; use RecoveryRequestTests; + use RegistrationTests; use LoginTests; use LogoutTests; // Challenges use MultiFactorChallengeTests; + use RecoveryChallengeTests; use SudoModeChallengeTests; // Settings From 71ae504e7cc72fa1cd760fe659099a0b6df32637 Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 18:27:24 +0200 Subject: [PATCH 11/12] WIP --- packages/bladebones/stubs/defaults/routes/web.stub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bladebones/stubs/defaults/routes/web.stub b/packages/bladebones/stubs/defaults/routes/web.stub index 6dd2c3d..76634b5 100644 --- a/packages/bladebones/stubs/defaults/routes/web.stub +++ b/packages/bladebones/stubs/defaults/routes/web.stub @@ -56,7 +56,7 @@ Route::middleware('guest')->group(function () { }); Route::middleware('auth')->group(function () { - Route::get('/home', fn() => view('home')); + Route::get('/home', fn () => view('home')); Route::delete('/logout', [LoginController::class, 'destroy'])->name('logout'); From 13a68183e7a89d13d8b24416cf28716f831ffb3f Mon Sep 17 00:00:00 2001 From: Claudio Dekker Date: Sun, 6 Aug 2023 23:18:14 +0200 Subject: [PATCH 12/12] WIP --- .../Partials/RecoveryChallengeViewTests.php | 2 +- .../RecoveryChallengeController.bladetmpl | 26 ++++++++++- .../RecoveryRequestController.bladetmpl | 6 ++- .../Feature/Console/GenerateCommandTest.php | 2 +- packages/core/lang/en/auth.php | 4 +- packages/core/src/Console/GenerateCommand.php | 27 +++++++---- .../Login/PasswordBasedAuthentication.php | 2 +- .../RecoveryChallengeController.php | 25 ++++++---- .../src/Http/Controllers/LoginController.php | 13 ++++-- .../Controllers/RecoveryRequestController.php | 11 +++-- .../core/src/Http/Modifiers/EmailBased.php | 14 +++--- .../core/src/Http/Modifiers/UsernameBased.php | 16 +++---- .../core/src/Testing/Flavors/EmailBased.php | 20 +------- .../src/Testing/Flavors/UsernameBased.php | 18 -------- packages/core/src/Testing/Helpers.php | 25 ++++++++++ .../Recovery/SubmitRecoveryChallengeTests.php | 46 +++++++++---------- .../ViewRecoveryChallengePageTests.php | 24 +++++----- ...SubmitPasswordBasedAuthenticationTests.php | 14 ++++++ .../SubmitPasswordBasedRegistrationTests.php | 14 ++++++ .../Partials/SubmitRecoveryRequestTests.php | 26 +++++------ .../AccountRecoveryNotification.bladetmpl} | 16 ++++--- 21 files changed, 211 insertions(+), 140 deletions(-) rename packages/core/{src/Notifications/AccountRecoveryNotification.php => templates/Notifications/AccountRecoveryNotification.bladetmpl} (81%) diff --git a/packages/bladebones/src/Testing/Partials/RecoveryChallengeViewTests.php b/packages/bladebones/src/Testing/Partials/RecoveryChallengeViewTests.php index 6788530..5e111b2 100644 --- a/packages/bladebones/src/Testing/Partials/RecoveryChallengeViewTests.php +++ b/packages/bladebones/src/Testing/Partials/RecoveryChallengeViewTests.php @@ -14,7 +14,7 @@ public function the_account_recovery_challenge_page_uses_blade_views(): void $response = $this->get(route('recover-account.challenge', [ 'token' => $token, - 'email' => $user->getEmailForPasswordReset(), + $this->usernameField() => $user->{$this->usernameField()}, ])); $response->assertViewIs('auth.challenges.recovery'); diff --git a/packages/bladebones/templates/Controllers/Challenges/RecoveryChallengeController.bladetmpl b/packages/bladebones/templates/Controllers/Challenges/RecoveryChallengeController.bladetmpl index 19bc27b..7a82ad5 100644 --- a/packages/bladebones/templates/Controllers/Challenges/RecoveryChallengeController.bladetmpl +++ b/packages/bladebones/templates/Controllers/Challenges/RecoveryChallengeController.bladetmpl @@ -1,7 +1,13 @@ +@php + $flavorTrait = str_replace("-", "", \Illuminate\Support\Str::title($flavor)); +@endphp namespace App\Http\Controllers\Auth\Challenges; use ClaudioDekker\LaravelAuth\Http\Controllers\Challenges\RecoveryChallengeController as BaseController; +@if ($flavor !== 'email-based') +use ClaudioDekker\LaravelAuth\Http\Modifiers\{{ $flavorTrait }}; +@endif use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; @@ -10,6 +16,20 @@ use Illuminate\Validation\ValidationException; class RecoveryChallengeController extends BaseController { +@php + $uses = []; + if ($flavor !== 'email-based') { + $uses[] = $flavorTrait; + } + + asort($uses); +@endphp +@if (count($uses) > 0) +@foreach($uses as $use) + use {{ $use }}; +@endforeach + +@endif /** * Handle an incoming request to view the account recovery challenge page. * @@ -52,7 +72,11 @@ class RecoveryChallengeController extends BaseController */ protected function sendInvalidRecoveryLinkResponse(Request $request): void { - abort(403, __('laravel-auth::auth.recovery.invalid')); +@if ($flavor === 'username-based') + abort(403, __('laravel-auth::auth.recovery.invalid', ['field' => 'username'])); +@else + abort(403, __('laravel-auth::auth.recovery.invalid', ['field' => 'email'])); +@endif } /** diff --git a/packages/bladebones/templates/Controllers/RecoveryRequestController.bladetmpl b/packages/bladebones/templates/Controllers/RecoveryRequestController.bladetmpl index 0c76b48..8b3770f 100644 --- a/packages/bladebones/templates/Controllers/RecoveryRequestController.bladetmpl +++ b/packages/bladebones/templates/Controllers/RecoveryRequestController.bladetmpl @@ -79,7 +79,11 @@ class RecoveryRequestController extends BaseController */ public function sendRecoveryLinkSentResponse(Request $request): RedirectResponse { - return redirect()->back()->with('status', __('laravel-auth::auth.recovery.sent')); +@if ($flavor === 'username-based') + return redirect()->back()->with('status', __('laravel-auth::auth.recovery.sent', ['field' => 'username'])); +@else + return redirect()->back()->with('status', __('laravel-auth::auth.recovery.sent', ['field' => 'email'])); +@endif } /** diff --git a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php index 04e8fa1..1e8869f 100644 --- a/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php +++ b/packages/bladebones/tests/Feature/Console/GenerateCommandTest.php @@ -334,7 +334,7 @@ protected function setUp(): void }); $this->mock->shouldIgnoreMissing(); - $this->artisan(GenerateCommand::class, ['--without-views' => true, '--kind' => 'email-based']) + $this->artisan(GenerateCommand::class, ['--without-views' => true, '--kind' => 'email-based']) ->expectsConfirmation('Do you want to send a verification email when users register?', 'no'); } diff --git a/packages/core/lang/en/auth.php b/packages/core/lang/en/auth.php index e5b2959..63fa66c 100644 --- a/packages/core/lang/en/auth.php +++ b/packages/core/lang/en/auth.php @@ -32,8 +32,8 @@ 'totp-registered' => 'Time-based one-time-password credential successfully registered.', ], 'recovery' => [ - 'sent' => 'If the provided email address is associated with an account, you will receive a recovery link shortly.', - 'invalid' => 'The provided email and recovery token combination are invalid.', + 'sent' => 'If the provided :field is associated with an account, you will receive a recovery link shortly.', + 'invalid' => 'The provided :field and recovery token combination are invalid.', 'throttle' => 'Too many recovery requests. Please try again in :seconds seconds.', ], 'verification' => [ diff --git a/packages/core/src/Console/GenerateCommand.php b/packages/core/src/Console/GenerateCommand.php index 9d3d283..c77c3a7 100644 --- a/packages/core/src/Console/GenerateCommand.php +++ b/packages/core/src/Console/GenerateCommand.php @@ -82,6 +82,7 @@ protected function install(): void { $this->installRoutes(); $this->installControllers(); + $this->installNotifications(); $this->installTests(); if (! $this->determinedOptions['withoutViews']) { @@ -101,15 +102,6 @@ abstract protected function installRoutes(): void; */ abstract protected function installViews(): void; - /** - * Installs the extending package's authentication tests. - */ - protected function installTests(): void - { - $this->rawGenerate('Tests.PruneUnclaimedUsersTest', base_path('tests/Unit/PruneUnclaimedUsersTest.php')); - $this->rawGenerate('Tests.UserTest', base_path('tests/Unit/UserTest.php')); - } - /** * Installs the extending package's authentication controllers. */ @@ -129,6 +121,23 @@ protected function installControllers(): void $this->generate('Controllers.Settings.RegisterTotpCredentialController', app_path('Http/Controllers/Auth/Settings/RegisterTotpCredentialController.php')); } + /** + * Installs the extending package's authentication notifications. + */ + protected function installNotifications(): void + { + $this->rawGenerate('Notifications.AccountRecoveryNotification', app_path('Notifications/AccountRecoveryNotification.php')); + } + + /** + * Installs the extending package's authentication tests. + */ + protected function installTests(): void + { + $this->rawGenerate('Tests.PruneUnclaimedUsersTest', base_path('tests/Unit/PruneUnclaimedUsersTest.php')); + $this->rawGenerate('Tests.UserTest', base_path('tests/Unit/UserTest.php')); + } + /** * Overrides some of the files in Laravel's application scaffolding with the core package's own versions. */ diff --git a/packages/core/src/Http/Concerns/Login/PasswordBasedAuthentication.php b/packages/core/src/Http/Concerns/Login/PasswordBasedAuthentication.php index 3e78322..7e1c2f4 100644 --- a/packages/core/src/Http/Concerns/Login/PasswordBasedAuthentication.php +++ b/packages/core/src/Http/Concerns/Login/PasswordBasedAuthentication.php @@ -80,7 +80,7 @@ protected function handlePasswordBasedAuthenticationRequest(Request $request) protected function validatePasswordBasedRequest(Request $request): void { $request->validate([ - ...$this->authenticationValidationRules(), + $this->usernameField() => ['required', ...$this->usernameValidationRules()], 'password' => 'required|string', ]); } diff --git a/packages/core/src/Http/Controllers/Challenges/RecoveryChallengeController.php b/packages/core/src/Http/Controllers/Challenges/RecoveryChallengeController.php index 1f558a7..9d22cf4 100644 --- a/packages/core/src/Http/Controllers/Challenges/RecoveryChallengeController.php +++ b/packages/core/src/Http/Controllers/Challenges/RecoveryChallengeController.php @@ -5,6 +5,7 @@ use ClaudioDekker\LaravelAuth\Events\AccountRecoveryFailed; use ClaudioDekker\LaravelAuth\Events\Mixins\EmitsLockoutEvent; use ClaudioDekker\LaravelAuth\Http\Concerns\InteractsWithRateLimiting; +use ClaudioDekker\LaravelAuth\Http\Modifiers\EmailBased; use ClaudioDekker\LaravelAuth\LaravelAuth; use ClaudioDekker\LaravelAuth\RecoveryCodeManager; use Illuminate\Cache\RateLimiting\Limit; @@ -18,6 +19,7 @@ abstract class RecoveryChallengeController { + use EmailBased; use EmitsLockoutEvent; use InteractsWithRateLimiting; @@ -52,11 +54,11 @@ abstract protected function sendRecoveryModeEnabledResponse(Request $request, Au /** * Handle an incoming request to view the account recovery challenge page. * - * @return mixed - * * @see static::sendInvalidRecoveryLinkResponse() * @see static::sendChallengePageResponse() * @see static::sendRecoveryModeEnabledResponse() + * + * @return mixed */ public function create(Request $request, string $token) { @@ -88,12 +90,12 @@ public function create(Request $request, string $token) /** * Handle an incoming account recovery challenge response. * - * @return mixed - * * @see static::sendRateLimitedResponse() * @see static::sendInvalidRecoveryLinkResponse() * @see static::sendInvalidRecoveryCodeResponse() * @see static::sendRecoveryModeEnabledResponse() + * + * @return mixed */ public function store(Request $request, string $token) { @@ -156,7 +158,7 @@ protected function resolveUser(Request $request): ?Authenticatable $query = LaravelAuth::userModel()::query(); return $query - ->where('email', $request->input('email')) + ->where($this->usernameField(), $request->input($this->usernameField())) ->first(); } @@ -228,10 +230,17 @@ protected function rateLimits(Request $request): array Limit::perMinute(5)->by('ip::'.$request->ip()), ]; - if ($request->has('email')) { - $limits[] = Limit::perMinute(5)->by('email::'.Str::transliterate(Str::lower($request->input('email')))); + if (! $request->has($this->usernameField())) { + return $limits; } - return $limits; + if (! is_string($username = $request->input($this->usernameField()))) { + return $limits; + } + + return [ + ...$limits, + Limit::perMinute(5)->by('username::'.Str::transliterate(Str::lower($username))), + ]; } } diff --git a/packages/core/src/Http/Controllers/LoginController.php b/packages/core/src/Http/Controllers/LoginController.php index 917eeae..2b5dfee 100644 --- a/packages/core/src/Http/Controllers/LoginController.php +++ b/packages/core/src/Http/Controllers/LoginController.php @@ -152,10 +152,17 @@ protected function rateLimits(Request $request): array Limit::perMinute(5)->by('ip::'.$request->ip()), ]; - if ($this->isPasswordBasedAuthenticationAttempt($request)) { - $limits[] = Limit::perMinute(5)->by('username::'.Str::transliterate(Str::lower($request->input($this->usernameField())))); + if (! $this->isPasswordBasedAuthenticationAttempt($request)) { + return $limits; + } + + if (! is_string($username = $request->input($this->usernameField()))) { + return $limits; } - return $limits; + return [ + ...$limits, + Limit::perMinute(5)->by('username::'.Str::transliterate(Str::lower($username))), + ]; } } diff --git a/packages/core/src/Http/Controllers/RecoveryRequestController.php b/packages/core/src/Http/Controllers/RecoveryRequestController.php index c6a059a..cf3a2f7 100644 --- a/packages/core/src/Http/Controllers/RecoveryRequestController.php +++ b/packages/core/src/Http/Controllers/RecoveryRequestController.php @@ -2,10 +2,11 @@ namespace ClaudioDekker\LaravelAuth\Http\Controllers; +use App\Notifications\AccountRecoveryNotification; use ClaudioDekker\LaravelAuth\Events\Mixins\EmitsLockoutEvent; use ClaudioDekker\LaravelAuth\Http\Concerns\InteractsWithRateLimiting; +use ClaudioDekker\LaravelAuth\Http\Modifiers\EmailBased; use ClaudioDekker\LaravelAuth\LaravelAuth; -use ClaudioDekker\LaravelAuth\Notifications\AccountRecoveryNotification; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\CanResetPassword; use Illuminate\Http\Request; @@ -15,6 +16,7 @@ abstract class RecoveryRequestController { + use EmailBased; use InteractsWithRateLimiting; use EmitsLockoutEvent; @@ -79,8 +81,9 @@ public function store(Request $request) return $this->sendRateLimitedResponse($request, $this->rateLimitExpiresInSeconds($request)); } + $this->incrementRateLimitingCounter($request); + return App::make(Timebox::class)->call(function () use ($request) { - $this->incrementRateLimitingCounter($request); $this->validateRecoveryRequest($request); if (! $user = $this->getUser($request)) { @@ -106,7 +109,7 @@ public function store(Request $request) protected function validateRecoveryRequest(Request $request): void { $request->validate([ - 'email' => 'required|email', + $this->usernameField() => ['required', ...$this->usernameValidationRules()], ]); } @@ -119,7 +122,7 @@ protected function getUser(Request $request): ?CanResetPassword $query = LaravelAuth::userModel()::query(); return $query - ->where('email', $request->input('email')) + ->where($this->usernameField(), $request->input($this->usernameField())) ->first(); } diff --git a/packages/core/src/Http/Modifiers/EmailBased.php b/packages/core/src/Http/Modifiers/EmailBased.php index a942ada..1df1b18 100644 --- a/packages/core/src/Http/Modifiers/EmailBased.php +++ b/packages/core/src/Http/Modifiers/EmailBased.php @@ -13,22 +13,20 @@ protected function usernameField(): string } /** - * Any flavor-specific validation rules used to validate a registration request. + * Any flavor-specific validation rules used to validate requests that require the username. */ - protected function registrationValidationRules(): array + protected function usernameValidationRules(): array { - return [ - $this->usernameField() => ['required', 'max:255', 'unique:users', 'email'], - ]; + return ['max:255', 'email']; } /** - * Any flavor-specific validation rules used to validate an authentication request. + * Any flavor-specific validation rules used to validate a registration request. */ - protected function authenticationValidationRules(): array + protected function registrationValidationRules(): array { return [ - $this->usernameField() => ['required', 'email'], + $this->usernameField() => ['required', 'unique:users', ...$this->usernameValidationRules()], ]; } } diff --git a/packages/core/src/Http/Modifiers/UsernameBased.php b/packages/core/src/Http/Modifiers/UsernameBased.php index 66279b7..bfd367b 100644 --- a/packages/core/src/Http/Modifiers/UsernameBased.php +++ b/packages/core/src/Http/Modifiers/UsernameBased.php @@ -13,23 +13,21 @@ protected function usernameField(): string } /** - * Any flavor-specific validation rules used to validate a registration request. + * Any flavor-specific validation rules used to validate requests that require the username. */ - protected function registrationValidationRules(): array + protected function usernameValidationRules(): array { - return [ - $this->usernameField() => ['required', 'max:255', 'unique:users'], - 'email' => ['required', 'max:255', 'email'], - ]; + return ['max:255', 'string']; } /** - * Any flavor-specific validation rules used to validate an authentication request. + * Any flavor-specific validation rules used to validate a registration request. */ - protected function authenticationValidationRules(): array + protected function registrationValidationRules(): array { return [ - $this->usernameField() => ['required'], + $this->usernameField() => ['required', 'unique:users', ...$this->usernameValidationRules()], + 'email' => ['required', 'max:255', 'email'], ]; } } diff --git a/packages/core/src/Testing/Flavors/EmailBased.php b/packages/core/src/Testing/Flavors/EmailBased.php index 310e845..2ffb788 100644 --- a/packages/core/src/Testing/Flavors/EmailBased.php +++ b/packages/core/src/Testing/Flavors/EmailBased.php @@ -40,27 +40,9 @@ protected function tooLongUsername(): string return str_repeat('a', 256).'@example.com'; } - protected function assertUsernameRequiredValidationError(TestResponse $response): void - { - $this->assertInstanceOf(ValidationException::class, $response->exception); - $this->assertSame([$this->usernameField() => [__('validation.required', ['attribute' => 'email'])]], $response->exception->errors()); - } - protected function assertUsernameMustBeValidValidationError(TestResponse $response): void { $this->assertInstanceOf(ValidationException::class, $response->exception); - $this->assertSame([$this->usernameField() => [__('validation.email', ['attribute' => 'email'])]], $response->exception->errors()); - } - - protected function assertUsernameTooLongValidationError(TestResponse $response): void - { - $this->assertInstanceOf(ValidationException::class, $response->exception); - $this->assertSame([$this->usernameField() => [__('validation.max.string', ['attribute' => 'email', 'max' => 255])]], $response->exception->errors()); - } - - protected function assertUsernameAlreadyExistsValidationError(TestResponse $response): void - { - $this->assertInstanceOf(ValidationException::class, $response->exception); - $this->assertSame([$this->usernameField() => [__('validation.unique', ['attribute' => 'email'])]], $response->exception->errors()); + $this->assertSame([$this->usernameField() => [__('validation.email', ['attribute' => $this->usernameField()])]], $response->exception->errors()); } } diff --git a/packages/core/src/Testing/Flavors/UsernameBased.php b/packages/core/src/Testing/Flavors/UsernameBased.php index ad00a6f..2d78397 100644 --- a/packages/core/src/Testing/Flavors/UsernameBased.php +++ b/packages/core/src/Testing/Flavors/UsernameBased.php @@ -40,27 +40,9 @@ protected function tooLongUsername(): string return str_repeat('a', 256); } - protected function assertUsernameRequiredValidationError(TestResponse $response): void - { - $this->assertInstanceOf(ValidationException::class, $response->exception); - $this->assertSame([$this->usernameField() => [__('validation.required', ['attribute' => 'username'])]], $response->exception->errors()); - } - protected function assertUsernameMustBeValidValidationError(TestResponse $response): void { $this->assertInstanceOf(ValidationException::class, $response->exception); $this->assertSame([$this->usernameField() => [__('laravel-auth::auth.failed')]], $response->exception->errors()); } - - protected function assertUsernameTooLongValidationError(TestResponse $response): void - { - $this->assertInstanceOf(ValidationException::class, $response->exception); - $this->assertSame([$this->usernameField() => [__('validation.max.string', ['attribute' => 'username', 'max' => 255])]], $response->exception->errors()); - } - - protected function assertUsernameAlreadyExistsValidationError(TestResponse $response): void - { - $this->assertInstanceOf(ValidationException::class, $response->exception); - $this->assertSame([$this->usernameField() => [__('validation.unique', ['attribute' => 'username'])]], $response->exception->errors()); - } } diff --git a/packages/core/src/Testing/Helpers.php b/packages/core/src/Testing/Helpers.php index b8e04d2..708542a 100644 --- a/packages/core/src/Testing/Helpers.php +++ b/packages/core/src/Testing/Helpers.php @@ -22,6 +22,7 @@ use Illuminate\Support\Str; use Illuminate\Support\Timebox; use Illuminate\Testing\TestResponse; +use Illuminate\Validation\ValidationException; use Mockery\MockInterface; trait Helpers @@ -134,6 +135,30 @@ protected function assertMissingRememberCookie(TestResponse $response, Authentic }), 'Remember cookie not found.'); } + protected function assertUsernameRequiredValidationError(TestResponse $response): void + { + $this->assertInstanceOf(ValidationException::class, $response->exception); + $this->assertSame([$this->usernameField() => [__('validation.required', ['attribute' => $this->usernameField()])]], $response->exception->errors()); + } + + protected function assertUsernameMustBeAStringValidationError(TestResponse $response): void + { + $this->assertInstanceOf(ValidationException::class, $response->exception); + $this->assertSame([$this->usernameField() => [__('validation.string', ['attribute' => $this->usernameField()])]], $response->exception->errors()); + } + + protected function assertUsernameTooLongValidationError(TestResponse $response): void + { + $this->assertInstanceOf(ValidationException::class, $response->exception); + $this->assertSame([$this->usernameField() => [__('validation.max.string', ['attribute' => $this->usernameField(), 'max' => 255])]], $response->exception->errors()); + } + + protected function assertUsernameAlreadyExistsValidationError(TestResponse $response): void + { + $this->assertInstanceOf(ValidationException::class, $response->exception); + $this->assertSame([$this->usernameField() => [__('validation.unique', ['attribute' => $this->usernameField()])]], $response->exception->errors()); + } + protected function mockWebauthnChallenge($challenge): void { $this->partialMock( diff --git a/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitRecoveryChallengeTests.php b/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitRecoveryChallengeTests.php index 59aa823..f6bcc8b 100644 --- a/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitRecoveryChallengeTests.php +++ b/packages/core/src/Testing/Partials/Challenges/Recovery/SubmitRecoveryChallengeTests.php @@ -25,7 +25,7 @@ public function the_user_can_begin_to_recover_the_account(): void $this->expectTimeboxWithEarlyReturn(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $email = $user->getEmailForPasswordReset(), + $this->usernameField() => $username = $user->{$this->usernameField()}, 'code' => 'PIPIM-7LTUT', ]); @@ -37,7 +37,7 @@ public function the_user_can_begin_to_recover_the_account(): void $this->assertSame(['H4PFK-ENVZV', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P'], $user->fresh()->recovery_codes); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); - $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); + $this->assertSame(0, $this->getRateLimitAttempts('username::'.$username)); Event::assertNothingDispatched(); Carbon::setTestNow(); } @@ -54,7 +54,7 @@ public function the_account_recovery_challenge_code_verification_request_accepts $this->expectTimeboxWithEarlyReturn(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $email = $user->getEmailForPasswordReset(), + $this->usernameField() => $username = $user->{$this->usernameField()}, 'code' => 'INVLD-CODES', ]); @@ -65,7 +65,7 @@ public function the_account_recovery_challenge_code_verification_request_accepts $this->assertFalse($repository->exists($user, $token)); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); - $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); + $this->assertSame(0, $this->getRateLimitAttempts('username::'.$username)); Event::assertNothingDispatched(); Carbon::setTestNow(); } @@ -80,7 +80,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_already_authen $this->assertTrue($repository->exists($userB, $token)); $response = $this->actingAs($userA)->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $userB->getEmailForPasswordReset(), + $this->usernameField() => $userB->{$this->usernameField()}, 'code' => 'PIPIM-7LTUT', ]); @@ -93,7 +93,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_already_authen } /** @test */ - public function the_user_cannot_begin_to_recover_the_account_when_the_provided_email_does_not_resolve_to_an_existing_user(): void + public function the_user_cannot_begin_to_recover_the_account_when_the_provided_username_does_not_resolve_to_an_existing_user(): void { Event::fake([Lockout::class, AccountRecoveryFailed::class]); $user = $this->generateUser(['recovery_codes' => $codes = ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); @@ -103,7 +103,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_provided_e $this->expectTimebox(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $email = 'nonexistent-user@example.com', + $this->usernameField() => $username = $this->nonExistentUsername(), 'code' => 'PIPIM-7LTUT', ]); @@ -112,12 +112,12 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_provided_e $response->assertSessionMissing('auth.recovery_mode.user_id'); $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid', ['field' => $this->usernameField()]), $response->exception->getMessage()); $this->assertTrue($repository->exists($user, $token)); $this->assertSame($codes, $user->fresh()->recovery_codes); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); - $this->assertSame(1, $this->getRateLimitAttempts('email::'.$email)); + $this->assertSame(1, $this->getRateLimitAttempts('username::'.$username)); Event::assertNothingDispatched(); } @@ -132,7 +132,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_recovery_t $this->expectTimebox(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $email = $userB->getEmailForPasswordReset(), + $this->usernameField() => $username = $userB->{$this->usernameField()}, 'code' => 'PIPIM-7LTUT', ]); @@ -141,12 +141,12 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_recovery_t $response->assertSessionMissing('auth.recovery_mode.user_id'); $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid', ['field' => $this->usernameField()]), $response->exception->getMessage()); $this->assertTrue($repository->exists($userA, $token)); $this->assertSame($codes, $userA->fresh()->recovery_codes); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); - $this->assertSame(1, $this->getRateLimitAttempts('email::'.$email)); + $this->assertSame(1, $this->getRateLimitAttempts('username::'.$username)); Event::assertNothingDispatched(); } @@ -158,7 +158,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_recovery_t $this->expectTimebox(); $response = $this->post(route('recover-account.challenge', ['token' => 'invalid-token']), [ - 'email' => $email = $user->getEmailForPasswordReset(), + $this->usernameField() => $username = $user->{$this->usernameField()}, 'code' => 'PIPIM-7LTUT', ]); @@ -167,11 +167,11 @@ public function the_user_cannot_begin_to_recover_the_account_when_the_recovery_t $response->assertSessionMissing('auth.recovery_mode.user_id'); $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid', ['field' => $this->usernameField()]), $response->exception->getMessage()); $this->assertSame($codes, $user->fresh()->recovery_codes); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); - $this->assertSame(1, $this->getRateLimitAttempts('email::'.$email)); + $this->assertSame(1, $this->getRateLimitAttempts('username::'.$username)); Event::assertNothingDispatched(); } @@ -186,7 +186,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_an_invalid_rec $this->expectTimebox(); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $email = $user->getEmailForPasswordReset(), + $this->usernameField() => $username = $user->{$this->usernameField()}, 'code' => 'INV4L-1DCD3', ]); @@ -199,7 +199,7 @@ public function the_user_cannot_begin_to_recover_the_account_when_an_invalid_rec $this->assertSame($codes, $user->fresh()->recovery_codes); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); - $this->assertSame(1, $this->getRateLimitAttempts('email::'.$email)); + $this->assertSame(1, $this->getRateLimitAttempts('username::'.$username)); Event::assertDispatched(AccountRecoveryFailed::class, fn (AccountRecoveryFailed $event) => $event->user->is($user)); Event::assertNotDispatched(Lockout::class); } @@ -216,7 +216,7 @@ public function account_recovery_challenge_requests_are_rate_limited_after_too_m $this->hitRateLimiter(250, ''); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $user->getEmailForPasswordReset(), + $this->usernameField() => $user->{$this->usernameField()}, 'code' => 'PIPIM-7LTUT', ]); @@ -240,7 +240,7 @@ public function account_recovery_challenge_requests_are_rate_limited_after_too_m $this->hitRateLimiter(5, 'ip::127.0.0.1'); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $user->getEmailForPasswordReset(), + $this->usernameField() => $user->{$this->usernameField()}, 'code' => 'PIPIM-7LTUT', ]); @@ -253,7 +253,7 @@ public function account_recovery_challenge_requests_are_rate_limited_after_too_m } /** @test */ - public function account_recovery_challenge_requests_are_rate_limited_after_too_many_failed_requests_for_one_email_address(): void + public function account_recovery_challenge_requests_are_rate_limited_after_too_many_failed_requests_for_one_username(): void { Carbon::setTestNow(now()); Event::fake([Lockout::class]); @@ -261,11 +261,11 @@ public function account_recovery_challenge_requests_are_rate_limited_after_too_m $repository = Password::getRepository(); $token = $repository->create($user); $this->assertTrue($repository->exists($user, $token)); - $email = $user->getEmailForPasswordReset(); - $this->hitRateLimiter(5, 'email::'.$email); + $username = $user->{$this->usernameField()}; + $this->hitRateLimiter(5, 'username::'.$username); $response = $this->post(route('recover-account.challenge', ['token' => $token]), [ - 'email' => $email, + $this->usernameField() => $username, 'code' => 'PIPIM-7LTUT', ]); diff --git a/packages/core/src/Testing/Partials/Challenges/Recovery/ViewRecoveryChallengePageTests.php b/packages/core/src/Testing/Partials/Challenges/Recovery/ViewRecoveryChallengePageTests.php index aa34e4f..f8be382 100644 --- a/packages/core/src/Testing/Partials/Challenges/Recovery/ViewRecoveryChallengePageTests.php +++ b/packages/core/src/Testing/Partials/Challenges/Recovery/ViewRecoveryChallengePageTests.php @@ -21,7 +21,7 @@ public function the_account_recovery_challenge_page_can_be_viewed(): void $response = $this->get(route('recover-account.challenge', [ 'token' => $token, - 'email' => $user->getEmailForPasswordReset(), + $this->usernameField() => $user->{$this->usernameField()}, ])); $response->assertOk(); @@ -40,7 +40,7 @@ public function the_account_recovery_challenge_page_is_skipped_when_the_user_doe $response = $this->get(route('recover-account.challenge', [ 'token' => $token, - 'email' => $email = $user->getEmailForPasswordReset(), + $this->usernameField() => $username = $user->{$this->usernameField()}, ])); $this->assertGuest(); @@ -50,7 +50,7 @@ public function the_account_recovery_challenge_page_is_skipped_when_the_user_doe $this->assertFalse($repository->exists($user, $token)); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); - $this->assertSame(0, $this->getRateLimitAttempts('email::'.$email)); + $this->assertSame(0, $this->getRateLimitAttempts('username::'.$username)); Event::assertNothingDispatched(); Carbon::setTestNow(); } @@ -67,7 +67,7 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_authen } /** @test */ - public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_provided_email_does_not_resolve_to_an_existing_user(): void + public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_provided_username_does_not_resolve_to_an_existing_user(): void { $user = $this->generateUser(['recovery_codes' => ['H4PFK-ENVZV', 'PIPIM-7LTUT', 'GPP13-AEXMR', 'WGAHD-95VNQ', 'BSFYG-VFG2N', 'AWOPQ-NWYJX', '2PVJM-QHPBM', 'STR7J-5ND0P']]); $token = Password::getRepository()->create($user); @@ -75,14 +75,14 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_pr $response = $this->get(route('recover-account.challenge', [ 'token' => $token, - 'email' => 'nonexistent-user@example.com', + $this->usernameField() => $this->nonExistentUsername(), ])); $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid', ['field' => $this->usernameField()]), $response->exception->getMessage()); } /** @test */ @@ -95,14 +95,14 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re $response = $this->get(route('recover-account.challenge', [ 'token' => $token, - 'email' => $userB->getEmailForPasswordReset(), + $this->usernameField() => $userB->{$this->usernameField()}, ])); $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid', ['field' => $this->usernameField()]), $response->exception->getMessage()); } /** @test */ @@ -113,14 +113,14 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re $response = $this->get(route('recover-account.challenge', [ 'token' => 'invalid-token', - 'email' => $user->getEmailForPasswordReset(), + $this->usernameField() => $user->{$this->usernameField()}, ])); $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid', ['field' => $this->usernameField()]), $response->exception->getMessage()); } /** @test */ @@ -134,14 +134,14 @@ public function the_account_recovery_challenge_page_cannot_be_viewed_when_the_re $response = $this->get(route('recover-account.challenge', [ 'token' => $token, - 'email' => $user->getEmailForPasswordReset(), + $this->usernameField() => $user->{$this->usernameField()}, ])); $response->assertForbidden(); $response->assertSessionMissing('auth.recovery_mode.user_id'); $response->assertSessionMissing('auth.recovery_mode.enabled_at'); $this->assertInstanceOf(HttpException::class, $response->exception); - $this->assertSame(__('laravel-auth::auth.recovery.invalid'), $response->exception->getMessage()); + $this->assertSame(__('laravel-auth::auth.recovery.invalid', ['field' => $this->usernameField()]), $response->exception->getMessage()); Carbon::setTestNow(); } } diff --git a/packages/core/src/Testing/Partials/SubmitPasswordBasedAuthenticationTests.php b/packages/core/src/Testing/Partials/SubmitPasswordBasedAuthenticationTests.php index 4d19241..35a3a50 100644 --- a/packages/core/src/Testing/Partials/SubmitPasswordBasedAuthenticationTests.php +++ b/packages/core/src/Testing/Partials/SubmitPasswordBasedAuthenticationTests.php @@ -10,6 +10,7 @@ use ClaudioDekker\LaravelAuth\Http\Middleware\EnsureSudoMode; use ClaudioDekker\LaravelAuth\LaravelAuth; use Illuminate\Auth\Events\Lockout; +use Illuminate\Http\UploadedFile; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Redirect; @@ -65,6 +66,19 @@ public function it_validates_that_the_username_is_required_during_password_based $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); } + /** @test */ + public function it_validates_that_the_username_is_a_string_during_password_based_authentication(): void + { + $this->expectTimebox(); + + $response = $this->submitPasswordBasedLoginAttempt([$this->usernameField() => UploadedFile::fake()->image('username.jpg')]); + + $this->assertUsernameMustBeAStringValidationError($response); + $this->assertGuest(); + $this->assertSame(1, $this->getRateLimitAttempts('')); + $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); + } + /** @test */ public function it_validates_that_the_username_is_valid_during_password_based_authentication(): void { diff --git a/packages/core/src/Testing/Partials/SubmitPasswordBasedRegistrationTests.php b/packages/core/src/Testing/Partials/SubmitPasswordBasedRegistrationTests.php index a737f24..aab0897 100644 --- a/packages/core/src/Testing/Partials/SubmitPasswordBasedRegistrationTests.php +++ b/packages/core/src/Testing/Partials/SubmitPasswordBasedRegistrationTests.php @@ -8,6 +8,7 @@ use ClaudioDekker\LaravelAuth\LaravelAuth; use Illuminate\Auth\Events\Lockout; use Illuminate\Auth\Events\Registered; +use Illuminate\Http\UploadedFile; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Event; use Illuminate\Validation\ValidationException; @@ -118,6 +119,19 @@ public function it_validates_that_the_username_does_not_exceed_255_characters_du $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); } + /** @test */ + public function it_validates_that_the_username_is_a_string_during_password_based_registration(): void + { + $this->expectTimebox(); + + $response = $this->submitPasswordBasedRegisterAttempt([$this->usernameField() => UploadedFile::fake()->image('username.jpg')]); + + $this->assertUsernameMustBeAStringValidationError($response); + $this->assertCount(0, LaravelAuth::userModel()::all()); + $this->assertSame(1, $this->getRateLimitAttempts('')); + $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); + } + /** @test */ public function it_validates_that_the_email_is_required_during_password_based_registration(): void { diff --git a/packages/core/src/Testing/Partials/SubmitRecoveryRequestTests.php b/packages/core/src/Testing/Partials/SubmitRecoveryRequestTests.php index c5f6467..9808842 100644 --- a/packages/core/src/Testing/Partials/SubmitRecoveryRequestTests.php +++ b/packages/core/src/Testing/Partials/SubmitRecoveryRequestTests.php @@ -2,8 +2,8 @@ namespace ClaudioDekker\LaravelAuth\Testing\Partials; +use App\Notifications\AccountRecoveryNotification; use App\Providers\RouteServiceProvider; -use ClaudioDekker\LaravelAuth\Notifications\AccountRecoveryNotification; use Illuminate\Auth\Events\Lockout; use Illuminate\Http\Request; use Illuminate\Support\Carbon; @@ -26,11 +26,11 @@ public function guests_can_request_an_account_recovery_link(): void $this->expectTimebox(); $response = $this->from('/foo')->post(route('recover-account'), [ - 'email' => $user->email, + $this->usernameField() => $this->defaultUsername(), ]); $response->assertRedirect('/foo'); - $response->assertSessionHas('status', __('laravel-auth::auth.recovery.sent')); + $response->assertSessionHas('status', __('laravel-auth::auth.recovery.sent', ['field' => $this->usernameField()])); $this->assertTrue($repository->recentlyCreatedToken($user)); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); @@ -39,7 +39,7 @@ public function guests_can_request_an_account_recovery_link(): void $route = Route::getRoutes()->match($action); $this->assertSame('recover-account.challenge', $route->getName()); - $this->assertSame($user->email, $action->query('email')); + $this->assertSame($user->{$this->usernameField()}, $action->query($this->usernameField())); $this->assertTrue($repository->exists($user, $route->parameter('token'))); return true; @@ -53,7 +53,7 @@ public function users_cannot_request_an_account_recovery_link_when_authenticated $this->actingAs($user = $this->generateUser()); $response = $this->post(route('recover-account'), [ - 'email' => $user->email, + $this->usernameField() => $this->defaultUsername(), ]); $response->assertRedirect(RouteServiceProvider::HOME); @@ -62,7 +62,7 @@ public function users_cannot_request_an_account_recovery_link_when_authenticated } /** @test */ - public function it_validates_that_the_email_is_required_when_requesting_an_account_recovery_link(): void + public function it_validates_that_the_username_is_required_when_requesting_an_account_recovery_link(): void { Notification::fake(); $this->assertSame(0, $this->getRateLimitAttempts('ip::127.0.0.1')); @@ -70,11 +70,11 @@ public function it_validates_that_the_email_is_required_when_requesting_an_accou $this->expectTimebox(); $response = $this->from('/foo')->post(route('recover-account'), [ - 'email' => '', + $this->usernameField() => '', ]); $this->assertInstanceOf(ValidationException::class, $response->exception); - $this->assertSame(['email' => [__('validation.required', ['attribute' => 'email'])]], $response->exception->errors()); + $this->assertSame([$this->usernameField() => [__('validation.required', ['attribute' => $this->usernameField()])]], $response->exception->errors()); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); $response->assertSessionMissing('status'); @@ -90,11 +90,11 @@ public function it_cannot_send_an_account_recovery_link_to_an_user_that_does_not $this->expectTimebox(); $response = $this->from('/foo')->post(route('recover-account'), [ - 'email' => 'foo@example.com', + $this->usernameField() => $this->nonExistentUsername(), ]); $response->assertRedirect('/foo'); - $response->assertSessionHas('status', __('laravel-auth::auth.recovery.sent')); + $response->assertSessionHas('status', __('laravel-auth::auth.recovery.sent', ['field' => $this->usernameField()])); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); Notification::assertNothingSent(); @@ -115,11 +115,11 @@ public function it_only_sends_a_fresh_recovery_link_when_one_has_not_been_sent_r $this->expectTimebox(); $response = $this->from('/foo')->post(route('recover-account'), [ - 'email' => $user->email, + $this->usernameField() => $this->defaultUsername(), ]); $response->assertRedirect('/foo'); - $response->assertSessionHas('status', __('laravel-auth::auth.recovery.sent')); + $response->assertSessionHas('status', __('laravel-auth::auth.recovery.sent', ['field' => $this->usernameField()])); $this->assertTrue($repository->recentlyCreatedToken($user)); $this->assertSame(1, $this->getRateLimitAttempts('')); $this->assertSame(1, $this->getRateLimitAttempts('ip::127.0.0.1')); @@ -136,7 +136,7 @@ public function account_recovery_requests_are_rate_limited_after_too_many_reques $this->hitRateLimiter(5, 'ip::127.0.0.1'); $response = $this->post(route('recover-account'), [ - 'email' => 'foo@example.com', + $this->usernameField() => $this->defaultUsername(), ]); $this->assertInstanceOf(ValidationException::class, $response->exception); diff --git a/packages/core/src/Notifications/AccountRecoveryNotification.php b/packages/core/templates/Notifications/AccountRecoveryNotification.bladetmpl similarity index 81% rename from packages/core/src/Notifications/AccountRecoveryNotification.php rename to packages/core/templates/Notifications/AccountRecoveryNotification.bladetmpl index c180757..116980b 100644 --- a/packages/core/src/Notifications/AccountRecoveryNotification.php +++ b/packages/core/templates/Notifications/AccountRecoveryNotification.bladetmpl @@ -1,6 +1,4 @@ - $this->token, +@if ($flavor === 'username-based') + 'username' => $notifiable->username, +@else 'email' => $notifiable->getEmailForPasswordReset(), +@endif ], false)); } }