diff --git a/server.js b/server.js index 57578a8..6eb7234 100644 --- a/server.js +++ b/server.js @@ -686,6 +686,23 @@ async function handleApi(req,res,pathname){ if(r.error)return json(res,401,{error:r.error}); return json(res,200,{ok:true,id:r.id},{'Set-Cookie':messages.sessionCookie(r.token)}); } + // Positions on the team root's own upline - i.e. everyone above us, up to and + // including the contract root. Fixed at registration, so cache indefinitely. + let _suiteUpCache=null, _suiteUpKey=''; + async function suiteRootUpline(roots){ + const key=roots.join(','); + if(_suiteUpCache&&_suiteUpKey===key)return _suiteUpCache; + const out=[]; + for(const r of roots){ + try{ + const rd=await chain.memberPublic(r); + if(rd&&Array.isArray(rd.uplineChain))rd.uplineChain.forEach(x=>{const n=Number(x);if(n&&!out.includes(n))out.push(n);}); + }catch(e){} + } + if(out.length){_suiteUpCache=out;_suiteUpKey=key;} + return out; + } + // ── Circle Suite entitlement helper (shared by suite-me and the tools) ──── async function suiteEntitlement(req){ const s=messages.authFromCookie(req); @@ -701,7 +718,14 @@ async function handleApi(req,res,pathname){ const cfg=getConfig(); const roots=String(cfg.teamRootId||cfg.orgRootId||'21').split(',').map(x=>Number(x.trim())).filter(Boolean); const chainIds=Array.isArray(d.uplineChain)?d.uplineChain.map(Number):[]; - const inOrg=roots.some(r=>Number(d.id)===r||chainIds.includes(r)); + // The org test only looks DOWNWARD: you are in if a root sits in your upline. + // The people ABOVE our root - the founders and contract owners, up through + // position #2 and the root account - can never pass that, yet the Suite is + // as much theirs as ours. Walk each root's own upline and entitle those + // positions too. Registration fixes a position's upline permanently, so this + // is computed once and memoised rather than hit on every request. + const upIds=await suiteRootUpline(roots); + const inOrg=roots.some(r=>Number(d.id)===r||chainIds.includes(r))||upIds.includes(Number(d.id)); const allow=String(cfg.suiteAllowlist||'').split(',').map(x=>Number(x.trim())).filter(Boolean); const beta=allow.length>0; const allowed=!beta||allow.includes(Number(d.id));