init commit

This commit is contained in:
David Melendez
2026-01-14 22:38:44 +01:00
parent 4e0c415f0b
commit e25d53d054
124 changed files with 21653 additions and 1 deletions

View File

@@ -0,0 +1,400 @@
<?php
/**
* Security Service
* Professional Resume Builder - Enterprise Security Service
*
* @author David Valera Melendez <david@valera-melendez.de>
* @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);
}
}