feat: verify email

This commit is contained in:
2026-05-20 20:40:23 +02:00
parent 75b2743e75
commit 7892fb9a02
16 changed files with 495 additions and 9 deletions
+61 -1
View File
@@ -7,6 +7,7 @@ use App\Http\Requests\RegisterRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -14,10 +15,11 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\View\View;
class AuthController extends Controller
{
public function register(RegisterRequest $request)
public function register(RegisterRequest $request): JsonResponse
{
$data = $request->validated();
@@ -33,9 +35,12 @@ class AuthController extends Controller
'avatar_url' => $avatarPath,
]);
$user->sendEmailVerificationNotification();
$token = $user->createToken('api', ['user'])->plainTextToken;
return response()->json([
'message' => 'Compte créé. Consulte tes emails pour le vérifier.',
'user' => new UserResource($user),
], 201)->cookie('token', $token, 60 * 24 * 30);
}
@@ -52,6 +57,12 @@ class AuthController extends Controller
], 401);
}
if (! $user->hasVerifiedEmail()) {
return response()->json([
'message' => 'Vérifie ton adresse email avant de te connecter.',
], 403);
}
$token = $user->createToken('api')->plainTextToken;
return response()->json([
@@ -59,6 +70,55 @@ class AuthController extends Controller
])->cookie('token', $token, 60 * 24 * 30);
}
public function verifyEmail(Request $request, string $id, string $hash): JsonResponse|View
{
$user = User::findOrFail($id);
abort_unless(
hash_equals($hash, sha1($user->getEmailForVerification())),
403
);
$alreadyVerified = $user->hasVerifiedEmail();
if (! $alreadyVerified) {
$user->markEmailAsVerified();
event(new Verified($user));
}
if ($request->expectsJson()) {
return response()->json([
'message' => $alreadyVerified
? 'Compte déjà vérifié.'
: 'Compte vérifié avec succès.',
'user' => new UserResource($user->fresh()),
]);
}
return view('auth.email-verified', [
'alreadyVerified' => $alreadyVerified,
'appName' => config('app.name'),
]);
}
public function sendVerificationEmail(Request $request): JsonResponse
{
$user = $request->user();
if ($user->hasVerifiedEmail()) {
return response()->json([
'message' => 'Compte déjà vérifié.',
]);
}
$user->sendEmailVerificationNotification();
return response()->json([
'message' => 'Email de vérification envoyé.',
]);
}
public function logout(): JsonResponse
{
$request = request();
+2
View File
@@ -18,6 +18,8 @@ class UserResource extends JsonResource
return [
'name' => $this->name,
'email' => $this->email,
'emailVerified' => $this->hasVerifiedEmail(),
'emailVerifiedAt' => $this->email_verified_at?->toISOString(),
'height' => $this->height,
'avatarUrl' => $this->avatar_url ? asset(Storage::url($this->avatar_url)) : null,
'bio' => $this->bio,
+58
View File
@@ -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 VerifyAccount extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(
public User $user,
public string $verificationUrl,
) {}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Vérifie ton compte Daily Meal',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'emails.verify-account',
with: [
'expiresInMinutes' => (int) config('auth.verification.expire', 60),
'userName' => $this->user->name,
'verificationUrl' => $this->verificationUrl,
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -14,7 +14,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements FilamentUser
class User extends Authenticatable implements FilamentUser, MustVerifyEmail
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasApiTokens, HasFactory, HasUlids, Notifiable;
+8
View File
@@ -3,6 +3,9 @@
namespace App\Providers;
use App\Broadcasting\ExpoPushChannel;
use App\Mail\VerifyAccount;
use App\Models\User;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\URL;
@@ -39,6 +42,11 @@ class AppServiceProvider extends ServiceProvider
Notification::extend('expo', fn ($app) => $app->make(ExpoPushChannel::class));
VerifyEmail::toMailUsing(
fn (User $notifiable, string $verificationUrl): VerifyAccount => (new VerifyAccount($notifiable, $verificationUrl))
->to($notifiable->getEmailForVerification())
);
Gate::define('viewApiDocs', function ($user = null) {
// Option A : Autoriser tout le monde (Attention : la doc sera publique hors local)
return true;