feat: add oauth/oidc support (#873)

This commit is contained in:
Miguel Ribeiro
2025-07-21 22:53:35 +02:00
committed by GitHub
parent 536bce2de5
commit c0d53e4423
73 changed files with 1169 additions and 242 deletions

View File

@@ -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/).

112
admin.php
View File

@@ -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;
}
?>
<section class="account-section">
<header>
<h2><?= translate('oidc_settings', $i18n) ?></h2>
</header>
<div class="admin-form">
<div class="form-group-inline">
<input type="checkbox" id="oidcEnabled" <?= $settings['oidc_oauth_enabled'] ? 'checked' : '' ?>
onchange="toggleOidcEnabled()" />
<label for="oidcEnabled"><?= translate('oidc_oauth_enabled', $i18n) ?></label>
</div>
<div class="form-group">
<input type="text" id="oidcName" placeholder="Provider Name" value="<?= $oidcSettings['name'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcClientId" placeholder="Client ID" value="<?= $oidcSettings['client_id'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcClientSecret" placeholder="Client Secret" value="<?= $oidcSettings['client_secret'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcAuthUrl" placeholder="Auth URL" value="<?= $oidcSettings['authorization_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcTokenUrl" placeholder="Token URL" value="<?= $oidcSettings['token_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcUserInfoUrl" placeholder="User Info URL"
value="<?= $oidcSettings['user_info_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcRedirectUrl" placeholder="Redirect URL"
value="<?= $oidcSettings['redirect_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcLogoutUrl" placeholder="Logout URL"
value="<?= $oidcSettings['logout_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcUserIdentifierField" placeholder="User Identifier Field"
value="<?= $oidcSettings['user_identifier_field'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcScopes" placeholder="Scopes" value="<?= $oidcSettings['scopes'] ?>" />
</div>
<div class="form-group">
<input type="hidden" id="oidcAuthStyle" placeholder="Auth Style"
value="<?= $oidcSettings['auth_style'] ?>" />
</div>
<div class="form-group-inline">
<input type="checkbox" id="oidcAutoCreateUser" <?= $oidcSettings['auto_create_user'] ? 'checked' : '' ?> />
<label for="oidcAutoCreateUser"><?= translate('create_user_automatically', $i18n) ?></label>
</div>
<div class="buttons">
<input type="submit" class="thin mobile-grow" value="<?= translate('save', $i18n) ?>"
id="saveOidcSettingsButton" onClick="saveOidcSettingsButton()" />
</div>
</div>
</section>
<section class="account-section">
<header>
<h2><?= translate('smtp_settings', $i18n) ?></h2>
@@ -347,7 +430,8 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0;
?>
</div>
<div class="form-group-inline">
<input type="checkbox" id="updateNotification" <?= $settings['update_notification'] ? 'checked' : '' ?> onchange="toggleUpdateNotification()"/>
<input type="checkbox" id="updateNotification" <?= $settings['update_notification'] ? 'checked' : '' ?>
onchange="toggleUpdateNotification()" />
<label for="updateNotification"><?= translate('show_update_notification', $i18n) ?></label>
</div>
<h3><?= translate('orphaned_logos', $i18n) ?></h3>
@@ -360,14 +444,22 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0;
<h3><?= translate('cronjobs', $i18n) ?></h3>
<div>
<div class="inline-row">
<input type="button" value="Check for Updates" class="button tiny mobile-grow" onclick="executeCronJob('checkforupdates')">
<input type="button" value="Send Notifications" class="button tiny mobile-grow" onclick="executeCronJob('sendnotifications')">
<input type="button" value="Send Cancellation Notifications" class="button tiny mobile-grow" onclick="executeCronJob('sendcancellationnotifications')">
<input type="button" value="Send Password Reset Emails" class="button tiny mobile-grow" onclick="executeCronJob('sendresetpasswordemails')">
<input type="button" value="Send Verification Emails" class="button tiny mobile-grow" onclick="executeCronJob('sendverificationemails')">
<input type="button" value="Update Exchange Rates" class="button tiny mobile-grow" onclick="executeCronJob('updateexchange')">
<input type="button" value="Update Next Payments" class="button tiny mobile-grow" onclick="executeCronJob('updatenextpayment')">
<input type="button" value="Store Total Yearly Cost" class="button tiny mobile-grow" onclick="executeCronJob('storetotalyearlycost')">
<input type="button" value="Check for Updates" class="button tiny mobile-grow"
onclick="executeCronJob('checkforupdates')">
<input type="button" value="Send Notifications" class="button tiny mobile-grow"
onclick="executeCronJob('sendnotifications')">
<input type="button" value="Send Cancellation Notifications" class="button tiny mobile-grow"
onclick="executeCronJob('sendcancellationnotifications')">
<input type="button" value="Send Password Reset Emails" class="button tiny mobile-grow"
onclick="executeCronJob('sendresetpasswordemails')">
<input type="button" value="Send Verification Emails" class="button tiny mobile-grow"
onclick="executeCronJob('sendverificationemails')">
<input type="button" value="Update Exchange Rates" class="button tiny mobile-grow"
onclick="executeCronJob('updateexchange')">
<input type="button" value="Update Next Payments" class="button tiny mobile-grow"
onclick="executeCronJob('updatenextpayment')">
<input type="button" value="Store Total Yearly Cost" class="button tiny mobile-grow"
onclick="executeCronJob('storetotalyearlycost')">
</div>
<div class="inline-row">
<textarea id="cronjobResult" class="thin" readonly></textarea>
@@ -401,4 +493,4 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0;
<?php
require_once 'includes/footer.php';
?>
?>

View File

@@ -0,0 +1,48 @@
<?php
require_once '../../includes/connect_endpoint.php';
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => 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)
]));
}

View File

@@ -0,0 +1,100 @@
<?php
require_once '../../includes/connect_endpoint.php';
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => 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)
]));
}

View File

@@ -1,90 +1,105 @@
<?php
session_start();
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'];
// 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();
}
}
}
?>

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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" => "Εργασίες συντήρησης",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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" => "メンテナンスタスク",

View File

@@ -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" => "유지보수 작업",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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" => "Задачи обслуживания",

View File

@@ -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",

View File

@@ -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" => "Одржавање",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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" => "Втрачений логотип",

View File

@@ -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ì",

View File

@@ -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" => "维护任务",

View File

@@ -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" => "維護工作",

View File

@@ -0,0 +1,128 @@
<?php
function generate_username_from_email($email)
{
if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
return null;
}
// Take the part before the @, remove non-alphanumeric characters, and lowercase
$username = strtolower(preg_replace('/[^a-zA-Z0-9._-]/', '', explode('@', $email)[0]));
return $username;
}
// get OIDC settings
$stmt = $db->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();
}
}
}
?>

View File

@@ -0,0 +1,179 @@
<?php
// Try to extract first and last name from "name"
$fullName = $userInfo['name'] ?? '';
$parts = explode(' ', trim($fullName), 2);
$firstname = $parts[0] ?? '';
$lastname = $parts[1] ?? '';
// Defaults
$language = 'en';
$avatar = "images/avatars/0.svg";
$budget = 0;
$main_currency_id = 1; // Euro
$password = bin2hex(random_bytes(16)); // 32-character random password
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// Insert user
$query = "INSERT INTO user (username, email, oidc_sub, main_currency, avatar, language, budget, firstname, lastname, password)
VALUES (:username, :email, :oidc_sub, :main_currency, :avatar, :language, :budget, :firstname, :lastname, :password)";
$stmt = $db->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');

View File

@@ -0,0 +1,63 @@
<?php
if (!isset($userData)) {
die("User data missing for OIDC login.");
}
$userId = $userData['id'];
$username = $userData['username'];
$language = $userData['language'];
$main_currency = $userData['main_currency'];
$_SESSION['username'] = $username;
$_SESSION['loggedin'] = true;
$_SESSION['main_currency'] = $main_currency;
$_SESSION['userId'] = $userId;
$_SESSION['from_oidc'] = true; // Indicate this session is from OIDC login
$cookieExpire = time() + (86400 * 30); // 30 days
// generate remember token
$token = bin2hex(random_bytes(32));
$addLoginTokens = "INSERT INTO login_tokens (user_id, token) VALUES (:userId, :token)";
$addLoginTokensStmt = $db->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();

View File

@@ -1,3 +1,3 @@
<?php
$version = "v3.3.1";
$version = "v4.0.0";
?>

View File

@@ -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;
}
?>
<!DOCTYPE html>
<html dir="<?= $languages[$lang]['dir'] ?>">
@@ -297,6 +339,16 @@ if ($adminRow['smtp_address'] != "" && $adminRow['server_url'] != "") {
?>
<div class="form-group">
<input type="submit" value="<?= translate('login', $i18n) ?>">
<?php
if ($oidcEnabled) {
?>
<span class="or-separator"><?= translate('or', $i18n) ?></span>
<a class="button secondary-button" href="<?= htmlspecialchars($oidc_auth_url) ?>">
<?= translate('login_with', $i18n) ?> <?= htmlspecialchars($oidc_name) ?>
</a>
<?php
}
?>
</div>
<?php
if ($loginFailed) {

View File

@@ -1,6 +1,19 @@
<?php
require_once 'includes/connect.php';
session_start();
$logoutOIDC = false;
// Check if user is logged in with OIDC
if (isset($_SESSION['from_oidc']) && $_SESSION['from_oidc'] === true) {
$logoutOIDC = true;
// get OIDC settings
$stmt = $db->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();
?>
exit();

View File

@@ -1,6 +1,5 @@
<?php
/** @noinspection PhpUndefinedVariableInspection */
$db->exec('CREATE TABLE IF NOT EXISTS migrations (
id INTEGER PRIMARY KEY,
migration TEXT NOT NULL,

View File

@@ -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;

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "from_email" column to the notifications table.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('notifications') where name='from_email'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a URL column to the subscriptions table.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('subscriptions') where name='url'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "language" column to the user table and sets all values to english.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('user') where name='language'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -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;

View File

@@ -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,

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "activated" column to the subscriptions table and sets all values to true.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('subscriptions') WHERE name='inactive'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -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;

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "order" column to the categories table so that they can be sorted and initializes all values to their id.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('categories') WHERE name='order'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "order" column to the payment_methods table so that they can be sorted and initializes all values to their id.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('payment_methods') WHERE name='order'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "encryption" column to the notifications table so that the encryption type can be stored.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('notifications') WHERE name='encryption'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -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();

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "color_theme" column to the settings table and sets it to blue as default.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('settings') where name='color_theme'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "hide_disabled" column to the settings table and sets to false as default.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('settings') where name='hide_disabled'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -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 "",

View File

@@ -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 "",

View File

@@ -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;

View File

@@ -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;

View File

@@ -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'");

View File

@@ -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 "",

View File

@@ -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;

View File

@@ -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,

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "cancellation_date" column to the subscriptions table.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('subscriptions') where name='cancellation_date'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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'])) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)) {

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds a "show_subscription_progress" column to the settings table and sets to false as default.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('settings') where name='show_subscription_progress'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

View File

@@ -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;

View File

@@ -1,7 +1,6 @@
<?php
// This migration adds "firstname" and "lastname" columns to the user table
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('user') where name='firstname'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;

40
migrations/000038.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
// This migration adds a "oidc_oauth_enabled" colum to the "admin" table
// It also adds a "oidc_sub" column to the "user" table
// It also adds a "oauth_settings" table to store OAuth settings.
$columnQuery = $db->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
)");
}

View File

@@ -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;
});
}

View File

@@ -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);