feat: reset password
This commit is contained in:
parent
7f60bc756f
commit
19e65e36f3
|
|
@ -58,7 +58,6 @@ MAIL_FROM_ADDRESS="hello@example.com"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
EXPO_ACCESS_TOKEN=
|
EXPO_ACCESS_TOKEN=
|
||||||
MOBILE_PASSWORD_RESET_URL=bowly://reset-password
|
|
||||||
|
|
||||||
STRAVA_CLIENT_ID=
|
STRAVA_CLIENT_ID=
|
||||||
STRAVA_CLIENT_SECRET=
|
STRAVA_CLIENT_SECRET=
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use App\Models\User;
|
||||||
use Illuminate\Auth\Events\PasswordReset;
|
use Illuminate\Auth\Events\PasswordReset;
|
||||||
use Illuminate\Auth\Events\Verified;
|
use Illuminate\Auth\Events\Verified;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
|
@ -21,6 +22,7 @@ use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Support\ValidatedInput;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class AuthController extends Controller
|
class AuthController extends Controller
|
||||||
|
|
@ -145,22 +147,7 @@ class AuthController extends Controller
|
||||||
|
|
||||||
public function resetPassword(ResetPasswordRequest $request): JsonResponse
|
public function resetPassword(ResetPasswordRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$data = $request->validated();
|
$status = $this->resetPasswordFor($request->safe());
|
||||||
$data['email'] = strtolower($data['email']);
|
|
||||||
|
|
||||||
$status = Password::reset(
|
|
||||||
$data,
|
|
||||||
function (User $user, string $password): void {
|
|
||||||
$user->forceFill([
|
|
||||||
'password' => Hash::make($password),
|
|
||||||
'remember_token' => Str::random(60),
|
|
||||||
])->save();
|
|
||||||
|
|
||||||
$user->tokens()->delete();
|
|
||||||
|
|
||||||
event(new PasswordReset($user));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($status !== Password::PASSWORD_RESET) {
|
if ($status !== Password::PASSWORD_RESET) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
|
@ -173,6 +160,34 @@ class AuthController extends Controller
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function showResetPasswordForm(Request $request, string $token): View
|
||||||
|
{
|
||||||
|
return view('auth.reset-password', [
|
||||||
|
'appName' => config('app.name'),
|
||||||
|
'email' => (string) $request->query('email', ''),
|
||||||
|
'passwordReset' => false,
|
||||||
|
'token' => $token,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetPasswordFromView(ResetPasswordRequest $request): RedirectResponse|View
|
||||||
|
{
|
||||||
|
$status = $this->resetPasswordFor($request->safe());
|
||||||
|
|
||||||
|
if ($status !== Password::PASSWORD_RESET) {
|
||||||
|
return back()
|
||||||
|
->withInput($request->only('email'))
|
||||||
|
->withErrors(['email' => __($status)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('auth.reset-password', [
|
||||||
|
'appName' => config('app.name'),
|
||||||
|
'email' => strtolower($request->validated('email')),
|
||||||
|
'passwordReset' => true,
|
||||||
|
'token' => '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function logout(): JsonResponse
|
public function logout(): JsonResponse
|
||||||
{
|
{
|
||||||
$request = request();
|
$request = request();
|
||||||
|
|
@ -263,6 +278,26 @@ class AuthController extends Controller
|
||||||
return filled($path) && ! Str::startsWith($path, ['http://', 'https://']);
|
return filled($path) && ! Str::startsWith($path, ['http://', 'https://']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resetPasswordFor(ValidatedInput $data): string
|
||||||
|
{
|
||||||
|
$credentials = $data->all();
|
||||||
|
$credentials['email'] = strtolower($credentials['email']);
|
||||||
|
|
||||||
|
return Password::reset(
|
||||||
|
$credentials,
|
||||||
|
function (User $user, string $password): void {
|
||||||
|
$user->forceFill([
|
||||||
|
'password' => Hash::make($password),
|
||||||
|
'remember_token' => Str::random(60),
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
$user->tokens()->delete();
|
||||||
|
|
||||||
|
event(new PasswordReset($user));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $data
|
* @param array<string, mixed> $data
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ResetPasswordMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public User $user,
|
||||||
|
public string $resetUrl,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: 'Réinitialise ton mot de passe Bowli',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: 'emails.reset-password',
|
||||||
|
with: [
|
||||||
|
'expiresInMinutes' => (int) config('auth.passwords.users.expire', 60),
|
||||||
|
'resetUrl' => $this->resetUrl,
|
||||||
|
'userName' => $this->user->name,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Broadcasting\ExpoPushChannel;
|
use App\Broadcasting\ExpoPushChannel;
|
||||||
|
use App\Mail\ResetPasswordMail;
|
||||||
use App\Mail\VerifyAccount;
|
use App\Mail\VerifyAccount;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Auth\Notifications\ResetPassword;
|
use Illuminate\Auth\Notifications\ResetPassword;
|
||||||
|
|
@ -62,15 +63,19 @@ class AppServiceProvider extends ServiceProvider
|
||||||
->to($notifiable->getEmailForVerification())
|
->to($notifiable->getEmailForVerification())
|
||||||
);
|
);
|
||||||
|
|
||||||
ResetPassword::createUrlUsing(function (User $notifiable, string $token): string {
|
$passwordResetUrl = function (User $notifiable, string $token): string {
|
||||||
$passwordResetUrl = rtrim((string) config('services.mobile.password_reset_url'), '?&');
|
return route('password.reset', [
|
||||||
$query = http_build_query([
|
|
||||||
'email' => $notifiable->getEmailForPasswordReset(),
|
'email' => $notifiable->getEmailForPasswordReset(),
|
||||||
'token' => $token,
|
'token' => $token,
|
||||||
], '', '&', PHP_QUERY_RFC3986);
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
return "{$passwordResetUrl}?{$query}";
|
ResetPassword::createUrlUsing($passwordResetUrl);
|
||||||
});
|
|
||||||
|
ResetPassword::toMailUsing(
|
||||||
|
fn (User $notifiable, string $token): ResetPasswordMail => (new ResetPasswordMail($notifiable, $passwordResetUrl($notifiable, $token)))
|
||||||
|
->to($notifiable->getEmailForPasswordReset())
|
||||||
|
);
|
||||||
|
|
||||||
Gate::define('viewApiDocs', function ($user = null) {
|
Gate::define('viewApiDocs', function ($user = null) {
|
||||||
// Option A : Autoriser tout le monde (Attention : la doc sera publique hors local)
|
// Option A : Autoriser tout le monde (Attention : la doc sera publique hors local)
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,6 @@ return [
|
||||||
'access_token' => env('EXPO_ACCESS_TOKEN'),
|
'access_token' => env('EXPO_ACCESS_TOKEN'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'mobile' => [
|
|
||||||
'password_reset_url' => env('MOBILE_PASSWORD_RESET_URL', 'bowly://reset-password'),
|
|
||||||
],
|
|
||||||
|
|
||||||
'strava' => [
|
'strava' => [
|
||||||
'client_id' => env('STRAVA_CLIENT_ID'),
|
'client_id' => env('STRAVA_CLIENT_ID'),
|
||||||
'client_secret' => env('STRAVA_CLIENT_SECRET'),
|
'client_secret' => env('STRAVA_CLIENT_SECRET'),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Nouveau mot de passe</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; min-height: 100vh; display: grid; place-items: center; background: #f5efe6; color: #232323; font-family: Arial, sans-serif;">
|
||||||
|
<main style="width: min(100% - 32px, 520px); background: #ffffff; border-radius: 8px; padding: 32px; box-sizing: border-box;">
|
||||||
|
<p style="margin: 0 0 8px; color: #426fb3; font-size: 14px; font-weight: 700; text-transform: uppercase;">
|
||||||
|
{{ $appName }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 style="margin: 0 0 16px; color: #000000; font-size: 28px; line-height: 34px;">
|
||||||
|
{{ $passwordReset ? 'Mot de passe modifié' : 'Nouveau mot de passe' }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
@if ($passwordReset)
|
||||||
|
<p style="margin: 0; color: #232323; font-size: 16px; line-height: 24px;">
|
||||||
|
Ton mot de passe a bien été réinitialisé. Tu peux retourner dans l’application et te connecter.
|
||||||
|
</p>
|
||||||
|
@else
|
||||||
|
<p style="margin: 0 0 24px; color: #232323; font-size: 16px; line-height: 24px;">
|
||||||
|
Choisis un nouveau mot de passe pour sécuriser ton compte.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div style="margin: 0 0 16px; padding: 12px 14px; border-radius: 8px; background: #fff2f2; color: #9f2d2d; font-size: 14px; line-height: 20px;">
|
||||||
|
{{ $errors->first() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('password.web-update') }}" style="display: grid; gap: 16px;">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<input type="hidden" name="token" value="{{ $token }}">
|
||||||
|
|
||||||
|
<label style="display: grid; gap: 8px; color: #232323; font-size: 14px; font-weight: 700;">
|
||||||
|
Email
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
value="{{ old('email', $email) }}"
|
||||||
|
required
|
||||||
|
autocomplete="email"
|
||||||
|
style="width: 100%; box-sizing: border-box; border: 1px solid #f5efe6; border-radius: 8px; padding: 12px 14px; color: #232323; font-size: 16px;"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label style="display: grid; gap: 8px; color: #232323; font-size: 14px; font-weight: 700;">
|
||||||
|
Nouveau mot de passe
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
minlength="8"
|
||||||
|
autocomplete="new-password"
|
||||||
|
style="width: 100%; box-sizing: border-box; border: 1px solid #f5efe6; border-radius: 8px; padding: 12px 14px; color: #232323; font-size: 16px;"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label style="display: grid; gap: 8px; color: #232323; font-size: 14px; font-weight: 700;">
|
||||||
|
Confirmer le mot de passe
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password_confirmation"
|
||||||
|
required
|
||||||
|
minlength="8"
|
||||||
|
autocomplete="new-password"
|
||||||
|
style="width: 100%; box-sizing: border-box; border: 1px solid #f5efe6; border-radius: 8px; padding: 12px 14px; color: #232323; font-size: 16px;"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button type="submit" style="min-height: 48px; border: 0; border-radius: 8px; background: #007f3a; color: #ffffff; font-size: 16px; font-weight: 700; cursor: pointer;">
|
||||||
|
Modifier le mot de passe
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Réinitialise ton mot de passe Bowli</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; background: #f5efe6; color: #232323; font-family: Arial, sans-serif;">
|
||||||
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background: #f5efe6; padding: 32px 16px;">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width: 560px; background: #ffffff; border-radius: 8px; overflow: hidden;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px;">
|
||||||
|
<p style="margin: 0 0 8px; color: #007f3a; font-size: 14px; font-weight: 700; text-transform: uppercase;">
|
||||||
|
Bowli
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 style="margin: 0 0 16px; color: #000000; font-size: 28px; line-height: 34px;">
|
||||||
|
Réinitialise ton mot de passe
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 16px; color: #232323; font-size: 16px; line-height: 24px;">
|
||||||
|
Bonjour {{ $userName ?: 'à toi' }},
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 24px; color: #232323; font-size: 16px; line-height: 24px;">
|
||||||
|
Clique sur le bouton ci-dessous pour créer un nouveau mot de passe. Le lien ouvre une page sécurisée de l’API Bowli.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" style="margin: 0 0 24px;">
|
||||||
|
<tr>
|
||||||
|
<td style="background: #007f3a; border-radius: 999px;">
|
||||||
|
<a href="{{ $resetUrl }}" style="display: inline-block; padding: 14px 24px; color: #ffffff; font-size: 16px; font-weight: 700; text-decoration: none;">
|
||||||
|
Choisir un nouveau mot de passe
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 16px; color: #666666; font-size: 14px; line-height: 22px;">
|
||||||
|
Ce lien expire dans {{ $expiresInMinutes }} minutes.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 16px; color: #666666; font-size: 14px; line-height: 22px;">
|
||||||
|
Si le bouton ne fonctionne pas, copie ce lien dans ton navigateur :
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 16px; word-break: break-all; color: #007f3a; font-size: 14px; line-height: 22px;">
|
||||||
|
<a href="{{ $resetUrl }}" style="color: #007f3a;">{{ $resetUrl }}</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="margin: 0; color: #666666; font-size: 14px; line-height: 22px;">
|
||||||
|
Si tu n’as pas demandé de réinitialisation, tu peux ignorer cet email.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\AuthController;
|
||||||
use App\Http\Controllers\StravaController;
|
use App\Http\Controllers\StravaController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::view('email/verify', 'auth.verify-email')->name('verification.notice');
|
Route::view('email/verify', 'auth.verify-email')->name('verification.notice');
|
||||||
|
Route::get('password/reset/{token}', [AuthController::class, 'showResetPasswordForm'])->name('password.reset');
|
||||||
|
Route::post('password/reset', [AuthController::class, 'resetPasswordFromView'])
|
||||||
|
->middleware('throttle:6,1')
|
||||||
|
->name('password.web-update');
|
||||||
Route::get('strava/callback', [StravaController::class, 'redirectToMobileApp'])->name('strava.web-callback');
|
Route::get('strava/callback', [StravaController::class, 'redirectToMobileApp'])->name('strava.web-callback');
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Mail\ResetPasswordMail;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Auth\Notifications\ResetPassword;
|
use Illuminate\Auth\Notifications\ResetPassword;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Notification;
|
use Illuminate\Support\Facades\Notification;
|
||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
config()->set('services.mobile.password_reset_url', 'bowly://reset-password');
|
config()->set('app.url', 'https://api.bowli.test');
|
||||||
|
URL::forceRootUrl('https://api.bowli.test');
|
||||||
|
URL::forceScheme('https');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends a password reset email with a mobile app url', function () {
|
it('sends a password reset email with a clickable api web url', function () {
|
||||||
Notification::fake();
|
Notification::fake();
|
||||||
|
|
||||||
$user = User::factory()->create([
|
$user = User::factory()->create([
|
||||||
|
|
@ -32,14 +36,22 @@ it('sends a password reset email with a mobile app url', function () {
|
||||||
ResetPassword::class,
|
ResetPassword::class,
|
||||||
function (ResetPassword $notification) use ($user): bool {
|
function (ResetPassword $notification) use ($user): bool {
|
||||||
$mail = $notification->toMail($user);
|
$mail = $notification->toMail($user);
|
||||||
$url = $mail->actionUrl;
|
|
||||||
|
|
||||||
expect($url)->toStartWith('bowly://reset-password?');
|
expect($mail)->toBeInstanceOf(ResetPasswordMail::class);
|
||||||
|
|
||||||
|
$url = $mail->resetUrl;
|
||||||
|
|
||||||
|
expect($url)->toStartWith('https://api.bowli.test/password/reset/');
|
||||||
|
|
||||||
parse_str((string) parse_url($url, PHP_URL_QUERY), $query);
|
parse_str((string) parse_url($url, PHP_URL_QUERY), $query);
|
||||||
|
|
||||||
expect($query['email'])->toBe($user->email)
|
expect($query['email'])->toBe($user->email)
|
||||||
->and($query['token'])->not->toBeEmpty();
|
->and((string) parse_url($url, PHP_URL_PATH))->toStartWith('/password/reset/');
|
||||||
|
|
||||||
|
$mail
|
||||||
|
->assertSeeInHtml('Réinitialise ton mot de passe')
|
||||||
|
->assertSeeInHtml('Choisir un nouveau mot de passe')
|
||||||
|
->assertSeeInHtml($user->name);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +97,50 @@ it('resets the password and invalidates existing api tokens', function () {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows the api password reset form from the email link', function () {
|
||||||
|
$user = User::factory()->create([
|
||||||
|
'email' => 'leon@example.com',
|
||||||
|
]);
|
||||||
|
$token = Password::broker()->createToken($user);
|
||||||
|
|
||||||
|
$this
|
||||||
|
->get(route('password.reset', [
|
||||||
|
'email' => $user->email,
|
||||||
|
'token' => $token,
|
||||||
|
], absolute: false))
|
||||||
|
->assertOk()
|
||||||
|
->assertViewIs('auth.reset-password')
|
||||||
|
->assertSee('Nouveau mot de passe')
|
||||||
|
->assertSee($user->email);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets the password from the api web form', function () {
|
||||||
|
$user = User::factory()->create([
|
||||||
|
'email' => 'leon@example.com',
|
||||||
|
'password' => 'old-password',
|
||||||
|
]);
|
||||||
|
$token = Password::broker()->createToken($user);
|
||||||
|
|
||||||
|
$user->createToken('api');
|
||||||
|
|
||||||
|
$this
|
||||||
|
->post('/password/reset', [
|
||||||
|
'email' => 'LEON@example.com',
|
||||||
|
'password' => 'new-password',
|
||||||
|
'password_confirmation' => 'new-password',
|
||||||
|
'token' => $token,
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertViewIs('auth.reset-password')
|
||||||
|
->assertSee('Mot de passe modifié');
|
||||||
|
|
||||||
|
expect(Hash::check('new-password', $user->fresh()->password))->toBeTrue();
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('personal_access_tokens', [
|
||||||
|
'tokenable_id' => $user->getKey(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('rejects an invalid password reset token', function () {
|
it('rejects an invalid password reset token', function () {
|
||||||
$user = User::factory()->create([
|
$user = User::factory()->create([
|
||||||
'email' => 'leon@example.com',
|
'email' => 'leon@example.com',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue