From 37e58be5ec972812fd48d2669fbb1ce6f1e38347 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 14 Apr 2026 04:43:07 +0200 Subject: [PATCH] Fix local-auth sections not showing for normal users + backup-code modal signature settings load2FAStatus(): - Explicit credentials: 'same-origin' on the /me fetch (was relying on fetch defaults, which can behave oddly in some browsers/edges) - Fall back to window.CURRENT_USER (cached at login) if /me fails, so local-auth users still see their password/2FA sections after a transient error. Keeps cache in sync on each successful fetch. enterApp(): - Cache the logged-in user object on window.CURRENT_USER so modules that need the canLocalAuth flag don't have to re-fetch /me. 2FA regenerate modal: - Previous call passed a wrong-shape options object to showConfirm. Updated to the correct (message, callback, opts) signature with input:true, inputType:'password', placeholder, required. OIDC email_verified check: - Accept boolean true or string 'true' for robustness. Some IdPs serialize ID-token booleans as strings. --- public/js/auth.js | 32 +++++++++++++++++++++----------- src/routes/oidc.js | 4 +++- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/public/js/auth.js b/public/js/auth.js index 49099db..57655cb 100644 --- a/public/js/auth.js +++ b/public/js/auth.js @@ -147,6 +147,7 @@ document.addEventListener('DOMContentLoaded', function() { function enterApp(user, token) { window.AUTH_TOKEN = token; + window.CURRENT_USER = user; // cached so settings page doesn't re-fetch /me just to read canLocalAuth if (isNativeApp()) { // Mobile: persist token to Keychain/Keystore window.SecureStorage.set(TOKEN_KEY, token); @@ -356,15 +357,7 @@ document.addEventListener('DOMContentLoaded', function() { // Regenerate backup codes if (target.id === 'btn-regen-backup-codes' || target.closest('#btn-regen-backup-codes')) { e.preventDefault(); - showConfirm({ - title: 'Regenerate backup codes?', - message: 'This will invalidate your existing backup codes and generate 10 new ones. Enter your current password.', - inputType: 'password', - inputPlaceholder: 'Current password', - confirmText: 'Regenerate', - required: true, - requiredMsg: 'Password is required' - }, function(pw) { + showConfirm('Regenerate backup codes? This invalidates your existing ones. Enter your current password to confirm.', function(pw) { if (!pw) return; fetch('/api/auth/2fa/backup-codes', { method: 'POST', headers: getAuthHeaders(), @@ -375,6 +368,13 @@ document.addEventListener('DOMContentLoaded', function() { if (data.success && data.codes) showBackupCodesModal(data.codes); else showToast(data.error || 'Failed to regenerate codes', 'error'); }); + }, { + input: true, + inputType: 'password', + placeholder: 'Current password', + confirmText: 'Regenerate', + required: true, + requiredMsg: 'Password is required' }); } @@ -729,10 +729,20 @@ document.addEventListener('DOMContentLoaded', function() { // ---- 2FA STATUS ---- function load2FAStatus() { - fetch('/api/auth/me', { headers: getAuthHeaders() }) + fetch('/api/auth/me', { credentials: 'same-origin', headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { - if (!data || !data.user) return; + if (!data || !data.user) { + // /me failed — if we already have a cached user from login, fall back to that + // so reloaded Settings pages still render for local-auth users. + if (window.CURRENT_USER && window.CURRENT_USER.canLocalAuth === true) { + data = { user: window.CURRENT_USER }; + } else { + return; + } + } + // Keep cache in sync + window.CURRENT_USER = data.user; // Sections are display:none by default in the HTML. Only reveal them // if the server confirms this user has a real password hash // (canLocalAuth === true). SSO-auto-created users never see these UIs. diff --git a/src/routes/oidc.js b/src/routes/oidc.js index 3776983..19a83ec 100644 --- a/src/routes/oidc.js +++ b/src/routes/oidc.js @@ -203,7 +203,9 @@ router.get('/oidc/callback', async function(req, res) { return res.redirect(appUrl + '?error=sub_mismatch'); } if (!user.oidc_sub) { - if (emailVerified !== true) { + // Some IdPs (rare) serialize booleans as strings — accept both. + var verified = emailVerified === true || emailVerified === 'true'; + if (!verified) { console.warn('[OIDC] refusing to auto-link existing user ' + user.id + ' — IdP did not assert email_verified=true'); return res.redirect(appUrl + '?error=email_unverified'); }