From 1daacb80b12f58cb44af511106a9e203013fdc88 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 17 Dec 2025 19:27:59 +0100 Subject: [PATCH] Fix #11383 --- app/Http/Controllers/Auth/LoginController.php | 56 ++++-- app/Support/Steam.php | 176 +++++++++--------- 2 files changed, 125 insertions(+), 107 deletions(-) diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 2c330a516b..a4276e461b 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -31,6 +31,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Providers\RouteServiceProvider; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Support\Facades\Steam; use FireflyIII\User; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; @@ -68,7 +69,7 @@ class LoginController extends Controller protected string $redirectTo = RouteServiceProvider::HOME; private UserRepositoryInterface $repository; - private string $username = 'email'; + private string $username = 'email'; /** * Create a new controller instance. @@ -85,7 +86,7 @@ class LoginController extends Controller * * @throws ValidationException */ - public function login(Request $request): JsonResponse|RedirectResponse + public function login(Request $request): JsonResponse | RedirectResponse { $username = $request->get($this->username()); Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $username)); @@ -103,8 +104,7 @@ class LoginController extends Controller $this->username => trans('auth.failed'), ] ) - ->onlyInput($this->username) - ; + ->onlyInput($this->username); } Log::debug('Login data is present.'); @@ -128,11 +128,10 @@ class LoginController extends Controller // send a custom login event because laravel will also fire a login event if a "remember me"-cookie // restores the event. event(new ActuallyLoggedIn($this->guard()->user())); - return $this->sendLoginResponse($request); } Log::warning('Login attempt failed.'); - $username = (string) $request->get($this->username()); + $username = (string)$request->get($this->username()); $user = $this->repository->findByEmail($username); if (!$user instanceof User) { // send event to owner. @@ -185,10 +184,10 @@ class LoginController extends Controller /** * Log the user out of the application. */ - public function logout(Request $request): Redirector|RedirectResponse|Response + public function logout(Request $request): Redirector | RedirectResponse | Response { - $authGuard = config('firefly.authentication_guard'); - $logoutUrl = config('firefly.custom_logout_url'); + $authGuard = config('firefly.authentication_guard'); + $logoutUrl = config('firefly.custom_logout_url'); if ('remote_user_guard' === $authGuard && '' !== $logoutUrl) { return redirect($logoutUrl); } @@ -222,13 +221,13 @@ class LoginController extends Controller * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ - public function showLoginForm(Request $request): Factory|Redirector|RedirectResponse|View + public function showLoginForm(Request $request): Factory | Redirector | RedirectResponse | View { Log::channel('audit')->info('Show login form (1.1).'); - $count = DB::table('users')->count(); - $guard = config('auth.defaults.guard'); - $title = (string) trans('firefly.login_page_title'); + $count = DB::table('users')->count(); + $guard = config('auth.defaults.guard'); + $title = (string)trans('firefly.login_page_title'); if (0 === $count && 'web' === $guard) { return redirect(route('register')); @@ -248,16 +247,37 @@ class LoginController extends Controller $allowReset = false; } - $email = $request->old('email'); - $remember = $request->old('remember'); + $email = $request->old('email'); + $remember = $request->old('remember'); - $storeInCookie = config('google2fa.store_in_cookie', false); + $storeInCookie = config('google2fa.store_in_cookie', false); if (false !== $storeInCookie) { $cookieName = config('google2fa.cookie_name', 'google2fa_token'); - Cookie::queue(Cookie::make($cookieName, 'invalid-'.Carbon::now()->getTimestamp())); + Cookie::queue(Cookie::make($cookieName, 'invalid-' . Carbon::now()->getTimestamp())); } - $usernameField = $this->username(); + $usernameField = $this->username(); return view('auth.login', ['allowRegistration' => $allowRegistration, 'email' => $email, 'remember' => $remember, 'allowReset' => $allowReset, 'title' => $title, 'usernameField' => $usernameField]); } + + /** + * Send the response after the user was authenticated. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse + */ + protected function sendLoginResponse(Request $request) + { + $request->session()->regenerate(); + $this->clearLoginAttempts($request); + + if ($response = $this->authenticated($request, $this->guard()->user())) { + return $response; + } + $path = Steam::getSafeUrl(session()->pull('url.intended', route('index')), route('index')); + Log::debug(sprintf('SafeURL is %s', $path)); + return $request->wantsJson() ? new JsonResponse([], 204) : redirect()->to($path); + } + } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 939a9069b4..f89c94d349 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -23,9 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Support; -use FireflyIII\Support\Facades\Preferences; -use Deprecated; use Carbon\Carbon; +use Deprecated; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; @@ -33,6 +32,7 @@ use FireflyIII\Models\AccountMeta; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Support\Facades\Amount; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Http\Api\ExchangeRateConverter; use FireflyIII\Support\Singleton\PreferencesSingleton; use Illuminate\Support\Collection; @@ -43,7 +43,6 @@ use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use Safe\Exceptions\UrlException; use ValueError; - use function Safe\parse_url; use function Safe\preg_replace; @@ -55,38 +54,37 @@ class Steam public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array { Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s" (inclusive: %s)', $accounts->count(), $date->toIso8601String(), var_export($inclusive, true))); - $result = []; + $result = []; $convertToPrimary ??= Amount::convertToPrimary(); $primary ??= Amount::getPrimaryCurrency(); - $currencies = $this->getCurrencies($accounts); + $currencies = $this->getCurrencies($accounts); // balance(s) in all currencies for ALL accounts. $arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray()) - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') - ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s')) - ->groupBy(['transactions.account_id', 'transaction_currencies.code']) - ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray() - ; + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') + ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s')) + ->groupBy(['transactions.account_id', 'transaction_currencies.code']) + ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray(); Log::debug('Array of sums: ', $arrayOfSums); /** @var Account $account */ foreach ($accounts as $account) { - $return = [ + $return = [ 'pc_balance' => '0', 'balance' => '0', // this key is overwritten right away, but I must remember it is always created. ]; - $currency = $currencies[$account->id]; + $currency = $currencies[$account->id]; // second array - $accountSums = array_filter($arrayOfSums, fn (array $entry): bool => $entry['account_id'] === $account->id); + $accountSums = array_filter($arrayOfSums, fn(array $entry): bool => $entry['account_id'] === $account->id); if (0 === count($accountSums)) { $result[$account->id] = $return; continue; } - $sumsByCode = []; + $sumsByCode = []; foreach ($accountSums as $accountSum) { // $accountSum = array_values($accountSum)[0]; $sumOfAmount = (string)$accountSum['sum_of_amount']; @@ -95,7 +93,7 @@ class Steam } // Log::debug('All balances are (joined)', $others); // if there is no request to convert, take this as "balance" and "pc_balance". - $return['balance'] = $sumsByCode[$currency->code] ?? '0'; + $return['balance'] = $sumsByCode[$currency->code] ?? '0'; if (!$convertToPrimary) { unset($return['pc_balance']); // Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance'])); @@ -108,7 +106,7 @@ class Steam } // either way, the balance is always combined with the virtual balance: - $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance); + $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance); if ($convertToPrimary) { // the primary currency balance is combined with a converted virtual_balance: @@ -131,7 +129,8 @@ class Steam } /** - * Calls accountsBalancesOptimized for the given accounts and makes sure that inclusive is set to false, so it properly gets the balance of a range. + * Calls accountsBalancesOptimized for the given accounts and makes sure that inclusive is set to false, so it + * properly gets the balance of a range. */ public function accountsBalancesInRange(Collection $accounts, Carbon $start, Carbon $end, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array { @@ -160,10 +159,10 @@ class Steam // Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision)); if (str_contains($number, '.')) { if ('-' !== $number[0]) { - return bcadd($number, '0.'.str_repeat('0', $precision).'5', $precision); + return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision); } - return bcsub($number, '0.'.str_repeat('0', $precision).'5', $precision); + return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision); } return $number; @@ -305,11 +304,12 @@ class Steam "balance": balance in the account's currency OR user's primary currency if the account has no currency --> "pc_balance": balance in the user's primary currency, with all amounts converted to the primary currency. "EUR": balance in EUR (or whatever currencies the account has balance in) - TXT)] + TXT + )] public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array { - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty($date); if ($cache->has()) { @@ -324,27 +324,26 @@ class Steam $primary = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup); } // account balance thing. - $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency']; + $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency']; if ($currencyPresent) { $accountCurrency = $account->meta['currency']; } if (!$currencyPresent) { $accountCurrency = $this->getAccountCurrency($account); } - $hasCurrency = null !== $accountCurrency; - $currency = $hasCurrency ? $accountCurrency : $primary; - $return = [ + $hasCurrency = null !== $accountCurrency; + $currency = $hasCurrency ? $accountCurrency : $primary; + $return = [ 'pc_balance' => '0', 'balance' => '0', // this key is overwritten right away, but I must remember it is always created. ]; // balance(s) in all currencies. - $array = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') - ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s')) - ->get(['transaction_currencies.code', 'transactions.amount'])->toArray() - ; - $others = $this->groupAndSumTransactions($array, 'code', 'amount'); + $array = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') + ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s')) + ->get(['transaction_currencies.code', 'transactions.amount'])->toArray(); + $others = $this->groupAndSumTransactions($array, 'code', 'amount'); Log::debug('All balances are (joined)', $others); // if there is no request to convert, take this as "balance" and "pc_balance". $return['balance'] = $others[$currency->code] ?? '0'; @@ -359,7 +358,7 @@ class Steam } // either way, the balance is always combined with the virtual balance: - $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance); + $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance); if ($convertToPrimary) { // the primary currency balance is combined with a converted virtual_balance: @@ -373,7 +372,7 @@ class Steam $return['balance'] = bcadd($return['balance'], $virtualBalance); // Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance'])); } - $final = array_merge($return, $others); + $final = array_merge($return, $others); Log::debug('Final balance is', $final); $cache->store($final); @@ -391,7 +390,7 @@ class Steam Log::debug(sprintf('called finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); // set up cache - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('final-balance-in-range'); $cache->addProperty($start); @@ -402,15 +401,15 @@ class Steam // return $cache->get(); } - $balances = []; - $formatted = $start->format('Y-m-d'); + $balances = []; + $formatted = $start->format('Y-m-d'); Log::debug('Get first balance to start.'); // 2025-10-08 replaced finalAccountBalance with accountsBalancesOptimized: - $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup); - $startBalance = $this->accountsBalancesOptimized(new Collection()->push($account), $start, $primaryCurrency, $convertToPrimary, false)[$account->id]; - $accountCurrency = $this->getAccountCurrency($account); - $hasCurrency = $accountCurrency instanceof TransactionCurrency; - $currency = $accountCurrency ?? $primaryCurrency; + $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup); + $startBalance = $this->accountsBalancesOptimized(new Collection()->push($account), $start, $primaryCurrency, $convertToPrimary, false)[$account->id]; + $accountCurrency = $this->getAccountCurrency($account); + $hasCurrency = $accountCurrency instanceof TransactionCurrency; + $currency = $accountCurrency ?? $primaryCurrency; Log::debug(sprintf('Currency is %s', $currency->code)); // set start balances: @@ -422,7 +421,7 @@ class Steam Log::debug(sprintf('Also set start balance in %s', $primaryCurrency->code)); $startBalance[$primaryCurrency->code] ??= '0'; } - $currencies = [ + $currencies = [ $currency->id => $currency, $primaryCurrency->id => $primaryCurrency, ]; @@ -432,48 +431,47 @@ class Steam // sums up the balance changes per day. Log::debug(sprintf('Date >= %s and <= %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - $set = $account->transactions() - ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s')) - ->groupBy('transaction_journals.date') - ->groupBy('transactions.transaction_currency_id') - ->orderBy('transaction_journals.date', 'ASC') - ->whereNull('transaction_journals.deleted_at') - ->get( - [ // @phpstan-ignore-line - 'transaction_journals.date', - 'transactions.transaction_currency_id', - DB::raw('SUM(transactions.amount) AS sum_of_day'), - ] - ) - ; + $set = $account->transactions() + ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s')) + ->groupBy('transaction_journals.date') + ->groupBy('transactions.transaction_currency_id') + ->orderBy('transaction_journals.date', 'ASC') + ->whereNull('transaction_journals.deleted_at') + ->get( + [ // @phpstan-ignore-line + 'transaction_journals.date', + 'transactions.transaction_currency_id', + DB::raw('SUM(transactions.amount) AS sum_of_day'), + ] + ); - $currentBalance = $startBalance; - $converter = new ExchangeRateConverter(); + $currentBalance = $startBalance; + $converter = new ExchangeRateConverter(); /** @var Transaction $entry */ foreach ($set as $entry) { // get date object - $carbon = new Carbon($entry->date, $entry->date_tz); - $carbonKey = $carbon->format('Y-m-d'); + $carbon = new Carbon($entry->date, $entry->date_tz); + $carbonKey = $carbon->format('Y-m-d'); // make sure sum is a string: - $sumOfDay = (string)($entry->sum_of_day ?? '0'); + $sumOfDay = (string)($entry->sum_of_day ?? '0'); // #10426 make sure sum is not in scientific notation. - $sumOfDay = $this->floatalize($sumOfDay); + $sumOfDay = $this->floatalize($sumOfDay); // find currency of this entry, does not have to exist. $currencies[$entry->transaction_currency_id] ??= Amount::getTransactionCurrencyById($entry->transaction_currency_id); // make sure this $entry has its own $entryCurrency /** @var TransactionCurrency $entryCurrency */ - $entryCurrency = $currencies[$entry->transaction_currency_id]; + $entryCurrency = $currencies[$entry->transaction_currency_id]; Log::debug(sprintf('Processing transaction(s) on moment %s', $carbon->format('Y-m-d H:i:s'))); // add amount to current balance in currency code. - $currentBalance[$entryCurrency->code] ??= '0'; + $currentBalance[$entryCurrency->code] ??= '0'; $currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string)$currentBalance[$entryCurrency->code]); // if not requested to convert to primary currency, add the amount to "balance", do nothing else. @@ -491,7 +489,7 @@ class Steam } } // add to final array. - $balances[$carbonKey] = $currentBalance; + $balances[$carbonKey] = $currentBalance; Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance); } $cache->store($balances); @@ -508,7 +506,7 @@ class Steam */ public function floatalize(string $value): string { - $value = strtoupper($value); + $value = strtoupper($value); if (!str_contains($value, 'E')) { return $value; } @@ -532,8 +530,8 @@ class Steam public function getAccountCurrency(Account $account): ?TransactionCurrency { - $type = $account->accountType->type; - $list = config('firefly.valid_currency_account_types'); + $type = $account->accountType->type; + $list = config('firefly.valid_currency_account_types'); // return null if not in this list. if (!in_array($type, $list, true)) { @@ -586,15 +584,15 @@ class Steam { $list = []; - $set = auth()->user()->transactions() - ->whereIn('transactions.account_id', $accounts) - ->groupBy(['transactions.account_id', 'transaction_journals.user_id']) - ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line + $set = auth()->user()->transactions() + ->whereIn('transactions.account_id', $accounts) + ->groupBy(['transactions.account_id', 'transaction_journals.user_id']) + ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line ; /** @var Transaction $entry */ foreach ($set as $entry) { - $date = new Carbon($entry->max_date, config('app.timezone')); + $date = new Carbon($entry->max_date, config('app.timezone')); $date->setTimezone(config('app.timezone')); $list[(int)$entry->account_id] = $date; } @@ -612,14 +610,14 @@ class Steam if (null !== $cached) { return $cached; } - $locale = Preferences::get('locale', config('firefly.default_locale', 'equal'))->data; + $locale = Preferences::get('locale', config('firefly.default_locale', 'equal'))->data; if (is_array($locale)) { $locale = 'equal'; } if ('equal' === $locale) { $locale = $this->getLanguage(); } - $locale = (string)$locale; + $locale = (string)$locale; // Check for Windows to replace the locale correctly. if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) { @@ -663,8 +661,8 @@ class Steam */ public function getSafePreviousUrl(): string { - // Log::debug(sprintf('getSafePreviousUrl: "%s"', session()->previousUrl())); - return session()->previousUrl() ?? route('index'); + $res = $this->getSafeUrl(session()->previousUrl() ?? route('index'), route('index')); + Log::debug(sprintf('getSafePreviousUrl: "%s"', $res)); } /** @@ -673,8 +671,8 @@ class Steam public function getSafeUrl(string $unknownUrl, string $safeUrl): string { // Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl)); - $returnUrl = $safeUrl; - + $returnUrl = $safeUrl; +// die('in get safe url'); try { $unknownHost = parse_url($unknownUrl, PHP_URL_HOST); } catch (UrlException $e) { @@ -791,12 +789,12 @@ class Steam if (null === $preference) { $singleton->setPreference($key, $currency); } - $current = $amount; + $current = $amount; if ($currency->id !== $primary->id) { $current = $converter->convert($currency, $primary, $date, $amount); Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current)); } - $total = bcadd((string)$current, $total); + $total = bcadd((string)$current, $total); } return $total; @@ -810,8 +808,8 @@ class Steam $primary = Amount::getPrimaryCurrency(); $currencies[$primary->id] = $primary; - $ids = $accounts->pluck('id')->toArray(); - $result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get(); + $ids = $accounts->pluck('id')->toArray(); + $result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get(); /** @var AccountMeta $item */ foreach ($result as $item) { @@ -821,7 +819,7 @@ class Steam } } // collect those currencies, skip primary because we already have it. - $set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get(); + $set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get(); foreach ($set as $item) { $currencies[$item->id] = $item; } @@ -832,7 +830,7 @@ class Steam $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency']; if ($currencyPresent) { $currencyId = $account->meta['currency']->id; - $currencies[$currencyId] ??= $account->meta['currency']; + $currencies[$currencyId] ??= $account->meta['currency']; $accountCurrencies[$accountId] = $account->meta['currency']; } if (!$currencyPresent && !array_key_exists($accountId, $accountPreferences)) {