Skip to content
Snippets Groups Projects
Unverified Commit 9675852f authored by Maxime FRIESS's avatar Maxime FRIESS :blue_heart:
Browse files

[auth] Reworked password reset mecanism

parent effbe20a
Branches
Tags
No related merge requests found
......@@ -6,6 +6,8 @@
use App\Http\Requests\LoginRequest;
use App\Http\Requests\RecoverAccountRequest;
use App\Http\Requests\RegisterRequest;
use App\Http\Requests\ResetPasswordRequest;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
......@@ -108,8 +110,6 @@ public function register(RegisterRequest $request)
'password' => Hash::make($request->password),
]);
$token = Auth::guard('client')->login($user);
event(new Registered($user));
return response()->noContent();
......@@ -224,4 +224,60 @@ public function recover(RecoverAccountRequest $request)
], 429);
}
}
/**
* @OA\Post(
* tags={"Clients:Authentication"},
* path="/auth/reset-password",
* summary="Recover an account",
* @OA\RequestBody(
* ref="#/components/requestBodies/ResetPassword"
* ),
* @OA\Response(
* response="401",
* ref="#/components/responses/401"
* ),
* @OA\Response(
* response="422",
* ref="#/components/responses/422"
* ),
* @OA\Response(
* response="429",
* description="Too Many Requests",
* @OA\JsonContent(
* @OA\Property(
* property="message",
* type="string",
* ),
* )
* ),
* @OA\Response(
* response="204",
* description="OK",
* ),
* )
*/
public function reset_password(ResetPasswordRequest $request)
{
$status = Password::reset(
$request->only('email', 'password', 'token'),
function (User $user, string $password) {
$user->forceFill([
'password' => Hash::make($password)
]);
$user->save();
event(new PasswordReset($user));
}
);
if ($status == Password::INVALID_USER || $status == Password::INVALID_TOKEN) {
return response()->json([
"message" => __($status)
], 401);
} else if ($status == Password::PASSWORD_RESET) {
return response()->noContent();
}
}
}
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
/**
* @OA\RequestBody(
* request="ResetPassword",
* required=true,
* @OA\JsonContent(
* required={"token", "email", "password"},
* @OA\Property(
* property="token",
* type="string",
* description="Recovery token",
* ),
* @OA\Property(
* property="email",
* type="string",
* format="email",
* description="Email of the user",
* ),
* @OA\Property(
* property="password",
* type="string",
* description="New password of the user",
* ),
* ),
* ),
*/
class ResetPasswordRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|string|min:6',
];
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Password recovery</title>
<style>
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}code{font-family:monospace,monospace;font-size:1em}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}code{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-gray-400{--border-opacity:1;border-color:#cbd5e0;border-color:rgba(203,213,224,var(--border-opacity))}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-xl{max-width:36rem}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.uppercase{text-transform:uppercase}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tracking-wider{letter-spacing:.05em}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@-webkit-keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@-webkit-keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}}
</style>
<style>
body {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
</style>
</head>
<body class="antialiased">
<div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
<div class="max-w-xl mx-auto sm:px-6 lg:px-8">
<div class="flex items-center pt-8 sm:justify-start sm:pt-0">
<form action="{{ route('password.update') }}" method="post">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<label for="email">Email</label>
<input type="text" name="email" id="email" />
<br />
<label for="password">Password</label>
<input type="password" name="password" id="password" />
<br />
<label for="password_confirmation">Password Confirmation</label>
<input type="password" name="password_confirmation" id="password_confirmation" />
<br />
<input type="Submit" value="Change password" />
</form>
</div>
</div>
</div>
</body>
</html>
@extends('base')
@section('title', 'Password reset')
@section('code', 'Password reset')
@section('message', $message)
......@@ -21,6 +21,7 @@
Route::post('login', [Client\AuthController::class, 'login']);
Route::post('register', [Client\AuthController::class, 'register']);
Route::post('recover', [Client\AuthController::class, 'recover']);
Route::post('reset-password', [Client\AuthController::class, 'reset_password']);
Route::middleware('auth:client')->group(function () {
Route::post('logout', [Client\AuthController::class, 'logout']);
......@@ -41,7 +42,6 @@
Route::post('stop', [Server\AuthController::class, 'stop']);
});
});
});
Route::middleware(['api', 'auth:client'])->group(function () {
......
......@@ -28,7 +28,7 @@
}
// Route used for email verification
Route::get('/email/verify/{id}/{hash}', function (Request $request, $id) {
Route::get('/api/auth/verify/{id}/{hash}', function (Request $request, $id) {
$user = User::findOrFail($id);
if (User::findOrFail($id)->hasVerifiedEmail())
return view('already_validated');
......@@ -36,38 +36,3 @@
User::findOrFail($id)->markEmailAsVerified();
return view('validated');
})->middleware(['signed'])->name('verification.verify');
Route::get('/recover/{token}', function (string $token) {
return view('recover', ['token' => $token]);
})->middleware('guest')->name('password.reset');
Route::post('/reset-password', function (Request $request) {
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => 'required|string|min:6|confirmed',
]);
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function (User $user, string $password) {
$user->forceFill([
'password' => Hash::make($password)
]);
$user->save();
event(new PasswordReset($user));
}
);
if ($status == Password::INVALID_USER || $status == Password::INVALID_TOKEN) {
return response()->view('recovered', [
"message" => __($status)
], 401);
} else if ($status == Password::PASSWORD_RESET) {
return response()->view('recovered', [
"message" => __($status)
], 200);
}
})->name('password.update');
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment