From afd7e349d4e607fa8a89afee79802fb9b6e17d4e Mon Sep 17 00:00:00 2001 From: Amirmahdi Nourkazemi Date: Thu, 18 Sep 2025 23:18:36 +0330 Subject: [PATCH] feat: add verify with link and back to app --- app/Http/Controllers/UserController.php | 194 ++++++++++++------ app/Mail/VerifyEmailLinkMail.php | 62 ++++++ ...add_token_to_email_verifications_table.php | 28 +++ resources/views/emails/verified.blade.php | 91 ++++++++ resources/views/emails/verify-link.blade.php | 85 ++++++++ routes/api.php | 1 + 6 files changed, 403 insertions(+), 58 deletions(-) create mode 100644 app/Mail/VerifyEmailLinkMail.php create mode 100644 database/migrations/2025_09_18_194033_add_token_to_email_verifications_table.php create mode 100644 resources/views/emails/verified.blade.php create mode 100644 resources/views/emails/verify-link.blade.php diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index ee2638d..f44115f 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -139,44 +139,63 @@ public function login(Request $request) 'token' => $token, ]); } - public function resendEmailVerification(Request $request) - { - $request->validate([ - 'email' => 'required|email', - ]); - - $user = User::where('email', $request->email)->first(); - - if (!$user) { - return response()->json(['message' => 'User not found'], 404); - } - - if ($user->email_verified_at) { - return response()->json(['message' => 'Email is already verified'], 400); - } - - // Generate new code - $code = rand(1000, 9999); - - // Delete previous codes - \DB::table('email_verifications')->where('user_id', $user->id)->delete(); - - // Insert new verification code - \DB::table('email_verifications')->insert([ - 'user_id' => $user->id, - 'code' => $code, - 'expires_at' => now()->addMinutes(15), - 'created_at' => now(), - 'updated_at' => now(), - ]); - - // Send email - \Mail::to($user->email)->send(new \App\Mail\VerifyEmailCodeMail($code)); - - return response()->json([ - 'message' => 'Verification email resent successfully' - ]); + public function resendEmailVerification(Request $request) +{ + $data = $request->validate([ + 'email' => 'required|email', + 'method' => 'string|in:code,link|nullable', + 'package_name' => 'string|required', + ]); + + $method = $data['method'] ?? 'code'; + $packageNameValue = $data['package_name']; + unset($data['package_name']); + + $user = User::where('email', $data['email'])->first(); + // return response()->json(['message' => $user]); + + if (!$user) { + return response()->json(['message' => 'User not found'], 404); } + + if ($user->email_verified_at) { + return response()->json(['message' => 'Email is already verified'], 400); + } + + $code = rand(1000, 9999); + $token = Str::random(64); + + \DB::table('email_verifications')->where('user_id', $user->id)->delete(); + + \DB::table('email_verifications')->insert([ + 'user_id' => $user->id, + 'code' => $code, + 'token' => $token, + 'expires_at' => now()->addMinutes(15), + 'created_at' => now(), + 'updated_at' => now(), + ]); + + try { + if ($method === 'code') { + \Mail::to($user->email)->send(new \App\Mail\VerifyEmailCodeMail($code)); + } else { + $verificationUrl = url("api/auth/verify-email-link/{$token}?package={$packageNameValue}"); + \Mail::to($user->email)->send(new \App\Mail\VerifyEmailLinkMail($verificationUrl)); + } + } catch (\Exception $e) { + // rollback verification + \DB::table('email_verifications')->where('user_id', $user->id)->delete(); + + return response()->json([ + 'error' => $e->getMessage(), + 'message' => 'ارسال ایمیل با مشکل مواجه شد، لطفا دوباره تلاش کنید' + ], 400); + } + + return response()->json(['message' => 'Verification email resent successfully']); +} + public function register(Request $request) { $data = $request->validate([ @@ -186,11 +205,16 @@ public function register(Request $request) 'password' => 'string|nullable|required_with:email', 'mobile' => ['string', new MobileNumber, 'nullable', 'required_without:email'], 'fcm_token' => 'string|nullable', - 'package_name' => 'string|required' + 'package_name' => 'string|required', + 'method' => 'string|in:code,link|nullable' ]); - $packageNameValue = $data['package_name']; - unset($data['package_name']); + $method = $data['method'] ?? 'code'; + unset($data['method']); + + // Extract and remove package_name + $packageNameValue = $data['package_name']; + unset($data['package_name']); if (isset($data['mobile'])) { $data['mobile'] = MobileNumberHelper::formatMobile($data['mobile']); @@ -203,21 +227,35 @@ public function register(Request $request) return response()->json(['message' => 'Email already exists'], 400); } + $code = rand(1000, 9999); + $token = Str::random(64); // ✅ Check if email can receive a message before creating the user if (isset($data['email'])) { - $code = rand(1000, 9999); - - try { - // Try sending email - \Mail::to($data['email'])->send(new \App\Mail\VerifyEmailCodeMail($code)); - } catch (\Exception $e) { - return response()->json([ - 'code' => $e->getCode(), - 'error' => $e->getMessage(), - 'message' => 'لطفا آدرس ایمیل خود را بررسی کنید و مجدد تلاش کنید', + // $code = rand(1000, 9999); + try { + if ($method === 'code') { + \Mail::to($data['email'])->send(new \App\Mail\VerifyEmailCodeMail($code)); + } else { + $verificationUrl = url("api/auth/verify-email-link/{$token}?package={$packageNameValue}"); + \Mail::to($data['email'])->send(new \App\Mail\VerifyEmailLinkMail($verificationUrl)); + } + } catch (\Exception $e) { + return response()->json([ + 'error' => $e->getMessage(), + 'message' => 'لطفا آدرس ایمیل خود را بررسی کنید و مجدد تلاش کنید' + ], 400); + } + // try { + // // Try sending email + // \Mail::to($data['email'])->send(new \App\Mail\VerifyEmailCodeMail($code)); + // } catch (\Exception $e) { + // return response()->json([ + // 'code' => $e->getCode(), + // 'error' => $e->getMessage(), + // 'message' => 'لطفا آدرس ایمیل خود را بررسی کنید و مجدد تلاش کنید', - ], 400); - } + // ], 400); + // } } // Hash password @@ -237,15 +275,17 @@ public function register(Request $request) } // Save email verification code after user is created - if (isset($data['email'])) { - \DB::table('email_verifications')->insert([ - 'user_id' => $user->id, - 'code' => $code, + // if (isset($data['email'])) { + \DB::table('email_verifications')->where('user_id', $user->id)->delete(); + \DB::table('email_verifications')->insert([ + 'user_id' => $user->id, + 'code' => $code, + 'token' => $token, 'expires_at' => now()->addMinutes(15), 'created_at' => now(), 'updated_at' => now(), - ]); - } + ]); + // } // Create token $token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken; @@ -286,6 +326,44 @@ public function verifyEmailCode(Request $request) $token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken; return response()->json(['message' => 'Email verified successfully', 'token' => $token]); } + public function verifyEmailLink(Request $request, $token) +{ + $record = \DB::table('email_verifications') + ->where('token', $token) + ->where('expires_at', '>', now()) + ->first(); + + if (!$record) { + return response()->json(['message' => 'Invalid or expired token'], 400); + } + + $user = User::find($record->user_id); + if (!$user) { + return response()->json(['message' => 'User not found'], 404); + } + + $user->update(['email_verified_at' => now()]); + + \DB::table('email_verifications')->where('id', $record->id)->delete(); + + // Optionally redirect to app deep link + $packageName = $request->query('package'); + if ($packageName && $package = \App\Models\PackageName::where('name', $packageName)->first()) { + if ($package->web_app_url) { + return redirect()->away($package->web_app_url . '?verified=1'); + } + } + + // $packageName = $request->query('package') ?? 'default'; + $deepLink = "approagency://{$packageName}?verified=1"; + + return view('emails.verified', [ + 'status' => 'success', + 'message' => 'ایمیل شما با موفقیت تایید شد', + 'deepLink' => $deepLink, + ]); +} + public function loginOTP(Request $request) { $data = $request->validate([ diff --git a/app/Mail/VerifyEmailLinkMail.php b/app/Mail/VerifyEmailLinkMail.php new file mode 100644 index 0000000..cce6d63 --- /dev/null +++ b/app/Mail/VerifyEmailLinkMail.php @@ -0,0 +1,62 @@ +verificationUrl = $verificationUrl; + } + public function build() + { + return $this->subject('Verify Your Email Address') + ->markdown('emails.verify-link') + ->with([ + 'verificationUrl' => $this->verificationUrl, + ]); + } + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + subject: 'Verify Email Link Mail', + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'emails.verify-link', + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/database/migrations/2025_09_18_194033_add_token_to_email_verifications_table.php b/database/migrations/2025_09_18_194033_add_token_to_email_verifications_table.php new file mode 100644 index 0000000..6b23d89 --- /dev/null +++ b/database/migrations/2025_09_18_194033_add_token_to_email_verifications_table.php @@ -0,0 +1,28 @@ +string('token')->nullable()->after('code')->unique(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('email_verifications', function (Blueprint $table) { + // + }); + } +}; diff --git a/resources/views/emails/verified.blade.php b/resources/views/emails/verified.blade.php new file mode 100644 index 0000000..a64f8dc --- /dev/null +++ b/resources/views/emails/verified.blade.php @@ -0,0 +1,91 @@ + + + + + ایمیل تایید شد + + + + + +
+

{{ $message }}

+ + @if($deepLink) +

ایمیل شما با موفقیت تایید شد.

+ بازگشت به اپلیکیشن +

در حال انتقال خودکار به اپلیکیشن در ۳ ثانیه...

+ @endif +
+ + diff --git a/resources/views/emails/verify-link.blade.php b/resources/views/emails/verify-link.blade.php new file mode 100644 index 0000000..5827d3b --- /dev/null +++ b/resources/views/emails/verify-link.blade.php @@ -0,0 +1,85 @@ +@component('mail::message') +# تایید ایمیل شما + +برای تایید آدرس ایمیل خود روی دکمه زیر کلیک کنید: + +@php + // Extract package name from query string + $parsedUrl = parse_url($verificationUrl); + parse_str($parsedUrl['query'] ?? '', $queryParams); + $packageName = $queryParams['package'] ?? 'default'; + + // Create deep link after verification + $deepLink = "approagency://{$packageName}?verified=1"; +@endphp + +@component('mail::button', ['url' => $verificationUrl, 'style' => 'background-color: #0288d1; padding: 12px 24px; font-size: 18px; border-radius: 5px; text-decoration: none; color: white; text-align: center; display: inline-block;']) +تایید ایمیل +@endcomponent + +--- + +با تشکر، +{{ config('app.name') }} + +@push('styles') + +@endpush +@endcomponent diff --git a/routes/api.php b/routes/api.php index ce7fc41..5a4f0e7 100644 --- a/routes/api.php +++ b/routes/api.php @@ -35,6 +35,7 @@ Route::middleware(['throttle:3,1', 'auto-ban'])->post('login-otp', 'loginOTP'); Route::post('check-otp', 'checkOTP'); Route::post('verify-email-code','verifyEmailCode'); + Route::get('verify-email-link/{token}', [UserController::class, 'verifyEmailLink'])->name('verify.email.link'); Route::post('login', 'login'); Route::post('register', 'register'); Route::post('resend-email-verification', [UserController::class, 'resendEmailVerification']);