Security Features - luckydeva03/barbershop_app GitHub Wiki

๐Ÿ” Security Features

Panduan lengkap implementasi keamanan dalam barbershop management system untuk melindungi data dan mencegah serangan.

๐ŸŽฏ Security Overview

Sistem keamanan mencakup:

  • Authentication & Authorization: Dual guard system (web/admin)
  • Data Protection: Encryption, hashing, sanitization
  • Input Validation: Form validation, CSRF protection
  • Rate Limiting: API throttling, brute force protection
  • Audit Logging: Activity tracking, security monitoring
  • Database Security: Query protection, backup encryption

๐Ÿ›ก๏ธ Authentication Security

1. Dual Guard System

Guard Configuration (config/auth.php)

<?php

return [
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
        
        'api' => [
            'driver' => 'sanctum',
            'provider' => 'users',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
        
        'admins' => [
            'provider' => 'admins',
            'table' => 'admin_password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    'password_timeout' => 10800, // 3 hours
];

2. Enhanced User Model Security

Secure User Model (app/Models/User.php)

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Facades\Hash;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable, SoftDeletes;

    protected $fillable = [
        'name',
        'email',
        'phone',
        'google_id',
        'avatar',
        'email_verified_at',
        'password',
        'last_login_at',
        'login_count',
        'is_active',
    ];

    protected $hidden = [
        'password',
        'remember_token',
        'google_id',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'last_login_at' => 'datetime',
        'password' => 'hashed',
        'is_active' => 'boolean',
    ];

    protected $dates = [
        'deleted_at',
    ];

    // Security Methods
    public function recordLogin()
    {
        $this->update([
            'last_login_at' => now(),
            'login_count' => $this->login_count + 1,
        ]);
    }

    public function isActive()
    {
        return $this->is_active && !$this->deleted_at;
    }

    public function isSuspended()
    {
        return !$this->is_active || $this->deleted_at;
    }

    public function suspend($reason = null)
    {
        $this->update(['is_active' => false]);
        
        // Log suspension
        activity()
            ->performedOn($this)
            ->withProperties(['reason' => $reason])
            ->log('user_suspended');
    }

    public function activate()
    {
        $this->update(['is_active' => true]);
        
        activity()
            ->performedOn($this)
            ->log('user_activated');
    }

    // Password Security
    public function password(): Attribute
    {
        return Attribute::make(
            set: fn ($value) => Hash::make($value),
        );
    }

    public function changePassword($newPassword, $currentPassword = null)
    {
        if ($currentPassword && !Hash::check($currentPassword, $this->password)) {
            throw new \Exception('Current password is incorrect');
        }

        $this->update(['password' => $newPassword]);
        
        // Revoke all tokens for security
        $this->tokens()->delete();
        
        activity()
            ->performedOn($this)
            ->log('password_changed');
    }

    // Email Security
    protected function email(): Attribute
    {
        return Attribute::make(
            get: fn ($value) => $value,
            set: fn ($value) => strtolower($value),
        );
    }

    public function markEmailAsVerified()
    {
        $this->email_verified_at = now();
        $this->save();

        activity()
            ->performedOn($this)
            ->log('email_verified');
    }

    // API Token Management
    public function createSecureToken($name = 'api-token', $abilities = ['*'])
    {
        $token = $this->createToken($name, $abilities);
        
        activity()
            ->performedOn($this)
            ->withProperties(['token_name' => $name])
            ->log('api_token_created');
            
        return $token;
    }

    public function revokeAllTokens()
    {
        $count = $this->tokens()->count();
        $this->tokens()->delete();
        
        activity()
            ->performedOn($this)
            ->withProperties(['tokens_revoked' => $count])
            ->log('all_tokens_revoked');
    }

    // Security Scopes
    public function scopeActive($query)
    {
        return $query->where('is_active', true)->whereNull('deleted_at');
    }

    public function scopeVerified($query)
    {
        return $query->whereNotNull('email_verified_at');
    }

    public function scopeRecentlyActive($query, $days = 30)
    {
        return $query->where('last_login_at', '>=', now()->subDays($days));
    }
}

3. Login Security Middleware

Login Throttling Middleware (app/Http/Middleware/ThrottleLogins.php)

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;

class ThrottleLogins
{
    public function handle(Request $request, Closure $next, $maxAttempts = 5, $decayMinutes = 60)
    {
        $key = $this->throttleKey($request);
        
        if (RateLimiter::tooManyAttempts($key, $maxAttempts)) {
            $seconds = RateLimiter::availableIn($key);
            
            activity()
                ->withProperties([
                    'ip' => $request->ip(),
                    'email' => $request->input('email'),
                    'attempts' => RateLimiter::attempts($key),
                ])
                ->log('login_throttled');
            
            return response()->json([
                'message' => 'Too many login attempts. Please try again in ' . ceil($seconds / 60) . ' minutes.',
                'retry_after' => $seconds,
            ], 429);
        }

        $response = $next($request);

        // Clear attempts on successful login
        if ($response->isSuccessful() && $request->routeIs('login')) {
            RateLimiter::clear($key);
        } else {
            // Increment attempts on failed login
            RateLimiter::hit($key, $decayMinutes * 60);
        }

        return $response;
    }

    protected function throttleKey(Request $request)
    {
        return Str::lower($request->input('email')).'|'.$request->ip();
    }
}

4. Account Lockout System

Account Lockout Service (app/Services/AccountLockoutService.php)

<?php

namespace App\Services;

use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\RateLimiter;

class AccountLockoutService
{
    const MAX_ATTEMPTS = 5;
    const LOCKOUT_DURATION = 30; // minutes
    const TEMP_LOCKOUT_DURATION = 5; // minutes for first few attempts

    public static function recordFailedAttempt($email)
    {
        $key = self::getLockoutKey($email);
        $attempts = Cache::get($key, 0) + 1;
        
        if ($attempts >= self::MAX_ATTEMPTS) {
            self::lockAccount($email);
        } else {
            $duration = $attempts >= 3 ? self::TEMP_LOCKOUT_DURATION : 1;
            Cache::put($key, $attempts, now()->addMinutes($duration));
        }

        activity()
            ->withProperties([
                'email' => $email,
                'attempts' => $attempts,
                'ip' => request()->ip(),
            ])
            ->log('failed_login_attempt');

        return $attempts;
    }

    public static function lockAccount($email)
    {
        $key = self::getLockoutKey($email);
        Cache::put($key, self::MAX_ATTEMPTS, now()->addMinutes(self::LOCKOUT_DURATION));
        
        // Disable user account if exists
        $user = User::where('email', $email)->first();
        if ($user) {
            $user->suspend('Account locked due to multiple failed login attempts');
        }

        activity()
            ->withProperties([
                'email' => $email,
                'lockout_duration' => self::LOCKOUT_DURATION,
                'ip' => request()->ip(),
            ])
            ->log('account_locked');
    }

    public static function isLocked($email)
    {
        $key = self::getLockoutKey($email);
        return Cache::get($key, 0) >= self::MAX_ATTEMPTS;
    }

    public static function getAttempts($email)
    {
        return Cache::get(self::getLockoutKey($email), 0);
    }

    public static function clearAttempts($email)
    {
        Cache::forget(self::getLockoutKey($email));
        
        activity()
            ->withProperties(['email' => $email])
            ->log('login_attempts_cleared');
    }

    public static function getRemainingLockoutTime($email)
    {
        if (!self::isLocked($email)) {
            return 0;
        }

        $key = self::getLockoutKey($email);
        $expiry = Cache::getStore()->getPrefix() . $key;
        
        // This is implementation specific to your cache driver
        return max(0, now()->diffInMinutes(now()->addMinutes(self::LOCKOUT_DURATION)));
    }

    protected static function getLockoutKey($email)
    {
        return 'login_attempts:' . strtolower($email);
    }
}

๐Ÿ”’ Data Protection

1. Encryption Service

Sensitive Data Encryption (app/Services/EncryptionService.php)

<?php

namespace App\Services;

use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;

class EncryptionService
{
    /**
     * Encrypt sensitive data
     */
    public static function encryptSensitive($data)
    {
        if (is_null($data) || $data === '') {
            return null;
        }

        try {
            return Crypt::encrypt($data);
        } catch (\Exception $e) {
            throw new \Exception('Failed to encrypt data: ' . $e->getMessage());
        }
    }

    /**
     * Decrypt sensitive data
     */
    public static function decryptSensitive($encryptedData)
    {
        if (is_null($encryptedData) || $encryptedData === '') {
            return null;
        }

        try {
            return Crypt::decrypt($encryptedData);
        } catch (DecryptException $e) {
            throw new \Exception('Failed to decrypt data: ' . $e->getMessage());
        }
    }

    /**
     * Hash password with additional security
     */
    public static function hashPassword($password)
    {
        return password_hash($password, PASSWORD_ARGON2ID, [
            'memory_cost' => 65536, // 64 MB
            'time_cost' => 4,       // 4 iterations
            'threads' => 3,         // 3 threads
        ]);
    }

    /**
     * Verify password hash
     */
    public static function verifyPassword($password, $hash)
    {
        return password_verify($password, $hash);
    }

    /**
     * Generate secure random token
     */
    public static function generateSecureToken($length = 32)
    {
        return bin2hex(random_bytes($length));
    }

    /**
     * Hash sensitive identifiers (like phone numbers)
     */
    public static function hashIdentifier($identifier, $salt = null)
    {
        $salt = $salt ?: config('app.key');
        return hash_hmac('sha256', $identifier, $salt);
    }

    /**
     * Mask sensitive data for display
     */
    public static function maskData($data, $type = 'email')
    {
        if (!$data) return '';

        switch ($type) {
            case 'email':
                $parts = explode('@', $data);
                if (count($parts) === 2) {
                    $username = $parts[0];
                    $domain = $parts[1];
                    $maskedUsername = substr($username, 0, 2) . str_repeat('*', max(0, strlen($username) - 2));
                    return $maskedUsername . '@' . $domain;
                }
                break;

            case 'phone':
                $length = strlen($data);
                if ($length > 4) {
                    return str_repeat('*', $length - 4) . substr($data, -4);
                }
                break;

            case 'name':
                $words = explode(' ', $data);
                if (count($words) > 1) {
                    return $words[0] . ' ' . substr($words[1], 0, 1) . str_repeat('*', max(0, strlen($words[1]) - 1));
                } else {
                    return substr($data, 0, 2) . str_repeat('*', max(0, strlen($data) - 2));
                }
                break;

            default:
                return str_repeat('*', strlen($data));
        }

        return $data;
    }
}

2. Model Encryption Traits

Encrypted Attributes Trait (app/Traits/HasEncryptedAttributes.php)

<?php

namespace App\Traits;

use App\Services\EncryptionService;

trait HasEncryptedAttributes
{
    /**
     * Attributes that should be encrypted
     */
    protected $encrypted = [];

    /**
     * Boot the trait
     */
    public static function bootHasEncryptedAttributes()
    {
        static::saving(function ($model) {
            $model->encryptAttributes();
        });

        static::retrieved(function ($model) {
            $model->decryptAttributes();
        });
    }

    /**
     * Encrypt specified attributes
     */
    protected function encryptAttributes()
    {
        foreach ($this->getEncryptedAttributes() as $attribute) {
            if (isset($this->attributes[$attribute]) && !$this->isEncrypted($this->attributes[$attribute])) {
                $this->attributes[$attribute] = EncryptionService::encryptSensitive($this->attributes[$attribute]);
            }
        }
    }

    /**
     * Decrypt specified attributes
     */
    protected function decryptAttributes()
    {
        foreach ($this->getEncryptedAttributes() as $attribute) {
            if (isset($this->attributes[$attribute]) && $this->isEncrypted($this->attributes[$attribute])) {
                try {
                    $this->attributes[$attribute] = EncryptionService::decryptSensitive($this->attributes[$attribute]);
                } catch (\Exception $e) {
                    // Keep encrypted value if decryption fails
                    \Log::warning("Failed to decrypt attribute {$attribute}: " . $e->getMessage());
                }
            }
        }
    }

    /**
     * Get list of encrypted attributes
     */
    protected function getEncryptedAttributes()
    {
        return $this->encrypted ?? [];
    }

    /**
     * Check if value is encrypted
     */
    protected function isEncrypted($value)
    {
        // Laravel's encryption adds a prefix, so we can check for it
        return is_string($value) && strpos($value, 'eyJpdiI6') === 0;
    }

    /**
     * Set an encrypted attribute
     */
    public function setEncryptedAttribute($key, $value)
    {
        $this->attributes[$key] = EncryptionService::encryptSensitive($value);
    }

    /**
     * Get a decrypted attribute
     */
    public function getDecryptedAttribute($key)
    {
        $value = $this->attributes[$key] ?? null;
        
        if ($value && $this->isEncrypted($value)) {
            return EncryptionService::decryptSensitive($value);
        }

        return $value;
    }
}

๐Ÿ› ๏ธ Input Validation & Sanitization

1. Enhanced Form Validation

Security Validation Rules (app/Rules/SecurityValidation.php)

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class NoScriptTag implements Rule
{
    public function passes($attribute, $value)
    {
        return !preg_match('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', $value);
    }

    public function message()
    {
        return 'The :attribute field contains disallowed script tags.';
    }
}

class NoSqlInjection implements Rule
{
    protected $sqlKeywords = [
        'select', 'insert', 'update', 'delete', 'drop', 'create', 'alter',
        'exec', 'execute', 'union', 'declare', 'cast', 'convert'
    ];

    public function passes($attribute, $value)
    {
        if (!is_string($value)) {
            return true;
        }

        $value = strtolower($value);
        foreach ($this->sqlKeywords as $keyword) {
            if (strpos($value, $keyword) !== false) {
                return false;
            }
        }

        return true;
    }

    public function message()
    {
        return 'The :attribute field contains potentially dangerous content.';
    }
}

class StrongPassword implements Rule
{
    public function passes($attribute, $value)
    {
        return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/', $value);
    }

    public function message()
    {
        return 'The :attribute must be at least 8 characters and contain uppercase, lowercase, number and special character.';
    }
}

class SafeFileName implements Rule
{
    public function passes($attribute, $value)
    {
        // Check for directory traversal
        if (strpos($value, '..') !== false) {
            return false;
        }

        // Check for dangerous characters
        if (preg_match('/[<>:"|?*\\\\\/]/', $value)) {
            return false;
        }

        // Check for executable extensions
        $dangerousExtensions = ['exe', 'bat', 'cmd', 'scr', 'pif', 'vbs', 'js', 'jar', 'com'];
        $extension = strtolower(pathinfo($value, PATHINFO_EXTENSION));
        
        return !in_array($extension, $dangerousExtensions);
    }

    public function message()
    {
        return 'The :attribute contains invalid or dangerous characters.';
    }
}

2. Input Sanitization Service

Data Sanitization (app/Services/SanitizationService.php)

<?php

namespace App\Services;

use HTMLPurifier;
use HTMLPurifier_Config;

class SanitizationService
{
    /**
     * Sanitize HTML content
     */
    public static function sanitizeHtml($content, $allowedTags = null)
    {
        $config = HTMLPurifier_Config::createDefault();
        
        if ($allowedTags) {
            $config->set('HTML.Allowed', $allowedTags);
        } else {
            // Default allowed tags
            $config->set('HTML.Allowed', 'p,br,strong,em,u,ol,ul,li,a[href],img[src|alt],h1,h2,h3,h4,h5,h6');
        }
        
        $config->set('AutoFormat.RemoveEmpty', true);
        $config->set('AutoFormat.RemoveSpansWithoutAttributes', true);
        
        $purifier = new HTMLPurifier($config);
        return $purifier->purify($content);
    }

    /**
     * Sanitize text content (strip all HTML)
     */
    public static function sanitizeText($text)
    {
        return strip_tags(trim($text));
    }

    /**
     * Sanitize email
     */
    public static function sanitizeEmail($email)
    {
        $email = strtolower(trim($email));
        return filter_var($email, FILTER_SANITIZE_EMAIL);
    }

    /**
     * Sanitize phone number
     */
    public static function sanitizePhone($phone)
    {
        // Remove all non-numeric characters except +
        return preg_replace('/[^0-9+]/', '', $phone);
    }

    /**
     * Sanitize filename
     */
    public static function sanitizeFilename($filename)
    {
        // Remove directory traversal
        $filename = basename($filename);
        
        // Remove dangerous characters
        $filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);
        
        // Limit length
        if (strlen($filename) > 255) {
            $filename = substr($filename, 0, 255);
        }
        
        return $filename;
    }

    /**
     * Sanitize URL
     */
    public static function sanitizeUrl($url)
    {
        $url = trim($url);
        
        // Add protocol if missing
        if (!preg_match('/^https?:\/\//', $url)) {
            $url = 'http://' . $url;
        }
        
        return filter_var($url, FILTER_SANITIZE_URL);
    }

    /**
     * Remove XSS attempts
     */
    public static function removeXss($data)
    {
        if (is_array($data)) {
            return array_map([self::class, 'removeXss'], $data);
        }

        if (!is_string($data)) {
            return $data;
        }

        // Remove null bytes
        $data = str_replace("\0", '', $data);
        
        // Remove dangerous characters
        $data = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $data);
        
        // Remove script tags
        $data = preg_replace('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $data);
        
        // Remove javascript: urls
        $data = preg_replace('/javascript:/i', '', $data);
        
        // Remove on* events
        $data = preg_replace('/\s*on\w+\s*=\s*["\'][^"\']*["\']/i', '', $data);
        
        return $data;
    }

    /**
     * Escape output for safe display
     */
    public static function escapeOutput($data, $encoding = 'UTF-8')
    {
        if (is_array($data)) {
            return array_map(function($item) use ($encoding) {
                return self::escapeOutput($item, $encoding);
            }, $data);
        }

        return htmlspecialchars($data, ENT_QUOTES | ENT_HTML5, $encoding);
    }
}

๐Ÿ” Security Middleware

1. Content Security Policy

CSP Middleware (app/Http/Middleware/ContentSecurityPolicy.php)

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class ContentSecurityPolicy
{
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);

        $csp = [
            "default-src 'self'",
            "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://maps.googleapis.com",
            "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com",
            "font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com",
            "img-src 'self' data: https: blob:",
            "connect-src 'self' https://api.qrserver.com https://wa.me https://maps.googleapis.com",
            "frame-src 'self' https://www.google.com",
            "object-src 'none'",
            "base-uri 'self'",
            "form-action 'self'",
            "frame-ancestors 'none'",
            "upgrade-insecure-requests"
        ];

        $response->headers->set('Content-Security-Policy', implode('; ', $csp));
        
        return $response;
    }
}

2. Security Headers Middleware

Security Headers (app/Http/Middleware/SecurityHeaders.php)

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class SecurityHeaders
{
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);

        // Prevent XSS attacks
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        
        // Prevent MIME type sniffing
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        
        // Prevent clickjacking
        $response->headers->set('X-Frame-Options', 'DENY');
        
        // Referrer policy
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        
        // Feature policy
        $response->headers->set('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
        
        // HSTS (only in production with HTTPS)
        if (app()->environment('production') && $request->isSecure()) {
            $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
        }
        
        // Remove server information
        $response->headers->remove('Server');
        $response->headers->remove('X-Powered-By');
        
        return $response;
    }
}

3. API Rate Limiting

Enhanced Rate Limiting (app/Http/Middleware/ApiRateLimit.php)

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;

class ApiRateLimit
{
    public function handle(Request $request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
    {
        $key = $this->resolveRequestSignature($request);
        
        if (RateLimiter::tooManyAttempts($key, $maxAttempts)) {
            $seconds = RateLimiter::availableIn($key);
            
            // Log rate limit exceeded
            activity()
                ->withProperties([
                    'ip' => $request->ip(),
                    'user_agent' => $request->userAgent(),
                    'endpoint' => $request->path(),
                    'method' => $request->method(),
                ])
                ->log('api_rate_limit_exceeded');
            
            return response()->json([
                'error' => 'Rate limit exceeded',
                'message' => 'Too many requests. Please try again later.',
                'retry_after' => $seconds,
            ], 429);
        }

        RateLimiter::hit($key, $decayMinutes * 60);

        $response = $next($request);

        // Add rate limit headers
        $response->headers->add([
            'X-RateLimit-Limit' => $maxAttempts,
            'X-RateLimit-Remaining' => RateLimiter::remaining($key, $maxAttempts),
            'X-RateLimit-Reset' => now()->addSeconds(RateLimiter::availableIn($key))->timestamp,
        ]);

        return $response;
    }

    protected function resolveRequestSignature(Request $request)
    {
        $user = $request->user();
        
        if ($user) {
            return 'api:' . $user->id;
        }
        
        return 'api:' . $request->ip();
    }
}

๐Ÿ“‹ Audit Logging

1. Activity Logging Service

Security Activity Logger (app/Services/SecurityAuditService.php)

<?php

namespace App\Services;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Request;

class SecurityAuditService
{
    /**
     * Log security event
     */
    public static function logSecurityEvent($event, $data = [], $userId = null, $severity = 'info')
    {
        DB::table('security_audit_log')->insert([
            'user_id' => $userId ?: auth()->id(),
            'event' => $event,
            'severity' => $severity,
            'ip_address' => Request::ip(),
            'user_agent' => Request::userAgent(),
            'data' => json_encode($data),
            'created_at' => now(),
        ]);
    }

    /**
     * Log authentication events
     */
    public static function logAuth($event, $email = null, $success = true)
    {
        self::logSecurityEvent("auth.{$event}", [
            'email' => $email,
            'success' => $success,
            'session_id' => session()->getId(),
        ], null, $success ? 'info' : 'warning');
    }

    /**
     * Log permission violations
     */
    public static function logPermissionViolation($action, $resource = null, $userId = null)
    {
        self::logSecurityEvent('permission.violation', [
            'action' => $action,
            'resource' => $resource,
            'route' => Request::route()?->getName(),
            'url' => Request::fullUrl(),
        ], $userId, 'warning');
    }

    /**
     * Log data access
     */
    public static function logDataAccess($resource, $action, $resourceId = null)
    {
        self::logSecurityEvent('data.access', [
            'resource' => $resource,
            'action' => $action,
            'resource_id' => $resourceId,
        ]);
    }

    /**
     * Log suspicious activity
     */
    public static function logSuspiciousActivity($description, $data = [])
    {
        self::logSecurityEvent('suspicious.activity', array_merge([
            'description' => $description,
        ], $data), null, 'critical');
    }

    /**
     * Get recent security events
     */
    public static function getRecentEvents($limit = 100, $severity = null)
    {
        $query = DB::table('security_audit_log')
                   ->orderBy('created_at', 'desc')
                   ->limit($limit);

        if ($severity) {
            $query->where('severity', $severity);
        }

        return $query->get();
    }

    /**
     * Get security metrics
     */
    public static function getSecurityMetrics($days = 30)
    {
        $startDate = now()->subDays($days);

        return [
            'total_events' => DB::table('security_audit_log')
                               ->where('created_at', '>=', $startDate)
                               ->count(),
                               
            'failed_logins' => DB::table('security_audit_log')
                                ->where('event', 'auth.login')
                                ->where('created_at', '>=', $startDate)
                                ->whereJsonContains('data->success', false)
                                ->count(),
                                
            'permission_violations' => DB::table('security_audit_log')
                                        ->where('event', 'permission.violation')
                                        ->where('created_at', '>=', $startDate)
                                        ->count(),
                                        
            'suspicious_activities' => DB::table('security_audit_log')
                                        ->where('event', 'suspicious.activity')
                                        ->where('created_at', '>=', $startDate)
                                        ->count(),
                                        
            'by_severity' => DB::table('security_audit_log')
                              ->select('severity', DB::raw('count(*) as count'))
                              ->where('created_at', '>=', $startDate)
                              ->groupBy('severity')
                              ->pluck('count', 'severity')
                              ->toArray(),
        ];
    }
}

2. Audit Log Migration

Security Audit Log Table

<?php
// database/migrations/xxxx_create_security_audit_log_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('security_audit_log', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
            $table->string('event');
            $table->enum('severity', ['info', 'warning', 'error', 'critical'])->default('info');
            $table->ipAddress('ip_address')->nullable();
            $table->text('user_agent')->nullable();
            $table->json('data')->nullable();
            $table->timestamp('created_at');
            
            $table->index(['event', 'created_at']);
            $table->index(['severity', 'created_at']);
            $table->index(['user_id', 'created_at']);
        });
    }

    public function down()
    {
        Schema::dropIfExists('security_audit_log');
    }
};

๐Ÿ”’ File Upload Security

1. Secure File Upload Service

File Upload Security (app/Services/SecureFileUploadService.php)

<?php

namespace App\Services;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class SecureFileUploadService
{
    const ALLOWED_IMAGE_TYPES = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
    const ALLOWED_DOCUMENT_TYPES = ['pdf', 'doc', 'docx', 'txt'];
    const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB

    /**
     * Upload file securely
     */
    public static function upload(UploadedFile $file, $directory = 'uploads', $allowedTypes = null)
    {
        // Validate file
        self::validateFile($file, $allowedTypes);
        
        // Generate secure filename
        $filename = self::generateSecureFilename($file);
        
        // Store file
        $path = $file->storeAs($directory, $filename, 'secure');
        
        // Scan for malware (if available)
        if (self::hasAntivirusSupport()) {
            self::scanFile(storage_path('app/secure/' . $path));
        }
        
        SecurityAuditService::logSecurityEvent('file.upload', [
            'filename' => $filename,
            'original_name' => $file->getClientOriginalName(),
            'size' => $file->getSize(),
            'mime_type' => $file->getMimeType(),
            'directory' => $directory,
        ]);
        
        return $path;
    }

    /**
     * Validate uploaded file
     */
    protected static function validateFile(UploadedFile $file, $allowedTypes = null)
    {
        // Check file size
        if ($file->getSize() > self::MAX_FILE_SIZE) {
            throw new \Exception('File size exceeds maximum allowed size');
        }

        // Check file extension
        $extension = strtolower($file->getClientOriginalExtension());
        $allowedTypes = $allowedTypes ?: self::ALLOWED_IMAGE_TYPES;
        
        if (!in_array($extension, $allowedTypes)) {
            throw new \Exception('File type not allowed');
        }

        // Check MIME type
        $mimeType = $file->getMimeType();
        if (!self::isValidMimeType($mimeType, $extension)) {
            throw new \Exception('Invalid file type');
        }

        // Check for executable content
        if (self::containsExecutableContent($file)) {
            throw new \Exception('File contains potentially dangerous content');
        }
    }

    /**
     * Generate secure filename
     */
    protected static function generateSecureFilename(UploadedFile $file)
    {
        $extension = $file->getClientOriginalExtension();
        $hash = hash('sha256', $file->getContent() . time());
        return substr($hash, 0, 32) . '.' . strtolower($extension);
    }

    /**
     * Check if MIME type matches extension
     */
    protected static function isValidMimeType($mimeType, $extension)
    {
        $validMimes = [
            'jpg' => ['image/jpeg'],
            'jpeg' => ['image/jpeg'],
            'png' => ['image/png'],
            'gif' => ['image/gif'],
            'webp' => ['image/webp'],
            'pdf' => ['application/pdf'],
            'doc' => ['application/msword'],
            'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
            'txt' => ['text/plain'],
        ];

        return isset($validMimes[$extension]) && in_array($mimeType, $validMimes[$extension]);
    }

    /**
     * Check for executable content in file
     */
    protected static function containsExecutableContent(UploadedFile $file)
    {
        $content = $file->getContent();
        
        // Check for common executable signatures
        $dangerousSignatures = [
            '#!/bin/sh',
            '#!/bin/bash',
            '<?php',
            '<script',
            'eval(',
            'exec(',
            'system(',
            'shell_exec(',
            'MZ', // PE header
            '\x7fELF', // ELF header
        ];

        foreach ($dangerousSignatures as $signature) {
            if (strpos($content, $signature) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if antivirus support is available
     */
    protected static function hasAntivirusSupport()
    {
        return extension_loaded('clamav') || class_exists('ClamAV');
    }

    /**
     * Scan file for malware
     */
    protected static function scanFile($filepath)
    {
        // This is a placeholder - implement based on your antivirus solution
        // Example: ClamAV integration
        if (self::hasAntivirusSupport()) {
            // Implement actual malware scanning
            return true;
        }
        
        return true;
    }

    /**
     * Get file securely
     */
    public static function getSecureFile($path)
    {
        if (!Storage::disk('secure')->exists($path)) {
            throw new \Exception('File not found');
        }

        SecurityAuditService::logDataAccess('file', 'download', $path);
        
        return Storage::disk('secure')->path($path);
    }

    /**
     * Delete file securely
     */
    public static function deleteSecureFile($path)
    {
        if (Storage::disk('secure')->exists($path)) {
            $deleted = Storage::disk('secure')->delete($path);
            
            SecurityAuditService::logSecurityEvent('file.delete', [
                'path' => $path,
                'success' => $deleted,
            ]);
            
            return $deleted;
        }
        
        return false;
    }
}

๐Ÿ”ง Security Configuration

1. Enhanced Session Configuration

Session Security (config/session.php)

<?php

return [
    'driver' => env('SESSION_DRIVER', 'database'),
    'lifetime' => env('SESSION_LIFETIME', 120),
    'expire_on_close' => true,
    'encrypt' => true,
    'files' => storage_path('framework/sessions'),
    'connection' => env('SESSION_CONNECTION', null),
    'table' => 'sessions',
    'store' => env('SESSION_STORE', null),
    'lottery' => [2, 100],
    'cookie' => env('SESSION_COOKIE', Str::slug(env('APP_NAME', 'laravel'), '_').'_session'),
    'path' => '/',
    'domain' => env('SESSION_DOMAIN', null),
    'secure' => env('SESSION_SECURE_COOKIE', true),
    'http_only' => true,
    'same_site' => 'strict',
    'partitioned' => false,
];

2. Security Service Provider

Security Service Provider (app/Providers/SecurityServiceProvider.php)

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Http\Middleware\SecurityHeaders;
use App\Http\Middleware\ContentSecurityPolicy;
use App\Http\Middleware\ApiRateLimit;
use App\Services\SecurityAuditService;

class SecurityServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Register security services
        $this->app->singleton(SecurityAuditService::class);
    }

    public function boot()
    {
        // Global middleware
        $this->app['router']->middlewareGroup('security', [
            SecurityHeaders::class,
            ContentSecurityPolicy::class,
        ]);

        // API middleware
        $this->app['router']->middlewareGroup('api-secure', [
            'throttle:api',
            ApiRateLimit::class,
        ]);

        // Log security events
        $this->registerSecurityEventListeners();
    }

    protected function registerSecurityEventListeners()
    {
        // Login events
        \Event::listen('Illuminate\Auth\Events\Login', function ($event) {
            $event->user->recordLogin();
            SecurityAuditService::logAuth('login', $event->user->email, true);
        });

        \Event::listen('Illuminate\Auth\Events\Failed', function ($event) {
            SecurityAuditService::logAuth('login', $event->credentials['email'] ?? null, false);
        });

        \Event::listen('Illuminate\Auth\Events\Logout', function ($event) {
            SecurityAuditService::logAuth('logout', $event->user->email, true);
        });

        // Password reset events
        \Event::listen('Illuminate\Auth\Events\PasswordReset', function ($event) {
            SecurityAuditService::logSecurityEvent('password.reset', [
                'email' => $event->user->email,
            ], $event->user->id);
        });
    }
}

๐Ÿ“Š Security Monitoring Commands

1. Security Scan Command

Security Scanner (app/Console/Commands/SecurityScan.php)

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\SecurityAuditService;
use App\Models\User;
use App\Models\Admin;

class SecurityScan extends Command
{
    protected $signature = 'security:scan {--days=7 : Number of days to analyze}';
    protected $description = 'Perform security analysis and generate report';

    public function handle()
    {
        $days = $this->option('days');
        
        $this->info("๐Ÿ” Starting security scan for the last {$days} days...");
        $this->newLine();

        // Get security metrics
        $metrics = SecurityAuditService::getSecurityMetrics($days);
        
        // Display summary
        $this->displaySecuritySummary($metrics);
        
        // Check for suspicious activities
        $this->checkSuspiciousActivities($days);
        
        // Check user accounts
        $this->checkUserAccounts();
        
        // Check failed logins
        $this->checkFailedLogins($days);
        
        $this->newLine();
        $this->info('โœ… Security scan completed');
        
        return 0;
    }

    protected function displaySecuritySummary($metrics)
    {
        $this->info('๐Ÿ“Š Security Summary:');
        
        $this->table(
            ['Metric', 'Count'],
            [
                ['Total Security Events', number_format($metrics['total_events'])],
                ['Failed Logins', number_format($metrics['failed_logins'])],
                ['Permission Violations', number_format($metrics['permission_violations'])],
                ['Suspicious Activities', number_format($metrics['suspicious_activities'])],
            ]
        );

        if (!empty($metrics['by_severity'])) {
            $this->newLine();
            $this->info('๐Ÿ“ˆ Events by Severity:');
            
            foreach ($metrics['by_severity'] as $severity => $count) {
                $icon = match($severity) {
                    'critical' => '๐Ÿ”ด',
                    'error' => '๐ŸŸ ',
                    'warning' => '๐ŸŸก',
                    default => '๐ŸŸข'
                };
                
                $this->line("  {$icon} {$severity}: {$count}");
            }
        }
    }

    protected function checkSuspiciousActivities($days)
    {
        $suspiciousEvents = SecurityAuditService::getRecentEvents(50, 'critical');
        
        if ($suspiciousEvents->isNotEmpty()) {
            $this->newLine();
            $this->warn('โš ๏ธ  Suspicious Activities Detected:');
            
            foreach ($suspiciousEvents->take(10) as $event) {
                $data = json_decode($event->data, true);
                $this->line("  โ€ข {$event->created_at}: {$data['description'] ?? $event->event}");
            }
            
            if ($suspiciousEvents->count() > 10) {
                $this->line("  ... and " . ($suspiciousEvents->count() - 10) . " more");
            }
        }
    }

    protected function checkUserAccounts()
    {
        // Check for inactive admin accounts
        $inactiveAdmins = Admin::where('last_login_at', '<', now()->subDays(90))
                              ->orWhereNull('last_login_at')
                              ->count();
        
        // Check for unverified users
        $unverifiedUsers = User::whereNull('email_verified_at')
                              ->where('created_at', '<', now()->subDays(7))
                              ->count();
        
        // Check for suspended accounts
        $suspendedUsers = User::where('is_active', false)->count();

        $this->newLine();
        $this->info('๐Ÿ‘ฅ Account Security:');
        $this->table(
            ['Account Type', 'Count', 'Status'],
            [
                ['Inactive Admins (90+ days)', $inactiveAdmins, $inactiveAdmins > 0 ? 'โš ๏ธ  Review' : 'โœ… OK'],
                ['Unverified Users (7+ days)', $unverifiedUsers, $unverifiedUsers > 10 ? 'โš ๏ธ  Review' : 'โœ… OK'],
                ['Suspended Accounts', $suspendedUsers, $suspendedUsers > 0 ? 'โ„น๏ธ  Monitor' : 'โœ… OK'],
            ]
        );
    }

    protected function checkFailedLogins($days)
    {
        $failedLogins = \DB::table('security_audit_log')
                          ->where('event', 'auth.login')
                          ->where('created_at', '>=', now()->subDays($days))
                          ->whereJsonContains('data->success', false)
                          ->select('ip_address', \DB::raw('count(*) as attempts'))
                          ->groupBy('ip_address')
                          ->having('attempts', '>', 10)
                          ->orderByDesc('attempts')
                          ->get();

        if ($failedLogins->isNotEmpty()) {
            $this->newLine();
            $this->warn('๐Ÿšจ High Failed Login Attempts:');
            
            $this->table(
                ['IP Address', 'Failed Attempts'],
                $failedLogins->map(function($item) {
                    return [$item->ip_address, $item->attempts];
                })->toArray()
            );
        }
    }
}

2. Security Cleanup Command

Security Cleanup (app/Console/Commands/SecurityCleanup.php)

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\AccountLockoutService;
use Illuminate\Support\Facades\DB;

class SecurityCleanup extends Command
{
    protected $signature = 'security:cleanup {--days=30 : Days to keep audit logs}';
    protected $description = 'Clean up old security logs and reset counters';

    public function handle()
    {
        $days = $this->option('days');
        
        $this->info("๐Ÿงน Starting security cleanup...");
        
        // Clean old audit logs
        $deletedLogs = $this->cleanAuditLogs($days);
        
        // Clean old sessions
        $deletedSessions = $this->cleanOldSessions();
        
        // Reset failed login attempts for resolved IPs
        $this->resetLoginAttempts();
        
        $this->newLine();
        $this->info("โœ… Security cleanup completed:");
        $this->line("  โ€ข Deleted {$deletedLogs} old audit log entries");
        $this->line("  โ€ข Deleted {$deletedSessions} old sessions");
        
        return 0;
    }

    protected function cleanAuditLogs($days)
    {
        $cutoffDate = now()->subDays($days);
        
        return DB::table('security_audit_log')
                 ->where('created_at', '<', $cutoffDate)
                 ->where('severity', '!=', 'critical') // Keep critical events longer
                 ->delete();
    }

    protected function cleanOldSessions()
    {
        return DB::table('sessions')
                 ->where('last_activity', '<', now()->subDays(30)->timestamp)
                 ->delete();
    }

    protected function resetLoginAttempts()
    {
        // This would depend on your cache implementation
        // Example for Redis:
        $this->info("  โ€ข Reset login attempt counters");
    }
}

Next: Performance Optimization untuk optimasi performa sistem.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ