From 9e3d22613f467d0f14ffcb583cccd92eb09deac5 Mon Sep 17 00:00:00 2001 From: Arthur Monney Date: Sat, 12 Nov 2022 17:05:50 +0100 Subject: [PATCH] :sparkles: Mis en place de la fonctionnalite de rappel et reinitialisation du mot de passe --- .env.example | 1 + .../Api/Auth/ForgotPasswordController.php | 22 ++++++ .../Api/Auth/ResetPasswordController.php | 33 +++++++++ .../Api/Auth/VerifyEmailController.php | 4 +- .../Requests/Api/ForgotPasswordRequest.php | 30 ++++++++ .../Requests/Api/ResetPasswordRequest.php | 39 +++++++++++ app/Providers/AuthServiceProvider.php | 5 ++ config/lcm.php | 2 + resources/lang/en/validation.php | 68 ++++++++++++------- resources/lang/fr/validation.php | 21 ++++-- routes/api.php | 6 ++ 11 files changed, 200 insertions(+), 31 deletions(-) create mode 100644 app/Http/Controllers/Api/Auth/ForgotPasswordController.php create mode 100644 app/Http/Controllers/Api/Auth/ResetPasswordController.php create mode 100644 app/Http/Requests/Api/ForgotPasswordRequest.php create mode 100644 app/Http/Requests/Api/ResetPasswordRequest.php diff --git a/.env.example b/.env.example index e2137bf2..4759d724 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,7 @@ APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://laravel.cm.test +FRONTEND_APP_URL=http://localhost::4200 LOG_CHANNEL=stack LOG_LEVEL=debug diff --git a/app/Http/Controllers/Api/Auth/ForgotPasswordController.php b/app/Http/Controllers/Api/Auth/ForgotPasswordController.php new file mode 100644 index 00000000..0c4a94ec --- /dev/null +++ b/app/Http/Controllers/Api/Auth/ForgotPasswordController.php @@ -0,0 +1,22 @@ +only('email') + ); + + return $status === Password::RESET_LINK_SENT + ? response()->json(['message' => __('L\'e-mail de réinitialisation du mot de passe a été envoyé avec succès !')]) + : response()->json(['error' => __('Un courriel ne pourrait être envoyé à cette adresse électronique !')], 401); + } +} diff --git a/app/Http/Controllers/Api/Auth/ResetPasswordController.php b/app/Http/Controllers/Api/Auth/ResetPasswordController.php new file mode 100644 index 00000000..e1f382f6 --- /dev/null +++ b/app/Http/Controllers/Api/Auth/ResetPasswordController.php @@ -0,0 +1,33 @@ +only('email', 'password', 'password_confirmation', 'token'), + function ($user) use ($request) { + $user->forceFill([ + 'password' => Hash::make($request->password), + 'remember_token' => Str::random(60), + ])->save(); + + event(new PasswordReset($user)); + } + ); + + return $status === Password::PASSWORD_RESET + ? response()->json(['message' => __('Votre mot de passe a été réinitialisé avec succès !')]) + : response()->json(['error' => __('Le jeton de réinitialisation du mot de passe est invalide !')], 401); + } +} diff --git a/app/Http/Controllers/Api/Auth/VerifyEmailController.php b/app/Http/Controllers/Api/Auth/VerifyEmailController.php index f1e6e46e..34c59dba 100644 --- a/app/Http/Controllers/Api/Auth/VerifyEmailController.php +++ b/app/Http/Controllers/Api/Auth/VerifyEmailController.php @@ -17,14 +17,14 @@ public function verify(Request $request): RedirectResponse $user = User::find($request->route('id')); if ($user->hasVerifiedEmail()) { - return redirect(env('FRONTEND_APP_URL') . '/email/verify/already-success'); + return redirect(config('lcm.spa_url') . '/email/verify/already'); } if ($user->markEmailAsVerified()) { event(new Verified($user)); } - return redirect(env('FRONTEND_APP_URL') . '/email/verify/success'); + return redirect(config('lcm.spa_url') . '/email/verify/success'); } public function resend(Request $request): JsonResponse diff --git a/app/Http/Requests/Api/ForgotPasswordRequest.php b/app/Http/Requests/Api/ForgotPasswordRequest.php new file mode 100644 index 00000000..5b682d8c --- /dev/null +++ b/app/Http/Requests/Api/ForgotPasswordRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'email' => 'required|email|exists:users,email', + ]; + } +} diff --git a/app/Http/Requests/Api/ResetPasswordRequest.php b/app/Http/Requests/Api/ResetPasswordRequest.php new file mode 100644 index 00000000..1e17c508 --- /dev/null +++ b/app/Http/Requests/Api/ResetPasswordRequest.php @@ -0,0 +1,39 @@ + + */ + public function rules(): array + { + return [ + 'token' => 'required', + 'email' => ['required', 'email', 'exists:users,email'], + 'password' => [ + 'required', + 'confirmed', + app()->environment('local') + ? Password::min(8) + : Password::min(8)->mixedCase()->numbers()->symbols(), + ], + ]; + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index d8bf2b52..290683c4 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -11,6 +11,7 @@ use App\Policies\NotificationPolicy; use App\Policies\ReplyPolicy; use App\Policies\ThreadPolicy; +use Illuminate\Auth\Notifications\ResetPassword; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Notifications\DatabaseNotification as Notification; use Illuminate\Support\Facades\Gate; @@ -34,6 +35,10 @@ public function boot(): void { $this->registerPolicies(); + ResetPassword::createUrlUsing(function ($user, string $token) { + return config('lcm.spa_url').'/auth/password/reset?token='.$token; + }); + Gate::before(function ($user) { return $user->hasRole('admin') ? true : null; }); diff --git a/config/lcm.php b/config/lcm.php index 737ce38a..4234c175 100644 --- a/config/lcm.php +++ b/config/lcm.php @@ -23,4 +23,6 @@ 'token' => env('SLACK_API_TOKEN', null), ], + 'spa_url' => env('FRONTEND_APP_URL', 'http://localhost:4200'), + ]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 49e3388b..ac8ddb0a 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -14,6 +14,7 @@ */ 'accepted' => 'The :attribute must be accepted.', + 'accepted_if' => 'The :attribute must be accepted when :other is :value.', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', @@ -24,37 +25,43 @@ 'before' => 'The :attribute must be a date before :date.', 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', 'between' => [ - 'numeric' => 'The :attribute must be between :min and :max.', + 'array' => 'The :attribute must have between :min and :max items.', 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute must be between :min and :max.', 'string' => 'The :attribute must be between :min and :max characters.', - 'array' => 'The :attribute must have between :min and :max items.', ], 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', + 'current_password' => 'The password is incorrect.', 'date' => 'The :attribute is not a valid date.', 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => 'The :attribute does not match the format :format.', + 'declined' => 'The :attribute must be declined.', + 'declined_if' => 'The :attribute must be declined when :other is :value.', 'different' => 'The :attribute and :other must be different.', 'digits' => 'The :attribute must be :digits digits.', 'digits_between' => 'The :attribute must be between :min and :max digits.', 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', + 'doesnt_end_with' => 'The :attribute may not end with one of the following: :values.', + 'doesnt_start_with' => 'The :attribute may not start with one of the following: :values.', 'email' => 'The :attribute must be a valid email address.', 'ends_with' => 'The :attribute must end with one of the following: :values.', + 'enum' => 'The selected :attribute is invalid.', 'exists' => 'The selected :attribute is invalid.', 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', 'gt' => [ - 'numeric' => 'The :attribute must be greater than :value.', + 'array' => 'The :attribute must have more than :value items.', 'file' => 'The :attribute must be greater than :value kilobytes.', + 'numeric' => 'The :attribute must be greater than :value.', 'string' => 'The :attribute must be greater than :value characters.', - 'array' => 'The :attribute must have more than :value items.', ], 'gte' => [ - 'numeric' => 'The :attribute must be greater than or equal :value.', - 'file' => 'The :attribute must be greater than or equal :value kilobytes.', - 'string' => 'The :attribute must be greater than or equal :value characters.', 'array' => 'The :attribute must have :value items or more.', + 'file' => 'The :attribute must be greater than or equal to :value kilobytes.', + 'numeric' => 'The :attribute must be greater than or equal to :value.', + 'string' => 'The :attribute must be greater than or equal to :value characters.', ], 'image' => 'The :attribute must be an image.', 'in' => 'The selected :attribute is invalid.', @@ -64,62 +71,75 @@ 'ipv4' => 'The :attribute must be a valid IPv4 address.', 'ipv6' => 'The :attribute must be a valid IPv6 address.', 'json' => 'The :attribute must be a valid JSON string.', + 'lowercase' => 'The :attribute must be lowercase.', 'lt' => [ - 'numeric' => 'The :attribute must be less than :value.', + 'array' => 'The :attribute must have less than :value items.', 'file' => 'The :attribute must be less than :value kilobytes.', + 'numeric' => 'The :attribute must be less than :value.', 'string' => 'The :attribute must be less than :value characters.', - 'array' => 'The :attribute must have less than :value items.', ], 'lte' => [ - 'numeric' => 'The :attribute must be less than or equal :value.', - 'file' => 'The :attribute must be less than or equal :value kilobytes.', - 'string' => 'The :attribute must be less than or equal :value characters.', 'array' => 'The :attribute must not have more than :value items.', + 'file' => 'The :attribute must be less than or equal to :value kilobytes.', + 'numeric' => 'The :attribute must be less than or equal to :value.', + 'string' => 'The :attribute must be less than or equal to :value characters.', ], + 'mac_address' => 'The :attribute must be a valid MAC address.', 'max' => [ - 'numeric' => 'The :attribute must not be greater than :max.', + 'array' => 'The :attribute must not have more than :max items.', 'file' => 'The :attribute must not be greater than :max kilobytes.', + 'numeric' => 'The :attribute must not be greater than :max.', 'string' => 'The :attribute must not be greater than :max characters.', - 'array' => 'The :attribute must not have more than :max items.', ], + 'max_digits' => 'The :attribute must not have more than :max digits.', 'mimes' => 'The :attribute must be a file of type: :values.', 'mimetypes' => 'The :attribute must be a file of type: :values.', 'min' => [ - 'numeric' => 'The :attribute must be at least :min.', + 'array' => 'The :attribute must have at least :min items.', 'file' => 'The :attribute must be at least :min kilobytes.', + 'numeric' => 'The :attribute must be at least :min.', 'string' => 'The :attribute must be at least :min characters.', - 'array' => 'The :attribute must have at least :min items.', ], + 'min_digits' => 'The :attribute must have at least :min digits.', 'multiple_of' => 'The :attribute must be a multiple of :value.', 'not_in' => 'The selected :attribute is invalid.', 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', - 'password' => 'The password is incorrect.', + 'password' => [ + 'letters' => 'The :attribute must contain at least one letter.', + 'mixed' => 'The :attribute must contain at least one uppercase and one lowercase letter.', + 'numbers' => 'The :attribute must contain at least one number.', + 'symbols' => 'The :attribute must contain at least one symbol.', + 'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.', + ], 'present' => 'The :attribute field must be present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', 'regex' => 'The :attribute format is invalid.', 'required' => 'The :attribute field is required.', + 'required_array_keys' => 'The :attribute field must contain entries for: :values.', 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_if_accepted' => 'The :attribute field is required when :other is accepted.', 'required_unless' => 'The :attribute field is required unless :other is in :values.', 'required_with' => 'The :attribute field is required when :values is present.', 'required_with_all' => 'The :attribute field is required when :values are present.', 'required_without' => 'The :attribute field is required when :values is not present.', 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'prohibited' => 'The :attribute field is prohibited.', - 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', - 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', 'same' => 'The :attribute and :other must match.', 'size' => [ - 'numeric' => 'The :attribute must be :size.', + 'array' => 'The :attribute must contain :size items.', 'file' => 'The :attribute must be :size kilobytes.', + 'numeric' => 'The :attribute must be :size.', 'string' => 'The :attribute must be :size characters.', - 'array' => 'The :attribute must contain :size items.', ], 'starts_with' => 'The :attribute must start with one of the following: :values.', 'string' => 'The :attribute must be a string.', - 'timezone' => 'The :attribute must be a valid zone.', + 'timezone' => 'The :attribute must be a valid timezone.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', - 'url' => 'The :attribute format is invalid.', + 'url' => 'The :attribute must be a valid URL.', 'uuid' => 'The :attribute must be a valid UUID.', /* diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index 85d1c171..7c395f15 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -25,16 +25,19 @@ 'before' => 'Le champ :attribute doit être une date antérieure au :date.', 'before_or_equal' => 'Le champ :attribute doit être une date antérieure ou égale au :date.', 'between' => [ - 'array' => 'Le tableau :attribute doit contenir entre :min et :max éléments.', - 'file' => 'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.', - 'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', - 'string' => 'Le texte :attribute doit contenir entre :min et :max caractères.', + 'array' => 'The :attribute must have between :min and :max items.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute must be between :min and :max.', + 'string' => 'The :attribute must be between :min and :max characters.', ], 'boolean' => 'Le champ :attribute doit être vrai ou faux.', 'confirmed' => 'Le champ de confirmation :attribute ne correspond pas.', + 'current_password' => 'Le mot de passe est incorrect.', 'date' => 'Le champ :attribute n\'est pas une date valide.', 'date_equals' => 'Le champ :attribute doit être une date égale à :date.', 'date_format' => 'Le champ :attribute ne correspond pas au format :format.', + 'declined' => 'Le champ :attribute doit être refusé.', + 'declined_if' => 'Le champ :attribute doit être décliné lorsque :other est :value.', 'different' => 'Les champs :attribute et :other doivent être différents.', 'digits' => 'Le champ :attribute doit contenir :digits chiffres.', 'digits_between' => 'Le champ :attribute doit contenir entre :min et :max chiffres.', @@ -42,6 +45,7 @@ 'distinct' => 'Le champ :attribute a une valeur en double.', 'email' => 'Le champ :attribute doit être une adresse email valide.', 'ends_with' => 'Le champ :attribute doit se terminer par une des valeurs suivantes : :values', + 'enum' => 'Le champ :attribute sélectionné n\'est pas valide.', 'exists' => 'Le champ :attribute sélectionné est invalide.', 'file' => 'Le champ :attribute doit être un fichier.', 'filled' => 'Le champ :attribute doit avoir une valeur.', @@ -65,6 +69,7 @@ 'ipv4' => 'Le champ :attribute doit être une adresse IPv4 valide.', 'ipv6' => 'Le champ :attribute doit être une adresse IPv6 valide.', 'json' => 'Le champ :attribute doit être un document JSON valide.', + 'lowercase' => 'The :attribute must be lowercase.', 'lt' => [ 'array' => 'Le tableau :attribute doit contenir moins de :value éléments.', 'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.', @@ -95,7 +100,13 @@ 'not_in' => 'Le champ :attribute sélectionné n\'est pas valide.', 'not_regex' => 'Le format du champ :attribute n\'est pas valide.', 'numeric' => 'Le champ :attribute doit contenir un nombre.', - 'password' => 'Le mot de passe est incorrect', + 'password' => [ + 'letters' => 'Le champ :attribute doit contenir au moins une lettre.', + 'mixed' => 'Le champ :attribute doit contenir au moins une lettre majuscule et une lettre minuscule.', + 'numbers' => 'Le champ :attribute doit contenir au moins un chiffre.', + 'symbols' => 'Le champ :attribute doit contenir au moins un symbole.', + 'uncompromised' => 'Le champ :attribute donné est apparu dans une fuite de données. Veuillez choisir un autre :attribut.', + ], 'present' => 'Le champ :attribute doit être présent.', 'prohibited' => 'Le champ :attribute est interdit.', 'prohibited_if' => 'Le champ :attribute est interdit quand :other a la valeur :value.', diff --git a/routes/api.php b/routes/api.php index 0cb7770f..7e051c23 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,7 +1,9 @@ group(function () { + Route::post('forgot', ForgotPasswordController::class); + Route::post('reset', ResetPasswordController::class); +}); /* Authenticated Routes */ Route::middleware('auth:sanctum')->group(function () {