Files
2025-11-30 11:57:22 +01:00

182 lines
4.9 KiB
PHP

<?php
namespace Paymenter\Extensions\Others\Security\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\User;
use Carbon\Carbon;
class BlockedIP extends Model
{
protected $table = 'ext_security_blocked_ips';
protected $fillable = [
'ip_address',
'reason',
'notes',
'is_permanent',
'blocked_at',
'blocked_until',
'blocked_by',
'unblocked_at',
'unblocked_by',
'is_active',
'failed_login_attempts',
'last_attempt_at',
];
protected $casts = [
'blocked_at' => 'datetime',
'blocked_until' => 'datetime',
'unblocked_at' => 'datetime',
'last_attempt_at' => 'datetime',
'is_permanent' => 'boolean',
'is_active' => 'boolean',
'failed_login_attempts' => 'integer',
];
public function blockedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'blocked_by');
}
public function unblockedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'unblocked_by');
}
/**
* Check if the IP block is currently active
*/
public function isCurrentlyActive(): bool
{
if (!$this->is_active) {
return false;
}
if ($this->is_permanent) {
return true;
}
if ($this->blocked_until && Carbon::now()->isAfter($this->blocked_until)) {
return false;
}
return true;
}
/**
* Unblock the IP
*/
public function unblock(?int $unblockedBy = null): void
{
$this->update([
'is_active' => false,
'unblocked_at' => now(),
'unblocked_by' => $unblockedBy,
]);
}
/**
* Get active block for an IP
*/
public static function getActiveBlock(string $ipAddress): ?self
{
return static::where('ip_address', $ipAddress)
->where('is_active', true)
->where(function ($query) {
$query->where('is_permanent', true)
->orWhere('blocked_until', '>', now())
->orWhereNull('blocked_until');
})
->first();
}
/**
* Check if an IP is blocked
*/
public static function isIPBlocked(string $ipAddress): bool
{
$block = static::getActiveBlock($ipAddress);
return $block && $block->isCurrentlyActive();
}
/**
* Block an IP address
*/
public static function blockIP(
string $ipAddress,
string $reason,
?int $blockedBy = null,
bool $isPermanent = false,
?Carbon $blockedUntil = null,
?string $notes = null
): self {
// Deactivate any existing blocks
static::where('ip_address', $ipAddress)
->where('is_active', true)
->update(['is_active' => false]);
return static::create([
'ip_address' => $ipAddress,
'reason' => $reason,
'notes' => $notes,
'is_permanent' => $isPermanent,
'blocked_at' => now(),
'blocked_until' => $blockedUntil,
'blocked_by' => $blockedBy,
'is_active' => true,
'failed_login_attempts' => 0,
]);
}
/**
* Record a failed login attempt
*/
public static function recordFailedAttempt(string $ipAddress): void
{
$existing = static::where('ip_address', $ipAddress)
->where('is_active', true)
->first();
if ($existing) {
$existing->increment('failed_login_attempts');
$existing->update(['last_attempt_at' => now()]);
} else {
static::create([
'ip_address' => $ipAddress,
'reason' => 'Failed login attempts',
'is_permanent' => false,
'blocked_at' => now(),
'is_active' => false, // Not blocked yet, just tracking attempts
'failed_login_attempts' => 1,
'last_attempt_at' => now(),
]);
}
}
/**
* Auto-block IP after too many failed attempts
*/
public static function autoBlockIfNeeded(string $ipAddress, int $maxAttempts, int $blockDurationMinutes): bool
{
$record = static::where('ip_address', $ipAddress)->first();
if (!$record || $record->failed_login_attempts < $maxAttempts) {
return false;
}
// Block the IP
static::blockIP(
$ipAddress,
"Auto-blocked after {$maxAttempts} failed login attempts",
null, // System blocked
false, // Not permanent
now()->addMinutes($blockDurationMinutes)
);
return true;
}
}