diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 3c78520..9aca739 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -14,7 +14,7 @@ class AuthController extends Controller public function showLoginForm() { - if (Auth::check()) { + if (Auth::check() && !session('dialog_first_login')) { return redirect($this->authService->getRedirectRouteForUser(Auth::user())); } return view('login'); @@ -22,20 +22,51 @@ class AuthController extends Controller public function login(Request $request) { - $credentials = $request->validate([ + // Custom validation check: treat all validation errors as "Invalid email/password" + $validator = \Illuminate\Support\Facades\Validator::make($request->all(), [ 'mail' => 'required|email|max:255', 'password' => 'required|string|max:100', ]); - $result = $this->authService->attemptLogin($credentials, $request); - - if (!$result['success']) { + if ($validator->fails()) { return back()->withInput($request->only('mail'))->withErrors([ - $result['error_key'] => $result['error_msg'], + 'mail' => 'Thông tin đăng nhập không đúng. Vui lòng nhập lại', ]); } - return redirect($this->authService->getRedirectRouteForUser($result['user'])); + $credentials = $validator->validated(); + $email = \Illuminate\Support\Str::lower($credentials['mail']); + $throttleKey = 'login-attempts:' . $email; + + // Brute-force check (lock after 5 failed attempts) + if (\Illuminate\Support\Facades\RateLimiter::tooManyAttempts($throttleKey, 5)) { + return back()->withInput($request->only('mail'))->with('dialog_error_brute_force', 'Tài khoản của bạn tạm thời bị khóa do nhập sai mật khẩu quá 5 lần. Vui lòng thử lại sau 15 phút.'); + } + + $result = $this->authService->attemptLogin($credentials, $request); + + if (!$result['success']) { + if ($result['error_key'] === 'permission') { + return back()->withInput($request->only('mail'))->with('dialog_error_permission', $result['error_msg']); + } + + // For invalid password / email (counts towards brute-force attempts) + \Illuminate\Support\Facades\RateLimiter::hit($throttleKey, 900); // 15 minutes lockout + + return back()->withInput($request->only('mail'))->withErrors([ + 'mail' => $result['error_msg'], + ]); + } + + // Clear rate limiter on success + \Illuminate\Support\Facades\RateLimiter::clear($throttleKey); + + $user = $result['user']; + if ($user->first_login == \App\Models\User::FIRST_LOGIN_TRUE) { + return redirect()->route('login')->with('dialog_first_login', true); + } + + return redirect($this->authService->getRedirectRouteForUser($user)); } public function logout(Request $request) diff --git a/app/Services/Auth/AuthService.php b/app/Services/Auth/AuthService.php index c5b40de..75f9b65 100644 --- a/app/Services/Auth/AuthService.php +++ b/app/Services/Auth/AuthService.php @@ -14,7 +14,7 @@ class AuthService implements AuthServiceInterface // Manual MD5 password check $user = User::where('mail', $credentials['mail'])->first(); if (!$user || $user->pass !== md5($credentials['password'])) { - return ['success' => false, 'error_key' => 'mail', 'error_msg' => __('auth.failed')]; + return ['success' => false, 'error_key' => 'mail', 'error_msg' => 'Thông tin đăng nhập không đúng. Vui lòng nhập lại']; } // Log the user in manually @@ -22,7 +22,7 @@ class AuthService implements AuthServiceInterface $request->session()->regenerate(); if ($user->status != User::STATUS_ACTIVE) { Auth::logout(); - return ['success' => false, 'error_key' => 'mail', 'error_msg' => __('auth.inactive')]; + return ['success' => false, 'error_key' => 'permission', 'error_msg' => 'Bạn không có quyền truy cập. Vui lòng liên hệ quản trị viên']; } return ['success' => true, 'user' => $user]; @@ -41,6 +41,10 @@ class AuthService implements AuthServiceInterface return route('user.change_password'); } + if ($user->role == User::ROLE_ADMIN) { + return route('admin.dashboard'); + } + return route('user.dashboard'); } } \ No newline at end of file diff --git a/bootstrap/app.php b/bootstrap/app.php index d664775..28bf868 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -25,5 +25,10 @@ return Application::configure(basePath: dirname(__DIR__)) ]); }) ->withExceptions(function (Exceptions $exceptions): void { - // + $exceptions->render(function (\Illuminate\Auth\AuthenticationException $e, \Illuminate\Http\Request $request) { + if ($request->expectsJson()) { + return response()->json(['message' => $e->getMessage()], 401); + } + return redirect()->guest(route('login'))->with('session_expired', true); + }); })->create(); diff --git a/docs/notification-review/README.md b/docs/notification-review/README.md index 7059e25..39dd5a6 100644 --- a/docs/notification-review/README.md +++ b/docs/notification-review/README.md @@ -19,9 +19,9 @@ The following reusable/common components are created and used across all display | Case ID | Feature | Display Case | Notification Type | Message | Screenshot Path | | :--- | :--- | :--- | :--- | :--- | :--- | -| **01** | Authentication | Login failed | Toast (Error) | Đăng nhập thất bại. Vui lòng kiểm tra lại Email hoặc Mật khẩu. | [case_01_login_failed.png](case_01_login_failed.png) | -| **02** | Authorization | Access denied | Error Dialog | Tài khoản của bạn không có quyền truy cập vào trang quản trị này. Vui lòng liên hệ với quản trị viên nếu đây là một sự nhầm lẫn. | [case_02_access_denied.png](case_02_access_denied.png) | -| **03** | Authentication | First login (change password required) | Info Dialog | Chào mừng bạn đến với hệ thống Thankcard! Đây là lần đầu tiên bạn đăng nhập, vui lòng đổi mật khẩu mới để tiếp tục sử dụng hệ thống. | [case_03_first_login.png](case_03_first_login.png) | +| **01** | Authentication | Login failed | Inline (Error) | Thông tin đăng nhập không đúng. Vui lòng nhập lại | [case_01_login_failed.png](case_01_login_failed.png) | +| **02** | Authorization | Access denied / disabled | Error Dialog | Bạn không có quyền truy cập. Vui lòng liên hệ quản trị viên | [case_02_access_denied.png](case_02_access_denied.png) | +| **03** | Authentication | First login | Info Dialog | Vui lòng đổi mật khẩu trước khi tiếp tục sử dụng hệ thống. | [case_03_first_login.png](case_03_first_login.png) | | **04** | User Management | Delete user confirmation | Confirm Dialog (Danger) | Bạn có chắc chắn muốn xóa tài khoản của nhân viên Nguyễn Văn A (MSNV: NV0001)? Hành động này không thể hoàn tác và tất cả dữ liệu liên quan sẽ bị xóa. | [case_04_delete_user_confirmation.png](case_04_delete_user_confirmation.png) | | **05** | User Management | Create user success | Success Dialog | Tài khoản nhân viên mới đã được tạo thành công trong hệ thống. Email kích hoạt đã được gửi tới người dùng. | [case_05_create_user_success.png](case_05_create_user_success.png) | | **06** | User Management | Email already exists | Error Dialog | Địa chỉ email 'nguyenvana@runsystem.net' đã được đăng ký bởi một nhân viên khác. Vui lòng sử dụng địa chỉ email khác. | [case_06_email_already_exists.png](case_06_email_already_exists.png) | diff --git a/lang/vi/auth.php b/lang/vi/auth.php index a1ac389..dfd2848 100644 --- a/lang/vi/auth.php +++ b/lang/vi/auth.php @@ -9,6 +9,6 @@ return [ 'forgot_password' => 'Quên mật khẩu?', 'system_name' => 'Hệ thống Thank Card', 'developed_by' => 'Được phát triển bởi GMO-Z.com RUNSYSTEM', - 'failed' => 'Email hoặc mật khẩu không chính xác.', - 'inactive' => 'Tài khoản của bạn đã bị khóa hoặc bạn đã nghỉ việc.', + 'failed' => 'Thông tin đăng nhập không đúng. Vui lòng nhập lại', + 'inactive' => 'Bạn không có quyền truy cập. Vui lòng liên hệ quản trị viên', ]; diff --git a/resources/views/components/error-dialog.blade.php b/resources/views/components/error-dialog.blade.php index 0a5a6a7..2af1477 100644 --- a/resources/views/components/error-dialog.blade.php +++ b/resources/views/components/error-dialog.blade.php @@ -1,4 +1,4 @@ -@props(['id', 'title' => 'Đã xảy ra lỗi', 'message' => '', 'buttonText' => 'Đóng']) +@props(['id', 'title' => 'Đã xảy ra lỗi', 'message' => '', 'buttonText' => 'Đóng', 'onclick' => null])