WebAuthnRecoveryController.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. <?php
  2. namespace App\Http\Controllers\Auth;
  3. use App\Extensions\WebauthnCredentialBroker;
  4. use App\Http\Controllers\Controller;
  5. use App\Http\Requests\WebauthnRecoveryRequest;
  6. use Illuminate\Auth\AuthenticationException;
  7. use Illuminate\Foundation\Auth\ResetsPasswords;
  8. use Illuminate\Http\JsonResponse;
  9. use Illuminate\Http\Request;
  10. use Illuminate\Support\Facades\Auth;
  11. use Illuminate\Support\Facades\Log;
  12. use Illuminate\Support\Facades\Password;
  13. use Illuminate\Validation\ValidationException;
  14. class WebAuthnRecoveryController extends Controller
  15. {
  16. use ResetsPasswords;
  17. /**
  18. * Let the user regain access to his account using email+password by resetting
  19. * the "use webauthn only" setting.
  20. *
  21. * @param \App\Http\Requests\WebauthnRecoveryRequest $request
  22. * @param \App\Extensions\WebauthnCredentialBroker $broker
  23. * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
  24. *
  25. * @throws \Illuminate\Validation\ValidationException
  26. */
  27. public function recover(WebauthnRecoveryRequest $request, WebauthnCredentialBroker $broker)
  28. {
  29. $credentials = $request->validated();
  30. $response = $broker->reset(
  31. $credentials,
  32. function ($user) use ($request) {
  33. // At this time, the WebAuthnUserProvider is already registered in the Laravel Service Container,
  34. // with a password_fallback value set using the useWebauthnOnly user setting (see AuthServiceProvider.php).
  35. // To ensure user login with email+pwd credentials, we replace the registered WebAuthnUserProvider instance
  36. // with a new instance configured with password_fallback On.
  37. $provider = new \Laragear\WebAuthn\Auth\WebAuthnUserProvider(
  38. app()->make('hash'),
  39. \App\Models\User::class,
  40. app()->make(\Laragear\WebAuthn\Assertion\Validator\AssertionValidator::class),
  41. true,
  42. );
  43. Auth::guard()->setProvider($provider);
  44. if (Auth::attempt($request->only('email', 'password'))) {
  45. if ($this->shouldRevokeAllCredentials($request)) {
  46. $user->flushCredentials();
  47. }
  48. $user->preferences['useWebauthnOnly'] = false;
  49. $user->save();
  50. Log::notice(sprintf('Legacy login restored for user ID #%s', $user->id));
  51. } else {
  52. throw new AuthenticationException();
  53. }
  54. }
  55. );
  56. return $response === Password::PASSWORD_RESET
  57. ? $this->sendRecoveryResponse($request, $response)
  58. : $this->sendRecoveryFailedResponse($request, $response);
  59. }
  60. /**
  61. * Check if the user has set to revoke all credentials.
  62. *
  63. * @param \App\Http\Requests\WebauthnRecoveryRequest $request
  64. * @return bool|mixed
  65. */
  66. protected function shouldRevokeAllCredentials(WebauthnRecoveryRequest $request) : mixed
  67. {
  68. return filter_var($request->header('WebAuthn-Unique'), FILTER_VALIDATE_BOOLEAN)
  69. ?: $request->input('revokeAll', true);
  70. }
  71. /**
  72. * Get the response for a successful account recovery.
  73. *
  74. * @param \Illuminate\Http\Request $request
  75. * @param string $response
  76. * @return \Illuminate\Http\JsonResponse
  77. */
  78. protected function sendRecoveryResponse(Request $request, string $response) : JsonResponse
  79. {
  80. return response()->json(['message' => __('auth.webauthn.webauthn_login_disabled')]);
  81. }
  82. /**
  83. * Get the response for a failed account recovery.
  84. *
  85. * @param \Illuminate\Http\Request $request
  86. * @param string $response
  87. * @return \Illuminate\Http\JsonResponse
  88. *
  89. * @throws \Illuminate\Validation\ValidationException
  90. */
  91. protected function sendRecoveryFailedResponse(Request $request, string $response) : JsonResponse
  92. {
  93. switch ($response) {
  94. case Password::INVALID_TOKEN:
  95. throw ValidationException::withMessages(['token' => [__('auth.webauthn.invalid_reset_token')]]);
  96. default:
  97. throw ValidationException::withMessages(['email' => [trans($response)]]);
  98. }
  99. }
  100. }