- Popup/banner quảng cáo cấu hình từ admin (loại, link, lịch hiển thị) - Đặt lại mật khẩu: token 80 ký tự kèm check digit Luhn thay cho token+email lộ trong URL, chỉ lưu bản băm, một lần dùng, hết hạn 60 phút - Xác thực 2 lớp qua email khi đăng nhập (mã 6 số, ghi nhớ thiết bị 90 ngày) - ApiAuthCheck chặn ngay tài khoản bị khoá dù JWT còn hạn (trước đây dùng được tới hết TTL) - Handler trả 404 thay vì 500 khi ModelNotFoundException không bọc HttpException - Test ClientTamperingTest: xác nhận IDOR/giả mạo dữ liệu từ client đều bị chặn Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
147 lines
5.1 KiB
PHP
147 lines
5.1 KiB
PHP
<?php
|
|
|
|
namespace App\Exceptions;
|
|
|
|
use Illuminate\Auth\AuthenticationException;
|
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Session\TokenMismatchException;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
use Throwable;
|
|
|
|
class Handler extends ExceptionHandler
|
|
{
|
|
protected $dontReport = [];
|
|
|
|
protected $dontFlash = [
|
|
'current_password',
|
|
'password',
|
|
'password_confirmation',
|
|
];
|
|
|
|
public function render($request, Throwable $e)
|
|
{
|
|
if ($request->is('api/*') || $request->is('v1/*') || $request->expectsJson()) {
|
|
return $this->renderApiException($request, $e);
|
|
}
|
|
|
|
return parent::render($request, $e);
|
|
}
|
|
|
|
protected function unauthenticated($request, AuthenticationException $exception)
|
|
{
|
|
if ($request->is('api/*') || $request->is('v1/*') || $request->expectsJson()) {
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => 401,
|
|
'message' => 'Bạn chưa đăng nhập hoặc phiên đăng nhập đã hết hạn.',
|
|
], 401);
|
|
}
|
|
|
|
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
|
}
|
|
|
|
private function renderApiException($request, Throwable $e)
|
|
{
|
|
if ($e instanceof TokenMismatchException) {
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => 419,
|
|
'message' => 'Phiên làm việc đã hết hạn, vui lòng thử lại.',
|
|
], 419);
|
|
}
|
|
|
|
if ($e instanceof ValidationException) {
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => 422,
|
|
'message' => $e->getMessage(),
|
|
'errors' => $e->errors(),
|
|
], 422);
|
|
}
|
|
|
|
if ($e instanceof AuthenticationException) {
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => 401,
|
|
'message' => 'Bạn chưa đăng nhập hoặc phiên đăng nhập đã hết hạn.',
|
|
], 401);
|
|
}
|
|
|
|
if ($e instanceof QueryException) {
|
|
if (($e->errorInfo[1] ?? null) == 1062) {
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => 422,
|
|
'message' => 'Dữ liệu đã tồn tại.',
|
|
], 422);
|
|
}
|
|
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => 500,
|
|
'message' => 'Lỗi cơ sở dữ liệu.',
|
|
], 500);
|
|
}
|
|
|
|
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
|
|
$e = new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException($e->getMessage(), $e);
|
|
}
|
|
|
|
if ($e instanceof HttpException) {
|
|
$status = $e->getStatusCode();
|
|
$messages = [
|
|
401 => 'Bạn chưa đăng nhập.',
|
|
402 => 'Thanh toán bắt buộc.',
|
|
403 => 'Bạn không có quyền thực hiện hành động này.',
|
|
404 => 'Không tìm thấy tài nguyên.',
|
|
405 => 'Phương thức không được phép.',
|
|
419 => 'Phiên làm việc đã hết hạn, vui lòng thử lại.',
|
|
422 => 'Dữ liệu không hợp lệ.',
|
|
429 => 'Quá nhiều yêu cầu, vui lòng thử lại sau.',
|
|
500 => 'Lỗi máy chủ nội bộ.',
|
|
502 => 'Cổng kết nối không hợp lệ.',
|
|
503 => 'Dịch vụ tạm thời không khả dụng.',
|
|
];
|
|
|
|
$previous = $e->getPrevious();
|
|
if ($previous instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
|
|
$modelClass = $previous->getModel();
|
|
$pageType = match ($modelClass) {
|
|
\App\Models\Product::class => 'service',
|
|
\App\Models\Customer::class => 'client',
|
|
\App\Models\Post::class => 'post',
|
|
default => 'unknown',
|
|
};
|
|
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => 404,
|
|
'message' => 'Dữ liệu không tồn tại.',
|
|
'page_type' => $pageType,
|
|
], 404);
|
|
}
|
|
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => $status,
|
|
'message' => $e->getMessage() ?: ($messages[$status] ?? 'Đã xảy ra lỗi.'),
|
|
], $status);
|
|
}
|
|
|
|
$status = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500;
|
|
|
|
return response()->json([
|
|
'error' => true,
|
|
'status' => $status,
|
|
'message' => config('app.debug') ? $e->getMessage() : 'Đã xảy ra lỗi máy chủ.',
|
|
], $status);
|
|
}
|
|
|
|
public function register()
|
|
{
|
|
//
|
|
}
|
|
}
|