feat: MFA enforcement - blocking overlay after email save until TFA is set up
This commit is contained in:
parent
44326c542d
commit
5cff118d0c
3 changed files with 49 additions and 34 deletions
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## 0.1.0.0045 — 2026-07-11
|
||||
|
||||
### Changed
|
||||
- MFA enforcement: after saving email in settings, if TOTP is not enabled, a blocking overlay forces the user to set up two-factor on the Authelia portal before proceeding
|
||||
|
||||
## 0.1.0.0044 — 2026-07-11
|
||||
|
||||
### Added
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.1.0.0044
|
||||
0.1.0.0045
|
||||
|
|
|
|||
76
main.go
76
main.go
|
|
@ -762,13 +762,16 @@ func checkMFAStatus(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Call Authelia API to check TOTP status
|
||||
// Attempt to call Authelia API to check TOTP status.
|
||||
// This requires an active user session (firstfactor completed),
|
||||
// so it may return 403 if called server-side without session.
|
||||
token := os.Getenv("AUTHELIA_SECRET")
|
||||
req, _ := http.NewRequest("GET", "http://authelia:9091/api/user/info", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
// Can't verify TOTP status server-side. Show prompt if email is set.
|
||||
json.NewEncoder(w).Encode(map[string]bool{"mfa_enabled": false})
|
||||
return
|
||||
}
|
||||
|
|
@ -1054,19 +1057,24 @@ const settingsHTML = `<!DOCTYPE html>
|
|||
<span id="savemsg" class="saved-msg">{{t .Lang "saved"}}</span>
|
||||
</div>
|
||||
</form>
|
||||
<!-- MFA Section -->
|
||||
<div id="mfa-section" style="display:none;margin-top:2rem;padding:1.5rem;background:#f7fafc;border-radius:8px;border:1px solid #e2e8f0;">
|
||||
<h3>🔐 Two-Factor Authentication</h3>
|
||||
<div id="mfa-active" style="display:none;">
|
||||
<p style="color:#38a169;font-weight:500;">✅ Two-factor authentication is active. Your account is secure.</p>
|
||||
</div>
|
||||
<div id="mfa-setup-prompt">
|
||||
<p>You have configured a work email. For security, enable two-factor authentication with an authenticator app (Google Authenticator, Authy, etc.).</p>
|
||||
<a class="btn-secondary" href="https://auth.nextwks.eu" target="_blank">Set Up Two-Factor Now →</a>
|
||||
<p class="field-note" style="margin-top:0.5rem;">
|
||||
After setting up, click refresh to verify.
|
||||
<button class="btn-small" onclick="checkMFA()" style="padding:0.25rem 0.75rem;border:1px solid #e2e8f0;border-radius:4px;background:#fff;cursor:pointer;">Verify Setup</button>
|
||||
<!-- MFA Enforcement Overlay -->
|
||||
<div id="mfa-overlay" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:2000;align-items:center;justify-content:center;">
|
||||
<div style="background:#fff;border-radius:12px;padding:2.5rem;width:480px;max-width:90%;text-align:center;">
|
||||
<h3 style="font-size:1.3rem;margin-bottom:0.75rem;">🔐 Two-Factor Authentication Required</h3>
|
||||
<p style="color:#4a5568;font-size:0.95rem;margin-bottom:1rem;">
|
||||
You have added a work email. For security, you must enable two-factor authentication before continuing.
|
||||
</p>
|
||||
<div style="background:#f7fafc;padding:1rem;border-radius:8px;margin-bottom:1.25rem;text-align:left;">
|
||||
<p style="font-size:0.88rem;color:#4a5568;"><strong>1.</strong> Open the Authelia portal and log in.</p>
|
||||
<p style="font-size:0.88rem;color:#4a5568;"><strong>2.</strong> Go to <strong>Security → Two-Factor</strong> to set up your authenticator app.</p>
|
||||
<p style="font-size:0.88rem;color:#4a5568;"><strong>3.</strong> Scan the QR code with Google Authenticator, Authy, or similar.</p>
|
||||
<p style="font-size:0.88rem;color:#4a5568;"><strong>4.</strong> Come back here and click <strong>Verify</strong>.</p>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:0.75rem;">
|
||||
<a class="btn-secondary" href="https://auth.nextwks.eu" target="_blank" style="width:100%;text-align:center;">Open Authelia Portal →</a>
|
||||
<button onclick="verifyMFA()" style="padding:0.6rem 1rem;border:1px solid #3182ce;border-radius:6px;background:#ebf8ff;color:#3182ce;font-size:0.9rem;cursor:pointer;font-weight:500;">✓ I've Set Up Two-Factor — Verify</button>
|
||||
</div>
|
||||
<p id="mfa-verify-msg" style="margin-top:0.75rem;font-size:0.85rem;color:#718096;display:none;">Checking... If verification keeps failing, make sure you completed the setup on the Authelia portal.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1079,29 +1087,21 @@ const settingsHTML = `<!DOCTYPE html>
|
|||
{{end}}
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
checkMFA();
|
||||
});
|
||||
|
||||
async function checkMFA() {
|
||||
const resp = await fetch('/api/user/mfa-status');
|
||||
const data = await resp.json();
|
||||
const section = document.getElementById('mfa-section');
|
||||
const prompt = document.getElementById('mfa-setup-prompt');
|
||||
const active = document.getElementById('mfa-active');
|
||||
const emailField = document.querySelector('input[name="email"]');
|
||||
return data.mfa_enabled === true;
|
||||
}
|
||||
|
||||
if (!emailField || !emailField.value) {
|
||||
section.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
section.style.display = 'block';
|
||||
if (data.mfa_enabled) {
|
||||
prompt.style.display = 'none';
|
||||
active.style.display = 'block';
|
||||
async function verifyMFA() {
|
||||
const msg = document.getElementById('mfa-verify-msg');
|
||||
msg.style.display = 'block';
|
||||
msg.textContent = 'Checking...';
|
||||
const enabled = await checkMFA();
|
||||
if (enabled) {
|
||||
document.getElementById('mfa-overlay').style.display = 'none';
|
||||
} else {
|
||||
prompt.style.display = 'block';
|
||||
active.style.display = 'none';
|
||||
msg.textContent = 'Two-factor not detected yet. Make sure you set it up on the Authelia portal, then click Verify again.';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1112,7 +1112,17 @@ const settingsHTML = `<!DOCTYPE html>
|
|||
const resp = await fetch('/settings/save', {method:'POST', body:new URLSearchParams(data)});
|
||||
const msg = document.getElementById('savemsg');
|
||||
if (resp.ok) { msg.style.display = 'inline'; setTimeout(() => msg.style.display = 'none', 3000); }
|
||||
setTimeout(checkMFA, 1000);
|
||||
|
||||
// Check if email was saved and enforce MFA
|
||||
const emailField = document.querySelector('input[name="email"]');
|
||||
if (emailField && emailField.value) {
|
||||
setTimeout(async () => {
|
||||
const enabled = await checkMFA();
|
||||
if (!enabled) {
|
||||
document.getElementById('mfa-overlay').style.display = 'flex';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue