validasi login

This commit is contained in:
rahmagustin 2026-03-30 19:26:23 +07:00
parent a97343dd39
commit 4d686d031e
2 changed files with 108 additions and 52 deletions

View File

@ -12,41 +12,85 @@
class LoginRequest extends FormRequest
{
/**
* Izinkan semua user akses request ini
*/
public function authorize(): bool
{
return true;
}
/**
* Aturan validasi input
*/
public function rules(): array
{
return [
'username' => ['required', 'string'],
'password' => ['required', 'string'],
'password' => ['required', 'string', 'min:8'],
];
}
public function authenticate(): void
{
$user = User::where('username', $this->username)->first();
if (!$user) {
throw ValidationException::withMessages([
'username' => 'Username tidak terdaftar.',
]);
}
if (!Auth::attempt([
'username' => $this->username,
'password' => $this->password,
])) {
throw ValidationException::withMessages([
'password' => 'Password yang dimasukkan salah.',
]);
}
}
/**
* Pesan error custom (biar lebih user-friendly)
*/
public function messages(): array
{
return [
'username.required' => 'Username wajib diisi.',
'username.string' => 'Username harus berupa teks.',
'password.required' => 'Password wajib diisi.',
'password.string' => 'Password harus berupa teks.',
'password.min' => 'Password minimal 6 karakter.',
];
}
/**
* Proses autentikasi login
*/
public function authenticate(): void
{
// Cek limit login (anti brute force)
$this->ensureIsNotRateLimited();
// Jika dua-duanya kosong (optional tambahan biar lebih jelas)
if (!$this->username && !$this->password) {
throw ValidationException::withMessages([
'username' => 'Username dan password wajib diisi.',
]);
}
// Cek apakah username ada
$user = User::where('username', $this->username)->first();
if (!$user) {
throw ValidationException::withMessages([
'username' => 'Username tidak terdaftar.',
]);
}
// Cek password
if (!Auth::attempt([
'username' => $this->username,
'password' => $this->password,
])) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'password' => 'Password yang dimasukkan salah.',
]);
}
// Reset limit kalau berhasil login
RateLimiter::clear($this->throttleKey());
}
/**
* Cek apakah sudah terlalu banyak percobaan login
*/
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
if (!RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
@ -55,16 +99,17 @@ public function ensureIsNotRateLimited(): void
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'username' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
'username' => 'Terlalu banyak percobaan login. Coba lagi dalam ' . $seconds . ' detik.',
]);
}
/**
* Key unik untuk rate limiter
*/
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('username')) . '|' . $this->ip());
return Str::transliterate(
Str::lower($this->input('username')) . '|' . $this->ip()
);
}
}

View File

@ -2,20 +2,15 @@
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Login Admin | SIG TPS Kabupaten Nganjuk</title>
<!-- plugins:css -->
<!-- CSS -->
<link rel="stylesheet" href="{{ asset('assets/admin/vendors/feather/feather.css') }}">
<link rel="stylesheet" href="{{ asset('assets/admin/vendors/ti-icons/css/themify-icons.css') }}">
<link rel="stylesheet" href="{{ asset('assets/admin/vendors/css/vendor.bundle.base.css') }}">
<!-- endinject -->
<!-- Plugin css for this page -->
<!-- End plugin css for this page -->
<!-- inject:css -->
<link rel="stylesheet" href="{{ asset('assets/admin/css/vertical-layout-light/style.css') }}">
<!-- endinject -->
<link rel="shortcut icon" href="{{ asset('assets/admin/images/iconsig.png') }}" />
</head>
@ -25,63 +20,79 @@
<div class="px-0 content-wrapper d-flex align-items-center auth">
<div class="mx-0 row w-100">
<div class="mx-auto col-lg-4">
<div class="px-4 py-5 text-left auth-form-light px-sm-5">
<div class="brand-logo">
<div class="text-center brand-logo">
<img src="{{ asset('assets/admin/images/sig-logo.png') }}" alt="logo">
</div>
<h4>Halo Admin!</h4>
<h6 class="font-weight-light">Silakan masuk untuk melanjutkan.</h6>
<form class="pt-3" method="POST" action="{{ route('login') }}">
<!-- FORM LOGIN -->
<form class="pt-3" method="POST" action="{{ route('login') }}" novalidate>
@csrf
@if ($errors->any())
<div class="mb-4 alert alert-danger">
<!-- ERROR GLOBAL -->
{{-- @if ($errors->any())
<div class="alert alert-danger">
{{ $errors->first() }}
</div>
@endif
@endif --}}
<!-- USERNAME -->
<div class="form-group">
<input type="text"
class="form-control form-control-lg {{ session('error') ? 'is-invalid' : '' }}"
name="username" placeholder="Username" value="{{ old('username') }}" required>
<input type="text" name="username"
class="form-control form-control-lg @error('username') is-invalid @enderror"
placeholder="Username" value="{{ old('username') }}">
@error('username')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
<!-- PASSWORD -->
<div class="form-group">
<input type="password"
class="form-control form-control-lg {{ session('error') ? 'is-invalid' : '' }}"
name="password" placeholder="Password" required>
<input type="password" name="password"
class="form-control form-control-lg @error('password') is-invalid @enderror"
placeholder="Password">
@error('password')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
<!-- BUTTON -->
<div class="mt-4">
<button type="submit"
class="btn btn-block btn-primary btn-lg font-weight-medium auth-form-btn">
MASUK
</button>
</div>
</form>
<!-- END FORM -->
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
</div>
<!-- page-body-wrapper ends -->
</div>
<!-- container-scroller -->
<!-- plugins:js -->
<!-- JS -->
<script src="{{ asset('assets/admin/vendors/js/vendor.bundle.base.js') }}"></script>
<!-- endinject -->
<!-- Plugin js for this page -->
<!-- End plugin js for this page -->
<!-- inject:js -->
<script src="{{ asset('assets/admin/js/off-canvas.js') }}"></script>
<script src="{{ asset('assets/admin/js/hoverable-collapse.js') }}"></script>
<script src="{{ asset('assets/admin/js/template.js') }}"></script>
<script src="{{ asset('assets/admin/js/settings.js') }}"></script>
<script src="{{ asset('assets/admin/js/todolist.js') }}"></script>
<!-- endinject -->
</body>
</html>