587 lines
20 KiB
PHP
587 lines
20 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Authentication Controller
|
|
* Professional Resume Builder - Enterprise Auth System
|
|
*
|
|
* @author David Valera Melendez <david@valera-melendez.de>
|
|
* @created 2025-08-08
|
|
* @location Made in Germany 🇩🇪
|
|
*/
|
|
|
|
namespace App\Http\Controllers\Auth;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Auth\LoginRequest;
|
|
use App\Http\Requests\Auth\RegisterRequest;
|
|
use App\Http\Requests\Auth\UpdateProfileRequest;
|
|
use App\Interfaces\UserRepositoryInterface;
|
|
use App\Services\AuthService;
|
|
use App\Services\ProfileCompletionService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\View\View;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Exception;
|
|
|
|
/**
|
|
* Authentication Controller
|
|
* Handles user authentication, registration, and session management with repository pattern
|
|
*/
|
|
class AuthController extends Controller
|
|
{
|
|
/**
|
|
* User repository instance
|
|
*/
|
|
private UserRepositoryInterface $userRepository;
|
|
|
|
/**
|
|
* AuthService instance
|
|
*/
|
|
private AuthService $authService;
|
|
|
|
/**
|
|
* Profile completion service instance
|
|
*/
|
|
private ProfileCompletionService $profileCompletionService;
|
|
|
|
/**
|
|
* Maximum login attempts before lockout
|
|
*/
|
|
private const MAX_LOGIN_ATTEMPTS = 5;
|
|
|
|
/**
|
|
* Account lockout duration in minutes
|
|
*/
|
|
private const LOCKOUT_DURATION = 15;
|
|
|
|
/**
|
|
* Create a new controller instance with dependency injection
|
|
*/
|
|
public function __construct(
|
|
UserRepositoryInterface $userRepository,
|
|
AuthService $authService,
|
|
ProfileCompletionService $profileCompletionService
|
|
) {
|
|
$this->userRepository = $userRepository;
|
|
$this->authService = $authService;
|
|
$this->profileCompletionService = $profileCompletionService;
|
|
$this->middleware('guest')->except(['logout', 'profile', 'updateProfile', 'activity']);
|
|
$this->middleware('auth')->only(['logout', 'profile', 'updateProfile', 'activity']);
|
|
}
|
|
|
|
/**
|
|
* Show the login form
|
|
*/
|
|
public function showLoginForm(): View
|
|
{
|
|
return view('auth.login', [
|
|
'title' => 'Sign In - Professional Resume Builder',
|
|
'description' => 'Access your professional resume builder account'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Handle a login request with enhanced maintainability
|
|
*
|
|
* @param LoginRequest $request
|
|
* @return RedirectResponse
|
|
*/
|
|
public function login(LoginRequest $request): RedirectResponse
|
|
{
|
|
try {
|
|
$credentials = $request->validated();
|
|
$loginResult = $this->authService->attemptLogin($credentials);
|
|
|
|
return $this->handleLoginResult($loginResult, $request);
|
|
|
|
} catch (ValidationException $e) {
|
|
return $this->handleLoginValidationError($e, $request);
|
|
} catch (Exception $e) {
|
|
return $this->handleLoginSystemError($e, $request);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the result of a login attempt
|
|
*
|
|
* @param array $loginResult
|
|
* @param LoginRequest $request
|
|
* @return RedirectResponse
|
|
*/
|
|
private function handleLoginResult(array $loginResult, LoginRequest $request): RedirectResponse
|
|
{
|
|
if ($loginResult['success']) {
|
|
return $this->handleSuccessfulLogin($loginResult, $request);
|
|
}
|
|
|
|
return $this->handleFailedLogin($loginResult, $request);
|
|
}
|
|
|
|
/**
|
|
* Handle successful login response
|
|
*
|
|
* @param array $loginResult
|
|
* @param LoginRequest $request
|
|
* @return RedirectResponse
|
|
*/
|
|
private function handleSuccessfulLogin(array $loginResult, LoginRequest $request): RedirectResponse
|
|
{
|
|
$request->session()->regenerate();
|
|
|
|
Log::info('User logged in successfully', [
|
|
'user_id' => $loginResult['user']->id,
|
|
'email' => $loginResult['user']->email,
|
|
'ip_address' => $request->ip(),
|
|
'user_agent' => $request->userAgent(),
|
|
'login_method' => 'standard'
|
|
]);
|
|
|
|
$welcomeMessage = $this->getWelcomeMessage($loginResult['user']);
|
|
$redirectUrl = $this->getPostLoginRedirectUrl($loginResult['user']);
|
|
|
|
return redirect()->intended($redirectUrl)->with('success', $welcomeMessage);
|
|
}
|
|
|
|
/**
|
|
* Handle failed login response
|
|
*
|
|
* @param array $loginResult
|
|
* @param LoginRequest $request
|
|
* @return RedirectResponse
|
|
*/
|
|
private function handleFailedLogin(array $loginResult, LoginRequest $request): RedirectResponse
|
|
{
|
|
$errorMessage = $this->getLoginErrorMessage($loginResult['reason']);
|
|
|
|
Log::warning('Login attempt failed', [
|
|
'email' => $request->input('email'),
|
|
'reason' => $loginResult['reason'],
|
|
'ip_address' => $request->ip(),
|
|
'user_agent' => $request->userAgent()
|
|
]);
|
|
|
|
return back()
|
|
->withErrors(['email' => $errorMessage])
|
|
->onlyInput('email');
|
|
}
|
|
|
|
/**
|
|
* Handle validation errors during login
|
|
*
|
|
* @param ValidationException $e
|
|
* @param LoginRequest $request
|
|
* @return RedirectResponse
|
|
*/
|
|
private function handleLoginValidationError(ValidationException $e, LoginRequest $request): RedirectResponse
|
|
{
|
|
Log::warning('Login validation error', [
|
|
'email' => $request->input('email'),
|
|
'errors' => $e->errors(),
|
|
'ip_address' => $request->ip()
|
|
]);
|
|
|
|
return back()
|
|
->withErrors($e->errors())
|
|
->onlyInput('email');
|
|
}
|
|
|
|
/**
|
|
* Handle system errors during login
|
|
*
|
|
* @param Exception $e
|
|
* @param LoginRequest $request
|
|
* @return RedirectResponse
|
|
*/
|
|
private function handleLoginSystemError(Exception $e, LoginRequest $request): RedirectResponse
|
|
{
|
|
Log::error('Login system error', [
|
|
'error_message' => $e->getMessage(),
|
|
'error_trace' => $e->getTraceAsString(),
|
|
'email' => $request->input('email'),
|
|
'ip_address' => $request->ip(),
|
|
'timestamp' => now()
|
|
]);
|
|
|
|
return back()
|
|
->withErrors(['email' => 'A system error occurred. Please try again or contact support.'])
|
|
->onlyInput('email');
|
|
}
|
|
|
|
/**
|
|
* Get personalized welcome message
|
|
*
|
|
* @param User $user
|
|
* @return string
|
|
*/
|
|
private function getWelcomeMessage(User $user): string
|
|
{
|
|
$timeOfDay = $this->getTimeOfDay();
|
|
$firstName = $user->first_name ?? 'there';
|
|
|
|
return "Good {$timeOfDay}, {$firstName}! Welcome back to your professional resume builder.";
|
|
}
|
|
|
|
/**
|
|
* Get post-login redirect URL based on user profile and activity state
|
|
*
|
|
* Intelligently determines the most appropriate landing page based on:
|
|
* - Profile completion percentage (incomplete profiles → profile page)
|
|
* - Resume creation status (no resumes → dashboard for guidance)
|
|
* - Default → main dashboard
|
|
*
|
|
* @param User $user The authenticated user
|
|
* @return string The route name for redirection
|
|
*/
|
|
private function getPostLoginRedirectUrl(User $user): string
|
|
{
|
|
$profileCompletion = $this->profileCompletionService->calculateCompletion($user);
|
|
|
|
if ($profileCompletion < 50) {
|
|
return route('profile');
|
|
}
|
|
|
|
$userStats = $this->userRepository->getUserStatistics($user->id);
|
|
if (($userStats['resumes_count'] ?? 0) === 0) {
|
|
return route('dashboard');
|
|
}
|
|
|
|
return route('dashboard');
|
|
}
|
|
|
|
/**
|
|
* Get appropriate error message based on failure reason
|
|
*
|
|
* @param string $reason
|
|
* @return string
|
|
*/
|
|
private function getLoginErrorMessage(string $reason): string
|
|
{
|
|
switch ($reason) {
|
|
case 'account_locked':
|
|
return 'Account is temporarily locked due to too many failed attempts. Please try again later.';
|
|
case 'account_inactive':
|
|
return 'Your account has been deactivated. Please contact support.';
|
|
case 'invalid_credentials':
|
|
return 'The provided credentials do not match our records.';
|
|
case 'too_many_attempts':
|
|
return 'Too many failed attempts. Account has been locked for security.';
|
|
case 'rate_limited':
|
|
return 'Too many login attempts. Please wait before trying again.';
|
|
default:
|
|
return 'Login failed. Please check your credentials and try again.';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get time of day greeting
|
|
*
|
|
* @return string
|
|
*/
|
|
private function getTimeOfDay(): string
|
|
{
|
|
$hour = now()->hour;
|
|
|
|
if ($hour < 12) {
|
|
return 'morning';
|
|
} elseif ($hour < 17) {
|
|
return 'afternoon';
|
|
} else {
|
|
return 'evening';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show the registration form
|
|
*/
|
|
public function showRegistrationForm(): View
|
|
{
|
|
return view('auth.register', [
|
|
'title' => 'Create Account - Professional Resume Builder',
|
|
'description' => 'Join thousands of professionals creating outstanding resumes'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Handle a registration request
|
|
*/
|
|
public function register(RegisterRequest $request): RedirectResponse
|
|
{
|
|
try {
|
|
$userData = $request->validated();
|
|
|
|
$user = $this->authService->createUser($userData);
|
|
|
|
if ($user) {
|
|
Auth::login($user);
|
|
|
|
// Log successful registration
|
|
logger()->info('User registered successfully', [
|
|
'user_id' => $user->id,
|
|
'email' => $user->email,
|
|
'ip' => $request->ip(),
|
|
'user_agent' => $request->userAgent()
|
|
]);
|
|
|
|
return redirect()->route('dashboard')
|
|
->with('success', 'Welcome! Your account has been created successfully.');
|
|
}
|
|
|
|
return back()->withErrors([
|
|
'email' => 'Unable to create account. Please try again.',
|
|
])->withInput();
|
|
|
|
} catch (\Exception $e) {
|
|
logger()->error('Registration error', [
|
|
'error' => $e->getMessage(),
|
|
'email' => $request->input('email'),
|
|
'ip' => $request->ip()
|
|
]);
|
|
|
|
return back()->withErrors([
|
|
'email' => 'An error occurred during registration. Please try again.',
|
|
])->withInput();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle logout request
|
|
*/
|
|
public function logout(Request $request): RedirectResponse
|
|
{
|
|
// Log the logout
|
|
logger()->info('User logged out', [
|
|
'user_id' => Auth::id(),
|
|
'ip' => $request->ip()
|
|
]);
|
|
|
|
Auth::logout();
|
|
|
|
$request->session()->invalidate();
|
|
$request->session()->regenerateToken();
|
|
|
|
return redirect()->route('login')
|
|
->with('success', 'You have been successfully logged out.');
|
|
}
|
|
|
|
/**
|
|
* Show user profile with comprehensive analytics
|
|
*/
|
|
public function profile(): View
|
|
{
|
|
try {
|
|
$user = Auth::user();
|
|
|
|
// Get user statistics from repository
|
|
$userStats = $this->userRepository->getUserStatistics($user->id);
|
|
|
|
// Get profile completion analysis from service
|
|
$profileCompletion = $this->profileCompletionService->calculateCompletion($user);
|
|
$profileAnalysis = $this->profileCompletionService->getDetailedAnalysis($user);
|
|
|
|
// Prepare view data
|
|
$viewData = [
|
|
'title' => 'Profile Settings - Professional Resume Builder',
|
|
'user' => $user,
|
|
'userStats' => $userStats,
|
|
'profileCompletion' => $profileCompletion,
|
|
'profileAnalysis' => $profileAnalysis,
|
|
'accountSecurity' => [
|
|
'two_factor_enabled' => $user->two_factor_secret !== null,
|
|
'email_verified' => $user->email_verified_at !== null,
|
|
'is_locked' => $this->userRepository->isAccountLocked($user->email)
|
|
]
|
|
];
|
|
|
|
// Log profile view for analytics
|
|
Log::info('User profile viewed', [
|
|
'user_id' => $user->id,
|
|
'profile_completion' => $profileCompletion,
|
|
'ip_address' => request()->ip(),
|
|
'timestamp' => now()
|
|
]);
|
|
|
|
return view('auth.profile', $viewData);
|
|
|
|
} catch (Exception $e) {
|
|
Log::error('Error loading user profile', [
|
|
'error_message' => $e->getMessage(),
|
|
'user_id' => Auth::id(),
|
|
'ip_address' => request()->ip(),
|
|
'timestamp' => now()
|
|
]);
|
|
|
|
// Fallback with minimal data
|
|
return view('auth.profile', [
|
|
'title' => 'Profile Settings',
|
|
'user' => Auth::user(),
|
|
'userStats' => [],
|
|
'profileCompletion' => 0,
|
|
'error' => 'Unable to load profile data completely'
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update user profile using professional form request validation
|
|
*
|
|
* Handles comprehensive profile updates with:
|
|
* - Repository pattern for data persistence
|
|
* - Structured logging for security audit trails
|
|
* - Real-time profile completion calculation
|
|
* - User-friendly completion status messaging
|
|
*
|
|
* @param UpdateProfileRequest $request Validated profile update data
|
|
* @return RedirectResponse Response with success message or error state
|
|
* @throws Exception When profile update fails due to system errors
|
|
*/
|
|
public function updateProfile(UpdateProfileRequest $request): RedirectResponse
|
|
{
|
|
try {
|
|
$user = Auth::user();
|
|
$updateData = $request->validated();
|
|
|
|
$updatedUser = $this->userRepository->update($user->id, $updateData);
|
|
|
|
Log::info('User profile updated successfully', [
|
|
'user_id' => $user->id,
|
|
'updated_fields' => array_keys($updateData),
|
|
'ip_address' => $request->ip(),
|
|
'user_agent' => $request->userAgent(),
|
|
'timestamp' => now()
|
|
]);
|
|
|
|
$completionPercentage = $this->profileCompletionService
|
|
->calculateCompletion($updatedUser);
|
|
|
|
$message = "Profile updated successfully! ";
|
|
if ($completionPercentage < 100) {
|
|
$message .= "Your profile is {$completionPercentage}% complete.";
|
|
} else {
|
|
$message .= "Your profile is now 100% complete!";
|
|
}
|
|
|
|
return back()->with('success', $message);
|
|
|
|
} catch (Exception $e) {
|
|
Log::error('Profile update failed', [
|
|
'error_message' => $e->getMessage(),
|
|
'error_trace' => $e->getTraceAsString(),
|
|
'user_id' => Auth::id(),
|
|
'request_data' => $request->except(['password', 'password_confirmation']),
|
|
'ip_address' => $request->ip(),
|
|
'timestamp' => now()
|
|
]);
|
|
|
|
return back()
|
|
->withErrors(['error' => 'Unable to update profile. Please try again.'])
|
|
->withInput();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve comprehensive user activity and analytics data
|
|
*
|
|
* Provides detailed activity analytics including:
|
|
* - User profile and authentication information
|
|
* - Login activity patterns and security metrics
|
|
* - Profile completion analysis with detailed breakdown
|
|
* - Account security status and recommendations
|
|
* - Usage statistics (resumes, templates, exports)
|
|
*
|
|
* @return JsonResponse Structured activity data with metadata
|
|
* @throws Exception When analytics data retrieval fails
|
|
*/
|
|
public function activity(): JsonResponse
|
|
{
|
|
try {
|
|
$user = Auth::user();
|
|
|
|
$userStats = $this->userRepository->getUserStatistics($user->id);
|
|
|
|
$profileCompletion = $this->profileCompletionService
|
|
->calculateCompletion($user);
|
|
|
|
$profileAnalysis = $this->profileCompletionService
|
|
->getDetailedAnalysis($user);
|
|
|
|
$activityData = [
|
|
'user_info' => [
|
|
'id' => $user->id,
|
|
'name' => $user->first_name . ' ' . $user->last_name,
|
|
'email' => $user->email,
|
|
'member_since' => $user->created_at->format('F Y'),
|
|
'account_status' => $user->status ?? 'active'
|
|
],
|
|
'login_activity' => [
|
|
'last_login' => $user->last_login_at ? $user->last_login_at->format('Y-m-d H:i:s') : null,
|
|
'last_login_ip' => $user->last_login_ip,
|
|
'total_logins' => $userStats['total_logins'] ?? 0,
|
|
'failed_attempts' => $user->login_attempts ?? 0
|
|
],
|
|
'profile_metrics' => [
|
|
'completion_percentage' => $profileCompletion,
|
|
'completed_fields' => $profileAnalysis['completed_fields'],
|
|
'missing_fields' => $profileAnalysis['missing_fields'],
|
|
'completion_score' => $profileAnalysis['weighted_score']
|
|
],
|
|
'account_security' => [
|
|
'two_factor_enabled' => $user->two_factor_secret !== null,
|
|
'email_verified' => $user->email_verified_at !== null,
|
|
'password_updated' => $user->password_updated_at ? $user->password_updated_at->format('Y-m-d') : null,
|
|
'is_locked' => $this->userRepository->isAccountLocked($user->email)
|
|
],
|
|
'activity_summary' => [
|
|
'resumes_created' => $userStats['resumes_count'] ?? 0,
|
|
'templates_used' => $userStats['templates_used'] ?? 0,
|
|
'exports_generated' => $userStats['exports_count'] ?? 0,
|
|
'profile_views' => $userStats['profile_views'] ?? 0
|
|
]
|
|
];
|
|
|
|
Log::info('User activity data requested', [
|
|
'user_id' => $user->id,
|
|
'ip_address' => request()->ip(),
|
|
'timestamp' => now()
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $activityData,
|
|
'meta' => [
|
|
'generated_at' => now()->toISOString(),
|
|
'version' => '1.0'
|
|
]
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
Log::error('Failed to retrieve user activity data', [
|
|
'error_message' => $e->getMessage(),
|
|
'user_id' => Auth::id(),
|
|
'ip_address' => request()->ip(),
|
|
'timestamp' => now()
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Unable to retrieve activity data',
|
|
'error' => app()->environment('local') ? $e->getMessage() : 'Internal server error'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show forgot password form
|
|
*/
|
|
public function showForgotPasswordForm(): View
|
|
{
|
|
return view('auth.forgot-password', [
|
|
'title' => 'Reset Password - Professional Resume Builder',
|
|
'description' => 'Enter your email to receive password reset instructions'
|
|
]);
|
|
}
|
|
}
|