'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; } }