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.
This commit is contained in:
parent
c7a04626a3
commit
37e58be5ec
2 changed files with 24 additions and 12 deletions
|
|
@ -147,6 +147,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
function enterApp(user, token) {
|
function enterApp(user, token) {
|
||||||
window.AUTH_TOKEN = token;
|
window.AUTH_TOKEN = token;
|
||||||
|
window.CURRENT_USER = user; // cached so settings page doesn't re-fetch /me just to read canLocalAuth
|
||||||
if (isNativeApp()) {
|
if (isNativeApp()) {
|
||||||
// Mobile: persist token to Keychain/Keystore
|
// Mobile: persist token to Keychain/Keystore
|
||||||
window.SecureStorage.set(TOKEN_KEY, token);
|
window.SecureStorage.set(TOKEN_KEY, token);
|
||||||
|
|
@ -356,15 +357,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Regenerate backup codes
|
// Regenerate backup codes
|
||||||
if (target.id === 'btn-regen-backup-codes' || target.closest('#btn-regen-backup-codes')) {
|
if (target.id === 'btn-regen-backup-codes' || target.closest('#btn-regen-backup-codes')) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showConfirm({
|
showConfirm('Regenerate backup codes? This invalidates your existing ones. Enter your current password to confirm.', function(pw) {
|
||||||
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) {
|
|
||||||
if (!pw) return;
|
if (!pw) return;
|
||||||
fetch('/api/auth/2fa/backup-codes', {
|
fetch('/api/auth/2fa/backup-codes', {
|
||||||
method: 'POST', headers: getAuthHeaders(),
|
method: 'POST', headers: getAuthHeaders(),
|
||||||
|
|
@ -375,6 +368,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
if (data.success && data.codes) showBackupCodesModal(data.codes);
|
if (data.success && data.codes) showBackupCodesModal(data.codes);
|
||||||
else showToast(data.error || 'Failed to regenerate codes', 'error');
|
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 ----
|
// ---- 2FA STATUS ----
|
||||||
function load2FAStatus() {
|
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(r) { return r.json(); })
|
||||||
.then(function(data) {
|
.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
|
// Sections are display:none by default in the HTML. Only reveal them
|
||||||
// if the server confirms this user has a real password hash
|
// if the server confirms this user has a real password hash
|
||||||
// (canLocalAuth === true). SSO-auto-created users never see these UIs.
|
// (canLocalAuth === true). SSO-auto-created users never see these UIs.
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,9 @@ router.get('/oidc/callback', async function(req, res) {
|
||||||
return res.redirect(appUrl + '?error=sub_mismatch');
|
return res.redirect(appUrl + '?error=sub_mismatch');
|
||||||
}
|
}
|
||||||
if (!user.oidc_sub) {
|
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');
|
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');
|
return res.redirect(appUrl + '?error=email_unverified');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue