feat: add verify with link and back to app
This commit is contained in:
@@ -139,44 +139,63 @@ public function login(Request $request)
|
|||||||
'token' => $token,
|
'token' => $token,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
public function resendEmailVerification(Request $request)
|
public function resendEmailVerification(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$data = $request->validate([
|
||||||
'email' => 'required|email',
|
'email' => 'required|email',
|
||||||
]);
|
'method' => 'string|in:code,link|nullable',
|
||||||
|
'package_name' => 'string|required',
|
||||||
$user = User::where('email', $request->email)->first();
|
]);
|
||||||
|
|
||||||
if (!$user) {
|
$method = $data['method'] ?? 'code';
|
||||||
return response()->json(['message' => 'User not found'], 404);
|
$packageNameValue = $data['package_name'];
|
||||||
}
|
unset($data['package_name']);
|
||||||
|
|
||||||
if ($user->email_verified_at) {
|
$user = User::where('email', $data['email'])->first();
|
||||||
return response()->json(['message' => 'Email is already verified'], 400);
|
// return response()->json(['message' => $user]);
|
||||||
}
|
|
||||||
|
if (!$user) {
|
||||||
// Generate new code
|
return response()->json(['message' => 'User not found'], 404);
|
||||||
$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'
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
public function register(Request $request)
|
||||||
{
|
{
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
@@ -186,11 +205,16 @@ public function register(Request $request)
|
|||||||
'password' => 'string|nullable|required_with:email',
|
'password' => 'string|nullable|required_with:email',
|
||||||
'mobile' => ['string', new MobileNumber, 'nullable', 'required_without:email'],
|
'mobile' => ['string', new MobileNumber, 'nullable', 'required_without:email'],
|
||||||
'fcm_token' => 'string|nullable',
|
'fcm_token' => 'string|nullable',
|
||||||
'package_name' => 'string|required'
|
'package_name' => 'string|required',
|
||||||
|
'method' => 'string|in:code,link|nullable'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$packageNameValue = $data['package_name'];
|
$method = $data['method'] ?? 'code';
|
||||||
unset($data['package_name']);
|
unset($data['method']);
|
||||||
|
|
||||||
|
// Extract and remove package_name
|
||||||
|
$packageNameValue = $data['package_name'];
|
||||||
|
unset($data['package_name']);
|
||||||
|
|
||||||
if (isset($data['mobile'])) {
|
if (isset($data['mobile'])) {
|
||||||
$data['mobile'] = MobileNumberHelper::formatMobile($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);
|
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
|
// ✅ Check if email can receive a message before creating the user
|
||||||
if (isset($data['email'])) {
|
if (isset($data['email'])) {
|
||||||
$code = rand(1000, 9999);
|
// $code = rand(1000, 9999);
|
||||||
|
try {
|
||||||
try {
|
if ($method === 'code') {
|
||||||
// Try sending email
|
\Mail::to($data['email'])->send(new \App\Mail\VerifyEmailCodeMail($code));
|
||||||
\Mail::to($data['email'])->send(new \App\Mail\VerifyEmailCodeMail($code));
|
} else {
|
||||||
} catch (\Exception $e) {
|
$verificationUrl = url("api/auth/verify-email-link/{$token}?package={$packageNameValue}");
|
||||||
return response()->json([
|
\Mail::to($data['email'])->send(new \App\Mail\VerifyEmailLinkMail($verificationUrl));
|
||||||
'code' => $e->getCode(),
|
}
|
||||||
'error' => $e->getMessage(),
|
} catch (\Exception $e) {
|
||||||
'message' => 'لطفا آدرس ایمیل خود را بررسی کنید و مجدد تلاش کنید',
|
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
|
// Hash password
|
||||||
@@ -237,15 +275,17 @@ public function register(Request $request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save email verification code after user is created
|
// Save email verification code after user is created
|
||||||
if (isset($data['email'])) {
|
// if (isset($data['email'])) {
|
||||||
\DB::table('email_verifications')->insert([
|
\DB::table('email_verifications')->where('user_id', $user->id)->delete();
|
||||||
'user_id' => $user->id,
|
\DB::table('email_verifications')->insert([
|
||||||
'code' => $code,
|
'user_id' => $user->id,
|
||||||
|
'code' => $code,
|
||||||
|
'token' => $token,
|
||||||
'expires_at' => now()->addMinutes(15),
|
'expires_at' => now()->addMinutes(15),
|
||||||
'created_at' => now(),
|
'created_at' => now(),
|
||||||
'updated_at' => now(),
|
'updated_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Create token
|
// Create token
|
||||||
$token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken;
|
$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;
|
$token = $user->createToken('token', $user->is_admin ? ['admin'] : ['user'])->plainTextToken;
|
||||||
return response()->json(['message' => 'Email verified successfully', 'token' => $token]);
|
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)
|
public function loginOTP(Request $request)
|
||||||
{
|
{
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
Route::middleware(['throttle:3,1', 'auto-ban'])->post('login-otp', 'loginOTP');
|
Route::middleware(['throttle:3,1', 'auto-ban'])->post('login-otp', 'loginOTP');
|
||||||
Route::post('check-otp', 'checkOTP');
|
Route::post('check-otp', 'checkOTP');
|
||||||
Route::post('verify-email-code','verifyEmailCode');
|
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('login', 'login');
|
||||||
Route::post('register', 'register');
|
Route::post('register', 'register');
|
||||||
Route::post('resend-email-verification', [UserController::class, 'resendEmailVerification']);
|
Route::post('resend-email-verification', [UserController::class, 'resendEmailVerification']);
|
||||||
|
|||||||
Reference in New Issue
Block a user