validate([ 'email' => 'required|email' ], [ 'email.required' => 'La mail è obbligatoria', 'email.email' => 'Inserisci un indirizzo email valido' ]); $this->logSecurityEvent('reset_requested', $request->email); try { $user = $this->findUserInMasterDatabase($request->email); if (!$user) { $this->logSecurityEvent('reset_requested_invalid_email', $request->email); return back()->with('success', 'Se l\'email esiste nel sistema, riceverai le istruzioni per il reset della password.'); } if ($this->hasRecentResetRequest($request->email)) { $this->logSecurityEvent('reset_requested_too_soon', $request->email); return back()->with('error', 'È già stata inviata una richiesta di reset per questa email. Controlla la tua casella di posta o riprova più tardi.'); } $token = Str::random(64); $this->storeResetToken($request->email, $token); $this->sendPasswordResetEmail($request->email, $token, $user); $this->logSecurityEvent('reset_email_sent', $request->email); return back()->with('success', 'Se l\'email esiste nel sistema, riceverai le istruzioni per il reset della password.'); } catch (\Exception $e) { $this->logSecurityEvent('reset_request_failed', $request->email, ['error' => $e->getMessage()]); return back()->with('error', 'Errore durante l\'invio dell\'email. Riprova più tardi.'); } } /** * Show password reset form */ public function showResetForm($token) { return view('auth.password-reset-form', ['token' => $token]); } /** * Reset password */ public function resetPassword(Request $request) { $request->validate([ 'token' => 'required', 'email' => 'required|email', 'password' => 'required|min:6|confirmed' ], [ 'email.required' => 'La mail è obbligatoria', 'email.email' => 'Inserisci un indirizzo email valido', 'password.required' => 'La password è obbligatoria', 'password.min' => 'La password deve essere di almeno 6 caratteri', 'password.confirmed' => 'Le password non coincidono' ]); try { // Verify reset token $resetRecord = $this->verifyResetToken($request->email, $request->token); if (!$resetRecord) { return back()->with('error', 'Token non valido o scaduto.'); } // Get user from master database $user = $this->findUserInMasterDatabase($request->email); if (!$user) { return back()->with('error', 'Utente non trovato.'); } // Update password in both databases $this->updatePasswordInBothDatabases($request->email, $request->password, $user); // Delete reset token $this->deleteResetToken($request->email); // Send password change notification $this->sendPasswordChangeNotification($request->email, $user->name); Log::info('Password reset completed with notification', [ 'email' => $request->email ]); return redirect('/')->with('success', 'Password aggiornata con successo. Ti abbiamo inviato una email di conferma.'); } catch (\Exception $e) { Log::error('Password reset failed', [ 'email' => $request->email, 'error' => $e->getMessage() ]); return back()->with('error', 'Errore durante il reset della password. Riprova più tardi.'); } } /** * Find user in master database */ private function findUserInMasterDatabase($email) { try { $masterConfig = [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE'), 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ]; config(['database.connections.master_reset' => $masterConfig]); $user = DB::connection('master_reset') ->table('users') ->where('email', $email) ->first(); DB::purge('master_reset'); return $user; } catch (\Exception $e) { Log::error('Failed to find user in master database', [ 'email' => $email, 'error' => $e->getMessage() ]); return null; } } /** * Store reset token in master database */ private function storeResetToken($email, $token) { try { $masterConfig = [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE'), 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ]; config(['database.connections.master_reset_token' => $masterConfig]); DB::connection('master_reset_token') ->table('password_resets') ->where('email', $email) ->delete(); DB::connection('master_reset_token') ->table('password_resets') ->insert([ 'email' => $email, 'token' => Hash::make($token), 'created_at' => Carbon::now() ]); DB::purge('master_reset_token'); } catch (\Exception $e) { Log::error('Failed to store reset token', [ 'email' => $email, 'error' => $e->getMessage() ]); throw $e; } } /** * Verify reset token */ private function verifyResetToken($email, $token) { try { $masterConfig = [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE'), 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ]; config(['database.connections.master_verify' => $masterConfig]); $resetRecord = DB::connection('master_verify') ->table('password_resets') ->where('email', $email) ->first(); DB::purge('master_verify'); if (!$resetRecord) { return null; } $isValidToken = Hash::check($token, $resetRecord->token); $isNotExpired = Carbon::parse($resetRecord->created_at)->addHours(24)->isFuture(); if ($isValidToken && $isNotExpired) { return $resetRecord; } return null; } catch (\Exception $e) { Log::error('Failed to verify reset token', [ 'email' => $email, 'error' => $e->getMessage() ]); return null; } } /** * Update password in both master and tenant databases */ private function updatePasswordInBothDatabases($email, $newPassword, $masterUser) { $hashedPassword = Hash::make($newPassword); try { $masterConfig = [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE'), 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ]; config(['database.connections.master_update' => $masterConfig]); DB::connection('master_update') ->table('users') ->where('email', $email) ->update(['password' => $hashedPassword]); DB::purge('master_update'); Log::info('Password updated in master database', ['email' => $email]); if ($masterUser->tenant_database) { $tenantConfig = [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => $masterUser->tenant_database, 'username' => $masterUser->tenant_username, 'password' => $masterUser->tenant_password, 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ]; config(['database.connections.tenant_update' => $tenantConfig]); DB::connection('tenant_update') ->table('users') ->where('email', $email) ->update(['password' => $hashedPassword]); DB::purge('tenant_update'); Log::info('Password updated in tenant database', [ 'email' => $email, 'tenant_database' => $masterUser->tenant_database ]); } } catch (\Exception $e) { Log::error('Failed to update password in databases', [ 'email' => $email, 'error' => $e->getMessage() ]); throw $e; } } /** * Delete reset token */ private function deleteResetToken($email) { try { $masterConfig = [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE'), 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ]; config(['database.connections.master_delete_token' => $masterConfig]); DB::connection('master_delete_token') ->table('password_resets') ->where('email', $email) ->delete(); DB::purge('master_delete_token'); } catch (\Exception $e) { Log::error('Failed to delete reset token', [ 'email' => $email, 'error' => $e->getMessage() ]); } } private function sendPasswordChangeNotification($email, $name) { try { $emailData = [ 'name' => $name, 'email' => $email, 'change_time' => now()->format('d/m/Y H:i'), 'ip_address' => request()->ip() ]; Mail::send('emails.password-changed', $emailData, function ($message) use ($email, $name) { $message->to($email, $name) ->subject('La tua password è stata modificata') ->from(config('mail.from.address'), config('mail.from.name')); }); Log::info('Password change notification sent', [ 'email' => $email, 'name' => $name ]); return true; } catch (\Exception $e) { Log::error('Failed to send password change notification', [ 'email' => $email, 'error' => $e->getMessage() ]); return false; } } /** * Send password reset email */ private function sendPasswordResetEmail($email, $token, $user) { try { $resetUrl = url('/password-reset/' . $token . '?email=' . urlencode($email)); $companyName = 'Leezard'; $emailData = [ 'name' => $user->name, 'email' => $email, 'reset_url' => $resetUrl, 'company' => $companyName, 'expires_at' => Carbon::now()->addHours(24)->format('d/m/Y H:i') ]; Mail::send('emails.password-reset', $emailData, function ($message) use ($email, $companyName, $user) { $message->to($email, $user->name) ->subject('Reset Password - Leezard') ->from(config('mail.from.address'), config('mail.from.name')); }); Log::info('Password reset email sent', ['email' => $email]); } catch (\Exception $e) { Log::error('Failed to send password reset email', [ 'email' => $email, 'error' => $e->getMessage() ]); throw $e; } } private function logSecurityEvent($event, $email, $additionalData = []) { Log::info('Password Reset Security Event', array_merge([ 'event' => $event, 'email' => $email, 'ip' => request()->ip(), 'user_agent' => request()->userAgent(), 'timestamp' => now() ], $additionalData)); } private function hasRecentResetRequest($email) { try { $masterConfig = [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE'), 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ]; config(['database.connections.master_check_recent' => $masterConfig]); $recentRequest = DB::connection('master_check_recent') ->table('password_resets') ->where('email', $email) ->where('created_at', '>', Carbon::now()->subMinutes(5)) // 5 minutes cooldown ->first(); DB::purge('master_check_recent'); return $recentRequest !== null; } catch (\Exception $e) { Log::error('Failed to check recent reset requests', [ 'email' => $email, 'error' => $e->getMessage() ]); return false; } } }