This commit is contained in:
+138
-1
@@ -78,6 +78,12 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) {
|
||||
<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">
|
||||
@@ -143,7 +149,11 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) {
|
||||
<!-- 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>
|
||||
<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 -->
|
||||
@@ -181,6 +191,17 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) {
|
||||
<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 "remembered" 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>
|
||||
@@ -550,6 +571,45 @@ function generate2FAQRCode(issuer, accountName, secret) {
|
||||
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 >= 8) score++;
|
||||
if (pw.length >= 12) score++;
|
||||
if (pw.length >= 16) 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() {
|
||||
@@ -788,7 +848,84 @@ function generate2FAQRCode(issuer, accountName, secret) {
|
||||
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 + ' · ' : '') + (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>
|
||||
|
||||
Reference in New Issue
Block a user