From c0d53e4423996595e5c82404af92e077c00eae47 Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Mon, 21 Jul 2025 22:53:35 +0200 Subject: [PATCH] feat: add oauth/oidc support (#873) --- README.md | 5 + admin.php | 112 ++++++++- endpoints/admin/enableoidc.php | 48 ++++ endpoints/admin/saveoidcsettings.php | 100 ++++++++ includes/checksession.php | 163 +++++++------ includes/i18n/cs.php | 5 + includes/i18n/da.php | 5 + includes/i18n/de.php | 5 + includes/i18n/el.php | 5 + includes/i18n/en.php | 5 + includes/i18n/es.php | 5 + includes/i18n/fr.php | 5 + includes/i18n/id.php | 5 + includes/i18n/it.php | 5 + includes/i18n/jp.php | 5 + includes/i18n/ko.php | 5 + includes/i18n/nl.php | 7 +- includes/i18n/pl.php | 5 + includes/i18n/pt.php | 5 + includes/i18n/pt_br.php | 5 + includes/i18n/ru.php | 5 + includes/i18n/sl.php | 5 + includes/i18n/sr.php | 5 + includes/i18n/sr_lat.php | 5 + includes/i18n/tr.php | 5 + includes/i18n/uk.php | 5 + includes/i18n/vi.php | 5 + includes/i18n/zh_cn.php | 5 + includes/i18n/zh_tw.php | 5 + includes/oidc/handle_oidc_callback.php | 128 ++++++++++ includes/oidc/oidc_create_user.php | 179 ++++++++++++++ includes/oidc/oidc_login.php | 63 +++++ includes/version.php | 2 +- login.php | 52 +++++ logout.php | 23 +- migrations/000001.php | 1 - migrations/000002.php | 1 - migrations/000003.php | 1 - migrations/000004.php | 1 - migrations/000005.php | 1 - migrations/000006.php | 1 - migrations/000007.php | 1 - migrations/000008.php | 1 - migrations/000009.php | 1 - migrations/000010.php | 1 - migrations/000011.php | 1 - migrations/000012.php | 1 - migrations/000013.php | 1 - migrations/000014.php | 1 - migrations/000015.php | 1 - migrations/000016.php | 1 - migrations/000017.php | 1 - migrations/000018.php | 1 - migrations/000019.php | 1 - migrations/000020.php | 2 - migrations/000021.php | 1 - migrations/000022.php | 1 - migrations/000023.php | 1 - migrations/000024.php | 1 - migrations/000025.php | 1 - migrations/000026.php | 1 - migrations/000027.php | 1 - migrations/000028.php | 1 - migrations/000029.php | 2 - migrations/000030.php | 3 - migrations/000031.php | 1 - migrations/000032.php | 2 - migrations/000033.php | 1 - migrations/000036.php | 1 - migrations/000037.php | 1 - migrations/000038.php | 40 ++++ scripts/admin.js | 308 ++++++++++++++++--------- styles/login.css | 26 ++- 73 files changed, 1169 insertions(+), 242 deletions(-) create mode 100644 endpoints/admin/enableoidc.php create mode 100644 endpoints/admin/saveoidcsettings.php create mode 100644 includes/oidc/handle_oidc_callback.php create mode 100644 includes/oidc/oidc_create_user.php create mode 100644 includes/oidc/oidc_login.php create mode 100644 migrations/000038.php diff --git a/README.md b/README.md index 83d187d..0c0be77 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ - [Docker-Compose](#docker-compose) - [Usage](#usage) - [Screenshots](#screenshots) +- [OIDC](#oidc) - [API Documentation](#api-documentation) - [Contributing](#contributing) - [Contributors](#contributors) @@ -168,6 +169,10 @@ If you want to trigger an Update of the exchange rates, change your main currenc ![Screenshot](screenshots/wallos-dashboard-mobile-light.png) ![Screenshot](screenshots/wallos-dashboard-mobile-dark.png) +## OIDC + +OIDC can be enabled on the Admin page and can be used with providers that support OAuth. + ## API Documentation Wallos provides a comprehensive API that allows you to interact with the application programmatically. The API documentation is available at [https://api.wallosapp.com/](https://api.wallosapp.com/). diff --git a/admin.php b/admin.php index e035401..33e549d 100644 --- a/admin.php +++ b/admin.php @@ -11,6 +11,29 @@ $stmt = $db->prepare('SELECT * FROM admin'); $result = $stmt->execute(); $settings = $result->fetchArray(SQLITE3_ASSOC); +// get OIDC settings +$stmt = $db->prepare('SELECT * FROM oauth_settings WHERE id = 1'); +$result = $stmt->execute(); +$oidcSettings = $result->fetchArray(SQLITE3_ASSOC); + +if ($oidcSettings === false) { + // Table is empty or no row with id=1, set defaults + $oidcSettings = [ + 'name' => '', + 'client_id' => '', + 'client_secret' => '', + 'authorization_url' => '', + 'token_url' => '', + 'user_info_url' => '', + 'redirect_url' => '', + 'logout_url' => '', + 'user_identifier_field' => 'sub', + 'scopes' => 'openid email profile', + 'auth_style' => 'auto', + 'auto_create_user' => 0 + ]; +} + // get user accounts $stmt = $db->prepare('SELECT id, username, email FROM user ORDER BY id ASC'); $result = $stmt->execute(); @@ -182,6 +205,66 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0; } ?> +
+
+

+
+
+
+ + onchange="toggleOidcEnabled()" /> + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ /> + +
+
+ +
+
+ +
+

@@ -347,7 +430,8 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0; ?>
- onchange="toggleUpdateNotification()"/> + + onchange="toggleUpdateNotification()" />

@@ -360,14 +444,22 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0;

- - - - - - - - + + + + + + + +
@@ -401,4 +493,4 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0; +?> \ No newline at end of file diff --git a/endpoints/admin/enableoidc.php b/endpoints/admin/enableoidc.php new file mode 100644 index 0000000..327249d --- /dev/null +++ b/endpoints/admin/enableoidc.php @@ -0,0 +1,48 @@ + false, + "message" => translate('session_expired', $i18n) + ])); +} + +// Check that user is an admin +if ($userId !== 1) { + die(json_encode([ + "success" => false, + "message" => translate('error', $i18n) + ])); +} + +if ($_SERVER["REQUEST_METHOD"] === "POST") { + + $postData = file_get_contents("php://input"); + $data = json_decode($postData, true); + + $oidcEnabled = isset($data['oidcEnabled']) ? $data['oidcEnabled'] : 0; + + $stmt = $db->prepare('UPDATE admin SET oidc_oauth_enabled = :oidcEnabled WHERE id = 1'); + $stmt->bindParam(':oidcEnabled', $oidcEnabled, SQLITE3_INTEGER); + $stmt->execute(); + + if ($db->changes() > 0) { + die(json_encode([ + "success" => true, + "message" => translate('success', $i18n) + ])); + } else { + die(json_encode([ + "success" => false, + "message" => translate('error', $i18n) + ])); + } + +} else { + die(json_encode([ + "success" => false, + "message" => translate('error', $i18n) + ])); +} \ No newline at end of file diff --git a/endpoints/admin/saveoidcsettings.php b/endpoints/admin/saveoidcsettings.php new file mode 100644 index 0000000..b50ff57 --- /dev/null +++ b/endpoints/admin/saveoidcsettings.php @@ -0,0 +1,100 @@ + false, + "message" => translate('session_expired', $i18n) + ])); +} + +// Check that user is an admin +if ($userId !== 1) { + die(json_encode([ + "success" => false, + "message" => translate('error', $i18n) + ])); +} + +if ($_SERVER["REQUEST_METHOD"] === "POST") { + + $postData = file_get_contents("php://input"); + $data = json_decode($postData, true); + + $oidcName = isset($data['oidcName']) ? trim($data['oidcName']) : ''; + $oidcClientId = isset($data['oidcClientId']) ? trim($data['oidcClientId']) : ''; + $oidcClientSecret = isset($data['oidcClientSecret']) ? trim($data['oidcClientSecret']) : ''; + $oidcAuthUrl = isset($data['oidcAuthUrl']) ? trim($data['oidcAuthUrl']) : ''; + $oidcTokenUrl = isset($data['oidcTokenUrl']) ? trim($data['oidcTokenUrl']) : ''; + $oidcUserInfoUrl = isset($data['oidcUserInfoUrl']) ? trim($data['oidcUserInfoUrl']) : ''; + $oidcRedirectUrl = isset($data['oidcRedirectUrl']) ? trim($data['oidcRedirectUrl']) : ''; + $oidcLogoutUrl = isset($data['oidcLogoutUrl']) ? trim($data['oidcLogoutUrl']) : ''; + $oidcUserIdentifierField = isset($data['oidcUserIdentifierField']) ? trim($data['oidcUserIdentifierField']) : ''; + $oidcScopes = isset($data['oidcScopes']) ? trim($data['oidcScopes']) : ''; + $oidcAuthStyle = isset($data['oidcAuthStyle']) ? trim($data['oidcAuthStyle']) : ''; + $oidcAutoCreateUser = isset($data['oidcAutoCreateUser']) ? (int)$data['oidcAutoCreateUser'] : 0; + + $checkStmt = $db->prepare('SELECT COUNT(*) as count FROM oauth_settings WHERE id = 1'); + $result = $checkStmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + if ($row['count'] > 0) { + // Update existing row + $stmt = $db->prepare('UPDATE oauth_settings SET + name = :oidcName, + client_id = :oidcClientId, + client_secret = :oidcClientSecret, + authorization_url = :oidcAuthUrl, + token_url = :oidcTokenUrl, + user_info_url = :oidcUserInfoUrl, + redirect_url = :oidcRedirectUrl, + logout_url = :oidcLogoutUrl, + user_identifier_field = :oidcUserIdentifierField, + scopes = :oidcScopes, + auth_style = :oidcAuthStyle, + auto_create_user = :oidcAutoCreateUser + WHERE id = 1'); + } else { + // Insert new row + $stmt = $db->prepare('INSERT INTO oauth_settings ( + id, name, client_id, client_secret, authorization_url, token_url, user_info_url, redirect_url, logout_url, user_identifier_field, scopes, auth_style, auto_create_user + ) VALUES ( + 1, :oidcName, :oidcClientId, :oidcClientSecret, :oidcAuthUrl, :oidcTokenUrl, :oidcUserInfoUrl, :oidcRedirectUrl, :oidcLogoutUrl, :oidcUserIdentifierField, :oidcScopes, :oidcAuthStyle, :oidcAutoCreateUser + )'); + } + + $stmt->bindParam(':oidcName', $oidcName, SQLITE3_TEXT); + $stmt->bindParam(':oidcClientId', $oidcClientId, SQLITE3_TEXT); + $stmt->bindParam(':oidcClientSecret', $oidcClientSecret, SQLITE3_TEXT); + $stmt->bindParam(':oidcAuthUrl', $oidcAuthUrl, SQLITE3_TEXT); + $stmt->bindParam(':oidcTokenUrl', $oidcTokenUrl, SQLITE3_TEXT); + $stmt->bindParam(':oidcUserInfoUrl', $oidcUserInfoUrl, SQLITE3_TEXT); + $stmt->bindParam(':oidcRedirectUrl', $oidcRedirectUrl, SQLITE3_TEXT); + $stmt->bindParam(':oidcLogoutUrl', $oidcLogoutUrl, SQLITE3_TEXT); + $stmt->bindParam(':oidcUserIdentifierField', $oidcUserIdentifierField, SQLITE3_TEXT); + $stmt->bindParam(':oidcScopes', $oidcScopes, SQLITE3_TEXT); + $stmt->bindParam(':oidcAuthStyle', $oidcAuthStyle, SQLITE3_TEXT); + $stmt->bindParam(':oidcAutoCreateUser', $oidcAutoCreateUser, SQLITE3_INTEGER); + $stmt->execute(); + + if ($db->changes() > 0) { + $db->close(); + die(json_encode([ + "success" => true, + "message" => translate('success', $i18n) + ])); + } else { + $db->close(); + die(json_encode([ + "success" => false, + "message" => translate('error', $i18n) + ])); + } + +} else { + die(json_encode([ + "success" => false, + "message" => translate('error', $i18n) + ])); +} \ No newline at end of file diff --git a/includes/checksession.php b/includes/checksession.php index 8250bb5..862bd5f 100644 --- a/includes/checksession.php +++ b/includes/checksession.php @@ -1,90 +1,105 @@ prepare($sql); - $stmt->bindValue(':username', $username, SQLITE3_TEXT); - $result = $stmt->execute(); - $userData = $result->fetchArray(SQLITE3_ASSOC); - $userId = $userData['id']; +// Handle OIDC first +if (session_status() === PHP_SESSION_NONE) { + session_start(); +} - if ($userData === false) { - header('Location: logout.php'); - exit(); - } else { - $_SESSION['userId'] = $userData['id']; - } +if (isset($_GET['code']) && isset($_GET['state'])) { + // This request is coming from the OIDC login flow + $code = $_GET['code']; + $state = $_GET['state']; + + require_once 'includes/oidc/handle_oidc_callback.php'; - if ($userData['avatar'] == "") { - $userData['avatar'] = "0"; - } } else { - - if (isset($_COOKIE['wallos_login'])) { - $cookie = explode('|', $_COOKIE['wallos_login'], 3); - $username = $cookie[0]; - $token = $cookie[1]; - $main_currency = $cookie[2]; - + if (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] === true) { + $username = $_SESSION['username']; + $main_currency = $_SESSION['main_currency']; $sql = "SELECT * FROM user WHERE username = :username"; $stmt = $db->prepare($sql); $stmt->bindValue(':username', $username, SQLITE3_TEXT); $result = $stmt->execute(); + $userData = $result->fetchArray(SQLITE3_ASSOC); + $userId = $userData['id']; - if ($result) { - $userData = $result->fetchArray(SQLITE3_ASSOC); - if (!isset($userData['id'])) { - $db->close(); - header("Location: logout.php"); - exit(); - } - - if ($userData['avatar'] == "") { - $userData['avatar'] = "0"; - } - $userId = $userData['id']; - $main_currency = $userData['main_currency']; - - $adminQuery = "SELECT login_disabled FROM admin"; - $adminResult = $db->query($adminQuery); - $adminRow = $adminResult->fetchArray(SQLITE3_ASSOC); - if ($adminRow['login_disabled'] == 1) { - $sql = "SELECT * FROM login_tokens WHERE user_id = :userId"; - $stmt = $db->prepare($sql); - $stmt->bindParam(':userId', $userId, SQLITE3_TEXT); - } else { - $sql = "SELECT * FROM login_tokens WHERE user_id = :userId AND token = :token"; - $stmt = $db->prepare($sql); - $stmt->bindParam(':userId', $userId, SQLITE3_TEXT); - $stmt->bindParam(':token', $token, SQLITE3_TEXT); - } - $result = $stmt->execute(); - $row = $result->fetchArray(SQLITE3_ASSOC); - - if ($row != false) { - $_SESSION['username'] = $username; - $_SESSION['token'] = $token; - $_SESSION['loggedin'] = true; - $_SESSION['main_currency'] = $main_currency; - $_SESSION['userId'] = $userId; - } else { - $db->close(); - header("Location: logout.php"); - exit(); - } - } else { - $db->close(); - header("Location: logout.php"); + if ($userData === false) { + header('Location: logout.php'); exit(); + } else { + $_SESSION['userId'] = $userData['id']; } - + if ($userData['avatar'] == "") { + $userData['avatar'] = "0"; + } } else { - $db->close(); - header("Location: login.php"); - exit(); + + if (isset($_COOKIE['wallos_login'])) { + $cookie = explode('|', $_COOKIE['wallos_login'], 3); + $username = $cookie[0]; + $token = $cookie[1]; + $main_currency = $cookie[2]; + + $sql = "SELECT * FROM user WHERE username = :username"; + $stmt = $db->prepare($sql); + $stmt->bindValue(':username', $username, SQLITE3_TEXT); + $result = $stmt->execute(); + + if ($result) { + $userData = $result->fetchArray(SQLITE3_ASSOC); + if (!isset($userData['id'])) { + $db->close(); + header("Location: logout.php"); + exit(); + } + + if ($userData['avatar'] == "") { + $userData['avatar'] = "0"; + } + $userId = $userData['id']; + $main_currency = $userData['main_currency']; + + $adminQuery = "SELECT login_disabled FROM admin"; + $adminResult = $db->query($adminQuery); + $adminRow = $adminResult->fetchArray(SQLITE3_ASSOC); + if ($adminRow['login_disabled'] == 1) { + $sql = "SELECT * FROM login_tokens WHERE user_id = :userId"; + $stmt = $db->prepare($sql); + $stmt->bindParam(':userId', $userId, SQLITE3_TEXT); + } else { + $sql = "SELECT * FROM login_tokens WHERE user_id = :userId AND token = :token"; + $stmt = $db->prepare($sql); + $stmt->bindParam(':userId', $userId, SQLITE3_TEXT); + $stmt->bindParam(':token', $token, SQLITE3_TEXT); + } + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + if ($row != false) { + $_SESSION['username'] = $username; + $_SESSION['token'] = $token; + $_SESSION['loggedin'] = true; + $_SESSION['main_currency'] = $main_currency; + $_SESSION['userId'] = $userId; + } else { + $db->close(); + header("Location: logout.php"); + exit(); + } + } else { + $db->close(); + header("Location: logout.php"); + exit(); + } + + + } else { + $db->close(); + header("Location: login.php"); + exit(); + } } } + + ?> \ No newline at end of file diff --git a/includes/i18n/cs.php b/includes/i18n/cs.php index ce2cd95..d683692 100644 --- a/includes/i18n/cs.php +++ b/includes/i18n/cs.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Přihlaste se, prosím", "stay_logged_in" => "Zůstat přihlášený (30 dní)", "login" => "Přihlásit", + "login_with" => "Přihlásit se pomocí", + "or" => "nebo", "login_failed" => "Přihlašovací údaje jsou nesprávné", "registration_successful" => "Úspěšná registrace", "user_email_waiting_verification" => "Váš e-mail musí být ověřen. Zkontrolujte prosím svůj e-mail.", @@ -347,6 +349,9 @@ $i18n = [ "delete_user" => "Odstranit uživatele", "delete_user_info" => "Odstraněním uživatele se odstraní také všechna jeho předplatná a nastavení.", "create_user" => "Vytvořit uživatele", + "oidc_settings" => "Nastavení OIDC", + "oidc_oauth_enabled" => "Povolit OIDC/OAuth", + "create_user_automatically" => "Automaticky vytvořit uživatele", "smtp_settings" => "Nastavení SMTP", "smtp_usage_info" => "Bude použito pro obnovení hesla a další systémové e-maily.", "maintenance_tasks" => "Úkoly údržby", diff --git a/includes/i18n/da.php b/includes/i18n/da.php index 82085c6..806e94d 100644 --- a/includes/i18n/da.php +++ b/includes/i18n/da.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Log venligst ind", "stay_logged_in" => "Forbliv logget ind (30 dage)", "login" => "Login", + "login_with" => "Log ind med", + "or" => "eller", "login_failed" => "Loginoplysningerne er forkerte", "registration_successful" => "Registreringen lykkedes", "user_email_waiting_verification" => "Din e-mail skal bekræftes. Tjek venligst din indbakke.", @@ -347,6 +349,9 @@ $i18n = [ "delete_user" => "Slet bruger", "delete_user_info" => "Sletning af en bruger vil også slette alle deres abonnementer og indstillinger.", "create_user" => "Opret bruger", + "oidc_settings" => "OIDC-indstillinger", + "oidc_oauth_enabled" => "Aktivér OIDC/OAuth", + "create_user_automatically" => "Opret bruger automatisk", "smtp_settings" => "SMTP-indstillinger", "smtp_usage_info" => "Vil blive brugt til adgangskodenulstilling og andre systemmails.", // Maintenance Tasks diff --git a/includes/i18n/de.php b/includes/i18n/de.php index 3a4e249..28251f0 100644 --- a/includes/i18n/de.php +++ b/includes/i18n/de.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Bitte einloggen", "stay_logged_in" => "Angemeldet bleiben (30 Tage)", "login" => "Login", + "login_with" => "Einloggen mit", + "or" => "oder", "login_failed" => "Loginangaben sind nicht korrekt", "registration_successful" => "Registrierung erfolgreich", "user_email_waiting_verification" => "Ihre E-Mail muss noch verifiziert werden. Bitte überprüfen Sie Ihre E-Mail.", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Benutzer löschen", "delete_user_info" => "Durch das Löschen eines Benutzers werden auch alle seine Abonnements und Einstellungen gelöscht.", "create_user" => "Benutzer erstellen", + "oidc_settings" => "OIDC Einstellungen", + "oidc_oauth_enabled" => "OIDC/OAuth aktivieren", + "create_user_automatically" => "Benutzer automatisch erstellen", "smtp_settings" => "SMTP Einstellungen", "smtp_usage_info" => "Wird für die Passwortwiederherstellung und andere System-E-Mails verwendet", "maintenance_tasks" => "Wartungsaufgaben", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index f0aa556..c409852 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Παρακαλώ συνδέσου", "stay_logged_in" => "Μείνε συνδεδεμένος (30 ημέρες)", "login" => "Σύνδεση", + "login_with" => "Σύνδεση με", + "or" => "ή", "login_failed" => "Τα στοιχεία σύνδεσης είναι λανθασμένα", "registration_successful" => "Επιτυχής Εγγραφή", "user_email_waiting_verification" => "Το email σας πρέπει να επαληθευτεί. Παρακαλούμε ελέγξτε το email σας", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Διαγραφή χρήστη", "delete_user_info" => "Η διαγραφή ενός χρήστη θα διαγράψει επίσης όλες τις συνδρομές και τις ρυθμίσεις του.", "create_user" => "Δημιουργία χρήστη", + "oidc_settings" => "Ρυθμίσεις OIDC", + "oidc_oauth_enabled" => "Ενεργοποίηση OIDC/OAuth", + "create_user_automatically" => "Δημιουργία χρήστη αυτόματα", "smtp_settings" => "SMTP ρυθμίσεις", "smtp_usage_info" => "Θα χρησιμοποιηθεί για ανάκτηση κωδικού πρόσβασης και άλλα μηνύματα ηλεκτρονικού ταχυδρομείου συστήματος.", "maintenance_tasks" => "Εργασίες συντήρησης", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index 25f5d6b..7de3094 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Please login", "stay_logged_in" => "Stay logged in (30 days)", "login" => "Login", + "login_with" => "Login with", + "or" => "or", "login_failed" => "Login details are incorrect", "registration_successful" => "Registration successful", "user_email_waiting_verification" => "Your email needs to be verified. Please check your email.", @@ -347,6 +349,9 @@ $i18n = [ "delete_user" => "Delete User", "delete_user_info" => "Deleting a user will also delete all their subscriptions and settings.", "create_user" => "Create User", + "oidc_settings" => "OIDC Settings", + "oidc_oauth_enabled" => "Enable OIDC/OAuth", + "create_user_automatically" => "Create user automatically", "smtp_settings" => "SMTP Settings", "smtp_usage_info" => "Will be used for password recovery and other system emails.", "maintenance_tasks" => "Maintenance Tasks", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index a747efc..0f4289f 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Por favor, inicia sesión", "stay_logged_in" => "Mantener sesión iniciada (30 días)", "login" => "Iniciar Sesión", + "login_with" => "Iniciar sesión con", + "or" => "o", "login_failed" => "Los detalles de inicio de sesión son incorrectos", "registration_successful" => "Registro efectuado con éxito", "user_email_waiting_verification" => "Tu correo electrónico necesita ser verificado. Por favor, compruebe su correo electrónico", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Eliminar Usuario", "delete_user_info" => "Al eliminar un usuario, también se eliminarán todas sus suscripciones y configuraciones.", "create_user" => "Crear Usuario", + "oidc_settings" => "Configuración OIDC", + "oidc_oauth_enabled" => "Habilitar OIDC/OAuth", + "create_user_automatically" => "Crear usuario automáticamente", "smtp_settings" => "Configuración SMTP", "smtp_usage_info" => "Se utilizará para recuperar contraseñas y otros correos electrónicos del sistema.", "maintenance_tasks" => "Tareas de Mantenimiento", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index 849c1d8..c9e3d10 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Veuillez vous connecter", "stay_logged_in" => "Rester connecté (30 jours)", "login" => "Se connecter", + "login_with" => "Se connecter avec", + "or" => "ou", "login_failed" => "Les détails de connexion sont incorrects", "registration_successful" => "Inscription réussie", "user_email_waiting_verification" => "Votre email doit être vérifié. Veuillez vérifier votre email", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Supprimer l'utilisateur", "delete_user_info" => "La suppression d'un utilisateur supprimera également tous ses abonnements et paramètres.", "create_user" => "Créer un utilisateur", + "oidc_settings" => "Paramètres OIDC", + "oidc_auth_enabled" => "Authentification OIDC activée", + "create_user_automatically" => "Créer un utilisateur automatiquement", "smtp_settings" => "Paramètres SMTP", "smtp_usage_info" => "Sera utilisé pour la récupération du mot de passe et d'autres e-mails système.", "maintenance_tasks" => "Tâches de maintenance", diff --git a/includes/i18n/id.php b/includes/i18n/id.php index 08d956b..672a614 100644 --- a/includes/i18n/id.php +++ b/includes/i18n/id.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Silakan masuk", "stay_logged_in" => "Tetap masuk (30 hari)", "login" => "Masuk", + "login_with" => "Masuk dengan", + "or" => "atau", "login_failed" => "Detail masuk salah", "registration_successful" => "Pendaftaran berhasil", "user_email_waiting_verification" => "Email Anda perlu diverifikasi. Silakan periksa email Anda.", @@ -347,6 +349,9 @@ $i18n = [ "delete_user" => "Hapus Pengguna", "delete_user_info" => "Menghapus pengguna juga akan menghapus semua langganan dan pengaturan mereka.", "create_user" => "Buat Pengguna", + "oidc_settings" => "Pengaturan OIDC", + "oidc_oauth_enabled" => "Aktifkan OIDC/OAuth", + "create_user_automatically" => "Buat pengguna secara otomatis", "smtp_settings" => "Pengaturan SMTP", "smtp_usage_info" => "Akan digunakan untuk pemulihan kata sandi dan email sistem lainnya.", "maintenance_tasks" => "Tugas Pemeliharaan", diff --git a/includes/i18n/it.php b/includes/i18n/it.php index 4776ec8..e584b3b 100644 --- a/includes/i18n/it.php +++ b/includes/i18n/it.php @@ -22,6 +22,8 @@ $i18n = [ "please_login" => 'Per favore, accedi', "stay_logged_in" => 'Rimani connesso (30 giorni)', "login" => 'Accedi', + "login_with" => 'Accedi con', + "or" => 'o', "login_failed" => 'Le credenziali non sono corrette', "registration_successful" => "L'account è stato creato con successo", "user_email_waiting_verification" => "L'e-mail deve essere verificata. Controlla la tua email", @@ -364,6 +366,9 @@ $i18n = [ "delete_user" => "Elimina utente", "delete_user_info" => "L'eliminazione di un utente eliminerà anche tutte le sue iscrizioni e impostazioni.", "create_user" => "Crea utente", + "oidc_settings" => "Impostazioni OIDC", + "oidc_auth_enabled" => "Autenticazione OIDC abilitata", + "create_user_automatically" => "Crea utente automaticamente", "smtp_settings" => "Impostazioni SMTP", "smtp_usage_info" => "Verrà utilizzato per il recupero della password e altre e-mail di sistema.", "maintenance_tasks" => "Compiti di manutenzione", diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index 168fad8..5f916ee 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "ログインしてください", "stay_logged_in" => "ログインしたままにする (30日)", "login" => "ログイン", + "login_with" => "ログインする", + "or" => "または", "login_failed" => "ログイン情報が間違っています", "registration_successful" => "登録に成功", "user_email_waiting_verification" => "Eメールの確認が必要です。メールを確認してください。", @@ -347,6 +349,9 @@ $i18n = [ "delete_user" => "ユーザーを削除", "delete_user_info" => "ユーザーを削除すると、そのユーザーのサブスクリプションと設定もすべて削除されます。", "create_user" => "ユーザーを作成", + "oidc_settings" => "OIDC設定", + "oidc_auth_enabled" => "OIDC認証を有効にする", + "create_user_automatically" => "OIDCユーザーを自動的に作成する", "smtp_settings" => "SMTP設定", "smtp_usage_info" => "パスワードの回復やその他のシステム電子メールに使用されます。", "maintenance_tasks" => "メンテナンスタスク", diff --git a/includes/i18n/ko.php b/includes/i18n/ko.php index b66feff..4305bf0 100644 --- a/includes/i18n/ko.php +++ b/includes/i18n/ko.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "로그인 해 주세요.", "stay_logged_in" => "로그인 유지 (30일)", "login" => "로그인", + "login_with" => "다음으로 로그인", + "or" => "또는", "login_failed" => "로그인 정보가 부정확합니다.", "registration_successful" => "등록 성공", "user_email_waiting_verification" => "이메일을 인증해야 합니다. 이메일을 확인해 주세요.", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "유저 삭제", "delete_user_info" => "사용자를 삭제하면 모든 구독 및 설정도 삭제됩니다.", "create_user" => "유저 생성", + "oidc_settings" => "OIDC 설정", + "oidc_auth_enabled" => "OIDC 인증 활성화", + "create_user_automatically" => "사용자 자동 생성", "smtp_settings" => "SMTP 설정", "smtp_usage_info" => "비밀번호 복구 및 기타 시스템 이메일에 사용됩니다.", "maintenance_tasks" => "유지보수 작업", diff --git a/includes/i18n/nl.php b/includes/i18n/nl.php index 90a42da..444b1bf 100644 --- a/includes/i18n/nl.php +++ b/includes/i18n/nl.php @@ -20,7 +20,9 @@ $i18n = [ // Login Page "please_login" => "Login", "stay_logged_in" => "Ingelogd blijven (30 dagen)", - "login" => "Inloggen", + "login" => "Inloggen", + "login_with" => "Inloggen met", + "or" => "of", "login_failed" => "Inloggegevens zijn onjuist", "registration_successful" => "Registratie succesvol", "user_email_waiting_verification" => "Je e-mail moet worden geverifieerd. Controleer het e-mail bericht.", @@ -347,6 +349,9 @@ $i18n = [ "delete_user" => "Gebruiker verwijderen", "delete_user_info" => "Het verwijderen van een gebruiker zal ook al hun abonnementen en instellingen verwijderen.", "create_user" => "Gebruiker aanmaken", + "oidc_settings" => "OIDC-instellingen", + "oidc_oauth_enabled" => "OIDC/OAuth inschakelen", + "create_user_automatically" => "Gebruiker automatisch aanmaken", "smtp_settings" => "SMTP-instellingen", "smtp_usage_info" => "Wordt gebruikt voor wachtwoordherstel en andere systeem e-mails.", "maintenance_tasks" => "Onderhoudstaken", diff --git a/includes/i18n/pl.php b/includes/i18n/pl.php index 06c036e..2f5b6f9 100644 --- a/includes/i18n/pl.php +++ b/includes/i18n/pl.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Proszę się zalogować", "stay_logged_in" => "Pozostań zalogowany (30 dni)", "login" => "Zaloguj się", + "login_with" => "Zaloguj się przez", + "or" => "lub", "login_failed" => "Dane logowania są nieprawidłowe", "registration_successful" => "Pomyślnie zarejestrowano", "user_email_waiting_verification" => "Twój adres e-mail musi zostać zweryfikowany. Sprawdź swój adres e-mail", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Usuń użytkownika", "delete_user_info" => "Usunięcie użytkownika spowoduje również usunięcie wszystkich jego subskrypcji i ustawień.", "create_user" => "Utwórz użytkownika", + "oidc_settings" => "Ustawienia OIDC", + "oidc_auth_enabled" => "Włącz uwierzytelnianie OIDC", + "create_user_automatically" => "Automatycznie twórz użytkowników", "smtp_settings" => "Ustawienia SMTP", "smtp_usage_info" => "Będzie używany do odzyskiwania hasła i innych e-maili systemowych.", "maintenance_tasks" => "Zadania konserwacyjne", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index 98df33b..12b9c15 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Por favor inicie sessão", "stay_logged_in" => "Manter sessão (30 dias)", "login" => "Iniciar Sessão", + "login_with" => "Iniciar sessão com", + "or" => "ou", "login_failed" => "Dados de autenticação incorrectos", "registration_successful" => "Registo efectuado com sucesso.", "user_email_waiting_verification" => "O seu e-mail precisa de ser validado. Verifique o seu correio eletrónico", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Apagar Utilizador", "delete_user_info" => "Apagar utilizador irá remover todas as suas subscrições e dados associados.", "create_user" => "Criar Utilizador", + "oidc_settings" => "Definições OIDC", + "oidc_auth_enabled" => "Activar autenticação OIDC", + "create_user_automatically" => "Criar utilizador automaticamente", "smtp_settings" => "Definições SMTP", "smtp_usage_info" => "Será usado para recuperações de password e outros emails do sistema.", "maintenance_tasks" => "Tarefas de Manutenção", diff --git a/includes/i18n/pt_br.php b/includes/i18n/pt_br.php index 0f8c9e0..37434f2 100644 --- a/includes/i18n/pt_br.php +++ b/includes/i18n/pt_br.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Por favor, faça o login", "stay_logged_in" => "Me manter logado (30 dias)", "login" => "Login", + "login_with" => "Entrar com", + "or" => "ou", "login_failed" => "As informações de login estão incorretas", "registration_successful" => "Registro bem-sucedido", "user_email_waiting_verification" => "Seu e-mail precisa ser validado. Por favor, verifique seu e-mail", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Excluir usuário", "delete_user_info" => "Excluir um usuário também excluirá todas as assinaturas e dados associados", "create_user" => "Criar usuário", + "oidc_settings" => "Configurações OIDC", + "oidc_auth_enabled" => "Habilitar autenticação OIDC", + "create_user_automatically" => "Criar usuário automaticamente", "smtp_settings" => "Configurações SMTP", "smtp_usage_info" => "Será usado para recuperação de senha e outros e-mails do sistema.", "maintenance_tasks" => "Tarefas de manutenção", diff --git a/includes/i18n/ru.php b/includes/i18n/ru.php index 68a65dd..b840bff 100644 --- a/includes/i18n/ru.php +++ b/includes/i18n/ru.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Пожалуйста, войдите", "stay_logged_in" => "Оставаться в системе (30 дней)", "login" => "Авторизоваться", + "login_with" => "Войти с помощью", + "or" => "или", "login_failed" => "Данные для входа неверны", "registration_successful" => "Регистрация прошла успешно", "user_email_waiting_verification" => "Ваша электронная почта нуждается в проверке. Пожалуйста, проверьте свою электронную почту", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Удалить пользователя", "delete_user_info" => "Удаление пользователя также приведет к удалению всех его подписок и настроек.", "create_user" => "Создать пользователя", + "oidc_settings" => "Настройки OIDC", + "oidc_auth_enabled" => "Включить OIDC аутентификацию", + "create_user_automatically" => "Автоматически создавать пользователей", "smtp_settings" => "Настройки SMTP", "smtp_usage_info" => "Будет использоваться для восстановления пароля и других системных писем.", "maintenance_tasks" => "Задачи обслуживания", diff --git a/includes/i18n/sl.php b/includes/i18n/sl.php index 5c7ec8f..01a9aa3 100644 --- a/includes/i18n/sl.php +++ b/includes/i18n/sl.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Prosim prijavite se", "stay_logged_in" => "Ostanite prijavljeni (30 dni)", "login" => "Prijava", + "login_with" => "Prijavite se z", + "or" => "ali", "login_failed" => "Podatki za prijavo so napačni", "registration_successful" => "Registracija uspešna", "user_email_waiting_verification" => "Vaš e-poštni naslov je treba preveriti. Prosim, preglejte vašo e-pošto.", @@ -339,6 +341,9 @@ $i18n = [ "delete_user" => "Izbriši uporabnika", "delete_user_info" => "Če izbrišete uporabnika, boste izbrisali tudi vse njegove naročnine in nastavitve.", "create_user" => "Ustvari uporabnika", + "oidc_settings" => "OIDC nastavitve", + "oidc_auth_enabled" => "Omogoči OIDC prijavo", + "create_user_automatically" => "Samodejno ustvari uporabnika", "smtp_settings" => "Nastavitve SMTP", "smtp_usage_info" => "Uporabljeno bo za obnovitev gesla in druge sistemske e-pošte.", "maintenance_tasks" => "Vzdrževalne naloge", diff --git a/includes/i18n/sr.php b/includes/i18n/sr.php index c8e401d..29dccb3 100644 --- a/includes/i18n/sr.php +++ b/includes/i18n/sr.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Молимо вас да се пријавите", "stay_logged_in" => "Остани пријављен (30 дана)", "login" => "Пријави се", + "login_with" => "Пријави се са", + "or" => "или", "login_failed" => "Подаци за пријаву нису исправни", "registration_successful" => "Пријава успешна", "user_email_waiting_verification" => "Ваша е-пошта треба да буде верификована. Молимо прегледајте Е-пошту", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Обриши корисника", "delete_user_info" => "Брисање корисника ће такође обрисати све његове претплате и податке.", "create_user" => "Креирај корисника", + "oidc_settings" => "OIDC подешавања", + "oidc_auth_enabled" => "OIDC аутентификација је омогућена", + "create_user_automatically" => "Креирај корисника аутоматски", "smtp_settings" => "SMTP подешавања", "smtp_usage_info" => "SMTP се користи за слање е-поште за обавештења.", "maintenance_tasks" => "Одржавање", diff --git a/includes/i18n/sr_lat.php b/includes/i18n/sr_lat.php index 576b793..d1b72c0 100644 --- a/includes/i18n/sr_lat.php +++ b/includes/i18n/sr_lat.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Molimo vas da se prijavite", "stay_logged_in" => "Ostani prijavljen (30 dana)", "login" => "Prijavi se", + "login_with" => "Prijavi se sa", + "or" => "ili", "login_failed" => "Podaci za prijavu nisu ispravni", "registration_successful" => "Registracija uspešna", "user_email_waiting_verification" => "Vaša e-pošta treba da bude verifikovana. Molimo pregledajte E-poštu", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Izbriši korisnika", "delete_user_info" => "Brisanjem korisnika izbrisaće se i sve njegove pretplate i podešavanja.", "create_user" => "Kreiraj korisnika", + "oidc_settings" => "OIDC podešavanja", + "oidc_auth_enabled" => "Omogući OIDC autentifikaciju", + "create_user_automatically" => "Kreiraj korisnika automatski", "smtp_settings" => "SMTP podešavanja", "smtp_usage_info" => "Koristiće se za oporavak lozinke i druge sistemske e-poruke.", "maintenance_tasks" => "Održavanje", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index 341fd2a..a88e996 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Lütfen giriş yapın", "stay_logged_in" => "Oturumu açık tut (30 gün)", "login" => "Giriş Yap", + "login_with" => "Şununla giriş yap", + "or" => "veya", "login_failed" => "Giriş bilgileri hatalı", "registration_successful" => "Kayıt başarılı", "user_email_waiting_verification" => "E-postanızın doğrulanması gerekiyor. Lütfen e-postanızı kontrol edin", @@ -346,6 +348,9 @@ $i18n = [ "delete_user" => "Kullanıcıyı Sil", "delete_user_info" => "Bir kullanıcının silinmesi aynı zamanda tüm aboneliklerinin ve ayarlarının da silinmesine neden olur.", "create_user" => "Kullanıcı Oluştur", + "oidc_settings" => "OpenID Connect Ayarları", + "oidc_auth_enabled" => "OpenID Connect Kimlik Doğrulaması Etkinleştirildi", + "create_user_automatically" => "OpenID Connect ile giriş yapıldığında kullanıcı otomatik olarak oluşturulsun", "smtp_settings" => "SMTP Ayarları", "smtp_usage_info" => "Şifre kurtarma ve diğer sistem e-postaları için kullanılacaktır.", "maintenance_tasks" => "Bakım Görevleri", diff --git a/includes/i18n/uk.php b/includes/i18n/uk.php index cdd2a3b..50528a3 100644 --- a/includes/i18n/uk.php +++ b/includes/i18n/uk.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Будь ласка, увійдіть", "stay_logged_in" => "Залишатися в системі (30 днів)", "login" => "Авторизуватися", + "login_with" => "Увійти з", + "or" => "або", "login_failed" => "Дані для входу невірні", "registration_successful" => "Реєстрація пройшла успішно", "user_email_waiting_verification" => "Ваша електронна адреса потребує перевірки. Будь ласка, перевірте свою електронну скриньку.", @@ -347,6 +349,9 @@ $i18n = [ "delete_user_info" => "Видалення користувача також призведе до видалення всіх його підписок та налаштувань.", "create_user" => "Створити користувача", "smtp_settings" => "Налаштування SMTP", + "oidc_settings" => "Налаштування OIDC", + "oidc_auth_enabled" => "Увімкнути OIDC автентифікацію", + "create_user_automatically" => "Автоматично створювати користувача при вході", "smtp_usage_info" => "Буде використовуватися для відновлення пароля та інших системних листів.", "maintenance_tasks" => "Завдання обслуговування", "orphaned_logos" => "Втрачений логотип", diff --git a/includes/i18n/vi.php b/includes/i18n/vi.php index fdecf11..4f4ab55 100644 --- a/includes/i18n/vi.php +++ b/includes/i18n/vi.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "Vui lòng đăng nhập", "stay_logged_in" => "Giữ đăng nhập (30 ngày)", "login" => "Đăng nhập", + "login_with" => "Đăng nhập với", + "or" => "hoặc", "login_failed" => "Thông tin đăng nhập không chính xác", "registration_successful" => "Đăng ký thành công", "user_email_waiting_verification" => "Email của bạn cần được xác minh. Vui lòng kiểm tra email.", @@ -347,6 +349,9 @@ $i18n = [ "delete_user" => "Xóa người dùng", "delete_user_info" => "Xóa một người dùng cũng sẽ xóa tất cả các đăng ký và cài đặt của họ.", "create_user" => "Tạo người dùng", + "oidc_settings" => "Cài đặt OIDC", + "oidc_auth_enabled" => "Xác thực OIDC đã được bật", + "create_user_automatically" => "Tạo người dùng tự động", "smtp_settings" => "Cài đặt SMTP", "smtp_usage_info" => "Sẽ được sử dụng cho việc khôi phục mật khẩu và các email hệ thống khác.", "maintenance_tasks" => "Nhiệm vụ bảo trì", diff --git a/includes/i18n/zh_cn.php b/includes/i18n/zh_cn.php index 9a021fc..d8b7cf0 100644 --- a/includes/i18n/zh_cn.php +++ b/includes/i18n/zh_cn.php @@ -22,6 +22,8 @@ $i18n = [ "please_login" => "请登录", "stay_logged_in" => "30 天内免登录", "login" => "登录", + "login_with" => "使用以下方式登录", + "or" => "或", "login_failed" => "登录信息错误", "registration_successful" => "注册成功", "user_email_waiting_verification" => "您的电子邮件需要验证。请检查您的电子邮件", @@ -364,6 +366,9 @@ $i18n = [ "delete_user" => "删除用户", "delete_user_info" => "删除用户也会删除其所有订阅和设置。", "create_user" => "创建用户", + "oidc_settings" => "OIDC 设置", + "oidc_auth_enabled" => "启用 OIDC 身份验证", + "create_user_automatically" => "当使用 OIDC 登录时自动创建用户", "smtp_settings" => "SMTP 设置", "smtp_usage_info" => "将用于密码恢复和其他系统电子邮件。", "maintenance_tasks" => "维护任务", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index c9f0a2b..098d67c 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -21,6 +21,8 @@ $i18n = [ "please_login" => "請先登入", "stay_logged_in" => "保持登入狀態(30 天)", "login" => "登入", + "login_with" => "使用以下方式登入", + "or" => "或", "login_failed" => "登入資訊錯誤", "registration_successful" => "註冊成功", "user_email_waiting_verification" => "您的電子郵件需要驗證。請檢查您的電子郵件信箱。", @@ -347,6 +349,9 @@ $i18n = [ "delete_user" => "刪除使用者", "delete_user_info" => "刪除使用者也會刪除其所有訂閱和設定。", "create_user" => "建立使用者", + "oidc_settings" => "OIDC 設定", + "oidc_auth_enabled" => "啟用 OIDC 身份驗證", + "create_user_automatically" => "當使用 OIDC 登入時自動建立使用者", "smtp_settings" => "SMTP 設定", "smtp_usage_info" => "用於密碼重設和其他系統郵件。", "maintenance_tasks" => "維護工作", diff --git a/includes/oidc/handle_oidc_callback.php b/includes/oidc/handle_oidc_callback.php new file mode 100644 index 0000000..c22fb3d --- /dev/null +++ b/includes/oidc/handle_oidc_callback.php @@ -0,0 +1,128 @@ +prepare('SELECT * FROM oauth_settings WHERE id = 1'); +$result = $stmt->execute(); +$oidcSettings = $result->fetchArray(SQLITE3_ASSOC); + +$tokenUrl = $oidcSettings['token_url']; +$redirectUri = $oidcSettings['redirect_url']; + +$postFields = [ + 'grant_type' => 'authorization_code', + 'code' => $_GET['code'], + 'redirect_uri' => $redirectUri, + 'client_id' => $oidcSettings['client_id'], + 'client_secret' => $oidcSettings['client_secret'], +]; + +$ch = curl_init($tokenUrl); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postFields)); +curl_setopt($ch, CURLOPT_POST, true); +curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']); +$response = curl_exec($ch); +curl_close($ch); + +$tokenData = json_decode($response, true); +if (!$tokenData || !isset($tokenData['access_token'])) { + die("OIDC token exchange failed."); +} + +$userInfoUrl = $oidcSettings['user_info_url']; + +$ch = curl_init($userInfoUrl); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ' . $tokenData['access_token'] +]); +$response = curl_exec($ch); +curl_close($ch); + +$userInfo = json_decode($response, true); +if (!$userInfo || !isset($userInfo[$oidcSettings['user_identifier_field']])) { + die("Failed to fetch OIDC user info."); +} + +$oidcSub = $userInfo[$oidcSettings['user_identifier_field']]; + +// Check if sub matches an existing user +$stmt = $db->prepare('SELECT * FROM user WHERE oidc_sub = :oidcSub'); +$stmt->bindValue(':oidcSub', $oidcSub, SQLITE3_TEXT); +$result = $stmt->execute(); +$userData = $result->fetchArray(SQLITE3_ASSOC); + +if ($userData) { + // User exists, log the user in + require_once('oidc_login.php'); + +} else { + // Might be an existing user with the same email + $email = $userInfo['email'] ?? null; + + if (!$email) { + // Login failed, we have nothing to go on with, redirect to login page with error + header("Location: login.php?error=oidc_user_not_found"); + exit(); + } + + $stmt = $db->prepare('SELECT * FROM user WHERE email = :email'); + $stmt->bindValue(':email', $email, SQLITE3_TEXT); + $result = $stmt->execute(); + $userData = $result->fetchArray(SQLITE3_ASSOC); + if ($userData) { + // Update existing user with OIDC sub + $stmt = $db->prepare('UPDATE user SET oidc_sub = :oidcSub WHERE id = :userId'); + $stmt->bindValue(':oidcSub', $oidcSub, SQLITE3_TEXT); + $stmt->bindValue(':userId', $userData['id'], SQLITE3_INTEGER); + $stmt->execute(); + + // Log the user in + require_once('oidc_login.php'); + } else { + // Check if auto-create is enabled + if ($oidcSettings['auto_create_user']) { + // Create a new user + + //check if username is already taken + $usernameBase = $userInfo['preferred_username'] ?? generate_username_from_email($email); + $username = $usernameBase; + $attempt = 1; + + while (true) { + $stmt = $db->prepare('SELECT COUNT(*) as count FROM user WHERE username = :username'); + $stmt->bindValue(':username', $username, SQLITE3_TEXT); + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + + if ($row['count'] == 0) { + break; // Username is available + } + + $username = $usernameBase . $attempt; + $attempt++; + } + + require_once('oidc_create_user.php'); + + + } else { + // Login failed, redirect to login page with error + header("Location: login.php?error=oidc_user_not_found"); + exit(); + } + } +} + + +?> \ No newline at end of file diff --git a/includes/oidc/oidc_create_user.php b/includes/oidc/oidc_create_user.php new file mode 100644 index 0000000..8e1a72b --- /dev/null +++ b/includes/oidc/oidc_create_user.php @@ -0,0 +1,179 @@ +prepare($query); +$stmt->bindValue(':username', $username, SQLITE3_TEXT); +$stmt->bindValue(':email', $email, SQLITE3_TEXT); +$stmt->bindValue(':oidc_sub', $oidcSub, SQLITE3_TEXT); +$stmt->bindValue(':main_currency', $main_currency_id, SQLITE3_INTEGER); +$stmt->bindValue(':avatar', $avatar, SQLITE3_TEXT); +$stmt->bindValue(':language', $language, SQLITE3_TEXT); +$stmt->bindValue(':budget', $budget, SQLITE3_INTEGER); +$stmt->bindValue(':firstname', $firstname, SQLITE3_TEXT); +$stmt->bindValue(':lastname', $lastname, SQLITE3_TEXT); +$stmt->bindValue(':password', $hashedPassword, SQLITE3_TEXT); + +if (!$stmt->execute()) { + die("Failed to create user"); +} + +// Get the user data into $userData +$stmt = $db->prepare("SELECT * FROM user WHERE username = :username"); +$stmt->bindValue(':username', $username, SQLITE3_TEXT); +$result = $stmt->execute(); +$userData = $result->fetchArray(SQLITE3_ASSOC); +$newUserId = $userData['id']; + +// Household +$stmt = $db->prepare("INSERT INTO household (name, user_id) VALUES (:name, :user_id)"); +$stmt->bindValue(':name', $username, SQLITE3_TEXT); +$stmt->bindValue(':user_id', $newUserId, SQLITE3_INTEGER); +$stmt->execute(); + +// Categories +$categories = [ + 'No category', 'Entertainment', 'Music', 'Utilities', 'Food & Beverages', + 'Health & Wellbeing', 'Productivity', 'Banking', 'Transport', 'Education', + 'Insurance', 'Gaming', 'News & Magazines', 'Software', 'Technology', + 'Cloud Services', 'Charity & Donations' +]; + +$stmt = $db->prepare("INSERT INTO categories (name, \"order\", user_id) VALUES (:name, :order, :user_id)"); +foreach ($categories as $index => $name) { + $stmt->bindValue(':name', $name, SQLITE3_TEXT); + $stmt->bindValue(':order', $index + 1, SQLITE3_INTEGER); + $stmt->bindValue(':user_id', $newUserId, SQLITE3_INTEGER); + $stmt->execute(); +} + +// Payment Methods +$payment_methods = [ + ['name' => 'PayPal', 'icon' => 'images/uploads/icons/paypal.png'], + ['name' => 'Credit Card', 'icon' => 'images/uploads/icons/creditcard.png'], + ['name' => 'Bank Transfer', 'icon' => 'images/uploads/icons/banktransfer.png'], + ['name' => 'Direct Debit', 'icon' => 'images/uploads/icons/directdebit.png'], + ['name' => 'Money', 'icon' => 'images/uploads/icons/money.png'], + ['name' => 'Google Pay', 'icon' => 'images/uploads/icons/googlepay.png'], + ['name' => 'Samsung Pay', 'icon' => 'images/uploads/icons/samsungpay.png'], + ['name' => 'Apple Pay', 'icon' => 'images/uploads/icons/applepay.png'], + ['name' => 'Crypto', 'icon' => 'images/uploads/icons/crypto.png'], + ['name' => 'Klarna', 'icon' => 'images/uploads/icons/klarna.png'], + ['name' => 'Amazon Pay', 'icon' => 'images/uploads/icons/amazonpay.png'], + ['name' => 'SEPA', 'icon' => 'images/uploads/icons/sepa.png'], + ['name' => 'Skrill', 'icon' => 'images/uploads/icons/skrill.png'], + ['name' => 'Sofort', 'icon' => 'images/uploads/icons/sofort.png'], + ['name' => 'Stripe', 'icon' => 'images/uploads/icons/stripe.png'], + ['name' => 'Affirm', 'icon' => 'images/uploads/icons/affirm.png'], + ['name' => 'AliPay', 'icon' => 'images/uploads/icons/alipay.png'], + ['name' => 'Elo', 'icon' => 'images/uploads/icons/elo.png'], + ['name' => 'Facebook Pay', 'icon' => 'images/uploads/icons/facebookpay.png'], + ['name' => 'GiroPay', 'icon' => 'images/uploads/icons/giropay.png'], + ['name' => 'iDeal', 'icon' => 'images/uploads/icons/ideal.png'], + ['name' => 'Union Pay', 'icon' => 'images/uploads/icons/unionpay.png'], + ['name' => 'Interac', 'icon' => 'images/uploads/icons/interac.png'], + ['name' => 'WeChat', 'icon' => 'images/uploads/icons/wechat.png'], + ['name' => 'Paysafe', 'icon' => 'images/uploads/icons/paysafe.png'], + ['name' => 'Poli', 'icon' => 'images/uploads/icons/poli.png'], + ['name' => 'Qiwi', 'icon' => 'images/uploads/icons/qiwi.png'], + ['name' => 'ShopPay', 'icon' => 'images/uploads/icons/shoppay.png'], + ['name' => 'Venmo', 'icon' => 'images/uploads/icons/venmo.png'], + ['name' => 'VeriFone', 'icon' => 'images/uploads/icons/verifone.png'], + ['name' => 'WebMoney', 'icon' => 'images/uploads/icons/webmoney.png'], +]; + +$stmt = $db->prepare("INSERT INTO payment_methods (name, icon, \"order\", user_id) VALUES (:name, :icon, :order, :user_id)"); +foreach ($payment_methods as $index => $method) { + $stmt->bindValue(':name', $method['name'], SQLITE3_TEXT); + $stmt->bindValue(':icon', $method['icon'], SQLITE3_TEXT); + $stmt->bindValue(':order', $index + 1, SQLITE3_INTEGER); + $stmt->bindValue(':user_id', $newUserId, SQLITE3_INTEGER); + $stmt->execute(); +} + +// Currencies +$currencies = [ + ['name' => 'Euro', 'symbol' => '€', 'code' => 'EUR'], + ['name' => 'US Dollar', 'symbol' => '$', 'code' => 'USD'], + ['name' => 'Japanese Yen', 'symbol' => '¥', 'code' => 'JPY'], + ['name' => 'Bulgarian Lev', 'symbol' => 'лв', 'code' => 'BGN'], + ['name' => 'Czech Republic Koruna', 'symbol' => 'Kč', 'code' => 'CZK'], + ['name' => 'Danish Krone', 'symbol' => 'kr', 'code' => 'DKK'], + ['name' => 'British Pound Sterling', 'symbol' => '£', 'code' => 'GBP'], + ['name' => 'Hungarian Forint', 'symbol' => 'Ft', 'code' => 'HUF'], + ['name' => 'Polish Zloty', 'symbol' => 'zł', 'code' => 'PLN'], + ['name' => 'Romanian Leu', 'symbol' => 'lei', 'code' => 'RON'], + ['name' => 'Swedish Krona', 'symbol' => 'kr', 'code' => 'SEK'], + ['name' => 'Swiss Franc', 'symbol' => 'Fr', 'code' => 'CHF'], + ['name' => 'Icelandic Króna', 'symbol' => 'kr', 'code' => 'ISK'], + ['name' => 'Norwegian Krone', 'symbol' => 'kr', 'code' => 'NOK'], + ['name' => 'Russian Ruble', 'symbol' => '₽', 'code' => 'RUB'], + ['name' => 'Turkish Lira', 'symbol' => '₺', 'code' => 'TRY'], + ['name' => 'Australian Dollar', 'symbol' => '$', 'code' => 'AUD'], + ['name' => 'Brazilian Real', 'symbol' => 'R$', 'code' => 'BRL'], + ['name' => 'Canadian Dollar', 'symbol' => '$', 'code' => 'CAD'], + ['name' => 'Chinese Yuan', 'symbol' => '¥', 'code' => 'CNY'], + ['name' => 'Hong Kong Dollar', 'symbol' => 'HK$', 'code' => 'HKD'], + ['name' => 'Indonesian Rupiah', 'symbol' => 'Rp', 'code' => 'IDR'], + ['name' => 'Israeli New Sheqel', 'symbol' => '₪', 'code' => 'ILS'], + ['name' => 'Indian Rupee', 'symbol' => '₹', 'code' => 'INR'], + ['name' => 'South Korean Won', 'symbol' => '₩', 'code' => 'KRW'], + ['name' => 'Mexican Peso', 'symbol' => 'Mex$', 'code' => 'MXN'], + ['name' => 'Malaysian Ringgit', 'symbol' => 'RM', 'code' => 'MYR'], + ['name' => 'New Zealand Dollar', 'symbol' => 'NZ$', 'code' => 'NZD'], + ['name' => 'Philippine Peso', 'symbol' => '₱', 'code' => 'PHP'], + ['name' => 'Singapore Dollar', 'symbol' => 'S$', 'code' => 'SGD'], + ['name' => 'Thai Baht', 'symbol' => '฿', 'code' => 'THB'], + ['name' => 'South African Rand', 'symbol' => 'R', 'code' => 'ZAR'], + ['name' => 'Ukrainian Hryvnia', 'symbol' => '₴', 'code' => 'UAH'], + ['name' => 'New Taiwan Dollar', 'symbol' => 'NT$', 'code' => 'TWD'], +]; + +$stmt = $db->prepare("INSERT INTO currencies (name, symbol, code, rate, user_id) + VALUES (:name, :symbol, :code, :rate, :user_id)"); +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.0, SQLITE3_FLOAT); + $stmt->bindValue(':user_id', $newUserId, SQLITE3_INTEGER); + $stmt->execute(); +} + +// Get actual Euro currency ID +$stmt = $db->prepare("SELECT id FROM currencies WHERE code = 'EUR' AND user_id = :user_id"); +$stmt->bindValue(':user_id', $newUserId, SQLITE3_INTEGER); +$result = $stmt->execute(); +$currency = $result->fetchArray(SQLITE3_ASSOC); +if ($currency) { + $stmt = $db->prepare("UPDATE user SET main_currency = :main_currency WHERE id = :user_id"); + $stmt->bindValue(':main_currency', $currency['id'], SQLITE3_INTEGER); + $stmt->bindValue(':user_id', $newUserId, SQLITE3_INTEGER); + $stmt->execute(); +} + +$userData['main_currency'] = $currency['id']; + +// Insert settings +$stmt = $db->prepare("INSERT INTO settings (dark_theme, monthly_price, convert_currency, remove_background, color_theme, hide_disabled, user_id, disabled_to_bottom, show_original_price, mobile_nav) + VALUES (2, 0, 0, 0, 'blue', 0, :user_id, 0, 0, 0)"); +$stmt->bindValue(':user_id', $newUserId, SQLITE3_INTEGER); +$stmt->execute(); + +// Log the user in +require_once('oidc_login.php'); \ No newline at end of file diff --git a/includes/oidc/oidc_login.php b/includes/oidc/oidc_login.php new file mode 100644 index 0000000..56c5c35 --- /dev/null +++ b/includes/oidc/oidc_login.php @@ -0,0 +1,63 @@ +prepare($addLoginTokens); +$addLoginTokensStmt->bindParam(':userId', $userId, SQLITE3_INTEGER); +$addLoginTokensStmt->bindParam(':token', $token, SQLITE3_TEXT); +$addLoginTokensStmt->execute(); + +$_SESSION['token'] = $token; +$cookieValue = $username . "|" . $token . "|" . $main_currency; +setcookie('wallos_login', $cookieValue, [ + 'expires' => $cookieExpire, + 'samesite' => 'Strict' +]); + +// Set language cookie +setcookie('language', $language, [ + 'expires' => $cookieExpire, + 'samesite' => 'Strict' +]); + +// Set sort order default +if (!isset($_COOKIE['sortOrder'])) { + setcookie('sortOrder', 'next_payment', [ + 'expires' => $cookieExpire, + 'samesite' => 'Strict' + ]); +} + +// Set color theme +$query = "SELECT color_theme FROM settings WHERE user_id = :userId"; +$stmt = $db->prepare($query); +$stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); +$result = $stmt->execute(); +$settings = $result->fetchArray(SQLITE3_ASSOC); +setcookie('colorTheme', $settings['color_theme'], [ + 'expires' => $cookieExpire, + 'samesite' => 'Strict' +]); + +// Done +$db->close(); +header("Location: ."); +exit(); diff --git a/includes/version.php b/includes/version.php index ddb4f43..5ed2b4d 100644 --- a/includes/version.php +++ b/includes/version.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/login.php b/login.php index 0f99730..8429327 100644 --- a/login.php +++ b/login.php @@ -108,6 +108,44 @@ if (isset($_COOKIE['colorTheme'])) { $colorTheme = $_COOKIE['colorTheme']; } +// Check if OIDC is Enabled +$oidcEnabled = false; +$oidcQuery = "SELECT oidc_oauth_enabled FROM admin"; +$oidcResult = $db->query($oidcQuery); +$oidcRow = $oidcResult->fetchArray(SQLITE3_ASSOC); +if ($oidcRow) { + $oidcEnabled = $oidcRow['oidc_oauth_enabled'] == 1; + if ($oidcEnabled) { + // Fetch OIDC settings + $oidcSettingsQuery = "SELECT * FROM oauth_settings WHERE id = 1"; + $oidcSettingsResult = $db->query($oidcSettingsQuery); + $oidcSettings = $oidcSettingsResult->fetchArray(SQLITE3_ASSOC); + if (!$oidcSettings) { + $oidcEnabled = false; + } else { + $oidc_name = $oidcSettings['name'] ?? ''; + + // Generate a CSRF-protecting state string + if (session_status() === PHP_SESSION_NONE) { + session_start(); + } + $state = bin2hex(random_bytes(16)); + $_SESSION['oidc_state'] = $state; + + // Build the OIDC authorization URL + $params = http_build_query([ + 'response_type' => 'code', + 'client_id' => $oidcSettings['client_id'], + 'redirect_uri' => $oidcSettings['redirect_url'], + 'scope' => $oidcSettings['scopes'], + 'state' => $state, + ]); + + $oidc_auth_url = rtrim($oidcSettings['authorization_url'], '?') . '?' . $params; + } + } +} + $loginFailed = false; $hasSuccessMessage = (isset($_GET['validated']) && $_GET['validated'] == "true") || (isset($_GET['registered']) && $_GET['registered'] == true) ? true : false; $userEmailWaitingVerification = false; @@ -234,6 +272,10 @@ if ($adminRow['smtp_address'] != "" && $adminRow['server_url'] != "") { $resetPasswordEnabled = true; } +if(isset($_GET['error']) && $_GET['error'] == "oidc_user_not_found") { + $loginFailed = true; +} + ?> @@ -297,6 +339,16 @@ if ($adminRow['smtp_address'] != "" && $adminRow['server_url'] != "") { ?>
+ + + + + +
prepare('SELECT * FROM oauth_settings WHERE id = 1'); + $result = $stmt->execute(); + $oidcSettings = $result->fetchArray(SQLITE3_ASSOC); + $logoutUrl = $oidcSettings['logout_url'] ?? ''; +} + // get token from cookie to remove from DB if (isset($_SESSION['token'])) { $token = $_SESSION['token']; @@ -15,6 +28,12 @@ session_destroy(); $cookieExpire = time() - 3600; setcookie('wallos_login', '', $cookieExpire); $db->close(); + +if ($logoutOIDC && !empty($logoutUrl)) { + $returnTo = urlencode($oidcSettings['redirect_url'] ?? ''); + header("Location: $logoutUrl?post_logout_redirect_uri=$returnTo"); + exit(); +} + header("Location: ."); -exit(); -?> \ No newline at end of file +exit(); \ No newline at end of file diff --git a/migrations/000001.php b/migrations/000001.php index a787c37..157fa9f 100644 --- a/migrations/000001.php +++ b/migrations/000001.php @@ -1,6 +1,5 @@ exec('CREATE TABLE IF NOT EXISTS migrations ( id INTEGER PRIMARY KEY, migration TEXT NOT NULL, diff --git a/migrations/000002.php b/migrations/000002.php index af478cf..327ba33 100644 --- a/migrations/000002.php +++ b/migrations/000002.php @@ -2,7 +2,6 @@ // This migration adds an "enabled" column to the payment_methods table and sets all values to 1. // It allows the user to disable payment methods without deleting them. -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('payment_methods') where name='enabled'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000003.php b/migrations/000003.php index 90d127f..27f8ced 100644 --- a/migrations/000003.php +++ b/migrations/000003.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('notifications') where name='from_email'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000004.php b/migrations/000004.php index 9f1ec44..0184741 100644 --- a/migrations/000004.php +++ b/migrations/000004.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('subscriptions') where name='url'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000005.php b/migrations/000005.php index b71bb79..2a531c6 100644 --- a/migrations/000005.php +++ b/migrations/000005.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('user') where name='language'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000006.php b/migrations/000006.php index e52c45e..5b64d61 100644 --- a/migrations/000006.php +++ b/migrations/000006.php @@ -2,7 +2,6 @@ // This migration adds a "provider" column to the fixer table and sets all values to 0. // It allows the user to chose a different provider for their fixer api keys. -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('fixer') where name='provider'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000007.php b/migrations/000007.php index f6e0723..8f09941 100644 --- a/migrations/000007.php +++ b/migrations/000007.php @@ -2,7 +2,6 @@ // This migration adds a new table to store the display and experimental settings // This settings will now be persisted across sessions and devices -/** @noinspection PhpUndefinedVariableInspection */ $db->exec('CREATE TABLE IF NOT EXISTS settings ( dark_theme BOOLEAN DEFAULT 0, monthly_price BOOLEAN DEFAULT 0, diff --git a/migrations/000008.php b/migrations/000008.php index bc0447f..0a54705 100644 --- a/migrations/000008.php +++ b/migrations/000008.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('subscriptions') WHERE name='inactive'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000009.php b/migrations/000009.php index 0fd0215..6f3048c 100644 --- a/migrations/000009.php +++ b/migrations/000009.php @@ -2,7 +2,6 @@ // This migration adds an "email" column to the members table. // It allows the household member to receive notifications when their subscriptions are about to expire. -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('household') where name='email'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000010.php b/migrations/000010.php index 5c65e19..bb8c754 100644 --- a/migrations/000010.php +++ b/migrations/000010.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('categories') WHERE name='order'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000011.php b/migrations/000011.php index 743f476..ef07f90 100644 --- a/migrations/000011.php +++ b/migrations/000011.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('payment_methods') WHERE name='order'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000012.php b/migrations/000012.php index 6b8be85..6e8bf7b 100644 --- a/migrations/000012.php +++ b/migrations/000012.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('notifications') WHERE name='encryption'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000013.php b/migrations/000013.php index e7271a4..8d4bb67 100644 --- a/migrations/000013.php +++ b/migrations/000013.php @@ -4,7 +4,6 @@ * This migration script updates the avatar field of the user table to use the new avatar path. */ -/** @noinspection PhpUndefinedVariableInspection */ $sql = "SELECT avatar FROM user"; $stmt = $db->prepare($sql); $result = $stmt->execute(); diff --git a/migrations/000014.php b/migrations/000014.php index 18c61ad..a8e0f7e 100644 --- a/migrations/000014.php +++ b/migrations/000014.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('settings') where name='color_theme'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000015.php b/migrations/000015.php index 28c55a9..1a1cb5f 100644 --- a/migrations/000015.php +++ b/migrations/000015.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('settings') where name='hide_disabled'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000016.php b/migrations/000016.php index b333136..1cbd13f 100644 --- a/migrations/000016.php +++ b/migrations/000016.php @@ -5,7 +5,6 @@ * Existing values on the notifications table will be split and migrated to the new tables. */ -/** @noinspection PhpUndefinedVariableInspection */ $db->exec('CREATE TABLE IF NOT EXISTS telegram_notifications ( enabled BOOLEAN DEFAULT 0, bot_token TEXT DEFAULT "", diff --git a/migrations/000017.php b/migrations/000017.php index feac392..e838607 100644 --- a/migrations/000017.php +++ b/migrations/000017.php @@ -4,7 +4,6 @@ * This migration adds tables to store the date about the new notification methods (pushover and discord) */ -/** @noinspection PhpUndefinedVariableInspection */ $db->exec('CREATE TABLE IF NOT EXISTS pushover_notifications ( enabled BOOLEAN DEFAULT 0, user_key TEXT DEFAULT "", diff --git a/migrations/000018.php b/migrations/000018.php index b7a2599..3b7fbf2 100644 --- a/migrations/000018.php +++ b/migrations/000018.php @@ -4,7 +4,6 @@ This migration adds a column to the users table to store a monthly budget that will be used to calculate statistics */ -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('users') where name='budget'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000019.php b/migrations/000019.php index 41ee5d8..728044a 100644 --- a/migrations/000019.php +++ b/migrations/000019.php @@ -4,7 +4,6 @@ This migration adds a column to the subscriptuons table to store individual choi The default value of 0 means global settings will be used */ -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('subscriptions') where name='notify_days_before'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000020.php b/migrations/000020.php index 9bd1ce9..b6eb10f 100644 --- a/migrations/000020.php +++ b/migrations/000020.php @@ -4,8 +4,6 @@ / It also creates the admin table to store the admin settings. */ -/** @noinspection PhpUndefinedVariableInspection */ - $tablesToUpdate = ['payment_methods', 'subscriptions', 'categories', 'currencies', 'fixer', 'household', 'settings', 'custom_colors', 'notification_settings', 'telegram_notifications', 'webhook_notifications', 'gotify_notifications', 'email_notifications', 'pushover_notifications', 'discord_notifications', 'last_exchange_update']; foreach ($tablesToUpdate as $table) { $columnQuery = $db->query("SELECT * FROM pragma_table_info('$table') WHERE name='user_id'"); diff --git a/migrations/000021.php b/migrations/000021.php index a849e80..8ba7cd3 100644 --- a/migrations/000021.php +++ b/migrations/000021.php @@ -4,7 +4,6 @@ * This migration adds tables to store the data about a new notification method Ntfy */ -/** @noinspection PhpUndefinedVariableInspection */ $db->exec('CREATE TABLE IF NOT EXISTS ntfy_notifications ( enabled BOOLEAN DEFAULT 0, host TEXT DEFAULT "", diff --git a/migrations/000022.php b/migrations/000022.php index bb7737e..e117e55 100644 --- a/migrations/000022.php +++ b/migrations/000022.php @@ -4,7 +4,6 @@ This migration adds a column to the admin table to enable the option to disable login */ -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('admin') where name='login_disabled'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000023.php b/migrations/000023.php index c126d5a..c193492 100644 --- a/migrations/000023.php +++ b/migrations/000023.php @@ -4,7 +4,6 @@ * This migration adds a table to store custom css styles per user */ -/** @noinspection PhpUndefinedVariableInspection */ $db->exec('CREATE TABLE IF NOT EXISTS custom_css_style ( css TEXT DEFAULT "", user_id INTEGER, diff --git a/migrations/000024.php b/migrations/000024.php index 72bd314..3679241 100644 --- a/migrations/000024.php +++ b/migrations/000024.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('subscriptions') where name='cancellation_date'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000025.php b/migrations/000025.php index 26d0afd..60a8c2c 100644 --- a/migrations/000025.php +++ b/migrations/000025.php @@ -2,7 +2,6 @@ // This migration adds a "disabled_to_bottom" column to the settings table. // This magration also adds a latest_version and update_notification columns to the admin table. -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('settings') where name='disabled_to_bottom'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000026.php b/migrations/000026.php index ef4f118..cf9d39f 100644 --- a/migrations/000026.php +++ b/migrations/000026.php @@ -2,7 +2,6 @@ // This migration adds a "other_emails" column to the email_notifications table. // It also adds a "show_original_price" column to the settings table. -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('email_notifications') where name='other_emails'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000027.php b/migrations/000027.php index d09b359..cc977bb 100644 --- a/migrations/000027.php +++ b/migrations/000027.php @@ -3,7 +3,6 @@ // this migration adds a "totp_enabled" column to the user table // it also adds a "totp" table to the database -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('user') where name='totp_enabled'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000028.php b/migrations/000028.php index 1c776d1..2120097 100644 --- a/migrations/000028.php +++ b/migrations/000028.php @@ -2,7 +2,6 @@ // This migration adds a "mobile_nav" column to the settings table -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('settings') where name='mobile_nav'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000029.php b/migrations/000029.php index 658971d..3e65c91 100644 --- a/migrations/000029.php +++ b/migrations/000029.php @@ -3,7 +3,6 @@ // This migration adds a "api_key" column to the user table // It also generates an API key for each user -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('user') where name='api_key'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; @@ -11,7 +10,6 @@ if ($columnRequired) { $db->exec('ALTER TABLE user ADD COLUMN api_key TEXT'); } -/** @noinspection PhpUndefinedVariableInspection */ $users = $db->query('SELECT * FROM user'); while ($user = $users->fetchArray(SQLITE3_ASSOC)) { if (empty($user['api_key'])) { diff --git a/migrations/000030.php b/migrations/000030.php index 9f9fa01..6bb1ff5 100644 --- a/migrations/000030.php +++ b/migrations/000030.php @@ -5,7 +5,6 @@ // Add the ignore_ssl column to the webhook_notifications table -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('webhook_notifications') where name='ignore_ssl'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; @@ -15,7 +14,6 @@ if ($columnRequired) { // Add the ignore_ssl column to the ntfy_notifications table -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('ntfy_notifications') where name='ignore_ssl'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; @@ -25,7 +23,6 @@ if ($columnRequired) { // Add the ignore_ssl column to the gotify_notifications table -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('gotify_notifications') where name='ignore_ssl'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000031.php b/migrations/000031.php index 2f7540c..8b7224f 100644 --- a/migrations/000031.php +++ b/migrations/000031.php @@ -2,7 +2,6 @@ // This migration adds a "replacement_subscription_id" column to the subscriptions table // to allow users to track savings by replacing one subscription with another -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('subscriptions') where name='replacement_subscription_id'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000032.php b/migrations/000032.php index e747d51..7aeac66 100644 --- a/migrations/000032.php +++ b/migrations/000032.php @@ -3,7 +3,6 @@ // This migration adds a start_date column to the subscriptions table to store the start date of the subscription // This migration adds a auto_renew column to the subscriptions table to store if the subscription renews automatically or needs manual renewal -/** @noinspection PhpUndefinedVariableInspection */ $tableQuery = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='total_yearly_cost'"); $tableRequired = $tableQuery->fetchArray(SQLITE3_ASSOC) === false; @@ -17,7 +16,6 @@ if ($tableRequired) { )'); } -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("PRAGMA table_info(subscriptions)"); $columns = []; while ($column = $columnQuery->fetchArray(SQLITE3_ASSOC)) { diff --git a/migrations/000033.php b/migrations/000033.php index cf9f192..d15ce91 100644 --- a/migrations/000033.php +++ b/migrations/000033.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('settings') where name='show_subscription_progress'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000036.php b/migrations/000036.php index 855da73..6721d8d 100644 --- a/migrations/000036.php +++ b/migrations/000036.php @@ -4,7 +4,6 @@ // Also removes the iterator column as it is not used anymore. // The cancelation payload will be used to send cancelation notifications to the webhook -/** @noinspection PhpUndefinedVariableInspection */ $columnQuery = $db->query("SELECT * FROM pragma_table_info('webhook_notifications') where name='cancelation_payload'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000037.php b/migrations/000037.php index fa5d5f6..51eedaa 100644 --- a/migrations/000037.php +++ b/migrations/000037.php @@ -1,7 +1,6 @@ query("SELECT * FROM pragma_table_info('user') where name='firstname'"); $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; diff --git a/migrations/000038.php b/migrations/000038.php new file mode 100644 index 0000000..a1e2fd2 --- /dev/null +++ b/migrations/000038.php @@ -0,0 +1,40 @@ +query("SELECT * FROM pragma_table_info('admin') where name='oidc_oauth_enabled'"); +$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; +if ($columnRequired) { + $db->exec('ALTER TABLE admin ADD COLUMN oidc_oauth_enabled INTEGER DEFAULT 0'); +} + +$columnQuery = $db->query("SELECT * FROM pragma_table_info('user') where name='oidc_sub'"); +$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; +if ($columnRequired) { + $db->exec('ALTER TABLE user ADD COLUMN oidc_sub TEXT'); +} + + +$tableQuery = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='oauth_settings'"); +$tableExists = $tableQuery->fetchArray(SQLITE3_ASSOC); + +if (!$tableExists) { + $db->exec("CREATE TABLE oauth_settings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + client_id TEXT NOT NULL, + client_secret TEXT NOT NULL, + authorization_url TEXT NOT NULL, + token_url TEXT NOT NULL, + user_info_url TEXT NOT NULL, + redirect_url TEXT NOT NULL, + logout_url TEXT, + user_identifier_field TEXT NOT NULL DEFAULT 'sub', + scopes TEXT NOT NULL DEFAULT 'openid email profile', + auth_style TEXT DEFAULT 'auto', + auto_create_user INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + )"); +} \ No newline at end of file diff --git a/scripts/admin.js b/scripts/admin.js index 52ce4ef..6ca6d67 100644 --- a/scripts/admin.js +++ b/scripts/admin.js @@ -1,28 +1,28 @@ function makeFetchCall(url, data, button) { return fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), }) - .then(response => response.json()) - .then(data => { + .then(response => response.json()) + .then(data => { if (data.success) { - showSuccessMessage(data.message); + showSuccessMessage(data.message); } else { - showErrorMessage(data.message); + showErrorMessage(data.message); } button.disabled = false; - }) - .catch((error) => { + }) + .catch((error) => { showErrorMessage(error); button.disabled = false; - }); + }); } -function testSmtpSettingsButton() { +function testSmtpSettingsButton() { const button = document.getElementById("testSmtpSettingsButton"); button.disabled = true; @@ -66,27 +66,27 @@ function saveSmtpSettingsButton() { }; fetch('endpoints/admin/savesmtpsettings.php', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), }) - .then(response => response.json()) - .then(data => { + .then(response => response.json()) + .then(data => { if (data.success) { - const emailVerificationCheckbox = document.getElementById('requireEmail'); - emailVerificationCheckbox.disabled = false; - showSuccessMessage(data.message); + const emailVerificationCheckbox = document.getElementById('requireEmail'); + emailVerificationCheckbox.disabled = false; + showSuccessMessage(data.message); } else { - showErrorMessage(data.message); + showErrorMessage(data.message); } button.disabled = false; - }) - .catch((error) => { + }) + .catch((error) => { showErrorMessage(error); button.disabled = false; - }); + }); } @@ -124,7 +124,7 @@ function backupDB() { button.disabled = false; }); } - + function openRestoreDBFileSelect() { document.getElementById('restoreDBFile').click(); }; @@ -145,26 +145,26 @@ function restoreDB() { method: 'POST', body: formData }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showSuccessMessage(data.message); - fetch('endpoints/db/migrate.php') - .then(response => response.text()) - .then(() => { - window.location.href = 'logout.php'; - }) - .catch(error => { - window.location.href = 'logout.php'; - }); - } else { - showErrorMessage(data.message); - } - }) - .catch(error => showErrorMessage('Error:', error)); + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + fetch('endpoints/db/migrate.php') + .then(response => response.text()) + .then(() => { + window.location.href = 'logout.php'; + }) + .catch(error => { + window.location.href = 'logout.php'; + }); + } else { + showErrorMessage(data.message); + } + }) + .catch(error => showErrorMessage('Error:', error)); } -function saveAccountRegistrationsButton () { +function saveAccountRegistrationsButton() { const button = document.getElementById('saveAccountRegistrations'); button.disabled = true; @@ -189,20 +189,20 @@ function saveAccountRegistrationsButton () { }, body: JSON.stringify(data) }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showSuccessMessage(data.message); + .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; - } else { - showErrorMessage(data.message); - button.disabled = false; - } - }) - .catch(error => { - showErrorMessage(error); - button.disabled = false; - }); + }); } function removeUser(userId) { @@ -217,19 +217,19 @@ function removeUser(userId) { }, 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(); + .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); } - } else { - showErrorMessage(data.message); - } - }) - .catch(error => showErrorMessage('Error:', error)); + }) + .catch(error => showErrorMessage('Error:', error)); } @@ -254,21 +254,21 @@ function addUserButton() { }, body: JSON.stringify(data) }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showSuccessMessage(data.message); + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + button.disabled = false; + window.location.reload(); + } else { + showErrorMessage(data.message); + button.disabled = false; + } + }) + .catch(error => { + showErrorMessage(error); button.disabled = false; - window.location.reload(); - } else { - showErrorMessage(data.message); - button.disabled = false; - } - }) - .catch(error => { - showErrorMessage(error); - button.disabled = false; - }); + }); } function deleteUnusedLogos() { @@ -276,21 +276,21 @@ function deleteUnusedLogos() { button.disabled = true; fetch('endpoints/admin/deleteunusedlogos.php') - .then(response => response.json()) - .then(data => { - if (data.success) { - showSuccessMessage(data.message); - const numberOfLogos = document.querySelector('.number-of-logos'); - numberOfLogos.innerText = '0'; - } else { - showErrorMessage(data.message); + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + const numberOfLogos = document.querySelector('.number-of-logos'); + numberOfLogos.innerText = '0'; + } else { + showErrorMessage(data.message); + button.disabled = false; + } + }) + .catch(error => { + showErrorMessage(error); button.disabled = false; - } - }) - .catch(error => { - showErrorMessage(error); - button.disabled = false; - }); + }); } function toggleUpdateNotification() { @@ -308,18 +308,18 @@ function toggleUpdateNotification() { }, body: JSON.stringify(data) }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showSuccessMessage(data.message); - if (notificationEnabled === 1) { - fetch('endpoints/cronjobs/checkforupdates.php'); + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + if (notificationEnabled === 1) { + fetch('endpoints/cronjobs/checkforupdates.php'); + } + } else { + showErrorMessage(data.message); } - } else { - showErrorMessage(data.message); - } - }) - .catch(error => showErrorMessage('Error:', error)); + }) + .catch(error => showErrorMessage('Error:', error)); } @@ -339,4 +339,92 @@ function executeCronJob(job) { console.error('Fetch error:', error); showErrorMessage('Error:', error); }); +} + +function toggleOidcEnabled() { + const toggle = document.getElementById("oidcEnabled"); + toggle.disabled = true; + + const oidcEnabled = toggle.checked ? 1 : 0; + + const data = { + oidcEnabled: oidcEnabled + }; + + fetch('endpoints/admin/enableoidc.php', { + 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); + } + toggle.disabled = false; + }) + .catch(error => { + showErrorMessage('Error:', error); + toggle.disabled = false; + }); + +} + +function saveOidcSettingsButton() { + const button = document.getElementById("saveOidcSettingsButton"); + button.disabled = true; + + const oidcName = document.getElementById("oidcName").value; + const oidcClientId = document.getElementById("oidcClientId").value; + const oidcClientSecret = document.getElementById("oidcClientSecret").value; + const oidcAuthUrl = document.getElementById("oidcAuthUrl").value; + const oidcTokenUrl = document.getElementById("oidcTokenUrl").value; + const oidcUserInfoUrl = document.getElementById("oidcUserInfoUrl").value; + const oidcRedirectUrl = document.getElementById("oidcRedirectUrl").value; + const oidcLogoutUrl = document.getElementById("oidcLogoutUrl").value; + const oidcUserIdentifierField = document.getElementById("oidcUserIdentifierField").value; + const oidcScopes = document.getElementById("oidcScopes").value; + const oidcAuthStyle = document.getElementById("oidcAuthStyle").value; + const oidcAutoCreateUser = document.getElementById("oidcAutoCreateUser").checked ? 1 : 0; + + const data = { + oidcName: oidcName, + oidcClientId: oidcClientId, + oidcClientSecret: oidcClientSecret, + oidcAuthUrl: oidcAuthUrl, + oidcTokenUrl: oidcTokenUrl, + oidcUserInfoUrl: oidcUserInfoUrl, + oidcRedirectUrl: oidcRedirectUrl, + oidcLogoutUrl: oidcLogoutUrl, + oidcUserIdentifierField: oidcUserIdentifierField, + oidcScopes: oidcScopes, + oidcAuthStyle: oidcAuthStyle, + oidcAutoCreateUser: oidcAutoCreateUser + }; + + + fetch('endpoints/admin/saveoidcsettings.php', { + 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:', error); + button.disabled = false; + }); } \ No newline at end of file diff --git a/styles/login.css b/styles/login.css index 491ea50..f1625c4 100644 --- a/styles/login.css +++ b/styles/login.css @@ -117,7 +117,8 @@ select { } input[type="submit"], -input[type="button"] { +input[type="button"], +a.button { width: 100%; padding: 15px; font-size: 16px; @@ -128,19 +129,29 @@ input[type="button"] { cursor: pointer; } -input[type="submit"]:hover { +a.button { + text-decoration: none; + display: inline-block; + text-align: center; + box-sizing: border-box; +} + +input[type="submit"]:hover, +a.button:hover { background-color: var(--hover-color); } input[type="button"].secondary-button, -button.button.secondary-button { +button.button.secondary-button, +a.button.secondary-button { background-color: #FFFFFF; color: var(--main-color); border: 2px solid var(--main-color); } input[type="button"].secondary-button:hover, -button.button.secondary-button:hover { +button.button.secondary-button:hover, +a.button.secondary-button:hover { background-color: #EEEEEE; color: var(--hover-color); border-color: var(--hover-color); @@ -159,6 +170,13 @@ input[type="checkbox"] { place-content: center; } +.or-separator { + text-align: center; + display: block; + margin: 3px 0px 7px; + font-size: 16px; +} + .error { display: block; color: var(--error-color);