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,447 @@
<?php
/**
* Authentication Service
* Professional Resume Builder - Enterprise Auth System
*
* @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\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
use Exception;
/**
* Authentication Service
* Handles user authentication, registration, and security features with repository pattern
*/
class AuthService
{
/**
* User repository instance
*/
private UserRepositoryInterface $userRepository;
/**
* Maximum login attempts per minute
*/
protected const MAX_LOGIN_ATTEMPTS = 5;
/**
* Login throttle key prefix
*/
protected const THROTTLE_KEY_PREFIX = 'login_attempts:';
/**
* Account lockout duration in minutes
*/
protected const LOCKOUT_DURATION = 15;
/**
* Create a new service instance with dependency injection
*/
public function __construct(UserRepositoryInterface $userRepository)
{
$this->userRepository = $userRepository;
}
/**
* Attempt user authentication with comprehensive security measures
*
* Implements enterprise-grade authentication with:
* - Rate limiting to prevent brute force attacks
* - Account lockout mechanisms for security
* - Detailed security event logging
* - Structured response format for consistent handling
*
* @param array $credentials User login credentials (email, password, remember)
* @return array Structured authentication result with success status and details
* @throws ValidationException When rate limiting is exceeded or validation fails
* @throws Exception When system errors occur during authentication
*/
public function attemptLogin(array $credentials): array
{
$email = $credentials['email'];
$password = $credentials['password'];
$remember = $credentials['remember'] ?? false;
try {
$this->checkRateLimit($email);
if ($this->userRepository->isAccountLocked($email)) {
Log::warning('Login attempt on locked account', [
'email' => $email,
'ip_address' => request()->ip()
]);
return [
'success' => false,
'reason' => 'account_locked',
'message' => 'Account is temporarily locked'
];
}
$user = $this->userRepository->findByEmail($email);
if (!$user) {
$this->incrementRateLimit($email);
return [
'success' => false,
'reason' => 'invalid_credentials',
'message' => 'User not found'
];
}
if (!$user->is_active) {
$this->incrementRateLimit($email);
Log::warning('Login attempt on inactive account', [
'user_id' => $user->id,
'email' => $email,
'ip_address' => request()->ip()
]);
return [
'success' => false,
'reason' => 'account_inactive',
'message' => 'Account is deactivated'
];
}
if (!Hash::check($password, $user->password)) {
$this->incrementRateLimit($email);
$this->userRepository->incrementLoginAttempts($email);
$updatedUser = $this->userRepository->findByEmail($email);
if ($updatedUser && $updatedUser->login_attempts >= self::MAX_LOGIN_ATTEMPTS) {
$this->userRepository->lockAccount($email, self::LOCKOUT_DURATION);
Log::warning('Account locked due to excessive failed attempts', [
'user_id' => $user->id,
'email' => $email,
'attempts' => $updatedUser->login_attempts
]);
return [
'success' => false,
'reason' => 'too_many_attempts',
'message' => 'Account locked due to failed attempts'
];
}
return [
'success' => false,
'reason' => 'invalid_credentials',
'message' => 'Invalid password'
];
}
Auth::login($user, $remember);
$this->userRepository->updateLastLogin($user->id, request()->ip());
$this->userRepository->resetLoginAttempts($email);
$this->clearRateLimit($email);
Log::info('Successful user authentication', [
'user_id' => $user->id,
'email' => $email,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent()
]);
return [
'success' => true,
'user' => $user,
'message' => 'Login successful'
];
} catch (ValidationException $e) {
throw $e;
} catch (Exception $e) {
Log::error('Authentication service error', [
'error_message' => $e->getMessage(),
'email' => $email,
'ip_address' => request()->ip(),
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'reason' => 'system_error',
'message' => 'System error occurred'
];
}
}
/**
* Create new user account with comprehensive security validation
*
* Implements secure user registration with:
* - Duplicate email detection using repository layer
* - Secure password hashing with bcrypt
* - Security-focused default settings
* - Comprehensive audit logging
* - Prepared data structure for repository storage
*
* @param array $userData User registration data (first_name, last_name, email, password)
* @return User The created user instance
* @throws ValidationException When email already exists or validation fails
* @throws Exception When user creation fails due to system errors
*/
public function createUser(array $userData): User
{
try {
if ($this->userRepository->findByEmail($userData['email'])) {
Log::warning('Registration attempt with existing email', [
'email' => $userData['email'],
'ip_address' => request()->ip()
]);
throw ValidationException::withMessages([
'email' => 'An account with this email address already exists.',
]);
}
$preparedData = [
'first_name' => $userData['first_name'],
'last_name' => $userData['last_name'],
'email' => $userData['email'],
'password' => Hash::make($userData['password']),
'status' => 'active',
'locale' => $userData['locale'] ?? 'en',
'timezone' => $userData['timezone'] ?? 'UTC',
'login_attempts' => 0,
'locked_until' => null,
'email_verified_at' => null,
'last_login_at' => null,
'last_login_ip' => null
];
$user = $this->userRepository->create($preparedData);
if ($user) {
Log::info('New user account created successfully', [
'user_id' => $user->id,
'email' => $user->email,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent()
]);
}
return $user;
} catch (ValidationException $e) {
throw $e;
} catch (Exception $e) {
Log::error('User creation failed', [
'error_message' => $e->getMessage(),
'email' => $userData['email'] ?? 'unknown',
'ip_address' => request()->ip(),
'trace' => $e->getTraceAsString()
]);
throw new Exception('Failed to create user account');
}
}
/**
* Update user profile information
*/
public function updateProfile(User $user, array $profileData): User
{
unset($profileData['password'], $profileData['email'], $profileData['is_active']);
$user->update($profileData);
logger()->info('User profile updated', [
'user_id' => $user->id,
'email' => $user->email,
'ip' => request()->ip(),
]);
return $user;
}
/**
* Change user password
*/
public function changePassword(User $user, string $currentPassword, string $newPassword): bool
{
if (!Hash::check($currentPassword, $user->password)) {
throw new \InvalidArgumentException('Current password is incorrect.');
}
$user->update(['password' => Hash::make($newPassword)]);
logger()->info('User password changed', [
'user_id' => $user->id,
'email' => $user->email
]);
return true;
}
/**
* Deactivate user account
*/
public function deactivateAccount(User $user): bool
{
$user->update(['is_active' => false]);
logger()->info('User account deactivated', [
'user_id' => $user->id,
'email' => $user->email,
'ip' => request()->ip(),
]);
return true;
}
/**
* Activate user account
*/
public function activateAccount(User $user): bool
{
$user->update(['is_active' => true]);
logger()->info('User account activated', [
'user_id' => $user->id,
'email' => $user->email,
'ip' => request()->ip(),
]);
return true;
}
/**
* Check if user has exceeded login rate limit
*/
protected function checkRateLimit(string $email): void
{
$key = $this->getRateLimitKey($email);
if (RateLimiter::tooManyAttempts($key, self::MAX_LOGIN_ATTEMPTS)) {
$seconds = RateLimiter::availableIn($key);
throw ValidationException::withMessages([
'email' => "Too many login attempts. Please try again in {$seconds} seconds.",
]);
}
}
/**
* Increment rate limit counter
*/
protected function incrementRateLimit(string $email): void
{
$key = $this->getRateLimitKey($email);
RateLimiter::hit($key, 60);
}
/**
* Clear rate limit counter
*/
protected function clearRateLimit(string $email): void
{
$key = $this->getRateLimitKey($email);
RateLimiter::clear($key);
}
/**
* Get rate limit key for email
*/
protected function getRateLimitKey(string $email): string
{
return self::THROTTLE_KEY_PREFIX . $email . '|' . request()->ip();
}
/**
* Send password reset notification
*/
public function sendPasswordResetNotification(string $email): bool
{
$user = User::where('email', $email)->first();
if (!$user) {
// Don't reveal if email exists or not for security
return true;
}
// Generate and send password reset token
// Implementation depends on your notification preferences
// $user->sendPasswordResetNotification($token);
logger()->info('Password reset requested', [
'user_id' => $user->id,
'email' => $user->email,
'ip' => request()->ip(),
]);
return true;
}
/**
* Verify email address
*/
public function verifyEmail(User $user): bool
{
if ($user->hasVerifiedEmail()) {
return true;
}
$user->markEmailAsVerified();
logger()->info('Email verified', [
'user_id' => $user->id,
'email' => $user->email,
'ip' => request()->ip(),
]);
return true;
}
/**
* Get user statistics
*/
public function getUserStats(User $user): array
{
return [
'account_created' => $user->created_at,
'last_login' => $user->last_login_at,
'profile_completion' => $this->calculateProfileCompletion($user),
'total_resumes' => $user->resumes()->count(),
'completed_resumes' => $user->completedResumes()->count(),
'is_verified' => $user->hasVerifiedEmail(),
];
}
/**
* Calculate profile completion percentage
*/
protected function calculateProfileCompletion(User $user): int
{
$fields = [
'first_name', 'last_name', 'email', 'phone',
'address', 'city', 'country', 'profession'
];
$completed = 0;
foreach ($fields as $field) {
if (!empty($user->$field)) {
$completed++;
}
}
return (int) (($completed / count($fields)) * 100);
}
}