user authentication
This commit is contained in:
175
web/templates/profile.html
Normal file
175
web/templates/profile.html
Normal file
@@ -0,0 +1,175 @@
|
||||
{{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>Multi‑Factor 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}}
|
||||
Reference in New Issue
Block a user