archetecture security fix
This commit is contained in:
@@ -8,6 +8,7 @@ use App\Services\Auth\ApiLoginSecurityService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
|
||||
|
||||
class AuthController extends BaseApiController
|
||||
@@ -24,6 +25,11 @@ class AuthController extends BaseApiController
|
||||
$password = (string) ($payload['password'] ?? '');
|
||||
|
||||
if ($email === '' || $password === '') {
|
||||
Log::notice('api_login: missing credentials', [
|
||||
'ip' => (string) $request->ip(),
|
||||
'email' => $this->maskEmail($email),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => 'Email and password are required.',
|
||||
@@ -32,6 +38,11 @@ class AuthController extends BaseApiController
|
||||
|
||||
$ip = $request->ip();
|
||||
if ($security->isIpBlocked((string) $ip)) {
|
||||
Log::notice('api_login: ip blocked', [
|
||||
'ip' => (string) $ip,
|
||||
'email' => $this->maskEmail($email),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => 'Too many failed attempts from your IP. Please try again later.',
|
||||
@@ -41,6 +52,24 @@ class AuthController extends BaseApiController
|
||||
$user = User::query()->whereRaw('LOWER(email) = ?', [$email])->first();
|
||||
if (!$user) {
|
||||
$security->logIpAttempt((string) $ip);
|
||||
Log::notice('api_login: unknown email', [
|
||||
'ip' => (string) $ip,
|
||||
'email' => $this->maskEmail($email),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => 'Invalid email or password.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
if (blank($user->password)) {
|
||||
$security->logIpAttempt((string) $ip);
|
||||
Log::notice('api_login: password missing', [
|
||||
'ip' => (string) $ip,
|
||||
'email' => $this->maskEmail($email),
|
||||
'user_id' => (int) $user->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
@@ -50,6 +79,12 @@ class AuthController extends BaseApiController
|
||||
|
||||
if (! verify_stored_password($password, (string) $user->password)) {
|
||||
$security->handleFailedLogin($user, $email, (string) $ip);
|
||||
Log::notice('api_login: password mismatch', [
|
||||
'ip' => (string) $ip,
|
||||
'email' => $this->maskEmail($email),
|
||||
'user_id' => (int) $user->id,
|
||||
'failed_attempts' => (int) ($user->fresh()?->failed_attempts ?? 0),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
@@ -60,6 +95,12 @@ class AuthController extends BaseApiController
|
||||
// Suspension is checked AFTER credentials are verified so that the
|
||||
// response shape cannot be used to enumerate which emails exist.
|
||||
if ($user->is_suspended) {
|
||||
Log::notice('api_login: suspended account', [
|
||||
'ip' => (string) $ip,
|
||||
'email' => $this->maskEmail($email),
|
||||
'user_id' => (int) $user->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => 'Account suspended. Please reset your password.',
|
||||
@@ -71,6 +112,12 @@ class AuthController extends BaseApiController
|
||||
|
||||
$fresh = $user->fresh();
|
||||
if (!$fresh) {
|
||||
Log::warning('api_login: fresh user reload failed after successful verification', [
|
||||
'ip' => (string) $ip,
|
||||
'email' => $this->maskEmail($email),
|
||||
'user_id' => (int) $user->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => 'Invalid email or password.',
|
||||
@@ -82,6 +129,19 @@ class AuthController extends BaseApiController
|
||||
return response()->json($security->buildLoginResponse($fresh, $jwtToken));
|
||||
}
|
||||
|
||||
private function maskEmail(string $email): string
|
||||
{
|
||||
$email = trim(strtolower($email));
|
||||
if ($email === '' || ! str_contains($email, '@')) {
|
||||
return $email;
|
||||
}
|
||||
|
||||
[$local, $domain] = explode('@', $email, 2);
|
||||
$localPrefix = substr($local, 0, min(2, strlen($local)));
|
||||
|
||||
return $localPrefix . str_repeat('*', max(strlen($local) - strlen($localPrefix), 0)) . '@' . $domain;
|
||||
}
|
||||
|
||||
public function refresh(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user