Files
jakach-login/app-code/account/index.php
T
janis 01a9d0ea95
Deploy / deploy (push) Has been cancelled
update password strengh check
2026-05-08 00:05:23 +02:00

933 lines
37 KiB
PHP

<?php
include "../api/utils/security.php";
secure_session_start();
// Check if the user is logged in
if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) {
header("LOCATION:/?send_to=/account/");
exit();
}
?>
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Account Page</title>
<!-- Bootstrap CSS -->
<?php
include "../assets/components.php";
print_csrf_script();
?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <!-- Google Material Icons -->
<style>
.modal.modal-front {
z-index: 1065;
}
.modal-backdrop.modal-front-backdrop {
z-index: 1060;
}
.qr-wrap {
display: inline-block;
background: #fff;
padding: 16px;
margin: 0 16px 16px;
border-radius: 6px;
}
.qr-secret {
margin: 0 16px 16px;
}
</style>
</head>
<body>
<!-- Main Container -->
<div class="container my-5">
<div class="row">
<!-- Profile Sidebar -->
<div class="col-md-4">
<div class="card">
<div class="card-body text-center">
<h3 id="user-name" class="card-title">Loading...</h3>
<p id="user-email" class="text-muted">Loading...</p>
</div>
</div>
</div>
<!-- Account Settings -->
<div class="col-md-8">
<div class="card">
<div class="card-header">
<!-- Tabs for General and Password sections -->
<ul class="nav nav-tabs" id="accountTab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="general-tab" data-bs-toggle="tab" href="#general" role="tab" aria-controls="general" aria-selected="true"><span class="material-icons">person</span></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="password-tab" data-bs-toggle="tab" href="#password" role="tab" aria-controls="password" aria-selected="false"><span class="material-icons">lock</span></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="2fa-tab" data-bs-toggle="tab" href="#2fa" role="tab" aria-controls="2fa" aria-selected="false"><span class="material-icons">security</span></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="passkey-tab" data-bs-toggle="tab" href="#passkey" role="tab" aria-controls="passkey" aria-selected="false"><span class="material-icons">fingerprint</span></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="message-tab" data-bs-toggle="tab" href="#message" role="tab" aria-controls="message" aria-selected="false"><span class="material-icons">message</span></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="domains-tab" data-bs-toggle="tab" href="#domains" role="tab" aria-controls="domains" aria-selected="false"><span class="material-icons">language</span></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="activity-tab" data-bs-toggle="tab" href="#activity" role="tab" aria-controls="activity" aria-selected="false"><span class="material-icons">history</span></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="sessions-tab" data-bs-toggle="tab" href="#sessions" role="tab" aria-controls="sessions" aria-selected="false"><span class="material-icons">devices</span></a>
</li>
<?php
if($_SESSION["permissions"][0]==="1"){
echo('<li class="nav-item" role="presentation">
<a class="nav-link" href="/account/manage_users.php" role="tab" aria-controls="message" aria-selected="false"><span class="material-icons">people</span></a>
</li>');
}
?>
<li class="nav-item" role="presentation">
<a class="nav-link" href="/login/logout.php" role="tab" aria-selected="false"><span class="material-icons">logout</span></a>
</li>
</ul>
</div>
<div class="card-body">
<!-- Tab Content -->
<div class="tab-content" id="accountTabContent">
<!-- General Account Details -->
<div class="tab-pane fade show active" id="general" role="tabpanel" aria-labelledby="general-tab">
<form id="account-form">
<!-- Name -->
<div class="mb-3">
<label for="name" class="form-label">Full Name</label>
<input type="text" class="form-control" id="name" placeholder="Loading...">
</div>
<!-- Email -->
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<input type="email" class="form-control" id="email" placeholder="Set email">
</div>
<!-- Telegram ID -->
<div class="mb-3">
<label for="telegram" class="form-label">Telegram ID</label>
<input type="text" class="form-control" id="telegram" placeholder="Set telegram id">
</div>
<div class="mb-3">
<label for="user_token" class="form-label">User token</label>
<input type="text" class="form-control" id="user_token" disabled>
</div>
<div class="mb-3">
<label for="last_login" class="form-label">Last login</label>
<input type="text" class="form-control" id="last_login" disabled>
</div>
<!-- Save Changes Button -->
<button type="button" id="save-button" class="btn btn-success">Save Changes</button>
<a class="btn btn-danger" onclick="delete_all_logmein();">Delete all &quot;remember me&quot; sessions</a>
</form>
</div>
<!-- Password Change Section -->
<div class="tab-pane fade" id="password" role="tabpanel" aria-labelledby="password-tab">
<form id="password-form">
<!-- Old Password -->
<div class="mb-3">
<label for="old-password" class="form-label">Old Password</label>
<input type="password" class="form-control" id="old-password" placeholder="Enter old password" required>
</div>
<!-- New Password -->
<div class="mb-3">
<label for="new-password" class="form-label">New Password</label>
<input type="password" class="form-control" id="new-password" placeholder="Enter new password" required oninput="updatePasswordStrength()">
<div id="passwordStrengthBar" class="progress mt-2" style="height: 6px; display:none;">
<div id="passwordStrengthFill" class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
<small id="passwordStrengthText" class="form-text mt-1"></small>
</div>
<!-- Confirm New Password -->
<div class="mb-3">
<label for="confirm-password" class="form-label">Confirm New Password</label>
<input type="password" class="form-control" id="confirm-password" placeholder="Confirm new password" required>
</div>
<!-- Change Password Button -->
<button type="button" id="change-password-button" class="btn btn-warning">Change Password</button>
</form>
</div>
<div class="tab-pane fade" id="2fa" role="tabpanel" aria-labelledby="2fa-tab">
<p>If you enable this you will need to enter a pin before beeing able to log in.</p>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="twofa-switch">
<label class="form-check-label" for="twofa-switch">Enable 2FA</label>
</div>
</div>
<div class="tab-pane fade" id="passkey" role="tabpanel" aria-labelledby="passkey-tab">
<p>Using a passkey you can login with e.g. your fingerprint. If this is enabled you can still login using your password but using a passkey is faster.</p>
<br>
<button id="create-passkey" class="btn btn-primary" onclick="createRegistration()">Register passkey</button>
</div>
<div class="tab-pane fade" id="message" role="tabpanel" aria-labelledby="message-tab">
<p>You can get a message via telegram whenever somebody logs in to your account</p>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="message-switch">
<label class="form-check-label" for="message-switch">Enable login messages</label>
</div>
</div>
<div class="tab-pane fade" id="domains" role="tabpanel" aria-labelledby="domains-tab">
<p>These external domains have been approved to receive your login data. You can revoke access at any time.</p>
<div id="confirmedDomainsList" class="list-group"></div>
<p id="noDomainsMessage" class="text-muted mt-3" style="display:none;">No external domains approved yet.</p>
</div>
<div class="tab-pane fade" id="activity" role="tabpanel" aria-labelledby="activity-tab">
<p>Recent activity on your account.</p>
<div id="activityLogList" class="list-group" style="max-height: 400px; overflow-y: auto;"></div>
<p id="noActivityMessage" class="text-muted mt-3" style="display:none;">No activity recorded yet.</p>
</div>
<div class="tab-pane fade" id="sessions" role="tabpanel" aria-labelledby="sessions-tab">
<p>These devices have been &quot;remembered&quot; and can log in without authentication.</p>
<div id="sessionsList" class="list-group"></div>
<p id="noSessionsMessage" class="text-muted mt-3" style="display:none;">No remembered sessions.</p>
<button class="btn btn-danger mt-3" onclick="revokeAllSessions()">Revoke all sessions</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Error Modal -->
<div class="modal fade" id="errorModal" tabindex="-1" aria-labelledby="errorModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="errorModalLabel">Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="errorModalMessage">
<!-- Error message will go here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- twofa Modal -->
<div class="modal fade" id="twofaModal" tabindex="-1" aria-labelledby="twofaModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="twofaModalLabel">Success</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="twofaModalMessage">
<!-- Success message will go here -->
</div>
<div class="text-center">
<div class="qr-wrap">
<div id="qrcode"></div>
</div>
</div>
<div class="qr-secret">
<label for="twofa-secret" class="form-label">Secret</label>
<div class="input-group">
<input type="text" id="twofa-secret" class="form-control" readonly>
<button type="button" class="btn btn-outline-secondary" onclick="copy2FaSecret()">Copy</button>
</div>
</div>
<div class="p-3">
<input type="text" id="twofa-confirm-pin" class="form-control" placeholder="Current 2FA code">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" onclick="confirm2FaEnrollment()">Confirm 2FA</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Success Modal -->
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="successModalLabel">Success</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="successModalMessage">
<!-- Success message will go here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- JavaScript -->
<script>
document.addEventListener('DOMContentLoaded', () => {
// Fetch user data from the backend API
fetch('/api/account/get_user_data.php') // Replace with the actual API endpoint
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
return response.json();
})
.then(user => {
// Populate the fields with user data
document.getElementById('user-name').textContent = user.name;
document.getElementById('user-email').textContent = user.email;
// Fill input fields
document.getElementById('name').value = user.name;
document.getElementById('user_token').value = user.user_token;
document.getElementById('email').value = user.email;
document.getElementById('telegram').value = user.telegram_id;
document.getElementById('twofa-switch').checked = user.twofa_enabled;
document.getElementById('message-switch').checked = user.login_message;
document.getElementById('last_login').value = user.last_login;
})
.catch(error => {
console.error('Error:', error);
showErrorModal('Failed to load user data. Please try again later.');
});
// Handle Save Changes button click
const saveButton = document.getElementById('save-button');
saveButton.addEventListener('click', () => {
const updatedUser = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
telegram_id: document.getElementById('telegram').value
};
// Send updated data to the backend
fetch('/api/account/update_user_data.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.csrfToken
},
body: JSON.stringify(updatedUser)
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to save user data');
}
return response.json();
})
.then(data => {
if (data.success) {
document.getElementById('user-name').textContent = updatedUser.name;
document.getElementById('user-email').textContent = updatedUser.email;
showSuccessModal("Data updated");
} else {
showErrorModal('Failed to update user data. ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
showErrorModal('Failed to save user data. Please try again later.');
});
});
// Handle Change Password button click
const changePasswordButton = document.getElementById('change-password-button');
changePasswordButton.addEventListener('click', () => {
const oldPassword = document.getElementById('old-password').value;
const newPassword = document.getElementById('new-password').value;
const confirmPassword = document.getElementById('confirm-password').value;
// Validate password fields
if (newPassword !== confirmPassword) {
showErrorModal('New password and confirm password do not match.');
return;
}
const passwordData = {
old_password: oldPassword,
new_password: newPassword
};
// Send password change request to the backend
fetch('/api/account/update_pw.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.csrfToken
},
body: JSON.stringify(passwordData)
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to change password');
}
return response.json();
})
.then(data => {
if (data.success) {
delete_all_logmein();
showSuccessModal('Password updated.');
} else {
showErrorModal('Failed to update password. ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
showErrorModal('Failed to change password. Please try again later.');
});
});
////////////////////////////////////////////
const switchElement = document.getElementById('twofa-switch');
// Add an event listener for when the switch is changed
switchElement.addEventListener('change', async function () {
// Get the current state of the switch
const isEnabled = switchElement.checked;
try {
// Send the state to the backend using a POST request
const response = await fetch('/api/account/update_2fa.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.csrfToken,
},
body: JSON.stringify({
enable_2fa: isEnabled, // Send the new state of 2FA
}),
});
// Check if the response is successful
const result = await response.json();
if (response.ok) {
// Handle success
if(isEnabled==false){
showSuccessModal(result.message || (isEnabled ? '2FA enabled successfully.' : '2FA disabled successfully.'));
}else{
if (result.pending) {
show2FaModal(result.message, result.token);
} else {
showSuccessModal(result.message || '2FA enabled successfully.');
}
}
} else {
// Handle error
switchElement.checked = false;
showErrorModal('Error: ' + (result.message || 'An error occurred while updating 2FA.'));
}
} catch (error) {
console.error('Error:', error);
switchElement.checked = false;
showErrorModal('Failed to send request. Please try again later.');
}
});
const switchElement2 = document.getElementById('message-switch');
// Add an event listener for when the switch is changed
switchElement2.addEventListener('change', async function () {
// Get the current state of the switch
const isEnabled = switchElement2.checked;
if(document.getElementById('telegram').value.length!=0){
try {
// Send the state to the backend using a POST request
const response = await fetch('/api/account/update_message.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.csrfToken,
},
body: JSON.stringify({
enable_message: isEnabled, // Send the new state of 2FA
}),
});
// Check if the response is successful
const result = await response.json();
if (response.ok) {
// Handle success
showSuccessModal(result.message || (isEnabled ? 'Login messages enabled successfully.' : 'Login messages disabled successfully.'));
} else {
// Handle error
showErrorModal('Error: ' + (result.message || 'An error occurred while updating login messages.'));
}
} catch (error) {
console.error('Error:', error);
showErrorModal('Failed to send request. Please try again later.');
}
}else{
showErrorModal("Please configure your Telegram ID first.");
}
});
});
// Function to show error modal
function showErrorModal(message) {
document.getElementById('errorModalMessage').textContent = message;
const errorElement = document.getElementById('errorModal');
const twofaIsOpen = document.getElementById('twofaModal').classList.contains('show');
errorElement.classList.toggle('modal-front', twofaIsOpen);
const errorModal = new bootstrap.Modal(document.getElementById('errorModal'));
errorModal.show();
if (twofaIsOpen) {
setTimeout(() => {
const backdrops = document.querySelectorAll('.modal-backdrop');
const latestBackdrop = backdrops[backdrops.length - 1];
if (latestBackdrop) {
latestBackdrop.classList.add('modal-front-backdrop');
}
}, 0);
}
}
function showSuccessModal(message) {
document.getElementById('successModalMessage').textContent = message;
const errorModal = new bootstrap.Modal(document.getElementById('successModal'));
errorModal.show();
}
function show2FaModal(message,secret) {
document.getElementById('twofaModalMessage').textContent = message;
document.getElementById('qrcode').innerHTML = '';
document.getElementById('twofa-secret').value = secret;
document.getElementById('twofa-confirm-pin').value = '';
const errorModal = new bootstrap.Modal(document.getElementById('twofaModal'));
generate2FAQRCode("Jakach Login",'<?php echo($_SESSION["username"]) ?>',secret);
errorModal.show();
}
async function copy2FaSecret() {
const secretInput = document.getElementById('twofa-secret');
secretInput.select();
secretInput.setSelectionRange(0, secretInput.value.length);
if (navigator.clipboard) {
await navigator.clipboard.writeText(secretInput.value);
} else {
document.execCommand('copy');
}
}
async function confirm2FaEnrollment() {
const pin = document.getElementById('twofa-confirm-pin').value.trim();
if (!pin) {
showErrorModal('Enter the current 2FA code.');
return;
}
const response = await fetch('/api/account/update_2fa.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.csrfToken,
},
body: JSON.stringify({
enable_2fa: true,
twofa_pin: pin
}),
});
const result = await response.json();
const twofaSwitch = document.getElementById('twofa-switch');
if (response.ok && result.success) {
bootstrap.Modal.getInstance(document.getElementById('twofaModal')).hide();
if (twofaSwitch) {
twofaSwitch.checked = true;
}
showSuccessModal(result.message || '2FA enabled successfully.');
} else {
if (twofaSwitch) {
twofaSwitch.checked = false;
}
showErrorModal(result.message || 'Invalid 2FA code.');
}
}
function generate2FAQRCode(issuer, accountName, secret) {
// Create the OTP URI
const uri = `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(accountName)}?secret=${secret}&issuer=${encodeURIComponent(issuer)}`;
// Use qrcode.js to generate and display the QR code
new QRCode(document.getElementById("qrcode"), {
text: uri,
width: 300,
height: 300
});
}
function updatePasswordStrength() {
const pw = document.getElementById('new-password').value;
const bar = document.getElementById('passwordStrengthBar');
const fill = document.getElementById('passwordStrengthFill');
const text = document.getElementById('passwordStrengthText');
if (pw.length === 0) {
bar.style.display = 'none';
text.textContent = '';
return;
}
bar.style.display = 'block';
let score = 0;
if (pw.length >= 12) score++;
if (pw.length >= 16) score++;
if (pw.length >= 20) score++;
if (/[a-z]/.test(pw) && /[A-Z]/.test(pw)) score++;
if (/\d/.test(pw)) score++;
if (/[^a-zA-Z0-9]/.test(pw)) score++;
const levels = [
{ min: 0, label: 'Very weak', class: 'bg-danger', pct: 10 },
{ min: 1, label: 'Weak', class: 'bg-danger', pct: 25 },
{ min: 2, label: 'Fair', class: 'bg-warning', pct: 45 },
{ min: 3, label: 'Good', class: 'bg-info', pct: 65 },
{ min: 4, label: 'Strong', class: 'bg-primary', pct: 80 },
{ min: 5, label: 'Very strong', class: 'bg-success', pct: 100 },
];
let level = levels[0];
for (const l of levels) {
if (score >= l.min) level = l;
}
fill.className = 'progress-bar ' + level.class;
fill.style.width = level.pct + '%';
text.textContent = level.label + ' (' + pw.length + ' characters)';
text.className = 'form-text mt-1 text-' + (score >= 3 ? 'success' : score >= 2 ? 'warning' : 'danger');
}
//webauthn js
async function createRegistration() {
try {
// check browser support
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
throw new Error('Browser not supported.');
}
// get create args
let rep = await window.fetch('/api/account/update_passkey.php?fn=getCreateArgs' + getGetParams(), {method:'GET', cache:'no-cache'});
//let rep = await window.fetch('/test/server.php?fn=getCreateArgs' + getGetParams(), {method:'GET', cache:'no-cache'});
const createArgs = await rep.json();
// error handling
if (createArgs.success === false) {
throw new Error(createArgs.msg || 'unknown error occured');
}
// replace binary base64 data with ArrayBuffer. a other way to do this
// is the reviver function of JSON.parse()
recursiveBase64StrToArrayBuffer(createArgs);
// create credentials
const cred = await navigator.credentials.create(createArgs);
// create object
const authenticatorAttestationResponse = {
transports: cred.response.getTransports ? cred.response.getTransports() : null,
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null
};
// check auth on server side
rep = await window.fetch('/api/account/update_passkey.php?fn=processCreate' + getGetParams(), {
//rep = await window.fetch('/test/server.php?fn=processCreate' + getGetParams(), {
method : 'POST',
headers : {
'X-CSRF-Token': window.csrfToken
},
body : JSON.stringify(authenticatorAttestationResponse),
cache : 'no-cache'
});
const authenticatorAttestationServerResponse = await rep.json();
// prompt server response
if (authenticatorAttestationServerResponse.success) {
reloadServerPreview();
showSuccessModal(authenticatorAttestationServerResponse.msg || 'Registrated passkey successfully');
} else {
throw new Error(authenticatorAttestationServerResponse.msg);
}
} catch (err) {
reloadServerPreview();
showErrorModal(err.message || 'unknown error occured');
}
}
/**
* convert RFC 1342-like base64 strings to array buffer
* @param {mixed} obj
* @returns {undefined}
*/
function recursiveBase64StrToArrayBuffer(obj) {
let prefix = '=?BINARY?B?';
let suffix = '?=';
if (typeof obj === 'object') {
for (let key in obj) {
if (typeof obj[key] === 'string') {
let str = obj[key];
if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {
str = str.substring(prefix.length, str.length - suffix.length);
let binary_string = window.atob(str);
let len = binary_string.length;
let bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
obj[key] = bytes.buffer;
}
} else {
recursiveBase64StrToArrayBuffer(obj[key]);
}
}
}
}
/**
* Convert a ArrayBuffer to Base64
* @param {ArrayBuffer} buffer
* @returns {String}
*/
function arrayBufferToBase64(buffer) {
let binary = '';
let bytes = new Uint8Array(buffer);
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa(binary);
}
function ascii_to_hex(str) {
let hex = '';
for (let i = 0; i < str.length; i++) {
let ascii = str.charCodeAt(i).toString(16);
hex += ('00' + ascii).slice(-2); // Ensure each hex value is 2 characters long
}
return hex;
}
/**
* Get URL parameter
* @returns {String}
*/
function getGetParams() {
let url = '';
url += '&apple=1';
url += '&yubico=1';
url += '&solo=1'
url += '&hypersecu=1';
url += '&google=1';
url += '&microsoft=1';
url += '&mds=1';
url += '&requireResidentKey=0';
url += '&type_usb=1';
url += '&type_nfc=1';
url += '&type_ble=1';
url += '&type_int=1';
url += '&type_hybrid=1';
url += '&fmt_android-key=1';
url += '&fmt_android-safetynet=1';
url += '&fmt_apple=1';
url += '&fmt_fido-u2f=1';
url += '&fmt_none=1';
url += '&fmt_packed=1';
url += '&fmt_tpm=1';
url += '&rpId=auth.jakach.ch';
url += '&userId=' + encodeURIComponent(ascii_to_hex('<?php echo($_SESSION["username"]);?>'));
url += '&userName=' + encodeURIComponent('<?php echo($_SESSION["username"]);?>');
url += '&userDisplayName=' + encodeURIComponent('<?php echo($_SESSION["username"]);?>');
url += '&userVerification=discouraged';
return url;
}
function reloadServerPreview() {
//let iframe = document.getElementById('serverPreview');
//iframe.src = iframe.src;
}
function setAttestation(attestation) {
let inputEls = document.getElementsByTagName('input');
for (const inputEl of inputEls) {
if (inputEl.id && inputEl.id.match(/^(fmt|cert)\_/)) {
inputEl.disabled = !attestation;
}
if (inputEl.id && inputEl.id.match(/^fmt\_/)) {
inputEl.checked = attestation ? inputEl.id !== 'fmt_none' : inputEl.id === 'fmt_none';
}
if (inputEl.id && inputEl.id.match(/^cert\_/)) {
inputEl.checked = attestation ? inputEl.id === 'cert_mds' : false;
}
}
}
/**
* force https on load
* @returns {undefined}
*/
window.onload = function() {
if (location.protocol !== 'https:' && location.host !== 'localhost') {
location.href = location.href.replace('http', 'https');
}
}
function delete_all_logmein(){
fetch("/api/login/delete_keepmeloggedin.php", {
method: "POST",
headers: {
"X-CSRF-Token": window.csrfToken
}
});
}
function loadConfirmedDomains() {
fetch('/api/account/manage_domains.php')
.then(r => r.json())
.then(data => {
const list = document.getElementById('confirmedDomainsList');
const noMsg = document.getElementById('noDomainsMessage');
list.innerHTML = '';
if (!data.domains || data.domains.length === 0) {
noMsg.style.display = 'block';
return;
}
noMsg.style.display = 'none';
data.domains.forEach(d => {
const item = document.createElement('div');
item.className = 'list-group-item d-flex justify-content-between align-items-center';
item.innerHTML = '<span><strong>' + d.domain + '</strong><br><small class="text-muted">Approved: ' + d.confirmed_at + '</small></span>' +
'<button class="btn btn-sm btn-outline-danger" onclick="removeDomain(' + d.id + ')">Revoke</button>';
list.appendChild(item);
});
});
}
function removeDomain(id) {
fetch('/api/account/manage_domains.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.csrfToken
},
body: JSON.stringify({ id: id })
}).then(r => r.json()).then(data => {
if (data.success) {
loadConfirmedDomains();
showSuccessModal('Domain access revoked.');
} else {
showErrorModal(data.message || 'Failed to revoke domain.');
}
});
}
document.addEventListener('DOMContentLoaded', function() {
const domainsTab = document.getElementById('domains-tab');
if (domainsTab) {
domainsTab.addEventListener('shown.bs.tab', loadConfirmedDomains);
}
const activityTab = document.getElementById('activity-tab');
if (activityTab) {
activityTab.addEventListener('shown.bs.tab', loadActivityLog);
}
const sessionsTab = document.getElementById('sessions-tab');
if (sessionsTab) {
sessionsTab.addEventListener('shown.bs.tab', loadSessions);
}
});
function loadActivityLog() {
fetch('/api/account/get_activity_log.php')
.then(r => r.json())
.then(data => {
const list = document.getElementById('activityLogList');
const noMsg = document.getElementById('noActivityMessage');
list.innerHTML = '';
if (!data.entries || data.entries.length === 0) {
noMsg.style.display = 'block';
return;
}
noMsg.style.display = 'none';
data.entries.forEach(e => {
const item = document.createElement('div');
item.className = 'list-group-item';
const actionLabels = {
'login': 'Login',
'password_change': 'Password changed',
'2fa_enabled': '2FA enabled',
'2fa_disabled': '2FA disabled',
'sessions_revoked': 'Sessions revoked',
};
const label = actionLabels[e.action] || e.action;
item.innerHTML = '<div class="d-flex w-100 justify-content-between"><strong>' + label + '</strong><small class="text-muted">' + e.created_at + '</small></div>' +
'<small class="text-muted">' + (e.ip ? e.ip + ' &middot; ' : '') + (e.user_agent ? e.user_agent.substring(0, 60) + '...' : '') + '</small>' +
(e.details ? '<br><small>' + e.details + '</small>' : '');
list.appendChild(item);
});
});
}
function loadSessions() {
fetch('/api/account/manage_sessions.php')
.then(r => r.json())
.then(data => {
const list = document.getElementById('sessionsList');
const noMsg = document.getElementById('noSessionsMessage');
list.innerHTML = '';
if (!data.sessions || data.sessions.length === 0) {
noMsg.style.display = 'block';
return;
}
noMsg.style.display = 'none';
data.sessions.forEach(s => {
const item = document.createElement('div');
item.className = 'list-group-item d-flex justify-content-between align-items-center';
item.innerHTML = '<span><strong>' + (s.user_agent || 'Unknown device') + '</strong></span>';
list.appendChild(item);
});
});
}
function revokeAllSessions() {
fetch('/api/account/manage_sessions.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.csrfToken
}
}).then(r => r.json()).then(data => {
if (data.success) {
loadSessions();
showSuccessModal(data.message || 'All sessions revoked.');
} else {
showErrorModal(data.message || 'Failed to revoke sessions.');
}
});
}
</script>
</body>
</html>