mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-05-04 21:23:36 +00:00
Compare commits
3 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33ad34a719 | ||
|
|
dca1d962af | ||
|
|
0e97b4a6b8 |
@@ -158,10 +158,7 @@ final class TagController extends Controller
|
||||
'currency_id' => (string) $foreignCurrencyId,
|
||||
'currency_code' => $journal['foreign_currency_code'],
|
||||
];
|
||||
$response[$foreignKey]['difference'] = bcadd(
|
||||
(string) $response[$foreignKey]['difference'],
|
||||
Steam::positive($journal['foreign_amount'])
|
||||
);
|
||||
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
|
||||
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,10 +155,7 @@ final class TagController extends Controller
|
||||
'currency_id' => (string) $foreignCurrencyId,
|
||||
'currency_code' => $journal['foreign_currency_code'],
|
||||
];
|
||||
$response[$foreignKey]['difference'] = bcadd(
|
||||
(string) $response[$foreignKey]['difference'],
|
||||
Steam::positive($journal['foreign_amount'])
|
||||
);
|
||||
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
|
||||
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // intentional float
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,10 +255,7 @@ final class IndexController extends Controller
|
||||
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
|
||||
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
|
||||
if ($count > 0) {
|
||||
$avg = bcdiv(
|
||||
bcadd((string) $bill['amount_min'], (string) $bill['amount_max']),
|
||||
'2'
|
||||
);
|
||||
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2');
|
||||
$avg = bcmul($avg, (string) $count);
|
||||
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
|
||||
Log::debug(
|
||||
|
||||
@@ -198,13 +198,7 @@ final class BudgetLimitController extends Controller
|
||||
if ($request->expectsJson()) {
|
||||
$array = $limit->toArray();
|
||||
// add some extra metadata:
|
||||
$spentArr = $this->opsRepository->sumExpenses(
|
||||
$limit->start_date,
|
||||
$limit->end_date,
|
||||
null,
|
||||
new Collection()->push($budget),
|
||||
$currency
|
||||
);
|
||||
$spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
|
||||
$array['spent'] = $spentArr[$currency->id]['sum'] ?? '0';
|
||||
$array['left_formatted'] = Amount::formatAnything($limit->transactionCurrency, bcadd($array['spent'], (string) $array['amount']));
|
||||
$array['amount_formatted'] = Amount::formatAnything($limit->transactionCurrency, $limit['amount']);
|
||||
|
||||
@@ -245,7 +245,13 @@ final class IndexController extends Controller
|
||||
$inPast = $limitPeriod->startsBefore(now()) && $limitPeriod->endsBefore(now());
|
||||
$currency = $limit->transactionCurrency ?? $primaryCurrency;
|
||||
$amount = Steam::bcround($limit->amount, $currency->decimal_places);
|
||||
$spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
|
||||
$spent = $this->opsRepository->sumExpenses(
|
||||
$limit->start_date,
|
||||
$limit->end_date,
|
||||
null,
|
||||
new Collection()->push($budget),
|
||||
$currency
|
||||
);
|
||||
$spentAmount = $spent[$currency->id]['sum'] ?? '0';
|
||||
$array['budgeted'][] = [
|
||||
'id' => $limit->id,
|
||||
@@ -283,10 +289,7 @@ final class IndexController extends Controller
|
||||
|
||||
if (array_key_exists($currency->id, $spentArr) && array_key_exists('sum', $spentArr[$currency->id])) {
|
||||
$array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum'];
|
||||
$array['spent'][$currency->id]['spent_outside'] = bcmul(
|
||||
bcsub($spentInLimits[$currency->id], $spentArr[$currency->id]['sum']),
|
||||
'-1'
|
||||
);
|
||||
$array['spent'][$currency->id]['spent_outside'] = bcmul(bcsub($spentInLimits[$currency->id], $spentArr[$currency->id]['sum']), '-1');
|
||||
$array['spent'][$currency->id]['currency_id'] = $currency->id;
|
||||
$array['spent'][$currency->id]['currency_symbol'] = $currency->symbol;
|
||||
$array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places;
|
||||
|
||||
@@ -539,13 +539,7 @@ final class BudgetController extends Controller
|
||||
}
|
||||
|
||||
// get spent amount in this period for this currency.
|
||||
$sum = $this->opsRepository->sumExpenses(
|
||||
$currentStart,
|
||||
$currentEnd,
|
||||
$accounts,
|
||||
new Collection()->push($budget),
|
||||
$currency
|
||||
);
|
||||
$sum = $this->opsRepository->sumExpenses($currentStart, $currentEnd, $accounts, new Collection()->push($budget), $currency);
|
||||
$amount = Steam::positive($sum[$currency->id]['sum'] ?? '0');
|
||||
$chartData[0]['entries'][$title] = Steam::bcround($amount, $currency->decimal_places);
|
||||
|
||||
|
||||
@@ -122,13 +122,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
// if has one, calculate expenses and use that as a base.
|
||||
$repository = app(OperationsRepositoryInterface::class);
|
||||
$repository->setUser($autoBudget->budget->user);
|
||||
$spent = $repository->sumExpenses(
|
||||
$previousStart,
|
||||
$previousEnd,
|
||||
null,
|
||||
new Collection()->push($autoBudget->budget),
|
||||
$autoBudget->transactionCurrency
|
||||
);
|
||||
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection()->push($autoBudget->budget), $autoBudget->transactionCurrency);
|
||||
$currencyId = $autoBudget->transaction_currency_id;
|
||||
$spentAmount = $spent[$currencyId]['sum'] ?? '0';
|
||||
Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
|
||||
@@ -218,13 +212,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
// if has one, calculate expenses and use that as a base.
|
||||
$repository = app(OperationsRepositoryInterface::class);
|
||||
$repository->setUser($autoBudget->budget->user);
|
||||
$spent = $repository->sumExpenses(
|
||||
$previousStart,
|
||||
$previousEnd,
|
||||
null,
|
||||
new Collection()->push($autoBudget->budget),
|
||||
$autoBudget->transactionCurrency
|
||||
);
|
||||
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection()->push($autoBudget->budget), $autoBudget->transactionCurrency);
|
||||
$currencyId = $autoBudget->transaction_currency_id;
|
||||
$spentAmount = $spent[$currencyId]['sum'] ?? '0';
|
||||
Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
|
||||
|
||||
@@ -222,14 +222,7 @@ trait AugumentData
|
||||
$currentEnd->addMonth();
|
||||
}
|
||||
// primary currency amount.
|
||||
$expenses = $opsRepository->sumExpenses(
|
||||
$currentStart,
|
||||
$currentEnd,
|
||||
null,
|
||||
$budgetCollection,
|
||||
$entry->transactionCurrency,
|
||||
$this->convertToPrimary
|
||||
);
|
||||
$expenses = $opsRepository->sumExpenses($currentStart, $currentEnd, null, $budgetCollection, $entry->transactionCurrency, $this->convertToPrimary);
|
||||
$spent = $expenses[$currency->id]['sum'] ?? '0';
|
||||
$entry->pc_spent = $spent;
|
||||
|
||||
|
||||
@@ -354,10 +354,7 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
|
||||
/** @var RecurrenceRepetition $repetition */
|
||||
foreach ($set as $repetition) {
|
||||
$recurrence = $this->collection
|
||||
->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)
|
||||
->first()
|
||||
;
|
||||
$recurrence = $this->collection->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)->first();
|
||||
$fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date);
|
||||
$recurrenceId = (int) $repetition->recurrence_id;
|
||||
$repId = (int) $repetition->id;
|
||||
|
||||
13
composer.lock
generated
13
composer.lock
generated
@@ -11511,21 +11511,22 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "14.1.4",
|
||||
"version": "14.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "13dd8d33c6a23da100acec635cd351bfa7ff1a98"
|
||||
"reference": "4991e47adce8e31e554aee8fdaabfc3b1d60707d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/13dd8d33c6a23da100acec635cd351bfa7ff1a98",
|
||||
"reference": "13dd8d33c6a23da100acec635cd351bfa7ff1a98",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4991e47adce8e31e554aee8fdaabfc3b1d60707d",
|
||||
"reference": "4991e47adce8e31e554aee8fdaabfc3b1d60707d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"nikic/php-parser": "^5.7.0",
|
||||
"php": ">=8.4",
|
||||
@@ -11576,7 +11577,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.1.4"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11596,7 +11597,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-23T17:00:20+00:00"
|
||||
"time": "2026-04-24T13:10:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
|
||||
@@ -78,8 +78,8 @@ return [
|
||||
'running_balance_column' => (bool)env_default_when_empty(env('USE_RUNNING_BALANCE'), true), // this is only the default value, is not used.
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2026-04-24',
|
||||
'build_time' => 1777003029,
|
||||
'version' => 'develop/2026-04-25',
|
||||
'build_time' => 1777088715,
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 28, // field is no longer used.
|
||||
|
||||
|
||||
66
package-lock.json
generated
66
package-lock.json
generated
@@ -3350,9 +3350,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/ajv": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3738,9 +3738,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.4",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
||||
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
|
||||
"version": "1.20.5",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
|
||||
"integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3752,7 +3752,7 @@
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"on-finished": "~2.4.1",
|
||||
"qs": "~6.14.0",
|
||||
"qs": "~6.15.1",
|
||||
"raw-body": "~2.5.3",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "~1.0.0"
|
||||
@@ -3779,22 +3779,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser/node_modules/qs": {
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
|
||||
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/bonjour-service": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz",
|
||||
@@ -6646,9 +6630,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "26.0.7",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.7.tgz",
|
||||
"integrity": "sha512-f7tL/iw0VQsx4nC5oNxBM2RjM8alNys5KzyiQTU6A9TI5TI89py4/Ez1cKFvHiLWsvzOXvuGUES+Kk/A2WiANQ==",
|
||||
"version": "26.0.8",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.8.tgz",
|
||||
"integrity": "sha512-BRzLom0mhDhV9v0QhgUUHWQJuwFmnr1194xEcNLYD6ym8y8s542n4jXUvRLnhNTbh9PmpU6kGZamyuGHQMsGjw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -7709,9 +7693,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
|
||||
"integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz",
|
||||
"integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -10812,9 +10796,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/ajv": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11686,9 +11670,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-middleware/node_modules/ajv": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11803,9 +11787,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/ajv": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11923,9 +11907,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/ajv": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
2
public/v1/js/.gitignore
vendored
2
public/v1/js/.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
*.LICENSE.txt
|
||||
*.js
|
||||
#webhooks
|
||||
!ff/*/*.js
|
||||
|
||||
1
public/v1/js/ff/auth/.htaccess
Normal file
1
public/v1/js/ff/auth/.htaccess
Normal file
@@ -0,0 +1 @@
|
||||
Options -Indexes
|
||||
98
public/v1/js/ff/auth/register.js
Normal file
98
public/v1/js/ff/auth/register.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* register.js
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
const form = document.querySelector('form[action="'+route+'"]');
|
||||
const errorBox = document.getElementById('client-errors');
|
||||
const errorList = document.getElementById('client-errors-list');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalBtnText = submitBtn.textContent;
|
||||
|
||||
function showErrors(errors) {
|
||||
errorList.innerHTML = errors.map(function(e) { return '<li>' + e + '</li>'; }).join('');
|
||||
errorBox.style.display = 'block';
|
||||
errorBox.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
async function sha1Hex(str) {
|
||||
const buf = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(str));
|
||||
return Array.from(new Uint8Array(buf))
|
||||
.map(function(b) { return b.toString(16).padStart(2, '0'); })
|
||||
.join('')
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
async function isPwned(password) {
|
||||
const hash = await sha1Hex(password);
|
||||
const prefix = hash.slice(0, 5);
|
||||
const suffix = hash.slice(5);
|
||||
const res = await fetch('https://api.pwnedpasswords.com/range/' + prefix, {
|
||||
headers: { 'Add-Padding': 'true' }
|
||||
});
|
||||
if (!res.ok) { return false; }
|
||||
const text = await res.text();
|
||||
return text.toUpperCase().split('\n').some(function(line) {
|
||||
return line.split(':')[0] === suffix;
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
errorBox.style.display = 'none';
|
||||
|
||||
const password = form.querySelector('[name="password"]').value;
|
||||
const confirm = form.querySelector('[name="password_confirmation"]').value;
|
||||
const verify = form.querySelector('[name="verify_password"]');
|
||||
const errors = [];
|
||||
|
||||
if (password.length < 16) {
|
||||
errors.push(passwordLengthError);
|
||||
}
|
||||
if (password !== confirm) {
|
||||
errors.push(passwordMatchError);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
showErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
if (verify && verify.checked) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = waitForVerify;
|
||||
try {
|
||||
if (await isPwned(password)) {
|
||||
errors.push(needSecurePassword);
|
||||
}
|
||||
} catch (_) {
|
||||
// network failure — let server validate
|
||||
}
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalBtnText;
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
showErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
});
|
||||
})();
|
||||
@@ -36,6 +36,7 @@
|
||||
"/public/v1/js/ff/accounts/show.js": "/public/v1/js/ff/accounts/show.js",
|
||||
"/public/v1/js/ff/admin/update/index.js": "/public/v1/js/ff/admin/update/index.js",
|
||||
"/public/v1/js/ff/admin/users.js": "/public/v1/js/ff/admin/users.js",
|
||||
"/public/v1/js/ff/auth/register.js": "/public/v1/js/ff/auth/register.js",
|
||||
"/public/v1/js/ff/bills/create.js": "/public/v1/js/ff/bills/create.js",
|
||||
"/public/v1/js/ff/bills/edit.js": "/public/v1/js/ff/bills/edit.js",
|
||||
"/public/v1/js/ff/bills/index.js": "/public/v1/js/ff/bills/index.js",
|
||||
|
||||
@@ -81,83 +81,11 @@
|
||||
@endsection
|
||||
@section('scripts')
|
||||
<script nonce="{{ $JS_NONCE }}">
|
||||
(function () {
|
||||
const form = document.querySelector('form[action="{{ route('register') }}"]');
|
||||
const errorBox = document.getElementById('client-errors');
|
||||
const errorList = document.getElementById('client-errors-list');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalBtnText = submitBtn.textContent;
|
||||
|
||||
function showErrors(errors) {
|
||||
errorList.innerHTML = errors.map(function(e) { return '<li>' + e + '</li>'; }).join('');
|
||||
errorBox.style.display = 'block';
|
||||
errorBox.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
async function sha1Hex(str) {
|
||||
const buf = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(str));
|
||||
return Array.from(new Uint8Array(buf))
|
||||
.map(function(b) { return b.toString(16).padStart(2, '0'); })
|
||||
.join('')
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
async function isPwned(password) {
|
||||
const hash = await sha1Hex(password);
|
||||
const prefix = hash.slice(0, 5);
|
||||
const suffix = hash.slice(5);
|
||||
const res = await fetch('https://api.pwnedpasswords.com/range/' + prefix, {
|
||||
headers: { 'Add-Padding': 'true' }
|
||||
});
|
||||
if (!res.ok) { return false; }
|
||||
const text = await res.text();
|
||||
return text.toUpperCase().split('\n').some(function(line) {
|
||||
return line.split(':')[0] === suffix;
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
errorBox.style.display = 'none';
|
||||
|
||||
const password = form.querySelector('[name="password"]').value;
|
||||
const confirm = form.querySelector('[name="password_confirmation"]').value;
|
||||
const verify = form.querySelector('[name="verify_password"]');
|
||||
const errors = [];
|
||||
|
||||
if (password.length < 16) {
|
||||
errors.push('{{ blade_escape_js((string)trans('validation.min.string', ['attribute' => 'password', 'min' => 16])) }}');
|
||||
}
|
||||
if (password !== confirm) {
|
||||
errors.push('{{ blade_escape_js(trans('validation.confirmed', ['attribute' => 'password'])) }}');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
showErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
if (verify && verify.checked) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '{{ blade_escape_js(trans('validation.verifying_password')) }}';
|
||||
try {
|
||||
if (await isPwned(password)) {
|
||||
errors.push('{{ blade_escape_js(trans('validation.secure_password')) }}');
|
||||
}
|
||||
} catch (_) {
|
||||
// network failure — let server validate
|
||||
}
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalBtnText;
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
showErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
var route = '{{ route('register') }}';
|
||||
var passwordLengthError = '{{ blade_escape_js((string)trans('validation.min.string', ['attribute' => 'password', 'min' => 16])) }}';
|
||||
var passwordMatchError = '{{ blade_escape_js(trans('validation.confirmed', ['attribute' => 'password'])) }}';
|
||||
var waitForVerify = '{{ blade_escape_js(trans('validation.verifying_password')) }}';
|
||||
var needSecurePassword = '{{ blade_escape_js(trans('validation.secure_password')) }}';
|
||||
</script>
|
||||
<script nonce="{{$JS_NONCE}}" src="v1/js/ff/auth/register.js"></script>
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user