feat: forget-password & reset-password added
This commit is contained in:
@@ -18,9 +18,59 @@
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function requestPasswordReset(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
$status = Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
if ($status === Password::RESET_LINK_SENT) {
|
||||
return response()->json(['message' => __($status)]);
|
||||
}
|
||||
|
||||
return response()->json(['message' => __($status)], 400);
|
||||
}
|
||||
public function showResetForm($token)
|
||||
{
|
||||
return view('auth.reset-password', ['token' => $token]);
|
||||
}
|
||||
|
||||
public function resetPassword(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
'token' => 'required|string',
|
||||
'password' => 'required|string|confirmed|min:6',
|
||||
]);
|
||||
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user, $password) {
|
||||
$user->forceFill([
|
||||
'password' => $password,
|
||||
])->setRememberToken(Str::random(60));
|
||||
|
||||
$user->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
if ($status === Password::PASSWORD_RESET) {
|
||||
return response()->json(['message' => __($status)]);
|
||||
}
|
||||
|
||||
return response()->json(['message' => __($status)], 400);
|
||||
}
|
||||
public function login(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
|
||||
+1
-2
@@ -11,7 +11,6 @@
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/socialite": "^5.6",
|
||||
"laravel/telescope": "^5.11",
|
||||
"laravel/tinker": "^2.8",
|
||||
"masbug/flysystem-google-drive-ext": "^2.2",
|
||||
"predis/predis": "^2.1",
|
||||
@@ -24,10 +23,10 @@
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.18",
|
||||
"laravel/telescope": "^5.11",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"phpunit/phpunit": "^10.1",
|
||||
"spatie/laravel-ignition": "^2.0"
|
||||
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
Generated
+70
-70
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9014e7db7181477837583be158dad2c3",
|
||||
"content-hash": "93b523c73b637a7401d21d02b2b72234",
|
||||
"packages": [
|
||||
{
|
||||
"name": "beste/clock",
|
||||
@@ -2850,75 +2850,6 @@
|
||||
},
|
||||
"time": "2025-07-23T14:16:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/telescope",
|
||||
"version": "v5.11.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/telescope.git",
|
||||
"reference": "7684604e104e7755b70dcacfeee06888e2470689"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/telescope/zipball/7684604e104e7755b70dcacfeee06888e2470689",
|
||||
"reference": "7684604e104e7755b70dcacfeee06888e2470689",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"laravel/framework": "^8.37|^9.0|^10.0|^11.0|^12.0",
|
||||
"php": "^8.0",
|
||||
"symfony/console": "^5.3|^6.0|^7.0",
|
||||
"symfony/var-dumper": "^5.0|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-gd": "*",
|
||||
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||
"laravel/octane": "^1.4|^2.0|dev-develop",
|
||||
"orchestra/testbench": "^6.40|^7.37|^8.17|^9.0|^10.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.0|^10.5|^11.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Telescope\\TelescopeServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Telescope\\": "src/",
|
||||
"Laravel\\Telescope\\Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
},
|
||||
{
|
||||
"name": "Mohamed Said",
|
||||
"email": "mohamed@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "An elegant debug assistant for the Laravel framework.",
|
||||
"keywords": [
|
||||
"debugging",
|
||||
"laravel",
|
||||
"monitoring"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/telescope/issues",
|
||||
"source": "https://github.com/laravel/telescope/tree/v5.11.3"
|
||||
},
|
||||
"time": "2025-08-21T14:25:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
"version": "v2.10.1",
|
||||
@@ -9608,6 +9539,75 @@
|
||||
},
|
||||
"time": "2025-08-25T19:28:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/telescope",
|
||||
"version": "v5.11.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/telescope.git",
|
||||
"reference": "7684604e104e7755b70dcacfeee06888e2470689"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/telescope/zipball/7684604e104e7755b70dcacfeee06888e2470689",
|
||||
"reference": "7684604e104e7755b70dcacfeee06888e2470689",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"laravel/framework": "^8.37|^9.0|^10.0|^11.0|^12.0",
|
||||
"php": "^8.0",
|
||||
"symfony/console": "^5.3|^6.0|^7.0",
|
||||
"symfony/var-dumper": "^5.0|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-gd": "*",
|
||||
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||
"laravel/octane": "^1.4|^2.0|dev-develop",
|
||||
"orchestra/testbench": "^6.40|^7.37|^8.17|^9.0|^10.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.0|^10.5|^11.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Telescope\\TelescopeServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Telescope\\": "src/",
|
||||
"Laravel\\Telescope\\Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
},
|
||||
{
|
||||
"name": "Mohamed Said",
|
||||
"email": "mohamed@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "An elegant debug assistant for the Laravel framework.",
|
||||
"keywords": [
|
||||
"debugging",
|
||||
"laravel",
|
||||
"monitoring"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/telescope/issues",
|
||||
"source": "https://github.com/laravel/telescope/tree/v5.11.3"
|
||||
},
|
||||
"time": "2025-08-21T14:25:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
"version": "1.6.12",
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Get the migration connection name.
|
||||
*/
|
||||
public function getConnection(): ?string
|
||||
{
|
||||
return config('telescope.storage.database.connection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$schema = Schema::connection($this->getConnection());
|
||||
|
||||
$schema->create('telescope_entries', function (Blueprint $table) {
|
||||
$table->bigIncrements('sequence');
|
||||
$table->uuid('uuid');
|
||||
$table->uuid('batch_id');
|
||||
$table->string('family_hash')->nullable();
|
||||
$table->boolean('should_display_on_index')->default(true);
|
||||
$table->string('type', 20);
|
||||
$table->longText('content');
|
||||
$table->dateTime('created_at')->nullable();
|
||||
|
||||
$table->unique('uuid');
|
||||
$table->index('batch_id');
|
||||
$table->index('family_hash');
|
||||
$table->index('created_at');
|
||||
$table->index(['type', 'should_display_on_index']);
|
||||
});
|
||||
|
||||
$schema->create('telescope_entries_tags', function (Blueprint $table) {
|
||||
$table->uuid('entry_uuid');
|
||||
$table->string('tag');
|
||||
|
||||
$table->primary(['entry_uuid', 'tag']);
|
||||
$table->index('tag');
|
||||
|
||||
$table->foreign('entry_uuid')
|
||||
->references('uuid')
|
||||
->on('telescope_entries')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
|
||||
$schema->create('telescope_monitoring', function (Blueprint $table) {
|
||||
$table->string('tag')->primary();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$schema = Schema::connection($this->getConnection());
|
||||
|
||||
$schema->dropIfExists('telescope_entries_tags');
|
||||
$schema->dropIfExists('telescope_entries');
|
||||
$schema->dropIfExists('telescope_monitoring');
|
||||
}
|
||||
};
|
||||
@@ -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::create('password_resets', function (Blueprint $table) {
|
||||
$table->string('email')->index();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('password_resets');
|
||||
}
|
||||
};
|
||||
@@ -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('users', function (Blueprint $table) {
|
||||
$table->rememberToken()->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('remember_token');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
<!doctype html>
|
||||
<html lang="fa">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>بازیابی رمز عبور</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Tahoma', 'IRANYekan', sans-serif;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f7fb;
|
||||
direction: rtl;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
padding: 28px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 30px rgba(0,0,0,.06);
|
||||
width: 380px;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.msg {
|
||||
margin-top: 12px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2 style="margin:0 0 8px">بازیابی رمز عبور</h2>
|
||||
<p style="margin:0 0 12px;font-size:13px;color:#555">
|
||||
لطفاً رمز عبور جدید خود را وارد کنید
|
||||
</p>
|
||||
|
||||
<form id="resetForm" autocomplete="off">
|
||||
<input id="email" name="email" type="email" placeholder="ایمیل (خودکار پر شده)" required />
|
||||
<input id="password" name="password" type="password" placeholder="رمز عبور جدید" required />
|
||||
<input id="password_confirmation" name="password_confirmation" type="password" placeholder="تأیید رمز عبور" required />
|
||||
<input id="token" name="token" type="hidden" value="{{ $token }}" />
|
||||
<button type="submit">بازنشانی رمز عبور</button>
|
||||
</form>
|
||||
|
||||
<div id="result" class="msg" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('resetForm').addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const resultEl = document.getElementById('result');
|
||||
resultEl.style.display = 'block';
|
||||
resultEl.style.background = '#fff7ed';
|
||||
resultEl.style.color = '#7c2d12';
|
||||
resultEl.textContent = 'در حال پردازش...';
|
||||
|
||||
const email = document.getElementById('email').value.trim();
|
||||
const password = document.getElementById('password').value;
|
||||
const password_confirmation = document.getElementById('password_confirmation').value;
|
||||
const token = document.getElementById('token').value;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/reset-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, token, password, password_confirmation })
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
resultEl.style.background = '#ecfdf5';
|
||||
resultEl.style.color = '#064e3b';
|
||||
resultEl.textContent = 'رمز عبور با موفقیت بازنشانی شد. اکنون میتوانید وارد شوید.';
|
||||
} else {
|
||||
resultEl.style.background = '#fff1f2';
|
||||
resultEl.style.color = '#7f1d1d';
|
||||
if (json.errors) {
|
||||
const first = Object.values(json.errors)[0];
|
||||
resultEl.textContent = Array.isArray(first) ? first[0] : first;
|
||||
} else {
|
||||
resultEl.textContent = json.message || JSON.stringify(json);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
resultEl.style.background = '#fff1f2';
|
||||
resultEl.style.color = '#7f1d1d';
|
||||
resultEl.textContent = 'خطای شبکه.';
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
const qs = new URLSearchParams(window.location.search);
|
||||
const emailFromQuery = qs.get('email');
|
||||
if (emailFromQuery) document.getElementById('email').value = emailFromQuery;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+7
-1
@@ -23,7 +23,13 @@
|
||||
Route::post('{id}', 'update');
|
||||
Route::get('', 'index');
|
||||
});
|
||||
|
||||
Route::prefix('auth')->controller(UserController::class)->group(function () {
|
||||
Route::post('forgot-password', 'requestPasswordReset'); // send reset link
|
||||
Route::post('reset-password', 'resetPassword'); // actually reset
|
||||
});
|
||||
|
||||
Route::get('/reset-password/{token}', [UserController::class, 'showResetForm'])
|
||||
->name('password.reset');
|
||||
Route::prefix('auth')->controller(UserController::class)->group(function () {
|
||||
Route::middleware(['throttle:3,1', 'auto-ban'])->post('login-otp', 'loginOTP');
|
||||
Route::post('check-otp', 'checkOTP');
|
||||
|
||||
Reference in New Issue
Block a user