feat: verify email
This commit is contained in:
parent
38ea85c03b
commit
6966919887
|
|
@ -59,6 +59,15 @@ deploy-to-portainer:
|
||||||
- apk add --no-cache curl jq gettext
|
- apk add --no-cache curl jq gettext
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- |
|
||||||
|
export MAIL_MAILER="${MAIL_MAILER:-smtp}"
|
||||||
|
export MAIL_SCHEME="${MAIL_SCHEME:-null}"
|
||||||
|
export MAIL_FROM_NAME="${MAIL_FROM_NAME:-$APP_NAME}"
|
||||||
|
: "${MAIL_HOST:?Missing MAIL_HOST GitLab CI/CD variable}"
|
||||||
|
: "${MAIL_PORT:?Missing MAIL_PORT GitLab CI/CD variable}"
|
||||||
|
: "${MAIL_USERNAME:?Missing MAIL_USERNAME GitLab CI/CD variable}"
|
||||||
|
: "${MAIL_PASSWORD:?Missing MAIL_PASSWORD GitLab CI/CD variable}"
|
||||||
|
: "${MAIL_FROM_ADDRESS:?Missing MAIL_FROM_ADDRESS GitLab CI/CD variable}"
|
||||||
- envsubst < docker-compose.prod.yml > docker-compose.rendered.yml
|
- envsubst < docker-compose.prod.yml > docker-compose.rendered.yml
|
||||||
|
|
||||||
- |
|
- |
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ use App\Http\Requests\Auth\LoginRequest;
|
||||||
use App\Http\Requests\Auth\RegisterRequest;
|
use App\Http\Requests\Auth\RegisterRequest;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Hash;
|
use Hash;
|
||||||
|
use Illuminate\Auth\Events\Verified;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Illuminate\View\View;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
class AuthController extends Controller
|
class AuthController extends Controller
|
||||||
|
|
@ -36,6 +38,8 @@ class AuthController extends Controller
|
||||||
$this->resolveDeviceName($data)
|
$this->resolveDeviceName($data)
|
||||||
)->plainTextToken;
|
)->plainTextToken;
|
||||||
|
|
||||||
|
$user->sendEmailVerificationNotification();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'token' => $token,
|
'token' => $token,
|
||||||
'token_type' => 'Bearer',
|
'token_type' => 'Bearer',
|
||||||
|
|
@ -79,6 +83,47 @@ class AuthController extends Controller
|
||||||
return response()->json($user);
|
return response()->json($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function verifyEmail(Request $request, string $id, string $hash): JsonResponse|View
|
||||||
|
{
|
||||||
|
$user = User::findOrFail($id);
|
||||||
|
|
||||||
|
if (! hash_equals($hash, sha1($user->getEmailForVerification()))) {
|
||||||
|
abort(Response::HTTP_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $user->hasVerifiedEmail()) {
|
||||||
|
$user->markEmailAsVerified();
|
||||||
|
|
||||||
|
event(new Verified($user));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->expectsJson()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => __('auth.email_verified'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('auth.email-verified');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resendEmailVerificationNotification(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if ($user->hasVerifiedEmail()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => __('auth.email_already_verified'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->sendEmailVerificationNotification();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => __('auth.email_verification_sent'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function logout(Request $request): JsonResponse
|
public function logout(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$request->user()?->currentAccessToken()?->delete();
|
$request->user()?->currentAccessToken()?->delete();
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
||||||
use App\Enums\UserRole;
|
use App\Enums\UserRole;
|
||||||
|
use App\Notifications\VerifyEmailNotification;
|
||||||
use Filament\Models\Contracts\FilamentUser;
|
use Filament\Models\Contracts\FilamentUser;
|
||||||
use Filament\Models\Contracts\HasName;
|
use Filament\Models\Contracts\HasName;
|
||||||
use Filament\Panel;
|
use Filament\Panel;
|
||||||
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
@ -15,7 +16,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
class User extends Authenticatable implements FilamentUser, HasName
|
class User extends Authenticatable implements FilamentUser, HasName, MustVerifyEmail
|
||||||
{
|
{
|
||||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||||
use HasApiTokens, HasFactory, HasUuids, Notifiable, SoftDeletes;
|
use HasApiTokens, HasFactory, HasUuids, Notifiable, SoftDeletes;
|
||||||
|
|
@ -96,6 +97,11 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||||
return $this->belongsTo(Level::class, 'level_id');
|
return $this->belongsTo(Level::class, 'level_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sendEmailVerificationNotification(): void
|
||||||
|
{
|
||||||
|
$this->notify(new VerifyEmailNotification);
|
||||||
|
}
|
||||||
|
|
||||||
public function updateLevel(): void
|
public function updateLevel(): void
|
||||||
{
|
{
|
||||||
$level = Level::where('xpThreshold', '<=', $this->xp)
|
$level = Level::where('xpThreshold', '<=', $this->xp)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
|
||||||
|
class VerifyEmailNotification extends Notification
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notification's delivery channels.
|
||||||
|
*
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
return ['mail'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mail representation of the notification.
|
||||||
|
*/
|
||||||
|
public function toMail(object $notifiable): MailMessage
|
||||||
|
{
|
||||||
|
/** @var MustVerifyEmail $notifiable */
|
||||||
|
$verificationUrl = URL::temporarySignedRoute(
|
||||||
|
'verification.verify',
|
||||||
|
now()->addMinutes(Config::get('auth.verification.expire', 60)),
|
||||||
|
[
|
||||||
|
'id' => $notifiable->getKey(),
|
||||||
|
'hash' => sha1($notifiable->getEmailForVerification()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (new MailMessage)
|
||||||
|
->subject('Vérifie ton adresse email')
|
||||||
|
->view('mail.auth.verify-email', [
|
||||||
|
'notifiable' => $notifiable,
|
||||||
|
'verificationUrl' => $verificationUrl,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,14 @@ services:
|
||||||
DB_DATABASE: "${DB_DATABASE}"
|
DB_DATABASE: "${DB_DATABASE}"
|
||||||
DB_USERNAME: "${DB_USERNAME}"
|
DB_USERNAME: "${DB_USERNAME}"
|
||||||
DB_PASSWORD: "${DB_PASSWORD}"
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
|
MAIL_MAILER: "${MAIL_MAILER}"
|
||||||
|
MAIL_SCHEME: "${MAIL_SCHEME}"
|
||||||
|
MAIL_HOST: "${MAIL_HOST}"
|
||||||
|
MAIL_PORT: "${MAIL_PORT}"
|
||||||
|
MAIL_USERNAME: "${MAIL_USERNAME}"
|
||||||
|
MAIL_PASSWORD: "${MAIL_PASSWORD}"
|
||||||
|
MAIL_FROM_ADDRESS: "${MAIL_FROM_ADDRESS}"
|
||||||
|
MAIL_FROM_NAME: "${MAIL_FROM_NAME}"
|
||||||
volumes:
|
volumes:
|
||||||
- storage-data:/var/www/html/storage/app
|
- storage-data:/var/www/html/storage/app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,8 @@ return [
|
||||||
'password' => 'The provided password is incorrect.',
|
'password' => 'The provided password is incorrect.',
|
||||||
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
|
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
|
||||||
'logout_success' => 'You have been deconnecte',
|
'logout_success' => 'You have been deconnecte',
|
||||||
|
'email_verified' => 'Email address verified.',
|
||||||
|
'email_already_verified' => 'Email address already verified.',
|
||||||
|
'email_verification_sent' => 'Verification link sent.',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,8 @@ return [
|
||||||
'password' => 'Email ou mot de passe incorrect',
|
'password' => 'Email ou mot de passe incorrect',
|
||||||
'throttle' => 'Trop de tentatives veuillez reessayer dans :seconds seconds.',
|
'throttle' => 'Trop de tentatives veuillez reessayer dans :seconds seconds.',
|
||||||
'logout_success' => 'Vous êtes bien déconnecté',
|
'logout_success' => 'Vous êtes bien déconnecté',
|
||||||
|
'email_verified' => 'Adresse email vérifiée.',
|
||||||
|
'email_already_verified' => 'Adresse email déjà vérifiée.',
|
||||||
|
'email_verification_sent' => 'Lien de vérification envoyé.',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Email vérifié</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: #f4f7fb;
|
||||||
|
color: #172033;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(100% - 32px, 480px);
|
||||||
|
padding: 32px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e6ebf2;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: #101828;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: #475467;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Email vérifié</h1>
|
||||||
|
<p>Ton adresse email a bien été validée. Tu peux maintenant retourner sur l'application.</p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Vérifie ton adresse email</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; background: #f4f7fb; color: #172033; font-family: Arial, Helvetica, sans-serif;">
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background: #f4f7fb; padding: 32px 16px;">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width: 560px; overflow: hidden; background: #ffffff; border: 1px solid #e6ebf2; border-radius: 12px;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 32px 16px;">
|
||||||
|
<p style="margin: 0 0 8px; color: #657086; font-size: 14px;">{{ config('app.name') }}</p>
|
||||||
|
<h1 style="margin: 0; color: #101828; font-size: 28px; line-height: 1.2;">Vérifie ton adresse email</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0 32px 24px;">
|
||||||
|
<p style="margin: 0 0 16px; color: #344054; font-size: 16px; line-height: 1.6;">
|
||||||
|
Bonjour {{ $notifiable->username }},
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0 0 24px; color: #344054; font-size: 16px; line-height: 1.6;">
|
||||||
|
Clique sur le bouton ci-dessous pour confirmer ton adresse email et activer ton compte.
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0 0 28px;">
|
||||||
|
<a href="{{ $verificationUrl }}" style="display: inline-block; padding: 14px 22px; background: #16a34a; border-radius: 8px; color: #ffffff; font-size: 16px; font-weight: 700; text-decoration: none;">
|
||||||
|
Vérifier mon email
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0 0 16px; color: #667085; font-size: 14px; line-height: 1.6;">
|
||||||
|
Si le bouton ne fonctionne pas, ouvre ce lien dans ton navigateur :
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; word-break: break-all; color: #475467; font-size: 13px; line-height: 1.5;">
|
||||||
|
<a href="{{ $verificationUrl }}" style="color: #1570ef;">{{ $verificationUrl }}</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 32px 32px; border-top: 1px solid #edf1f7;">
|
||||||
|
<p style="margin: 0; color: #98a2b3; font-size: 13px; line-height: 1.5;">
|
||||||
|
Si tu n'es pas à l'origine de cette demande, tu peux ignorer cet email.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -11,6 +11,9 @@ use Illuminate\Support\Facades\Route;
|
||||||
Route::prefix('auth')->group(function (): void {
|
Route::prefix('auth')->group(function (): void {
|
||||||
Route::post('register', [AuthController::class, 'register']);
|
Route::post('register', [AuthController::class, 'register']);
|
||||||
Route::post('login', [AuthController::class, 'login']);
|
Route::post('login', [AuthController::class, 'login']);
|
||||||
|
Route::get('email/verify/{id}/{hash}', [AuthController::class, 'verifyEmail'])
|
||||||
|
->middleware(['signed', 'throttle:6,1'])
|
||||||
|
->name('verification.verify');
|
||||||
// Route::get('{provider}/redirect', [ProviderRedirectController::class, 'redirect']);
|
// Route::get('{provider}/redirect', [ProviderRedirectController::class, 'redirect']);
|
||||||
// Route::get('{provider}/callback', [ProviderCallbackController::class, 'callback']);
|
// Route::get('{provider}/callback', [ProviderCallbackController::class, 'callback']);
|
||||||
});
|
});
|
||||||
|
|
@ -18,12 +21,12 @@ Route::prefix('auth')->group(function (): void {
|
||||||
// List hunts
|
// List hunts
|
||||||
|
|
||||||
// Ensure is Admin
|
// Ensure is Admin
|
||||||
Route::middleware(['auth:sanctum', EnsureUserIsAdmin::class])->group(function () {
|
Route::middleware(['auth:sanctum', 'verified', EnsureUserIsAdmin::class])->group(function () {
|
||||||
Route::post('users/{user}/delete', [UsersController::class, 'softDeleteById'])->withTrashed();
|
Route::post('users/{user}/delete', [UsersController::class, 'softDeleteById'])->withTrashed();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure is Orga
|
// Ensure is Orga
|
||||||
Route::middleware(['auth:sanctum', EnsureUserIsOrga::class])->group(function () {
|
Route::middleware(['auth:sanctum', 'verified', EnsureUserIsOrga::class])->group(function () {
|
||||||
Route::post('hunts', [HuntsController::class, 'store']);
|
Route::post('hunts', [HuntsController::class, 'store']);
|
||||||
Route::post('hunts/{hunt}/steps', [HuntStepsController::class, 'store']);
|
Route::post('hunts/{hunt}/steps', [HuntStepsController::class, 'store']);
|
||||||
Route::match(['put', 'patch'], 'hunts/{hunt}', [HuntsController::class, 'update']);
|
Route::match(['put', 'patch'], 'hunts/{hunt}', [HuntsController::class, 'update']);
|
||||||
|
|
@ -32,6 +35,14 @@ Route::middleware(['auth:sanctum', EnsureUserIsOrga::class])->group(function ()
|
||||||
|
|
||||||
// Auth middleware
|
// Auth middleware
|
||||||
Route::middleware('auth:sanctum')->group(function (): void {
|
Route::middleware('auth:sanctum')->group(function (): void {
|
||||||
|
Route::post('auth/email/verification-notification', [AuthController::class, 'resendEmailVerificationNotification'])
|
||||||
|
->middleware('throttle:6,1')
|
||||||
|
->name('verification.send');
|
||||||
|
Route::get('auth/me', [AuthController::class, 'me']);
|
||||||
|
Route::post('auth/logout', [AuthController::class, 'logout']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware(['auth:sanctum', 'verified'])->group(function (): void {
|
||||||
Route::get('hunts', [HuntsController::class, 'index']);
|
Route::get('hunts', [HuntsController::class, 'index']);
|
||||||
Route::get('hunts/{hunt}', [HuntsController::class, 'show']);
|
Route::get('hunts/{hunt}', [HuntsController::class, 'show']);
|
||||||
// Auth
|
// Auth
|
||||||
|
|
@ -45,6 +56,4 @@ Route::middleware('auth:sanctum')->group(function (): void {
|
||||||
Route::post('hunts/{hunt}/participate', [HuntsController::class, 'participate']);
|
Route::post('hunts/{hunt}/participate', [HuntsController::class, 'participate']);
|
||||||
Route::delete('hunts/{hunt}/participate', [HuntsController::class, 'unparticipate']);
|
Route::delete('hunts/{hunt}/participate', [HuntsController::class, 'unparticipate']);
|
||||||
Route::post('hunts/{hunt}/steps/{step}/validate', [HuntStepsController::class, 'validateStep']);
|
Route::post('hunts/{hunt}/steps/{step}/validate', [HuntStepsController::class, 'validateStep']);
|
||||||
Route::get('auth/me', [AuthController::class, 'me']);
|
|
||||||
Route::post('auth/logout', [AuthController::class, 'logout']);
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\VerifyEmailNotification;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
it('sends a verification email after registration', function () {
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$this->postJson('/api/auth/register', [
|
||||||
|
'username' => 'new-user',
|
||||||
|
'email' => 'new-user@example.com',
|
||||||
|
'password' => 'Password1!',
|
||||||
|
'password_confirmation' => 'Password1!',
|
||||||
|
'device_name' => 'iphone',
|
||||||
|
])->assertCreated();
|
||||||
|
|
||||||
|
$user = User::where('email', 'new-user@example.com')->firstOrFail();
|
||||||
|
|
||||||
|
expect($user->hasVerifiedEmail())->toBeFalse();
|
||||||
|
|
||||||
|
Notification::assertSentTo($user, VerifyEmailNotification::class, function (VerifyEmailNotification $notification) use ($user) {
|
||||||
|
$mail = $notification->toMail($user);
|
||||||
|
|
||||||
|
expect($mail->subject)->toBe('Vérifie ton adresse email')
|
||||||
|
->and($mail->render())->toContain('Vérifier mon email');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resends a verification email for an authenticated unverified user', function () {
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$user = User::factory()->unverified()->create();
|
||||||
|
|
||||||
|
Sanctum::actingAs($user);
|
||||||
|
|
||||||
|
$this->postJson('/api/auth/email/verification-notification')
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('message', __('auth.email_verification_sent'));
|
||||||
|
|
||||||
|
Notification::assertSentTo($user, VerifyEmailNotification::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies the user email from a signed verification link', function () {
|
||||||
|
$user = User::factory()->unverified()->create();
|
||||||
|
|
||||||
|
$verificationUrl = URL::temporarySignedRoute(
|
||||||
|
'verification.verify',
|
||||||
|
now()->addMinutes(60),
|
||||||
|
[
|
||||||
|
'id' => $user->getKey(),
|
||||||
|
'hash' => sha1($user->getEmailForVerification()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->getJson($verificationUrl)
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('message', __('auth.email_verified'));
|
||||||
|
|
||||||
|
expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a confirmation page when the verification link is opened in a browser', function () {
|
||||||
|
$user = User::factory()->unverified()->create();
|
||||||
|
|
||||||
|
$verificationUrl = URL::temporarySignedRoute(
|
||||||
|
'verification.verify',
|
||||||
|
now()->addMinutes(60),
|
||||||
|
[
|
||||||
|
'id' => $user->getKey(),
|
||||||
|
'hash' => sha1($user->getEmailForVerification()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->get($verificationUrl)
|
||||||
|
->assertOk()
|
||||||
|
->assertSeeText('Email vérifié')
|
||||||
|
->assertSeeText("Ton adresse email a bien été validée. Tu peux maintenant retourner sur l'application.", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks verified routes for authenticated unverified users', function () {
|
||||||
|
$user = User::factory()->unverified()->create();
|
||||||
|
|
||||||
|
Sanctum::actingAs($user);
|
||||||
|
|
||||||
|
$this->getJson('/api/hunts')->assertForbidden();
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue