add feature: reset password

This commit is contained in:
ImMocha 2025-06-20 18:05:25 +08:00
parent c01df37af0
commit eedda5766b
9 changed files with 500 additions and 0 deletions

View File

@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Validator;
class ForgotPasswordController extends Controller
{
public function __construct()
{
$this->middleware('guest');
}
/**
* Menampilkan form lupa password
*/
public function showLinkRequestForm()
{
return view('auth.forgot_password');
}
/**
* Mengirim email reset password
*/
public function sendResetLinkEmail(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|email|exists:users,email',
], [
'email.required' => 'Email wajib diisi.',
'email.email' => 'Format email tidak valid.',
'email.exists' => 'Email tidak ditemukan dalam sistem kami.',
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
// Mengirim email reset password
$status = Password::sendResetLink(
$request->only('email')
);
if ($status === Password::RESET_LINK_SENT) {
return back()->with('status', 'Link reset password telah dikirim ke email Anda!');
}
return back()->withErrors(['email' => 'Terjadi kesalahan saat mengirim email reset password.']);
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use App\Models\User;
class ResetPasswordController extends Controller
{
public function __construct()
{
$this->middleware('guest');
}
/**
* Menampilkan form reset password
*/
public function showResetForm(Request $request, $token = null)
{
return view('auth.reset_password')->with([
'token' => $token,
'email' => $request->email
]);
}
/**
* Memproses reset password
*/
public function reset(Request $request)
{
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => 'required|min:8|confirmed',
], [
'token.required' => 'Token reset password diperlukan.',
'email.required' => 'Email wajib diisi.',
'email.email' => 'Format email tidak valid.',
'password.required' => 'Password wajib diisi.',
'password.min' => 'Password minimal 8 karakter.',
'password.confirmed' => 'Konfirmasi password tidak sesuai.',
]);
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function (User $user, string $password) {
$user->forceFill([
'password' => Hash::make($password)
])->setRememberToken(Str::random(60));
$user->save();
}
);
if ($status === Password::PASSWORD_RESET) {
return redirect()->route('login')->with('status', 'Password berhasil direset! Silakan login dengan password baru Anda.');
}
return back()->withErrors(['email' => $this->getResetErrorMessage($status)]);
}
/**
* Mendapatkan pesan error untuk reset password
*/
private function getResetErrorMessage($status)
{
switch ($status) {
case Password::INVALID_TOKEN:
return 'Token reset password tidak valid.';
case Password::INVALID_USER:
return 'Email tidak ditemukan.';
default:
return 'Terjadi kesalahan saat mereset password.';
}
}
}

View File

@ -7,6 +7,7 @@
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use App\Notifications\ResetPasswordNotification;
class User extends Authenticatable
{
@ -65,4 +66,15 @@ public function satisfactions()
{
return $this->hasMany(Satisfaction::class);
}
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
}

View File

@ -0,0 +1,125 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
class ResetPasswordNotification extends Notification
{
use Queueable;
/**
* The password reset token.
*
* @var string
*/
public $token;
/**
* The callback that should be used to create the reset password URL.
*
* @var \Closure|null
*/
public static $createUrlCallback;
/**
* The callback that should be used to build the mail message.
*
* @var \Closure|null
*/
public static $toMailCallback;
/**
* Create a new notification instance.
*
* @param string $token
* @return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
}
return $this->buildMailMessage($this->resetUrl($notifiable));
}
/**
* Get the reset password notification mail message for the given URL.
*
* @param string $url
* @return \Illuminate\Notifications\Messages\MailMessage
*/
protected function buildMailMessage($url)
{
return (new MailMessage)
->subject('Reset Password - Syeba Air')
->view('emails.reset_password', ['actionUrl' => $url]);
}
/**
* Get the reset URL for the given notifiable.
*
* @param mixed $notifiable
* @return string
*/
protected function resetUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable, $this->token);
}
return url(route('password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
}
/**
* Set a callback that should be used when creating the reset password button URL.
*
* @param \Closure $callback
* @return void
*/
public static function createUrlUsing($callback)
{
static::$createUrlCallback = $callback;
}
/**
* Set a callback that should be used when building the notification mail message.
*
* @param \Closure $callback
* @return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}

View File

@ -0,0 +1,45 @@
@extends('layouts.auth')
@section('title', 'Lupa Password')
@section('content')
<h2 class="auth-title">LUPA PASSWORD</h2>
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
</div>
@endif
<p class="text-muted mb-4">
Masukkan email Anda dan kami akan mengirimkan link untuk mereset password.
</p>
<form method="POST" action="{{ route('password.email') }}">
@csrf
<div class="mb-3">
<input type="email" class="form-control @error('email') is-invalid @enderror"
placeholder="Masukkan E-mail Anda" name="email" value="{{ old('email') }}" required autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
<button type="submit" class="btn btn-auth">KIRIM LINK RESET</button>
</form>
<div class="auth-footer">
Ingat password Anda? <a href="{{ route('login') }}">Login</a>
</div>
@endsection

View File

@ -41,6 +41,10 @@
<button type="submit" class="btn btn-auth">LOGIN</button>
</form>
<div class="text-center mb-3">
<a href="{{ route('password.request') }}" class="text-decoration-none">Lupa Password?</a>
</div>
<div class="auth-footer">
Belum Punya akun? <a href="{{ route('register') }}">Daftar</a>
</div>

View File

@ -0,0 +1,65 @@
@extends('layouts.auth')
@section('title', 'Reset Password')
@section('content')
<h2 class="auth-title">RESET PASSWORD</h2>
@if ($errors->any())
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
</div>
@endif
<form method="POST" action="{{ route('password.update') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="mb-3">
<input type="email" class="form-control @error('email') is-invalid @enderror"
placeholder="E-mail Anda" name="email" value="{{ $email ?? old('email') }}" required readonly>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
<div class="mb-3 position-relative">
<input type="password" class="form-control @error('password') is-invalid @enderror"
placeholder="Password Baru" name="password" id="password" required>
<span class="position-absolute top-50 end-0 translate-middle-y me-2 password-toggle"
onclick="togglePassword('password')" style="cursor: pointer;">
<i class="bi bi-eye-slash" id="password-icon"></i>
</span>
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
<div class="mb-3 position-relative">
<input type="password" class="form-control @error('password_confirmation') is-invalid @enderror"
placeholder="Konfirmasi Password Baru" name="password_confirmation" id="password_confirmation" required>
<span class="position-absolute top-50 end-0 translate-middle-y me-2 password-toggle"
onclick="togglePassword('password_confirmation')" style="cursor: pointer;">
<i class="bi bi-eye-slash" id="password_confirmation-icon"></i>
</span>
@error('password_confirmation')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
<button type="submit" class="btn btn-auth">RESET PASSWORD</button>
</form>
<div class="auth-footer">
Ingat password Anda? <a href="{{ route('login') }}">Login</a>
</div>
@endsection

View File

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Password - Syeba Air</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #f4f4f4;
}
.container {
background-color: #ffffff;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.header {
text-align: center;
margin-bottom: 30px;
}
.logo {
font-size: 24px;
font-weight: bold;
color: #28a745;
margin-bottom: 10px;
}
.title {
font-size: 20px;
margin-bottom: 20px;
color: #333;
}
.content {
margin-bottom: 30px;
}
.button {
display: inline-block;
padding: 12px 30px;
background-color: #28a745;
color: #ffffff;
text-decoration: none;
border-radius: 5px;
font-weight: bold;
text-align: center;
margin: 20px 0;
}
.button:hover {
background-color: #125522;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-size: 14px;
color: #666;
text-align: center;
}
.warning {
background-color: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
padding: 15px;
border-radius: 5px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">UPTD Laboratorium Lingkungan Pada Dinas Lingkungan Hidup Kota Probolinggo</div>
<h1 class="title">Reset Password</h1>
</div>
<div class="content">
<p>Halo,</p>
<p>Kami menerima permintaan untuk mereset password akun Anda. Jika Anda yang melakukan permintaan ini, silakan klik tombol di bawah untuk mereset password Anda:</p>
<div style="text-align: center;">
<a href="{{ $actionUrl }}" class="button" style="color: #ffffff;">RESET PASSWORD</a>
</div>
<div class="warning">
<strong>Penting:</strong> Link ini akan kadaluarsa dalam 60 menit untuk keamanan akun Anda.
</div>
<p>Jika tombol di atas tidak berfungsi, Anda dapat menyalin dan menempelkan URL berikut ke browser Anda:</p>
<p style="word-break: break-all; color: #28a745;">{{ $actionUrl }}</p>
<p>Jika Anda tidak meminta reset password, silakan abaikan email ini. Password Anda tidak akan berubah.</p>
</div>
<div class="footer">
<p>Email ini dikirim secara otomatis, mohon jangan membalas email ini.</p>
<p>&copy; {{ date('Y') }} UPTD Laboratorium Lingkungan Pada Dinas Lingkungan Hidup Kota Probolinggo. Semua hak dilindungi.</p>
</div>
</div>
</body>
</html>

View File

@ -6,6 +6,8 @@
use App\Http\Controllers\SatisfactionController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Http\Controllers\Auth\ResetPasswordController;
use App\Http\Controllers\Admin\AdminController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\CustomerController;
@ -43,6 +45,14 @@
// Register Customer
Route::get('/register', [RegisterController::class, 'showRegistrationForm'])->name('register');
Route::post('/register', [RegisterController::class, 'register']);
// Forgot Password
Route::get('/forgot-password', [ForgotPasswordController::class, 'showLinkRequestForm'])->name('password.request');
Route::post('/forgot-password', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email');
// Reset Password
Route::get('/reset-password/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset');
Route::post('/reset-password', [ResetPasswordController::class, 'reset'])->name('password.update');
});
// Logout