From e1006e582388a7fab204f25c100347607b863e4e Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Sun, 26 May 2024 23:30:51 +0200 Subject: [PATCH] feat!: allow registration of multiple users (#340) feat: administration area feat: add reset password functionality --- README.md | 2 + admin.php | 175 +++- cronjobs | 2 + endpoints/admin/deleteuser.php | 132 +++ endpoints/admin/saveopenregistrations.php | 59 ++ endpoints/admin/savesmtpsettings.php | 64 ++ endpoints/categories/category.php | 17 +- endpoints/categories/sort.php | 5 +- endpoints/cronjobs/sendnotifications.php | 812 +++++++++--------- .../cronjobs/sendresetpasswordemails.php | 82 ++ endpoints/cronjobs/sendverificationemails.php | 85 ++ endpoints/cronjobs/updateexchange.php | 125 +-- endpoints/currency/currency.php | 17 +- endpoints/currency/fixer_api_key.php | 12 +- endpoints/currency/update_exchange.php | 31 +- endpoints/db/backup.php | 2 +- endpoints/db/import.php | 1 - endpoints/db/migrate.php | 9 + endpoints/db/restore.php | 2 +- endpoints/household/household.php | 14 +- .../savediscordnotifications.php | 21 +- .../notifications/saveemailnotifications.php | 18 +- .../notifications/savegotifynotifications.php | 18 +- .../savenotificationsettings.php | 18 +- .../savepushovernotifications.php | 20 +- .../savetelegramnotifications.php | 18 +- .../savewebhooknotifications.php | 18 +- .../testdiscordnotifications.php | 1 - .../notifications/testemailnotifications.php | 1 - .../notifications/testgotifynotifications.php | 1 - .../testpushovernotifications.php | 1 - .../testtelegramnotifications.php | 1 - .../testwebhooknotifications.php | 1 - endpoints/payments/add.php | 5 +- endpoints/payments/delete.php | 5 +- endpoints/payments/get.php | 18 +- endpoints/payments/payment.php | 8 +- endpoints/payments/rename.php | 4 +- endpoints/payments/sort.php | 5 +- endpoints/settings/colortheme.php | 5 +- endpoints/settings/convert_currency.php | 5 +- endpoints/settings/customtheme.php | 5 +- endpoints/settings/hide_disabled.php | 5 +- endpoints/settings/monthly_price.php | 5 +- endpoints/settings/remove_background.php | 5 +- endpoints/settings/resettheme.php | 5 +- endpoints/settings/theme.php | 5 +- endpoints/subscription/add.php | 12 +- endpoints/subscription/delete.php | 5 +- endpoints/subscription/get.php | 5 +- endpoints/subscriptions/get.php | 6 +- endpoints/user/budget.php | 5 - endpoints/user/delete_avatar.php | 5 +- endpoints/user/save_user.php | 84 +- includes/checksession.php | 7 +- includes/connect_endpoint.php | 8 + includes/getdbkeys.php | 21 +- includes/getsettings.php | 13 +- includes/i18n/de.php | 27 + includes/i18n/el.php | 27 + includes/i18n/en.php | 28 + includes/i18n/es.php | 27 + includes/i18n/fr.php | 27 + includes/i18n/it.php | 30 + includes/i18n/jp.php | 27 + includes/i18n/ko.php | 27 + includes/i18n/pl.php | 27 + includes/i18n/pt.php | 27 + includes/i18n/pt_br.php | 27 + includes/i18n/ru.php | 27 + includes/i18n/sr.php | 27 + includes/i18n/sr_lat.php | 27 + includes/i18n/tr.php | 28 + includes/i18n/zh_cn.php | 30 + includes/i18n/zh_tw.php | 27 + includes/list_subscriptions.php | 3 +- includes/version.php | 2 +- index.php | 10 +- login.php | 154 +++- logout.php | 9 +- migrations/000020.php | 54 ++ passwordreset.php | 224 +++++ registration.php | 312 ++++++- scripts/admin.js | 261 ++++-- service-worker.js | 1 + settings.php | 103 ++- stats.php | 61 +- styles/login.css | 52 ++ styles/styles.css | 48 +- verifyemail.php | 120 +++ 90 files changed, 3093 insertions(+), 829 deletions(-) create mode 100644 endpoints/admin/deleteuser.php create mode 100644 endpoints/admin/saveopenregistrations.php create mode 100644 endpoints/admin/savesmtpsettings.php create mode 100644 endpoints/cronjobs/sendresetpasswordemails.php create mode 100644 endpoints/cronjobs/sendverificationemails.php create mode 100644 migrations/000020.php create mode 100644 passwordreset.php create mode 100644 verifyemail.php diff --git a/README.md b/README.md index ad5492796..475095872 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ See instructions to run Wallos below. 0 1 * * * php /var/www/html/endpoints/cronjobs/updatenextpayment.php >> /var/log/cron/updatenextpayment.log 2>&1 0 2 * * * php /var/www/html/endpoints/cronjobs/updateexchange.php >> /var/log/cron/updateexchange.log 2>&1 0 9 * * * php /var/www/html/endpoints/cronjobs/sendnotifications.php >> /var/log/cron/sendnotifications.log 2>&1 +*/2 * * * * php /var/www/html/endpoints/cronjobs/sendverificationemails.php >> /var/log/cron/sendverificationemail.log 2>&1 +*/2 * * * * php /var/www/html/endpoints/cronjobs/sendresetpasswordemails.php >> /var/log/cron/sendresetpasswordemails.log 2>&1 ``` 5. If your web root is not `/var/www/html/` adjust the cronjobs above accordingly. diff --git a/admin.php b/admin.php index 0f3eaabc8..993d730a1 100644 --- a/admin.php +++ b/admin.php @@ -1,12 +1,183 @@ prepare('SELECT * FROM admin'); + $result = $stmt->execute(); + $settings = $result->fetchArray(SQLITE3_ASSOC); + + // get user accounts where id is not 1 + $stmt = $db->prepare('SELECT id, username, email FROM user ORDER BY id ASC'); + $result = $stmt->execute(); + + $users = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $users[] = $row; + } + $userCount = is_array($users) ? count($users) : 0; + ?>
+ + + = 0) { + ?> + + + + + + + + + \ No newline at end of file diff --git a/logout.php b/logout.php index 700b08786..3c7b112b3 100644 --- a/logout.php +++ b/logout.php @@ -4,16 +4,17 @@ // get token from cookie to remove from DB if (isset($_SESSION['token'])) { $token = $_SESSION['token']; - $sql = "DELETE FROM login_tokens WHERE token = ?"; + $sql = "DELETE FROM login_tokens WHERE token = :token AND user_id = :userId"; $stmt = $db->prepare($sql); - $stmt->bindValue(1, $token, SQLITE3_TEXT); + $stmt->bindParam(':token', $token, SQLITE3_TEXT); + $stmt->bindParam(':userId', $userId, SQLITE3_INTEGER); $stmt->execute(); } $_SESSION = array(); session_destroy(); $cookieExpire = time() - 3600; - setcookie('wallos_login', '', $cookieExpire); + setcookie('wallos_login', '', $cookieExpire); $db->close(); - header("Location: ."); + header("Location: ."); exit(); ?> \ No newline at end of file diff --git a/migrations/000020.php b/migrations/000020.php new file mode 100644 index 000000000..9bd1ce95b --- /dev/null +++ b/migrations/000020.php @@ -0,0 +1,54 @@ +query("SELECT * FROM pragma_table_info('$table') WHERE name='user_id'"); + $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; + + if ($columnRequired) { + $db->exec("ALTER TABLE $table ADD COLUMN user_id INTEGER DEFAULT 1"); + } +} + + +$db->exec('CREATE TABLE IF NOT EXISTS admin ( + id INTEGER PRIMARY KEY, + registrations_open BOOLEAN DEFAULT 0, + max_users INTEGER DEFAULT 0, + require_email_verification BOOLEAN DEFAULT 0, + server_url TEXT, + smtp_address TEXT, + smtp_port INTEGER DEFAULT 587, + smtp_username TEXT, + smtp_password TEXT, + from_email TEXT, + encryption TEXT DEFAULT "tls" +)'); + +$db->exec('INSERT INTO admin (id, registrations_open, require_email_verification, server_url, max_users, smtp_address, smtp_port, smtp_username, smtp_password, from_email, encryption) VALUES (1, 0, 0, "", 0, "", 587, "", "", "", "tls")'); + +$updateQuery = "UPDATE payment_methods SET icon = 'images/uploads/icons/' || icon WHERE id < 32 AND icon NOT LIKE '%/images/uploads/icons%'"; +$db->exec($updateQuery); + +$db->exec('CREATE TABLE IF NOT EXISTS email_verification ( + id INTEGER PRIMARY KEY, + user_id INTEGER, + email TEXT, + token TEXT, + email_sent BOOLEAN DEFAULT 0)'); + +$db->exec('CREATE TABLE IF NOT EXISTS password_resets ( + id INTEGER PRIMARY KEY, + user_id INTEGER, + email TEXT, + token TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + email_sent BOOLEAN DEFAULT 0)'); + +?> \ No newline at end of file diff --git a/passwordreset.php b/passwordreset.php new file mode 100644 index 000000000..b5a453eb1 --- /dev/null +++ b/passwordreset.php @@ -0,0 +1,224 @@ +close(); + header("Location: ."); + exit(); +} + +$requestMode = true; +$resetMode = false; + +$theme = "light"; +if (isset($_COOKIE['theme'])) { + $theme = $_COOKIE['theme']; +} + +$colorTheme = "blue"; +if (isset($_COOKIE['colorTheme'])) { + $colorTheme = $_COOKIE['colorTheme']; +} + +$settings = $db->querySingle("SELECT * FROM admin", true); +if ($settings['smtp_address'] == "" || $settings['server_url'] == "") { + header("Location: ."); +} else { + $resetPasswordEnabled = true; +} + +$hasSuccessMessage = false; +$hasErrorMessage = false; +$passwordsMismatch = false; +$hideForm = false; + +if (isset($_POST['email']) && $_POST['email'] != "" && isset($_GET['submit']) && $_GET['submit'] && !(isset($_GET['token'])) && !(isset($_POST['token']))) { + $requestMode = true; + $resetMode = false; + $email = $_POST['email']; + $user = $db->querySingle("SELECT * FROM user WHERE email = '$email'", true); + if ($user) { + $db->exec("DELETE FROM password_resets WHERE email = '$email'"); + $token = bin2hex(random_bytes(32)); + $db->exec("INSERT INTO password_resets (user_id, email, token) VALUES (" . $user['id'] . ", '$email', '$token')"); + } + $hasSuccessMessage = true; +} + +if (isset($_GET['token']) && $_GET['token'] != "" && isset($_GET['email']) && $_GET['email'] != "") { + $requestMode = false; + $resetMode = true; + $token = $_GET['token']; + $email = $_GET['email']; + $matchCount = "SELECT COUNT(*) FROM password_resets WHERE token = :token and email = :email"; + $stmt = $db->prepare($matchCount); + $stmt->bindValue(':token', $token, SQLITE3_TEXT); + $stmt->bindValue(':email', $email, SQLITE3_TEXT); + $count = $stmt->execute()->fetchArray(SQLITE3_NUM); + if ($count[0] == 0) { + $hasErrorMessage = true; + $hideForm = true; + } +} + +if (isset($_POST['password']) && $_POST['password'] != "" && isset($_POST['confirm_password']) && $_POST['confirm_password'] != "" && isset($_GET['submit']) && $_GET['submit']) { + $requestMode = false; + $resetMode = true; + $password = $_POST['password']; + $confirmPassword = $_POST['confirm_password']; + $token = $_POST['token']; + $email = $_POST['email']; + $resetQuery = "SELECT * FROM password_resets WHERE token = :token AND email = :email"; + $stmt = $db->prepare($resetQuery); + $stmt->bindValue(':token', $token, SQLITE3_TEXT); + $stmt->bindValue(':email', $email, SQLITE3_TEXT); + $reset = $stmt->execute()->fetchArray(SQLITE3_ASSOC); + + if ($reset) { + $user = $db->querySingle("SELECT * FROM user WHERE email = '" . $reset['email'] . "'", true); + if ($password == $confirmPassword) { + $passwordHash = password_hash($password, PASSWORD_DEFAULT); + $db->exec("UPDATE user SET password = '$passwordHash' WHERE id = " . $user['id']); + $db->exec("DELETE FROM password_resets WHERE token = '$token'"); + $hasSuccessMessage = true; + $hideForm = true; + } else { + $hasErrorMessage = true; + $passwordsMismatch = true; + } + } else { + $hasSuccessMessage = false; + $hasErrorMessage = true; + } +} + +?> + + + + + + "/> + Wallos - Subscription Tracker + + + + + + > + > + > + + + > + + +
+
+
+ Wallos Logo Wallos Logo +

+ +

+
+
+ +
+ + +
+
+ +
+ +
    +
  • +
+ +
    +
  • +
+ +
+ + + + +
+
+ + +
+
+ +
+ +
    +
  • +
+ +
    +
  • +
+ +
    +
  • +
+ + +
+
+
+ + + + +?> \ No newline at end of file diff --git a/registration.php b/registration.php index 225b5f0f3..689adb09f 100644 --- a/registration.php +++ b/registration.php @@ -16,11 +16,32 @@ function validate($value) { return $value; } +// If there's already a user on the database, redirect to login page if registrations are closed or maxn users is reached +$stmt = $db->prepare('SELECT COUNT(*) as userCount FROM user'); +$result = $stmt->execute(); +$userCountResult = $result->fetchArray(SQLITE3_ASSOC); +$userCount = $userCountResult['userCount']; + if ($userCount > 0) { - header("Location: login.php"); - exit(); + $stmt = $db->prepare('SELECT * FROM admin'); + $result = $stmt->execute(); + $settings = $result->fetchArray(SQLITE3_ASSOC); + + if ($settings['registrations_open'] == 0) { + header("Location: login.php"); + exit(); + } + + if ($settings['max_users'] != 0) { + + if ($userCount >= $settings['max_users']) { + header("Location: login.php"); + exit(); + } + } } + $theme = "light"; if (isset($_COOKIE['theme'])) { $theme = $_COOKIE['theme']; @@ -31,16 +52,100 @@ function validate($value) { $colorTheme = $_COOKIE['colorTheme']; } -$currencies = array(); -$query = "SELECT * FROM currencies"; -$result = $db->query($query); -while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - $currencyId = $row['id']; - $currencies[$currencyId] = $row; -} +$currencies = [ + ['id' => 1, 'name' => 'Euro', 'symbol' => '€', 'code' => 'EUR'], + ['id' => 2, 'name' => 'US Dollar', 'symbol' => '$', 'code' => 'USD'], + ['id' => 3, 'name' => 'Japanese Yen', 'symbol' => '¥', 'code' => 'JPY'], + ['id' => 4, 'name' => 'Bulgarian Lev', 'symbol' => 'лв', 'code' => 'BGN'], + ['id' => 5, 'name' => 'Czech Republic Koruna', 'symbol' => 'Kč', 'code' => 'CZK'], + ['id' => 6, 'name' => 'Danish Krone', 'symbol' => 'kr', 'code' => 'DKK'], + ['id' => 7, 'name' => 'British Pound Sterling', 'symbol' => '£', 'code' => 'GBP'], + ['id' => 8, 'name' => 'Hungarian Forint', 'symbol' => 'Ft', 'code' => 'HUF'], + ['id' => 9, 'name' => 'Polish Zloty', 'symbol' => 'zł', 'code' => 'PLN'], + ['id' => 10, 'name' => 'Romanian Leu', 'symbol' => 'lei', 'code' => 'RON'], + ['id' => 11, 'name' => 'Swedish Krona', 'symbol' => 'kr', 'code' => 'SEK'], + ['id' => 12, 'name' => 'Swiss Franc', 'symbol' => 'Fr', 'code' => 'CHF'], + ['id' => 13, 'name' => 'Icelandic Króna', 'symbol' => 'kr', 'code' => 'ISK'], + ['id' => 14, 'name' => 'Norwegian Krone', 'symbol' => 'kr', 'code' => 'NOK'], + ['id' => 15, 'name' => 'Russian Ruble', 'symbol' => '₽', 'code' => 'RUB'], + ['id' => 16, 'name' => 'Turkish Lira', 'symbol' => '₺', 'code' => 'TRY'], + ['id' => 17, 'name' => 'Australian Dollar', 'symbol' => '$', 'code' => 'AUD'], + ['id' => 18, 'name' => 'Brazilian Real', 'symbol' => 'R$', 'code' => 'BRL'], + ['id' => 19, 'name' => 'Canadian Dollar', 'symbol' => '$', 'code' => 'CAD'], + ['id' => 20, 'name' => 'Chinese Yuan', 'symbol' => '¥', 'code' => 'CNY'], + ['id' => 21, 'name' => 'Hong Kong Dollar', 'symbol' => 'HK$', 'code' => 'HKD'], + ['id' => 22, 'name' => 'Indonesian Rupiah', 'symbol' => 'Rp', 'code' => 'IDR'], + ['id' => 23, 'name' => 'Israeli New Sheqel', 'symbol' => '₪', 'code' => 'ILS'], + ['id' => 24, 'name' => 'Indian Rupee', 'symbol' => '₹', 'code' => 'INR'], + ['id' => 25, 'name' => 'South Korean Won', 'symbol' => '₩', 'code' => 'KRW'], + ['id' => 26, 'name' => 'Mexican Peso', 'symbol' => 'Mex$', 'code' => 'MXN'], + ['id' => 27, 'name' => 'Malaysian Ringgit', 'symbol' => 'RM', 'code' => 'MYR'], + ['id' => 28, 'name' => 'New Zealand Dollar', 'symbol' => 'NZ$', 'code' => 'NZD'], + ['id' => 29, 'name' => 'Philippine Peso', 'symbol' => '₱', 'code' => 'PHP'], + ['id' => 30, 'name' => 'Singapore Dollar', 'symbol' => 'S$', 'code' => 'SGD'], + ['id' => 31, 'name' => 'Thai Baht', 'symbol' => '฿', 'code' => 'THB'], + ['id' => 32, 'name' => 'South African Rand', 'symbol' => 'R', 'code' => 'ZAR'], +]; + +$categories = [ + ['id' => 1, 'name' => 'No category'], + ['id' => 2, 'name' => 'Entertainment'], + ['id' => 3, 'name' => 'Music'], + ['id' => 4, 'name' => 'Utilities'], + ['id' => 5, 'name' => 'Food & Beverages'], + ['id' => 6, 'name' => 'Health & Wellbeing'], + ['id' => 7, 'name' => 'Productivity'], + ['id' => 8, 'name' => 'Banking'], + ['id' => 9, 'name' => 'Transport'], + ['id' => 10, 'name' => 'Education'], + ['id' => 11, 'name' => 'Insurance'], + ['id' => 12, 'name' => 'Gaming'], + ['id' => 13, 'name' => 'News & Magazines'], + ['id' => 14, 'name' => 'Software'], + ['id' => 15, 'name' => 'Technology'], + ['id' => 16, 'name' => 'Cloud Services'], + ['id' => 17, 'name' => 'Charity & Donations'], +]; + +$payment_methods = [ + ['id' => 1, 'name' => 'PayPal', 'icon' => 'images/uploads/icons/paypal.png'], + ['id' => 2, 'name' => 'Credit Card', 'icon' => 'images/uploads/icons/creditcard.png'], + ['id' => 3, 'name' => 'Bank Transfer', 'icon' => 'images/uploads/icons/banktransfer.png'], + ['id' => 4, 'name' => 'Direct Debit', 'icon' => 'images/uploads/icons/directdebit.png'], + ['id' => 5, 'name' => 'Money', 'icon' => 'images/uploads/icons/money.png'], + ['id' => 6, 'name' => 'Google Pay', 'icon' => 'images/uploads/icons/googlepay.png'], + ['id' => 7, 'name' => 'Samsung Pay', 'icon' => 'images/uploads/icons/samsungpay.png'], + ['id' => 8, 'name' => 'Apple Pay', 'icon' => 'images/uploads/icons/applepay.png'], + ['id' => 9, 'name' => 'Crypto', 'icon' => 'images/uploads/icons/crypto.png'], + ['id' => 10, 'name' => 'Klarna', 'icon' => 'images/uploads/icons/klarna.png'], + ['id' => 11, 'name' => 'Amazon Pay', 'icon' => 'images/uploads/icons/amazonpay.png'], + ['id' => 12, 'name' => 'SEPA', 'icon' => 'images/uploads/icons/sepa.png'], + ['id' => 13, 'name' => 'Skrill', 'icon' => 'images/uploads/icons/skrill.png'], + ['id' => 14, 'name' => 'Sofort', 'icon' => 'images/uploads/icons/sofort.png'], + ['id' => 15, 'name' => 'Stripe', 'icon' => 'images/uploads/icons/stripe.png'], + ['id' => 16, 'name' => 'Affirm', 'icon' => 'images/uploads/icons/affirm.png'], + ['id' => 17, 'name' => 'AliPay', 'icon' => 'images/uploads/icons/alipay.png'], + ['id' => 18, 'name' => 'Elo', 'icon' => 'images/uploads/icons/elo.png'], + ['id' => 19, 'name' => 'Facebook Pay', 'icon' => 'images/uploads/icons/facebookpay.png'], + ['id' => 20, 'name' => 'GiroPay', 'icon' => 'images/uploads/icons/giropay.png'], + ['id' => 21, 'name' => 'iDeal', 'icon' => 'images/uploads/icons/ideal.png'], + ['id' => 22, 'name' => 'Union Pay', 'icon' => 'images/uploads/icons/unionpay.png'], + ['id' => 23, 'name' => 'Interac', 'icon' => 'images/uploads/icons/interac.png'], + ['id' => 24, 'name' => 'WeChat', 'icon' => 'images/uploads/icons/wechat.png'], + ['id' => 25, 'name' => 'Paysafe', 'icon' => 'images/uploads/icons/paysafe.png'], + ['id' => 26, 'name' => 'Poli', 'icon' => 'images/uploads/icons/poli.png'], + ['id' => 27, 'name' => 'Qiwi', 'icon' => 'images/uploads/icons/qiwi.png'], + ['id' => 28, 'name' => 'ShopPay', 'icon' => 'images/uploads/icons/shoppay.png'], + ['id' => 29, 'name' => 'Venmo', 'icon' => 'images/uploads/icons/venmo.png'], + ['id' => 30, 'name' => 'VeriFone', 'icon' => 'images/uploads/icons/verifone.png'], + ['id' => 31, 'name' => 'WebMoney', 'icon' => 'images/uploads/icons/webmoney.png'], +]; $passwordMismatch = false; +$usernameExists = false; +$emailExists = false; $registrationFailed = false; +$hasErrors = false; if (isset($_POST['username'])) { $username = validate($_POST['username']); $email = validate($_POST['email']); @@ -52,42 +157,139 @@ function validate($value) { if ($password != $confirm_password) { $passwordMismatch = true; - } else { - $query = "INSERT INTO user (username, email, password, main_currency, avatar, language) VALUES (:username, :email, :password, :main_currency, :avatar, :language)"; + $hasErrors = true; + } + + $emailQuery = "SELECT * FROM user WHERE email = :email"; + $stmtEmail = $db->prepare($emailQuery); + $stmtEmail->bindValue(':email', $email, SQLITE3_TEXT); + $resultEmail = $stmtEmail->execute(); + + if ($resultEmail->fetchArray()) { + $emailExists = true; + $hasErrors = true; + } + + $usernameQuery = "SELECT * FROM user WHERE username = :username"; + $stmtUsername = $db->prepare($usernameQuery); + $stmtUsername->bindValue(':username', $username, SQLITE3_TEXT); + $resultUsername = $stmtUsername->execute(); + + if ($resultUsername->fetchArray()) { + $usernameExists = true; + $hasErrors = true; + } + + $requireValidation = false; + + if ($hasErrors == false) { + $query = "INSERT INTO user (username, email, password, main_currency, avatar, language, budget) VALUES (:username, :email, :password, :main_currency, :avatar, :language, :budget)"; $stmt = $db->prepare($query); $hashedPassword = password_hash($password, PASSWORD_DEFAULT); $stmt->bindValue(':username', $username, SQLITE3_TEXT); $stmt->bindValue(':email', $email, SQLITE3_TEXT); $stmt->bindValue(':password', $hashedPassword, SQLITE3_TEXT); - $stmt->bindValue(':main_currency', $main_currency, SQLITE3_TEXT); + $stmt->bindValue(':main_currency', 1, SQLITE3_TEXT); $stmt->bindValue(':avatar', $avatar, SQLITE3_TEXT); $stmt->bindValue(':language', $language, SQLITE3_TEXT); + $stmt->bindValue(':budget', 0, SQLITE3_INTEGER); $result = $stmt->execute(); if ($result) { - $deleteQuery = "DELETE FROM household"; - $stmtDelete = $db->prepare($deleteQuery); - $stmtDelete->execute(); - - $deleteQuery = "DELETE FROM subscriptions"; - $stmtDelete = $db->prepare($deleteQuery); - $stmtDelete->execute(); - $deleteQuery = "DELETE FROM fixer"; - $stmtDelete = $db->prepare($deleteQuery); - $stmtDelete->execute(); + // Get id of the newly created user + $userId = $db->lastInsertRowID(); - $query = "INSERT INTO household (name) VALUES (:name)"; + // Add username as household member for that user + $query = "INSERT INTO household (name, user_id) VALUES (:name, :user_id)"; $stmt = $db->prepare($query); $stmt->bindValue(':name', $username, SQLITE3_TEXT); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); $stmt->execute(); + + if ($userId > 1) { + + // Add categories for that user + $query = 'INSERT INTO categories (name, "order", user_id) VALUES (:name, :order, :user_id)'; + $stmt = $db->prepare($query); + foreach ($categories as $index => $category) { + $stmt->bindValue(':name', $category['name'], SQLITE3_TEXT); + $stmt->bindValue(':order', $index + 1, SQLITE3_INTEGER); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->execute(); + } + + // Add payment methods for that user + $query = 'INSERT INTO payment_methods (name, icon, "order", user_id) VALUES (:name, :icon, :order, :user_id)'; + $stmt = $db->prepare($query); + foreach ($payment_methods as $index => $payment_method) { + $stmt->bindValue(':name', $payment_method['name'], SQLITE3_TEXT); + $stmt->bindValue(':icon', $payment_method['icon'], SQLITE3_TEXT); + $stmt->bindValue(':order', $index + 1, SQLITE3_INTEGER); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->execute(); + } + + // Add currencies for that user + $query = "INSERT INTO currencies (name, symbol, code, rate, user_id) VALUES (:name, :symbol, :code, :rate, :user_id)"; + $stmt = $db->prepare($query); + foreach ($currencies as $currency) { + $stmt->bindValue(':name', $currency['name'], SQLITE3_TEXT); + $stmt->bindValue(':symbol', $currency['symbol'], SQLITE3_TEXT); + $stmt->bindValue(':code', $currency['code'], SQLITE3_TEXT); + $stmt->bindValue(':rate', 1, SQLITE3_FLOAT); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->execute(); + } + + // Retrieve main currency id + $query = "SELECT id FROM currencies WHERE code = :code AND user_id = :user_id"; + $stmt = $db->prepare($query); + $stmt->bindValue(':code', $main_currency, SQLITE3_TEXT); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $currency = $result->fetchArray(SQLITE3_ASSOC); + + // Update user main currency + $query = "UPDATE user SET main_currency = :main_currency WHERE id = :user_id"; + $stmt = $db->prepare($query); + $stmt->bindValue(':main_currency', $currency['id'], SQLITE3_INTEGER); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->execute(); + + // Add settings for that user + $query = "INSERT INTO settings (dark_theme, monthly_price, convert_currency, remove_background, color_theme, hide_disabled, user_id) + VALUES (0, 0, 0, 0, 'blue', 0, :user_id)"; + $stmt = $db->prepare($query); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->execute(); + + // If email verification is required add the user to the email_verification table + $query = "SELECT * FROM admin"; + $stmt = $db->prepare($query); + $result = $stmt->execute(); + $settings = $result->fetchArray(SQLITE3_ASSOC); + + if ($settings['require_email_verification'] == 1) { + $query = "INSERT INTO email_verification (user_id, email, token, email_sent) VALUES (:user_id, :email, :token, 0)"; + $stmt = $db->prepare($query); + $token = bin2hex(random_bytes(32)); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->bindValue(':token', $token, SQLITE3_TEXT); + $stmt->bindValue(':email', $email, SQLITE3_TEXT); + $stmt->execute(); + + $requireValidation = true; + } + } + $db->close(); - header("Location: login.php"); + header("Location: login.php?registered=true&requireValidation=$requireValidation"); exit(); } else { $registrationFailed = true; } - } + } } ?> @@ -106,6 +308,7 @@ function validate($value) { > > > + @@ -147,7 +350,7 @@ function validate($value) { - + @@ -166,32 +369,59 @@ function validate($value) { ?> + - - - + + +
-
- - -
+ +
+ + +
+
response.json()) - .then(data => { - if (data.success) { - const link = document.createElement('a'); - const filename = data.file; - link.href = '.tmp/' + filename; - link.download = 'backup.zip'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - button.disabled = false; - } else { - showErrorMessage(data.errorMessage); - button.disabled = false; - } - }) - .catch(error => { - showErrorMessage(error); - button.disabled = false; - }); - } - - function openRestoreDBFileSelect() { - document.getElementById('restoreDBFile').click(); +function makeFetchCall(url, data, button) { + return fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + } else { + showErrorMessage(data.message); + } + button.disabled = false; + }) + .catch((error) => { + showErrorMessage(error); + button.disabled = false; + }); + +} + +function testSmtpSettingsButton() { + const button = document.getElementById("testSmtpSettingsButton"); + button.disabled = true; + + const smtpAddress = document.getElementById("smtpaddress").value; + const smtpPort = document.getElementById("smtpport").value; + const encryption = document.querySelector('input[name="encryption"]:checked').value; + const smtpUsername = document.getElementById("smtpusername").value; + const smtpPassword = document.getElementById("smtppassword").value; + const fromEmail = document.getElementById("fromemail").value; + + const data = { + smtpaddress: smtpAddress, + smtpport: smtpPort, + encryption: encryption, + smtpusername: smtpUsername, + smtppassword: smtpPassword, + fromemail: fromEmail }; - - function restoreDB() { - const input = document.getElementById('restoreDBFile'); - const file = input.files[0]; - - if (!file) { - console.error('No file selected'); - return; - } - - const formData = new FormData(); - formData.append('file', file); - - fetch('endpoints/db/restore.php', { + + makeFetchCall('endpoints/notifications/testemailnotifications.php', data, button); +} + +function saveSmtpSettingsButton() { + const button = document.getElementById("saveSmtpSettingsButton"); + button.disabled = true; + + const smtpAddress = document.getElementById("smtpaddress").value; + const smtpPort = document.getElementById("smtpport").value; + const encryption = document.querySelector('input[name="encryption"]:checked').value; + const smtpUsername = document.getElementById("smtpusername").value; + const smtpPassword = document.getElementById("smtppassword").value; + const fromEmail = document.getElementById("fromemail").value; + + const data = { + smtpaddress: smtpAddress, + smtpport: smtpPort, + encryption: encryption, + smtpusername: smtpUsername, + smtppassword: smtpPassword, + fromemail: fromEmail + }; + + fetch('endpoints/admin/savesmtpsettings.php', { method: 'POST', - body: formData - }) + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + const emailVerificationCheckbox = document.getElementById('requireEmail'); + emailVerificationCheckbox.disabled = false; + showSuccessMessage(data.message); + } else { + showErrorMessage(data.message); + } + button.disabled = false; + }) + .catch((error) => { + showErrorMessage(error); + button.disabled = false; + }); + +} + +function backupDB() { + const button = document.getElementById("backupDB"); + button.disabled = true; + + fetch('endpoints/db/backup.php') .then(response => response.json()) .then(data => { if (data.success) { - showSuccessMessage(data.message) - window.location.href = 'logout.php'; + const link = document.createElement('a'); + const filename = data.file; + link.href = '.tmp/' + filename; + link.download = 'backup.zip'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + button.disabled = false; } else { - showErrorMessage(data.message); + showErrorMessage(data.errorMessage); + button.disabled = false; } }) - .catch(error => showErrorMessage('Error:', error)); - } \ No newline at end of file + .catch(error => { + showErrorMessage(error); + button.disabled = false; + }); +} + +function openRestoreDBFileSelect() { + document.getElementById('restoreDBFile').click(); +}; + +function restoreDB() { + const input = document.getElementById('restoreDBFile'); + const file = input.files[0]; + + if (!file) { + console.error('No file selected'); + return; + } + + const formData = new FormData(); + formData.append('file', file); + + fetch('endpoints/db/restore.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message) + window.location.href = 'logout.php'; + } else { + showErrorMessage(data.message); + } + }) + .catch(error => showErrorMessage('Error:', error)); +} + +function saveAccountRegistrationsButton () { + const button = document.getElementById('saveAccountRegistrations'); + button.disabled = true; + + const open_registrations = document.getElementById('registrations').checked ? 1 : 0; + const max_users = document.getElementById('maxUsers').value; + const require_email_validation = document.getElementById('requireEmail').checked ? 1 : 0; + const server_url = document.getElementById('serverUrl').value; + + const data = { + open_registrations: open_registrations, + max_users: max_users, + require_email_validation: require_email_validation, + server_url: server_url + }; + + fetch('endpoints/admin/saveopenregistrations.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + button.disabled = false; + } else { + showErrorMessage(data.message); + button.disabled = false; + } + }) + .catch(error => { + showErrorMessage(error); + button.disabled = false; + }); +} + +function removeUser(userId) { + const data = { + userId: userId + }; + + fetch('endpoints/admin/deleteuser.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + const userContainer = document.querySelector(`.form-group-inline[data-userid="${userId}"]`); + if (userContainer) { + userContainer.remove(); + } + } else { + showErrorMessage(data.message); + } + }) + .catch(error => showErrorMessage('Error:', error)); + +} \ No newline at end of file diff --git a/service-worker.js b/service-worker.js index 5e81d39f8..29ced3cd6 100644 --- a/service-worker.js +++ b/service-worker.js @@ -28,6 +28,7 @@ self.addEventListener('install', function(event) { 'scripts/settings.js', 'scripts/notifications.js', 'scripts/registration.js', + 'scripts/admin.js', 'scripts/i18n/en.js', 'scripts/i18n/de.js', 'scripts/i18n/el.js', diff --git a/settings.php b/settings.php index 594423ad2..8b40556aa 100644 --- a/settings.php +++ b/settings.php @@ -52,7 +52,7 @@
- +
@@ -68,8 +68,10 @@
query($query); + $query = "SELECT * FROM currencies WHERE user_id = :userId"; + $query = $db->prepare($query); + $query->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $query->execute(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $currencyId = $row['id']; $currencies[$currencyId] = $row; @@ -135,8 +137,10 @@ query($sql); + $sql = "SELECT * FROM household WHERE user_id = :userId"; + $stmt = $db->prepare($sql); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); if ($result) { $household = array(); @@ -153,12 +157,12 @@