feat: update password
This commit is contained in:
parent
835dfcc677
commit
b3968a8181
|
|
@ -7,6 +7,7 @@ use App\Http\Requests\ForgotPasswordRequest;
|
||||||
use App\Http\Requests\LoginRequest;
|
use App\Http\Requests\LoginRequest;
|
||||||
use App\Http\Requests\RegisterRequest;
|
use App\Http\Requests\RegisterRequest;
|
||||||
use App\Http\Requests\ResetPasswordRequest;
|
use App\Http\Requests\ResetPasswordRequest;
|
||||||
|
use App\Http\Requests\UpdatePasswordRequest;
|
||||||
use App\Http\Requests\UpdateUserRequest;
|
use App\Http\Requests\UpdateUserRequest;
|
||||||
use App\Http\Resources\UserResource;
|
use App\Http\Resources\UserResource;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
|
@ -255,7 +256,7 @@ class AuthController extends Controller
|
||||||
Storage::disk()->delete($user->avatar_url);
|
Storage::disk()->delete($user->avatar_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $request->file('avatar')->store('avatars', );
|
$path = $request->file('avatar')->store('avatars');
|
||||||
$data['avatar_url'] = $path;
|
$data['avatar_url'] = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,6 +276,32 @@ class AuthController extends Controller
|
||||||
return new UserResource($user->fresh());
|
return new UserResource($user->fresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatePassword(UpdatePasswordRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
abort_if($user->isSuspended(), 403, __('api.auth.suspended'));
|
||||||
|
|
||||||
|
$currentAccessToken = $user->currentAccessToken();
|
||||||
|
|
||||||
|
$user->forceFill([
|
||||||
|
'password' => Hash::make($request->validated('password')),
|
||||||
|
'remember_token' => Str::random(60),
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
if ($currentAccessToken instanceof PersonalAccessToken) {
|
||||||
|
$user->tokens()
|
||||||
|
->whereKeyNot($currentAccessToken->getKey())
|
||||||
|
->delete();
|
||||||
|
} else {
|
||||||
|
$user->tokens()->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => __('api.auth.password_updated'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function destroy(Request $request): JsonResponse
|
public function destroy(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdatePasswordRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'current_password' => ['required', 'string', 'current_password:sanctum'],
|
||||||
|
'password' => ['required', 'string', 'min:8', 'confirmed', 'different:current_password'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
if ($this->has('currentPassword') && ! $this->has('current_password')) {
|
||||||
|
$this->merge([
|
||||||
|
'current_password' => $this->input('currentPassword'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->has('passwordConfirmation') && ! $this->has('password_confirmation')) {
|
||||||
|
$this->merge([
|
||||||
|
'password_confirmation' => $this->input('passwordConfirmation'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ return [
|
||||||
'verification_email_failed' => 'Unable to send the verification email right now. Please try again later.',
|
'verification_email_failed' => 'Unable to send the verification email right now. Please try again later.',
|
||||||
'password_reset_link_sent' => 'If an account exists for this address, a password reset link has been sent.',
|
'password_reset_link_sent' => 'If an account exists for this address, a password reset link has been sent.',
|
||||||
'password_reset' => 'Password reset successfully.',
|
'password_reset' => 'Password reset successfully.',
|
||||||
|
'password_updated' => 'Password updated successfully.',
|
||||||
'logout' => 'Logged out successfully.',
|
'logout' => 'Logged out successfully.',
|
||||||
'unauthenticated' => 'Unauthenticated.',
|
'unauthenticated' => 'Unauthenticated.',
|
||||||
'deleted' => 'Account deleted successfully.',
|
'deleted' => 'Account deleted successfully.',
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ return [
|
||||||
'verification_email_failed' => "Impossible d'envoyer l'email de vérification pour le moment. Réessaie plus tard.",
|
'verification_email_failed' => "Impossible d'envoyer l'email de vérification pour le moment. Réessaie plus tard.",
|
||||||
'password_reset_link_sent' => 'Si un compte existe avec cette adresse, un lien de réinitialisation vient d’être envoyé.',
|
'password_reset_link_sent' => 'Si un compte existe avec cette adresse, un lien de réinitialisation vient d’être envoyé.',
|
||||||
'password_reset' => 'Mot de passe réinitialisé avec succès.',
|
'password_reset' => 'Mot de passe réinitialisé avec succès.',
|
||||||
|
'password_updated' => 'Mot de passe modifié avec succès.',
|
||||||
'logout' => 'Déconnecté avec succès.',
|
'logout' => 'Déconnecté avec succès.',
|
||||||
'unauthenticated' => 'Non authentifié.',
|
'unauthenticated' => 'Non authentifié.',
|
||||||
'deleted' => 'Compte supprimé avec succès.',
|
'deleted' => 'Compte supprimé avec succès.',
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ Route::prefix('auth')->group(function (): void {
|
||||||
Route::post('logout', [AuthController::class, 'logout']);
|
Route::post('logout', [AuthController::class, 'logout']);
|
||||||
Route::get('me', [AuthController::class, 'me']);
|
Route::get('me', [AuthController::class, 'me']);
|
||||||
Route::patch('me', [AuthController::class, 'update'])->middleware(['verified', 'throttle:content-write']);
|
Route::patch('me', [AuthController::class, 'update'])->middleware(['verified', 'throttle:content-write']);
|
||||||
|
Route::patch('me/password', [AuthController::class, 'updatePassword'])->middleware('throttle:account-action');
|
||||||
Route::delete('me', [AuthController::class, 'destroy'])->middleware('throttle:account-action');
|
Route::delete('me', [AuthController::class, 'destroy'])->middleware('throttle:account-action');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
it('updates the authenticated user password and keeps the current api token', function () {
|
||||||
|
$user = User::factory()->create([
|
||||||
|
'password' => Hash::make('OldPassword123!'),
|
||||||
|
]);
|
||||||
|
$currentToken = $user->createToken('current-device');
|
||||||
|
$otherToken = $user->createToken('other-device');
|
||||||
|
|
||||||
|
$this
|
||||||
|
->withHeader('Authorization', 'Bearer '.$currentToken->plainTextToken)
|
||||||
|
->patchJson('/api/auth/me/password', [
|
||||||
|
'currentPassword' => 'OldPassword123!',
|
||||||
|
'password' => 'NewPassword123!',
|
||||||
|
'passwordConfirmation' => 'NewPassword123!',
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('message', __('api.auth.password_updated'));
|
||||||
|
|
||||||
|
expect(Hash::check('NewPassword123!', $user->fresh()->password))->toBeTrue();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('personal_access_tokens', [
|
||||||
|
'id' => $currentToken->accessToken->getKey(),
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseMissing('personal_access_tokens', [
|
||||||
|
'id' => $otherToken->accessToken->getKey(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects an invalid current password', function () {
|
||||||
|
$user = User::factory()->create([
|
||||||
|
'password' => Hash::make('OldPassword123!'),
|
||||||
|
]);
|
||||||
|
$currentToken = $user->createToken('current-device');
|
||||||
|
|
||||||
|
$this
|
||||||
|
->withHeader('Authorization', 'Bearer '.$currentToken->plainTextToken)
|
||||||
|
->patchJson('/api/auth/me/password', [
|
||||||
|
'currentPassword' => 'wrong-password',
|
||||||
|
'password' => 'NewPassword123!',
|
||||||
|
'passwordConfirmation' => 'NewPassword123!',
|
||||||
|
])
|
||||||
|
->assertUnprocessable()
|
||||||
|
->assertJsonValidationErrors(['current_password']);
|
||||||
|
|
||||||
|
expect(Hash::check('OldPassword123!', $user->fresh()->password))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requires authentication to update the password', function () {
|
||||||
|
$this
|
||||||
|
->patchJson('/api/auth/me/password', [
|
||||||
|
'currentPassword' => 'OldPassword123!',
|
||||||
|
'password' => 'NewPassword123!',
|
||||||
|
'passwordConfirmation' => 'NewPassword123!',
|
||||||
|
])
|
||||||
|
->assertUnauthorized();
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue