Files
gobsidian/web/templates/profile.html
2025-08-25 21:19:15 +01:00

176 lines
7.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "profile"}}
{{template "base" .}}
{{end}}
{{define "profile_content"}}
<div class="max-w-3xl mx-auto p-6">
<div class="mb-8">
<h1 class="text-3xl font-bold text-white mb-2">Your Profile</h1>
<p class="text-gray-400">Manage your account details and security</p>
</div>
<div class="space-y-8">
<!-- Email -->
<div class="bg-gray-800 rounded-lg p-6">
<h2 class="text-xl font-semibold text-white mb-4">
<i class="fas fa-envelope mr-2"></i>Email</h2>
<form id="email-form" class="space-y-4">
<div>
<label for="email" class="block text-sm font-medium text-gray-300 mb-2">Email</label>
<input type="email" id="email" name="email" value="{{.Email}}"
class="form-input" placeholder="you@example.com" required>
</div>
<div class="flex justify-end">
<button type="submit" class="btn-primary">
<i class="fas fa-save mr-2"></i>Update Email
</button>
</div>
</form>
</div>
<!-- Password -->
<div class="bg-gray-800 rounded-lg p-6">
<h2 class="text-xl font-semibold text-white mb-4">
<i class="fas fa-key mr-2"></i>Change Password</h2>
<form id="password-form" class="space-y-4">
<div>
<label for="current_password" class="block text-sm font-medium text-gray-300 mb-2">Current Password</label>
<input type="password" id="current_password" name="current_password" class="form-input" required>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="new_password" class="block text-sm font-medium text-gray-300 mb-2">New Password</label>
<input type="password" id="new_password" name="new_password" class="form-input" minlength="8" required>
</div>
<div>
<label for="confirm_password" class="block text-sm font-medium text-gray-300 mb-2">Confirm New Password</label>
<input type="password" id="confirm_password" name="confirm_password" class="form-input" minlength="8" required>
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="btn-primary">
<i class="fas fa-save mr-2"></i>Change Password
</button>
</div>
</form>
</div>
<!-- MFA -->
<div class="bg-gray-800 rounded-lg p-6">
<h2 class="text-xl font-semibold text-white mb-4">
<i class="fas fa-shield-halved mr-2"></i>MultiFactor Authentication</h2>
<div class="flex items-center justify-between">
<div>
<div class="text-white font-medium">Status: <span id="mfa-status" class="ml-1">{{if .MFAEnabled}}Enabled{{else}}Disabled{{end}}</span></div>
<div class="text-sm text-gray-400">Add an extra layer of security to your account</div>
</div>
<div class="space-x-2">
<button id="mfa-enable" class="btn-primary {{if .MFAEnabled}}hidden{{end}}"><i class="fas fa-toggle-on mr-2"></i>Enable</button>
<button id="mfa-disable" class="btn-danger {{if not .MFAEnabled}}hidden{{end}}"><i class="fas fa-toggle-off mr-2"></i>Disable</button>
</div>
</div>
<p class="text-xs text-gray-500 mt-3">Note: This simple flow generates or clears your MFA secret. A full QR/TOTP enrollment flow can be added later.</p>
</div>
</div>
</div>
{{end}}
{{define "profile_scripts"}}
<script>
function getCSRF() {
const m = document.cookie.match(/(?:^|; )csrf_token=([^;]+)/);
return m && m[1] ? decodeURIComponent(m[1]) : '';
}
function formToJSON(form) {
const fd = new FormData(form);
return new URLSearchParams(Array.from(fd.entries()));
}
document.addEventListener('DOMContentLoaded', () => {
const emailForm = document.getElementById('email-form');
const passwordForm = document.getElementById('password-form');
const btnEnable = document.getElementById('mfa-enable');
const btnDisable = document.getElementById('mfa-disable');
const mfaStatus = document.getElementById('mfa-status');
if (emailForm) emailForm.addEventListener('submit', async (e) => {
e.preventDefault();
try {
const res = await fetch('/editor/profile/email', {
method: 'POST',
headers: Object.assign({'X-CSRF-Token': getCSRF()}),
body: formToJSON(emailForm)
});
const data = await res.json().catch(() => ({}));
if (res.ok && data.success) {
showNotification('Email updated', 'success');
} else {
throw new Error(data.error || res.statusText);
}
} catch (err) {
showNotification('Update failed: ' + err.message, 'error');
}
});
if (passwordForm) passwordForm.addEventListener('submit', async (e) => {
e.preventDefault();
const newpw = document.getElementById('new_password').value;
const conf = document.getElementById('confirm_password').value;
if (newpw !== conf) {
showNotification('New passwords do not match', 'error');
return;
}
try {
const res = await fetch('/editor/profile/password', {
method: 'POST',
headers: Object.assign({'X-CSRF-Token': getCSRF()}),
body: formToJSON(passwordForm)
});
const data = await res.json().catch(() => ({}));
if (res.ok && data.success) {
passwordForm.reset();
showNotification('Password changed', 'success');
} else {
throw new Error(data.error || res.statusText);
}
} catch (err) {
showNotification('Password change failed: ' + err.message, 'error');
}
});
async function toggleMFA(enable) {
try {
const url = enable ? '/editor/profile/mfa/enable' : '/editor/profile/mfa/disable';
const res = await fetch(url, { method: 'POST', headers: {'X-CSRF-Token': getCSRF()} });
const data = await res.json().catch(() => ({}));
if (res.ok && data.success) {
if (enable) {
if (data.setup && data.redirect) {
// Enrollment flow: go to setup page; do not toggle UI yet
window.location.href = data.redirect;
return;
}
// Direct enable (no setup)
btnEnable.classList.add('hidden');
btnDisable.classList.remove('hidden');
mfaStatus.textContent = 'Enabled';
} else {
btnDisable.classList.add('hidden');
btnEnable.classList.remove('hidden');
mfaStatus.textContent = 'Disabled';
}
showNotification('MFA ' + (enable ? 'enabled' : 'disabled'), 'success');
} else {
throw new Error(data.error || res.statusText);
}
} catch (err) {
showNotification('MFA update failed: ' + err.message, 'error');
}
}
if (btnEnable) btnEnable.addEventListener('click', (e) => { e.preventDefault(); toggleMFA(true); });
if (btnDisable) btnDisable.addEventListener('click', (e) => { e.preventDefault(); toggleMFA(false); });
});
</script>
{{end}}