// Render-blocking auth gate — included as the FIRST
element on every // protected page so unauthenticated visitors are redirected before any markup // paints (prevents pages like /gstr1 from being viewable by direct URL access). (function(){ try{ // Session-only auth: login is mirrored into a SESSION COOKIE (no // expires/max-age) at login time — cookies, unlike sessionStorage, // are shared across every tab of the same browser, so Ctrl+Click / // "open in new tab" doesn't force a fresh login. A true session cookie // is still wiped by the browser itself when it fully closes (not just a // tab), so closing the browser still always ends the session — same // guarantee as before, just no longer a per-tab-only one. // Defensive cleanup: remove any leftover localStorage entries from an // even older version of this scheme, so a lingering copy can't grant access. try{ localStorage.removeItem('token'); localStorage.removeItem('portal_user'); }catch(e){} function getCookie(name){ var m=document.cookie.match(new RegExp('(?:^|; )'+name+'=([^;]*)')); return m?decodeURIComponent(m[1]):null; } if(!sessionStorage.getItem('portal_token')){ var cTok=getCookie('portal_token'), cUser=getCookie('portal_user'); if(cTok){ sessionStorage.setItem('portal_token',cTok); if(cUser) sessionStorage.setItem('portal_user',cUser); } } var tok=sessionStorage.getItem('portal_token'); if(!tok){ location.replace('/'); return; } // Token PRESENCE alone isn't enough — a token issued at login lives in // storage indefinitely even after its 12h JWT expiry (nothing clears it // on its own), so a tab left open/idle past that would otherwise render // the full page and let the user act right up until an API call 401s. // Decode the JWT payload (no verification needed client-side, just the // exp claim) and bounce immediately if it's already expired. function jwtExpired(t){ try{ var payload=JSON.parse(atob(t.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))); return !payload.exp || (Date.now()>=payload.exp*1000); }catch(e){ return true; } } function killSession(){ sessionStorage.clear();document.cookie='portal_token=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT';document.cookie='portal_user=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT';; location.replace('/'); } if(jwtExpired(tok)){ killSession(); return; } // Re-check periodically so a tab left open through expiry gets logged // out on its own, instead of only discovering it on the next API call. setInterval(function(){ var t=sessionStorage.getItem('portal_token'); if(!t||jwtExpired(t)) killSession(); }, 60000); // Page → required module key. Pages not listed here are accessible to any // authenticated user (e.g. the dashboard itself). var PAGE_MODULES={ '/gstr1':'gstr1', '/gstr2':'gstr2', '/itc04':'itc04', '/business-master':'business-master', '/admin':'admin', '/bom':'bom', '/items':'items', '/production':'production-create', '/issue-production':'production-issue', '/receipt-production':'production-receipt', '/close-production':'production-close', '/requirements':'production-requirements', '/batch-issuance':'production-batch-issuance', '/work-order':'production-work-order', '/purchase-request':'purchase-request', '/purchase-quotation':'purchase-quotation', '/purchase-order':'purchase-order', '/grpo':'purchase-grpo', '/approvals':'approvals', '/sap-approvals':'sap-approvals', '/vendor-register':'vendors', '/register':'customers', '/documents':'documents', '/reports':'reports', '/project-form':['projects-new','projects-view'], '/project-approvals':'projects-approvals', '/costing-pl':'finance-monthly', '/costing-comparison':'finance-comparison', '/cost-sheet':'finance-costsheet', '/balance-sheet':'finance-balancesheet', '/cash-flow':'finance-cashflow', '/salary':'salary', }; // Backward-compat: a user granted the OLD broad key (e.g. 'production', // before it was split into per-page sub-modules) still gets every new // sub-key under it, so nobody silently loses access when this ships. var LEGACY_BROAD_MODULES={production:'production-',purchase:'purchase-',costing:'finance-',projects:'projects-'}; var path=location.pathname.replace(/\/$/,'')||'/'; var reqRaw=PAGE_MODULES[path]; if(reqRaw){ var required=Array.isArray(reqRaw)?reqRaw:[reqRaw]; // any ONE of these keys is enough var raw=sessionStorage.getItem('portal_user'); var user=raw?JSON.parse(raw):null; var role=user&&user.role; var isAdmin=role==='admin'||role==='sap_adder'; var mods=user&&Array.isArray(user.modules)?user.modules:null; var allowed=isAdmin; // no modules explicitly granted → no access (was: unchecked = full access) if(!allowed&&mods){ for(var i=0;i