* @created 2025-08-08 * @location Made in Germany 🇩🇪 */ namespace App\Services; use App\Interfaces\UserRepositoryInterface; use App\Models\User; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Cache; use Carbon\Carbon; use Exception; /** * Security Service * Handles security-related operations and monitoring */ class SecurityService { /** * User repository instance */ private UserRepositoryInterface $userRepository; /** * Maximum failed attempts before account lockout */ private const MAX_FAILED_ATTEMPTS = 5; /** * Account lockout duration in minutes */ private const LOCKOUT_DURATION = 15; /** * Password reset token expiry in hours */ private const PASSWORD_RESET_EXPIRY = 24; /** * Create a new service instance */ public function __construct(UserRepositoryInterface $userRepository) { $this->userRepository = $userRepository; } /** * Generate secure password reset token * * @param User $user * @return string */ public function generatePasswordResetToken(User $user): string { $token = bin2hex(random_bytes(32)); $expiry = now()->addHours(self::PASSWORD_RESET_EXPIRY); // Store token in cache with expiry Cache::put( "password_reset:{$user->id}:{$token}", [ 'user_id' => $user->id, 'email' => $user->email, 'expires_at' => $expiry, 'created_at' => now() ], $expiry ); Log::info('Password reset token generated', [ 'user_id' => $user->id, 'email' => $user->email, 'expires_at' => $expiry, 'ip_address' => request()->ip() ]); return $token; } /** * Validate password reset token * * @param string $token * @param int $userId * @return bool */ public function validatePasswordResetToken(string $token, int $userId): bool { $cacheKey = "password_reset:{$userId}:{$token}"; $tokenData = Cache::get($cacheKey); if (!$tokenData) { Log::warning('Invalid password reset token used', [ 'user_id' => $userId, 'token' => substr($token, 0, 8) . '...', 'ip_address' => request()->ip() ]); return false; } if ($tokenData['user_id'] !== $userId) { Log::warning('Password reset token user mismatch', [ 'expected_user_id' => $userId, 'token_user_id' => $tokenData['user_id'], 'ip_address' => request()->ip() ]); return false; } return true; } /** * Reset user password using token * * @param string $token * @param int $userId * @param string $newPassword * @return bool * @throws Exception */ public function resetPassword(string $token, int $userId, string $newPassword): bool { try { if (!$this->validatePasswordResetToken($token, $userId)) { throw new Exception('Invalid or expired password reset token'); } $user = $this->userRepository->findById($userId); if (!$user) { throw new Exception('User not found'); } // Update password $hashedPassword = Hash::make($newPassword); $updateData = [ 'password' => $hashedPassword, 'password_updated_at' => now(), 'login_attempts' => 0, 'account_locked_until' => null ]; $success = $this->userRepository->update($userId, $updateData); if ($success) { // Remove used token $cacheKey = "password_reset:{$userId}:{$token}"; Cache::forget($cacheKey); Log::info('Password reset completed successfully', [ 'user_id' => $userId, 'email' => $user->email, 'ip_address' => request()->ip() ]); } return $success; } catch (Exception $e) { Log::error('Password reset failed', [ 'error_message' => $e->getMessage(), 'user_id' => $userId, 'ip_address' => request()->ip() ]); throw $e; } } /** * Check if user account is secure * * @param User $user * @return array */ public function checkAccountSecurity(User $user): array { $securityChecks = [ 'password_strength' => $this->checkPasswordStrength($user), 'two_factor_enabled' => $user->two_factor_secret !== null, 'email_verified' => $user->email_verified_at !== null, 'recent_password_change' => $this->checkRecentPasswordChange($user), 'suspicious_activity' => $this->checkSuspiciousActivity($user), 'account_locked' => $this->userRepository->isAccountLocked($user->email) ]; $securityScore = $this->calculateSecurityScore($securityChecks); return [ 'security_score' => $securityScore, 'checks' => $securityChecks, 'recommendations' => $this->getSecurityRecommendations($securityChecks) ]; } /** * Log security event * * @param string $event * @param User $user * @param array $details */ public function logSecurityEvent(string $event, User $user, array $details = []): void { $logData = array_merge([ 'event' => $event, 'user_id' => $user->id, 'email' => $user->email, 'ip_address' => request()->ip(), 'user_agent' => request()->userAgent(), 'timestamp' => now()->toISOString() ], $details); Log::warning("Security Event: {$event}", $logData); // Store in security events table (implement if needed) // SecurityEvent::create($logData); } /** * Generate two-factor authentication secret * * @param User $user * @return string */ public function generateTwoFactorSecret(User $user): string { $secret = bin2hex(random_bytes(16)); $this->userRepository->update($user->id, [ 'two_factor_secret' => $secret, 'two_factor_recovery_codes' => $this->generateRecoveryCodes() ]); Log::info('Two-factor authentication enabled', [ 'user_id' => $user->id, 'email' => $user->email, 'ip_address' => request()->ip() ]); return $secret; } /** * Verify two-factor authentication code * * @param User $user * @param string $code * @return bool */ public function verifyTwoFactorCode(User $user, string $code): bool { if (!$user->two_factor_secret) { return false; } // Implement TOTP verification logic here // This is a simplified version $isValid = $this->validateTOTP($user->two_factor_secret, $code); if ($isValid) { Log::info('Two-factor authentication verified', [ 'user_id' => $user->id, 'email' => $user->email, 'ip_address' => request()->ip() ]); } else { Log::warning('Invalid two-factor authentication code', [ 'user_id' => $user->id, 'email' => $user->email, 'ip_address' => request()->ip() ]); } return $isValid; } /** * Check password strength * * @param User $user * @return bool */ private function checkPasswordStrength(User $user): bool { // This is a simplified check - implement proper password strength validation return $user->password_updated_at && $user->password_updated_at->gt(now()->subMonths(6)); } /** * Check if password was changed recently * * @param User $user * @return bool */ private function checkRecentPasswordChange(User $user): bool { return $user->password_updated_at && $user->password_updated_at->gt(now()->subMonths(3)); } /** * Check for suspicious activity * * @param User $user * @return bool */ private function checkSuspiciousActivity(User $user): bool { // Check failed login attempts return $user->login_attempts > 2; } /** * Calculate security score * * @param array $checks * @return int */ private function calculateSecurityScore(array $checks): int { $score = 0; $totalChecks = count($checks); foreach ($checks as $check) { if ($check === true) { $score++; } } return (int) (($score / $totalChecks) * 100); } /** * Get security recommendations * * @param array $checks * @return array */ private function getSecurityRecommendations(array $checks): array { $recommendations = []; if (!$checks['two_factor_enabled']) { $recommendations[] = 'Enable two-factor authentication for enhanced security'; } if (!$checks['email_verified']) { $recommendations[] = 'Verify your email address'; } if (!$checks['recent_password_change']) { $recommendations[] = 'Consider changing your password regularly'; } if ($checks['suspicious_activity']) { $recommendations[] = 'Review recent login activity for suspicious behavior'; } return $recommendations; } /** * Generate recovery codes * * @return array */ private function generateRecoveryCodes(): array { $codes = []; for ($i = 0; $i < 10; $i++) { $codes[] = strtoupper(bin2hex(random_bytes(4))); } return $codes; } /** * Validate TOTP code (simplified implementation) * * @param string $secret * @param string $code * @return bool */ private function validateTOTP(string $secret, string $code): bool { // This is a placeholder - implement proper TOTP validation // You would typically use a library like spomky-labs/otphp return strlen($code) === 6 && is_numeric($code); } }