generateCaptchaCode(6); session([ 'login_captcha' => $captcha, 'login_captcha_created_at' => now()->getTimestamp(), ]); return $captcha; } private function ensureCaptchaValid(): void { $createdAt = (int) session('login_captcha_created_at', 0); $expired = $createdAt <= 0 || (now()->getTimestamp() - $createdAt) > $this->captchaTtlSeconds; if ($expired || (string) session('login_captcha', '') === '') { $this->refreshCaptcha(); } } public function index(){ $this->refreshCaptcha(); $data = [ 'title' => 'Login Admin | Order Gizi' ]; return view('auth.index', $data); } public function authanticate(Request $request){ $validated = $request->validate([ 'username' => 'required', 'password' => 'required', 'captcha' => 'required', 'website' => 'nullable', ]); if (trim((string) $request->input('website', '')) !== '') { return back()->withInput($request->only('username')) ->with(['alertError' => 'Gagal Login!']); } $now = time(); $rateKey = 'login:' . $request->ip() . ':' . strtolower((string) $request->input('username')); if (RateLimiter::tooManyAttempts($rateKey, $this->maxLoginAttempts)) { return back() ->withInput($request->only('username')) ->with(['alertError' => 'rate']); } $this->ensureCaptchaValid(); $expectedCaptcha = (string) session('login_captcha', ''); $givenCaptcha = strtoupper(preg_replace('/\s+/', '', (string) $request->input('captcha', ''))); if ($expectedCaptcha === '' || !hash_equals(strtoupper($expectedCaptcha), (string) $givenCaptcha)) { RateLimiter::hit($rateKey, $this->loginDecaySeconds); $this->refreshCaptcha(); return back() ->withInput($request->only('username')) ->with(['alertError' => 'captcha']); } // One-time use $request->session()->forget('login_captcha'); $request->session()->forget('login_captcha_created_at'); // IMPORTANT: only pass auth credentials to Auth::attempt // (do not include captcha / honeypot fields, otherwise Laravel will query non-existent columns) $credentials = [ 'username' => (string) ($validated['username'] ?? ''), 'password' => (string) ($validated['password'] ?? ''), ]; if(Auth::attempt($credentials)){ $request->session()->regenerate(); RateLimiter::clear($rateKey); return redirect()->intended('/dashboard'); } RateLimiter::hit($rateKey, $this->loginDecaySeconds); $this->refreshCaptcha(); return back()->with(['alertError' => 'Gagal Login!']); } public function logout() { Auth::logout(); request()->session()->invalidate(); request()->session()->regenerateToken(); return redirect('/login'); } public function captcha(Request $request){ $this->ensureCaptchaValid(); $captcha = (string) session('login_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); // noise lines 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 ); } // noise dots for ($i = 0; $i < 180; $i++) { imagesetpixel($img, random_int(0, $width - 1), random_int(0, $height - 1), $noise); } // draw text (built-in font to avoid font dependency) $font = 5; $textWidth = imagefontwidth($font) * strlen($captcha); $textHeight = imagefontheight($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'); } }