feat: add verify with link and back to app

This commit is contained in:
2025-09-18 23:18:36 +03:30
parent e44d4064df
commit afd7e349d4
6 changed files with 403 additions and 58 deletions
+96 -18
View File
@@ -140,12 +140,19 @@ public function login(Request $request)
]);
}
public function resendEmailVerification(Request $request)
{
$request->validate([
{
$data = $request->validate([
'email' => 'required|email',
'method' => 'string|in:code,link|nullable',
'package_name' => 'string|required',
]);
$user = User::where('email', $request->email)->first();
$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);
@@ -155,28 +162,40 @@ public function resendEmailVerification(Request $request)
return response()->json(['message' => 'Email is already verified'], 400);
}
// Generate new code
$code = rand(1000, 9999);
$token = Str::random(64);
// 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,
'token' => $token,
'expires_at' => now()->addMinutes(15),
'created_at' => now(),
'updated_at' => now(),
]);
// Send email
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([
'message' => 'Verification email resent successfully'
]);
'error' => $e->getMessage(),
'message' => 'ارسال ایمیل با مشکل مواجه شد، لطفا دوباره تلاش کنید'
], 400);
}
return response()->json(['message' => 'Verification email resent successfully']);
}
public function register(Request $request)
{
$data = $request->validate([
@@ -186,9 +205,14 @@ 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'
]);
$method = $data['method'] ?? 'code';
unset($data['method']);
// Extract and remove package_name
$packageNameValue = $data['package_name'];
unset($data['package_name']);
@@ -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);
// $code = rand(1000, 9999);
try {
// Try sending email
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([
'code' => $e->getCode(),
'error' => $e->getMessage(),
'message' => 'لطفا آدرس ایمیل خود را بررسی کنید و مجدد تلاش کنید',
'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);
// }
}
// Hash password
@@ -237,15 +275,17 @@ public function register(Request $request)
}
// Save email verification code after user is created
if (isset($data['email'])) {
// 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([
+62
View File
@@ -0,0 +1,62 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class VerifyEmailLinkMail extends Mailable
{
use Queueable, SerializesModels;
public $verificationUrl;
/**
* Create a new message instance.
*/
public function __construct($verificationUrl)
{
$this->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<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('email_verifications', function (Blueprint $table) {
$table->string('token')->nullable()->after('code')->unique();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('email_verifications', function (Blueprint $table) {
//
});
}
};
+91
View File
@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="fa">
<head>
<meta charset="utf-8">
<title>ایمیل تایید شد</title>
<style>
/* Reset default margins/paddings */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Tahoma', sans-serif;
direction: rtl;
text-align: center;
background-color: #f5f6fa;
color: #333;
padding: 50px 20px;
}
.container {
max-width: 500px;
margin: 0 auto;
background-color: #ffffff;
padding: 40px 30px;
border-radius: 15px;
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
}
h1 {
font-size: 28px;
color: #0288d1;
margin-bottom: 20px;
}
p {
font-size: 16px;
color: #555;
margin-bottom: 30px;
}
.button {
display: inline-block;
padding: 15px 30px;
font-size: 18px;
background: linear-gradient(135deg, #0288d1, #26c6da);
color: #fff;
text-decoration: none;
border-radius: 10px;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.button:hover {
background: linear-gradient(135deg, #026aa7, #00acc1);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.25);
}
.info {
font-size: 14px;
color: #999;
margin-top: 15px;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
const deepLink = "{{ $deepLink }}";
if(deepLink) {
setTimeout(function() {
window.location.href = deepLink;
}, 3000); // ۳ ثانیه = 3000 میلی‌ثانیه
}
});
</script>
</head>
<body>
<div class="container">
<h1>{{ $message }}</h1>
@if($deepLink)
<p>ایمیل شما با موفقیت تایید شد.</p>
<a href="{{ $deepLink }}" class="button">بازگشت به اپلیکیشن</a>
<p class="info">در حال انتقال خودکار به اپلیکیشن در ۳ ثانیه...</p>
@endif
</div>
</body>
</html>
@@ -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')
<style>
/* Body and general email structure */
body {
direction: rtl;
text-align: right;
font-family: 'Tahoma', sans-serif;
background-color: #f5f6fa;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
padding: 40px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.header {
font-size: 24px;
color: #0288d1;
margin-bottom: 20px;
text-align: center;
}
.content {
font-size: 16px;
color: #555;
margin-bottom: 30px;
line-height: 1.6;
}
.button {
display: inline-block;
padding: 12px 24px;
background-color: #0288d1;
color: white;
text-decoration: none;
border-radius: 5px;
text-align: center;
font-size: 18px;
margin: 20px 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease, transform 0.3s ease;
}
.button:hover {
background-color: #026aa7;
transform: translateY(-2px);
}
.footer {
font-size: 14px;
color: #888;
text-align: center;
}
</style>
@endpush
@endcomponent
+1
View File
@@ -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']);