fixing add captcha
This commit is contained in:
parent
6ff7e75360
commit
64f3c29e21
@ -12,8 +12,10 @@ class AuthController extends Controller
|
||||
private int $maxLoginAttempts = 10;
|
||||
|
||||
public function index(){
|
||||
$captcha = $this->generateCaptchaCode(6);
|
||||
session(['login_captcha' => $captcha]);
|
||||
$data = [
|
||||
'title' => 'Login Admin | Order Gizi'
|
||||
'title' => 'Login Admin | Order Gizi',
|
||||
];
|
||||
return view('auth.index', $data);
|
||||
}
|
||||
@ -22,6 +24,7 @@ class AuthController extends Controller
|
||||
$validated = $request->validate([
|
||||
'username' => 'required',
|
||||
'password' => 'required',
|
||||
'captcha' => 'required',
|
||||
'website' => 'nullable',
|
||||
]);
|
||||
|
||||
@ -32,6 +35,30 @@ class AuthController extends Controller
|
||||
|
||||
$now = time();
|
||||
$rateKey = 'login:' . $request->ip() . ':' . strtolower((string) $request->input('username'));
|
||||
$windowSeconds = 60;
|
||||
$maxAttempts = 10;
|
||||
$attempts = (array) $request->session()->get($rateKey, []);
|
||||
$attempts = array_values(array_filter($attempts, fn($ts) => is_int($ts) && $ts > ($now - $windowSeconds)));
|
||||
if(count($attempts) >= $maxAttempts){
|
||||
return back()->withInput($request->only('username'))
|
||||
->with(['alertError' => 'rate']);
|
||||
}
|
||||
|
||||
$backoffKey = $rateKey . ':backoff_until';
|
||||
$until = (int) $request->session()->get($backoffKey, 0);
|
||||
if($until > $now){
|
||||
return back()->withInput($request->only('username'))
|
||||
->with(['alertError' => 'backoff']);
|
||||
}
|
||||
|
||||
$expectedCaptcha = (string) session('login_captcha', '');
|
||||
$givenCaptcha = strtoupper(preg_replace('/\s+/', '', (string) $request->input('captcha', '')));
|
||||
if($expectedCaptcha === '' || !hash_equals(strtoupper($expectedCaptcha), (string) $givenCaptcha)){
|
||||
return back()->withInput($request->only('username'))
|
||||
->with(['alertError' => 'captcha']);
|
||||
}
|
||||
$request->session()->forget('login_captcha');
|
||||
|
||||
|
||||
// IMPORTANT: only pass auth credentials to Auth::attempt
|
||||
// (do not include captcha / honeypot fields, otherwise Laravel will query non-existent columns)
|
||||
@ -42,9 +69,18 @@ class AuthController extends Controller
|
||||
|
||||
if(Auth::attempt($credentials)){
|
||||
$request->session()->regenerate();
|
||||
$request->session()->forget($rateKey);
|
||||
$request->session()->forget($backoffKey);
|
||||
return redirect()->intended('/dashboard');
|
||||
}
|
||||
// record failed attempt
|
||||
$attempts[] = $now;
|
||||
$request->session()->put($rateKey, $attempts);
|
||||
|
||||
// set exponential backoff (1,2,4,8,16,30 seconds max) based on failures in window
|
||||
$failCount = count($attempts);
|
||||
$delay = min(30, (int) pow(2, max(0, $failCount - 1)));
|
||||
$request->session()->put($backoffKey, $now + $delay);
|
||||
return back()->with(['alertError' => 'Gagal Login!']);
|
||||
}
|
||||
|
||||
@ -55,4 +91,63 @@ class AuthController extends Controller
|
||||
request()->session()->regenerateToken();
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
public function captcha(){
|
||||
$captcha = (string) session('login_captcha', '');
|
||||
if($captcha === ''){
|
||||
$captcha = $this->generateCaptchaCode(6);
|
||||
session(['login_captcha' => $captcha]);
|
||||
}
|
||||
if(!function_exists('imagecreatetruecolor')){
|
||||
return response('GD extension is not available', Response::HTTP_INTERNAL_SERVER_ERROR)->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$width=140;
|
||||
$height=44;
|
||||
$img = imagecreatetruecolor($width, $height);
|
||||
$bg = imagecolorallocate($img, 245, 247, 250);
|
||||
$fg = imagecolorallocate($img, 35, 45, 70);
|
||||
$noise = imagecolorallocate($img, 120, 130, 150);
|
||||
|
||||
imagefilledrectangle($img, 0, 0, $width, $height, $bg);
|
||||
|
||||
for($i = 0; $i < 6; $i++){
|
||||
imageline(
|
||||
$img,
|
||||
random_int(0, $width),
|
||||
random_int(0, $height),
|
||||
random_int(0, $width),
|
||||
random_int(0, $height),
|
||||
$noise
|
||||
);
|
||||
}
|
||||
|
||||
for($i = 0; $i < 180; $i++){
|
||||
imagesetpixel($img, random_int(0, $width - 1), random_int(0, $height - 1), $noise);
|
||||
}
|
||||
|
||||
$font = 5;
|
||||
$textWidth = imagefontwidth($font) * strlen($captcha);
|
||||
$textHeight = imagefontwidth($font);
|
||||
$x = (int) (($width - $textWidth) / 2);
|
||||
$y = (int) (($height - $textHeight) / 2);
|
||||
imagestring($img, $font, $x, $y, $captcha, $fg);
|
||||
|
||||
ob_start();
|
||||
imagepng($img);
|
||||
$png = ob_get_clean();
|
||||
imagedestroy($img);
|
||||
return response($png, 200)->header('Content-Type', 'image/png')->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
||||
}
|
||||
|
||||
public function generateCaptchaCode(int $length = 6): string
|
||||
{
|
||||
$chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
|
||||
$out = '';
|
||||
$max = strlen($chars) - 1;
|
||||
for ($i = 0; $i < $length; $i++){
|
||||
$out .= $chars[random_int(0, $max)];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +64,8 @@
|
||||
Terlalu banyak percobaan login. Coba lagi dalam 1 menit.
|
||||
@elseif(session('alertError') === 'backoff')
|
||||
Mohon tunggu beberapa detik sebelum mencoba lagi.
|
||||
@elseif(session('alertError') === 'captcha')
|
||||
Captcha tidak sesuai
|
||||
@else
|
||||
Username atau password salah!
|
||||
@endif
|
||||
@ -98,6 +100,20 @@
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Captcha</label>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<img
|
||||
src="{{ route('captcha.login') }}?t={{ time() }}"
|
||||
alt="captcha"
|
||||
class="border rounded"
|
||||
style="height: 44px; width: 140px; object-fit: cover;"
|
||||
>
|
||||
<input type="text" name="captcha" class="form-control text-uppercase" placeholder="Masukkan kode di gambar" autocomplete="off" required>
|
||||
<a href="/login" class="btn btn-outline-secondary" title="Refresh captcha">Refresh</a>
|
||||
</div>
|
||||
<div class="form-text text-muted">Masukkan kode sesuai yang ditampilkan (huruf tidak membedakan kapital/kecil).</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-primary d-grid w-100" type="submit">Login</button>
|
||||
</div>
|
||||
|
||||
@ -22,6 +22,7 @@ use Illuminate\Support\Facades\Route;
|
||||
// });
|
||||
Route::get('/login', [AuthController::class, 'index'])->name('login')->middleware('guest');
|
||||
Route::post('/login', [AuthController::class, 'authanticate'])->middleware('throttle:login');
|
||||
Route::get('/captcha/login', [AuthController::class, 'captcha'])->name('captcha.login');
|
||||
|
||||
Route::group(['middleware' => ['auth']], function(){
|
||||
Route::group(['prefix' => 'dashboard'], function(){
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user