mirror of
https://github.com/ellite/Wallos.git
synced 2025-12-16 18:24:22 -06:00
feat: add oauth/oidc support (#873)
This commit is contained in:
@@ -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
|
||||
|
||||
 
|
||||
|
||||
## 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
112
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;
|
||||
}
|
||||
?>
|
||||
|
||||
<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';
|
||||
?>
|
||||
?>
|
||||
48
endpoints/admin/enableoidc.php
Normal file
48
endpoints/admin/enableoidc.php
Normal 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)
|
||||
]));
|
||||
}
|
||||
100
endpoints/admin/saveoidcsettings.php
Normal file
100
endpoints/admin/saveoidcsettings.php
Normal 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)
|
||||
]));
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" => "Εργασίες συντήρησης",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" => "メンテナンスタスク",
|
||||
|
||||
@@ -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" => "유지보수 작업",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" => "Задачи обслуживания",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" => "Одржавање",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" => "Втрачений логотип",
|
||||
|
||||
@@ -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ì",
|
||||
|
||||
@@ -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" => "维护任务",
|
||||
|
||||
@@ -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" => "維護工作",
|
||||
|
||||
128
includes/oidc/handle_oidc_callback.php
Normal file
128
includes/oidc/handle_oidc_callback.php
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
179
includes/oidc/oidc_create_user.php
Normal file
179
includes/oidc/oidc_create_user.php
Normal 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');
|
||||
63
includes/oidc/oidc_login.php
Normal file
63
includes/oidc/oidc_login.php
Normal 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();
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
$version = "v3.3.1";
|
||||
$version = "v4.0.0";
|
||||
?>
|
||||
52
login.php
52
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;
|
||||
}
|
||||
|
||||
?>
|
||||
<!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) {
|
||||
|
||||
23
logout.php
23
logout.php
@@ -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();
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
/** @noinspection PhpUndefinedVariableInspection */
|
||||
$db->exec('CREATE TABLE IF NOT EXISTS migrations (
|
||||
id INTEGER PRIMARY KEY,
|
||||
migration TEXT NOT NULL,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 "",
|
||||
|
||||
@@ -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 "",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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'");
|
||||
|
||||
@@ -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 "",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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'])) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
40
migrations/000038.php
Normal 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
|
||||
)");
|
||||
}
|
||||
308
scripts/admin.js
308
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;
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user