csrf on profile

This commit is contained in:
Miguel Ribeiro
2025-10-18 22:14:52 +02:00
parent 0d0bd13dfb
commit cc99a49a44
6 changed files with 178 additions and 194 deletions
+1 -7
View File
@@ -1,13 +1,7 @@
<?php
require_once '../../includes/connect_endpoint.php';
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
require_once '../../includes/validate_endpoint.php';
$input = json_decode(file_get_contents('php://input'), true);
if (isset($input['avatar'])) {
+63 -79
View File
@@ -2,6 +2,7 @@
require_once '../../includes/connect_endpoint.php';
require_once '../../includes/inputvalidation.php';
require_once '../../includes/validate_endpoint.php';
if (!function_exists('trigger_deprecation')) {
function trigger_deprecation($package, $version, $message, ...$args)
@@ -12,15 +13,6 @@ if (!function_exists('trigger_deprecation')) {
}
}
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n),
"reload" => false
]));
}
$statement = $db->prepare('SELECT totp_enabled FROM user WHERE id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$result = $statement->execute();
@@ -34,43 +26,69 @@ if ($row['totp_enabled'] == 0) {
]));
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);
if (isset($data['totpCode']) && $data['totpCode'] != "") {
require_once __DIR__ . '/../../libs/OTPHP/FactoryInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/Factory.php';
require_once __DIR__ . '/../../libs/OTPHP/ParameterTrait.php';
require_once __DIR__ . '/../../libs/OTPHP/OTPInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/OTP.php';
require_once __DIR__ . '/../../libs/OTPHP/TOTPInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/TOTP.php';
require_once __DIR__ . '/../../libs/Psr/Clock/ClockInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/InternalClock.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/Binary.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/EncoderInterface.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/Base32.php';
if (isset($data['totpCode']) && $data['totpCode'] != "") {
require_once __DIR__ . '/../../libs/OTPHP/FactoryInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/Factory.php';
require_once __DIR__ . '/../../libs/OTPHP/ParameterTrait.php';
require_once __DIR__ . '/../../libs/OTPHP/OTPInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/OTP.php';
require_once __DIR__ . '/../../libs/OTPHP/TOTPInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/TOTP.php';
require_once __DIR__ . '/../../libs/Psr/Clock/ClockInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/InternalClock.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/Binary.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/EncoderInterface.php';
require_once __DIR__ . '/../../libs/constant_time_encoding/Base32.php';
$totp_code = $data['totpCode'];
$totp_code = $data['totpCode'];
$statement = $db->prepare('SELECT totp_secret FROM totp WHERE user_id = :id');
$statement = $db->prepare('SELECT totp_secret FROM totp WHERE user_id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$secret = $row['totp_secret'];
$statement = $db->prepare('SELECT backup_codes FROM totp WHERE user_id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$backupCodes = $row['backup_codes'];
$clock = new OTPHP\InternalClock();
$totp = OTPHP\TOTP::createFromSecret($secret, $clock);
$totp->setPeriod(30);
if ($totp->verify($totp_code, null, 15)) {
$statement = $db->prepare('UPDATE user SET totp_enabled = 0 WHERE id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$secret = $row['totp_secret'];
$statement->execute();
$statement = $db->prepare('SELECT backup_codes FROM totp WHERE user_id = :id');
$statement = $db->prepare('DELETE FROM totp WHERE user_id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$result = $statement->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$backupCodes = $row['backup_codes'];
$statement->execute();
$clock = new OTPHP\InternalClock();
$totp = OTPHP\TOTP::createFromSecret($secret, $clock);
$totp->setPeriod(30);
die(json_encode([
"success" => true,
"message" => translate('success', $i18n),
"reload" => true
]));
} else {
// Compare the TOTP code agains the backup codes
// Normalize TOTP input
$totp_code = strtolower(trim((string) $totp_code));
if ($totp->verify($totp_code, null, 15)) {
// Decode and normalize backup codes
$backupCodes = json_decode($backupCodes, true);
$normalizedBackupCodes = array_map(function ($code) {
return strtolower(trim((string) $code));
}, $backupCodes);
// Search for the normalized code
if (($key = array_search($totp_code, $normalizedBackupCodes)) !== false) {
// Match found, disable TOTP
$statement = $db->prepare('UPDATE user SET totp_enabled = 0 WHERE id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$statement->execute();
@@ -85,53 +103,19 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
"reload" => true
]));
} else {
// Compare the TOTP code agains the backup codes
// Normalize TOTP input
$totp_code = strtolower(trim((string) $totp_code));
// Decode and normalize backup codes
$backupCodes = json_decode($backupCodes, true);
$normalizedBackupCodes = array_map(function ($code) {
return strtolower(trim((string) $code));
}, $backupCodes);
// Search for the normalized code
if (($key = array_search($totp_code, $normalizedBackupCodes)) !== false) {
// Match found, disable TOTP
$statement = $db->prepare('UPDATE user SET totp_enabled = 0 WHERE id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$statement->execute();
$statement = $db->prepare('DELETE FROM totp WHERE user_id = :id');
$statement->bindValue(':id', $userId, SQLITE3_INTEGER);
$statement->execute();
die(json_encode([
"success" => true,
"message" => translate('success', $i18n),
"reload" => true
]));
} else {
die(json_encode([
"success" => false,
"message" => translate('totp_code_incorrect', $i18n),
"reload" => false
]));
}
die(json_encode([
"success" => false,
"message" => translate('totp_code_incorrect', $i18n),
"reload" => false
]));
}
} else {
die(json_encode([
"success" => false,
"message" => translate('fields_missing', $i18n),
"reload" => false
]));
}
} else {
die(json_encode([
"success" => false,
"message" => translate('invalid_request_method', $i18n),
"message" => translate('fields_missing', $i18n),
"reload" => false
]));
}
+18 -26
View File
@@ -2,6 +2,7 @@
require_once '../../includes/connect_endpoint.php';
require_once '../../includes/inputvalidation.php';
require_once '../../includes/validate_endpoint.php';
if (!function_exists('trigger_deprecation')) {
function trigger_deprecation($package, $version, $message, ...$args)
@@ -12,14 +13,13 @@ if (!function_exists('trigger_deprecation')) {
}
}
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
$postData = file_get_contents("php://input");
$data = json_decode($postData, true) ?? [];
$action = $data['action'] ?? '';
if ($action === 'generate') {
if ($_SERVER["REQUEST_METHOD"] === "GET") {
function base32_encode($hex)
{
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
@@ -39,23 +39,19 @@ if ($_SERVER["REQUEST_METHOD"] === "GET") {
return $base32;
}
$data = $_GET;
if (isset($data['generate']) && $data['generate'] == true) {
$secret = base32_encode(bin2hex(random_bytes(20)));
$qrCodeUrl = "otpauth://totp/Wallos:" . $_SESSION['username'] . "?secret=" . $secret . "&issuer=Wallos";
$response = [
"success" => true,
"secret" => $secret,
"qrCodeUrl" => $qrCodeUrl
];
echo json_encode($response);
}
$secret = base32_encode(bin2hex(random_bytes(20)));
$qrCodeUrl = "otpauth://totp/Wallos:" . $_SESSION['username'] . "?secret=" . $secret . "&issuer=Wallos";
echo json_encode([
"success" => true,
"secret" => $secret,
"qrCodeUrl" => $qrCodeUrl,
]);
exit;
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);
if ($action === 'verify') {
if (isset($data['totpSecret']) && $data['totpSecret'] != "" && isset($data['totpCode']) && $data['totpCode'] != "") {
require_once __DIR__ . '/../../libs/OTPHP/FactoryInterface.php';
require_once __DIR__ . '/../../libs/OTPHP/Factory.php';
@@ -134,8 +130,4 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
"message" => translate('totp_code_incorrect', $i18n)
]));
}
}
+25 -36
View File
@@ -1,40 +1,29 @@
<?php
require_once '../../includes/connect_endpoint.php';
require_once '../../includes/validate_endpoint.php';
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);
$apiKey = bin2hex(random_bytes(32));
$sql = "UPDATE user SET api_key = :apiKey WHERE id = :userId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey, SQLITE3_TEXT);
$stmt->bindValue(':userId', $userId, SQLITE3_TEXT);
$result = $stmt->execute();
if ($result) {
$response = [
"success" => true,
"message" => translate('user_details_saved', $i18n),
"apiKey" => $apiKey
];
echo json_encode($response);
} else {
$response = [
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);
$apiKey = bin2hex(random_bytes(32));
$sql = "UPDATE user SET api_key = :apiKey WHERE id = :userId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey, SQLITE3_TEXT);
$stmt->bindValue(':userId', $userId, SQLITE3_TEXT);
$result = $stmt->execute();
if ($result) {
$response = [
"success" => true,
"message" => translate('user_details_saved', $i18n),
"apiKey" => $apiKey
];
echo json_encode($response);
} else {
$response = [
"success" => false,
"message" => translate('error_updating_user_data', $i18n)
];
echo json_encode($response);
}
}
?>
"message" => translate('error_updating_user_data', $i18n)
];
echo json_encode($response);
}
+1 -1
View File
@@ -1,6 +1,7 @@
<?php
require_once '../../includes/connect_endpoint.php';
require_once '../../includes/inputvalidation.php';
require_once '../../includes/validate_endpoint.php';
if (!file_exists('../../images/uploads/logos')) {
mkdir('../../images/uploads/logos', 0777, true);
@@ -343,4 +344,3 @@ if (
echo json_encode($response);
exit();
}
?>
+70 -45
View File
@@ -2,29 +2,40 @@ document.addEventListener('DOMContentLoaded', function () {
document.getElementById("userForm").addEventListener("submit", function (event) {
event.preventDefault();
document.getElementById("userSubmit").disabled = true;
const submitButton = document.getElementById("userSubmit");
submitButton.disabled = true;
const formData = new FormData(event.target);
formData.append("action", "save");
fetch("endpoints/user/save_user.php", {
method: "POST",
body: formData
headers: {
"X-CSRF-Token": window.csrfToken,
},
body: formData,
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById("avatar").src = document.getElementById("avatarImg").src;
var newUsername = document.getElementById("username").value;
const newUsername = document.getElementById("username").value;
document.getElementById("user").textContent = newUsername;
showSuccessMessage(data.message);
if (data.reload) {
location.reload();
}
} else {
showErrorMessage(data.errorMessage);
showErrorMessage(data.errorMessage || translate("failed_save_user"));
}
document.getElementById("userSubmit").disabled = false;
})
.catch(error => {
showErrorMessage(translate('unknown_error'));
console.error(error);
showErrorMessage(translate("unknown_error"));
})
.finally(() => {
submitButton.disabled = false;
});
});
@@ -81,6 +92,7 @@ function deleteAvatar(path) {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-CSRF-Token": window.csrfToken,
},
body: JSON.stringify({ avatar: path }),
})
@@ -102,31 +114,36 @@ function deleteAvatar(path) {
}
function enableTotp() {
const totpSecret = document.querySelector('#totp-secret');
const totpSecretCode = document.querySelector('#totp-secret-code');
const qrCode = document.getElementById('totp-qr-code');
totpSecret.value = '';
totpSecretCode.textContent = '';
qrCode.innerHTML = '';
const totpSecret = document.querySelector("#totp-secret");
const totpSecretCode = document.querySelector("#totp-secret-code");
const qrCode = document.getElementById("totp-qr-code");
totpSecret.value = "";
totpSecretCode.textContent = "";
qrCode.innerHTML = "";
fetch('endpoints/user/enable_totp.php?generate=true', {
method: 'GET'
fetch("endpoints/user/enable_totp.php", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": window.csrfToken,
},
body: JSON.stringify({ action: "generate" }),
})
.then(response => response.json())
.then(data => {
if (data.success) {
totpSecret.value = data.secret;
totpSecretCode.textContent = data.secret;
new QRCode(qrCode, data.qrCodeUrl);
openTotpPopup();
} else {
showErrorMessage(data.message);
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
totpSecret.value = data.secret;
totpSecretCode.textContent = data.secret;
new QRCode(qrCode, data.qrCodeUrl);
openTotpPopup();
} else {
showErrorMessage(data.message);
}
})
.catch(error => {
showErrorMessage(error);
});
.catch(error => {
console.error(error);
showErrorMessage(translate("unknown_error"));
});
}
function openTotpPopup() {
@@ -157,8 +174,9 @@ function submitTotp() {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-CSRF-Token": window.csrfToken,
},
body: JSON.stringify({ totpCode: totpCode, totpSecret: totpSecret }),
body: JSON.stringify({ totpCode: totpCode, totpSecret: totpSecret, action: 'verify' }),
})
.then(response => response.json())
.then(data => {
@@ -233,6 +251,7 @@ function submitDisableTotp() {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-CSRF-Token": window.csrfToken,
},
body: JSON.stringify({ totpCode: totpCode }),
})
@@ -253,29 +272,34 @@ function submitDisableTotp() {
}
function regenerateApiKey() {
const regenerateButton = document.getElementById('regenerateApiKey');
regenerateButton.disabled = true;
const regenerateButton = document.getElementById("regenerateApiKey");
regenerateButton.disabled = true;
fetch('endpoints/user/regenerateapikey.php', {
method: 'POST',
})
fetch("endpoints/user/regenerateapikey.php", {
method: "POST",
headers: {
"X-CSRF-Token": window.csrfToken,
},
})
.then(response => response.json())
.then(data => {
regenerateButton.disabled = false;
if (data.success) {
const newApiKey = data.apiKey;
document.getElementById('apikey').value = newApiKey;
showSuccessMessage(data.message);
} else {
showErrorMessage(data.message);
}
regenerateButton.disabled = false;
if (data.success) {
const newApiKey = data.apiKey;
document.getElementById("apikey").value = newApiKey;
showSuccessMessage(data.message);
} else {
showErrorMessage(data.message || translate("failed_regenerate_api_key"));
}
})
.catch(error => {
regenerateButton.disabled = false;
showErrorMessage(error);
console.error(error);
regenerateButton.disabled = false;
showErrorMessage(translate("unknown_error"));
});
}
function exportAsJson() {
fetch("endpoints/subscriptions/export.php")
.then(response => response.json())
@@ -337,6 +361,7 @@ function deleteAccount(userId) {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-CSRF-Token": window.csrfToken,
},
body: JSON.stringify({ userId: userId }),
})