/* Auth guard for the Work Package Suite. Included in the of every protected page (before other scripts). It confirms there is a valid session by calling /api/auth/me; if not, it sends the user to the login page. The real protection is server-side (the API refuses data requests without a session) — this guard is for UX so people land on the login screen instead of an empty app. It also exposes: window.WP_USER the logged-in user object (set once verified) window.wpLogout() clears the session and returns to the login page and dispatches a 'wp-auth-ready' event on document once WP_USER is set. */ (function () { 'use strict'; var inIframe = (function () { try { return window.top !== window.self; } catch (e) { return true; } })(); // Hide the page until we know the user is allowed, to avoid a flash of the app // before a redirect. A safety timer reveals it even if the check hangs. var root = document.documentElement; var style = document.createElement('style'); style.textContent = '.wp-auth-pending body{visibility:hidden!important}'; (document.head || root).appendChild(style); root.className += ' wp-auth-pending'; function reveal() { root.className = root.className.replace(/\bwp-auth-pending\b/, ''); } var safety = setTimeout(reveal, 4000); function goToLogin() { clearTimeout(safety); var next = encodeURIComponent(location.pathname + location.search); var url = 'login.html?next=' + next; // If we're inside the WP-creator iframe, redirect the whole window. var w = inIframe ? window.top : window; try { w.location.replace(url); } catch (e) { window.location.replace(url); } } window.wpLogout = function () { fetch('/api/auth/logout', { method: 'POST' }) .catch(function () {}) .then(function () { window.location.replace('login.html'); }); }; // Change-password dialog (uses POST /api/auth/password, which requires the // current password). Available from the top-right pill on any page. window.wpChangePassword = function () { if (document.getElementById('wp-pw-modal')) return; var ov = document.createElement('div'); ov.id = 'wp-pw-modal'; ov.style.cssText = 'position:fixed;inset:0;background:rgba(20,30,50,.5);display:flex;align-items:center;' + 'justify-content:center;z-index:10002;padding:20px;font:14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;'; var inp = 'width:100%;padding:9px 10px;margin-bottom:12px;border:1px solid #8d8d8d;border-radius:4px;font-size:14px;'; var lbl = 'display:block;font-size:12px;color:#525252;margin-bottom:4px;'; ov.innerHTML = '
' + '
Change password
' + '
' + '' + '' + '' + '' + '' + '' + '' + '
' + '
' + '' + '' + '
' + '
'; function close() { var m = document.getElementById('wp-pw-modal'); if (m) m.remove(); } function msg(text, ok) { var el = document.getElementById('wp-pw-msg'); el.style.display = 'block'; el.textContent = text; el.style.background = ok ? '#defbe6' : '#fff1f1'; el.style.color = ok ? '#0e6027' : '#da1e28'; } ov.addEventListener('click', function (e) { if (e.target === ov) close(); }); document.body.appendChild(ov); document.getElementById('wp-pw-cancel').onclick = close; document.getElementById('wp-pw-cur').focus(); document.getElementById('wp-pw-save').onclick = function () { var cur = document.getElementById('wp-pw-cur').value; var n1 = document.getElementById('wp-pw-new').value; var n2 = document.getElementById('wp-pw-new2').value; if (!cur || !n1) { msg('Please fill in every field.', false); return; } if (n1.length < 8) { msg('New password must be at least 8 characters.', false); return; } if (n1 !== n2) { msg('New passwords do not match.', false); return; } fetch('/api/auth/password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current_password: cur, new_password: n1 }) }) .then(function (r) { return r.json().catch(function () { return null; }).then(function (j) { return { ok: r.ok, status: r.status, j: j }; }); }) .then(function (res) { if (res.ok) { msg('Password updated.', true); setTimeout(close, 1200); } else { msg((res.j && res.j.detail) || ('Could not update (HTTP ' + res.status + ').'), false); } }) .catch(function () { msg('Could not reach the server.', false); }); }; }; function addLogoutPill(user) { if (inIframe) return; // the parent page already shows it if (document.getElementById('wp-logout-pill')) return; var pill = document.createElement('div'); pill.id = 'wp-logout-pill'; pill.style.cssText = 'position:fixed;top:12px;right:12px;z-index:10001;' + 'display:flex;align-items:center;gap:8px;background:#fff;border:1px solid #e0e0e0;' + 'box-shadow:0 1px 4px rgba(0,0,0,.16);border-radius:16px;padding:5px 12px;' + 'font:500 12px/1.2 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:#525252;'; function sep() { var s = document.createElement('span'); s.textContent = '·'; s.style.color = '#a8a8a8'; return s; } var who = document.createElement('span'); who.textContent = user.full_name || user.username; pill.appendChild(who); // Admins get a link to the Admin Console (hidden when already on it). var onAdmin = /(^|\/)admin\.html$/.test(location.pathname); if (user.role === 'admin' && !onAdmin) { var adm = document.createElement('a'); adm.href = 'admin.html'; adm.textContent = 'Admin'; adm.style.cssText = 'color:#0f62fe;text-decoration:none;font-weight:600;'; pill.appendChild(sep()); pill.appendChild(adm); } var pw = document.createElement('a'); pw.href = '#'; pw.textContent = 'Password'; pw.style.cssText = 'color:#0f62fe;text-decoration:none;font-weight:600;'; pw.addEventListener('click', function (e) { e.preventDefault(); window.wpChangePassword(); }); pill.appendChild(sep()); pill.appendChild(pw); var out = document.createElement('a'); out.href = '#'; out.textContent = 'Sign out'; out.style.cssText = 'color:#0f62fe;text-decoration:none;font-weight:600;'; out.addEventListener('click', function (e) { e.preventDefault(); window.wpLogout(); }); pill.appendChild(sep()); pill.appendChild(out); document.body.appendChild(pill); } fetch('/api/auth/me', { headers: { 'Accept': 'application/json' } }) .then(function (r) { if (r.status === 401 || r.status === 403) { goToLogin(); return; } if (!r.ok) { reveal(); clearTimeout(safety); return; } // unexpected; show page rather than trap return r.json().then(function (data) { clearTimeout(safety); window.WP_USER = data && data.user; reveal(); if (window.WP_USER) { try { document.dispatchEvent(new CustomEvent('wp-auth-ready', { detail: window.WP_USER })); } catch (e) {} if (document.body) addLogoutPill(window.WP_USER); else document.addEventListener('DOMContentLoaded', function () { addLogoutPill(window.WP_USER); }); } }); }) .catch(function () { goToLogin(); }); // API unreachable → send to login })();