PolHunter hand-off, nav entry, and the ledger embed
/api/my/polhunter signs a member across to polhunter.com with a five-minute single-use HMAC token (memberId, email, wallet, username); PolHunter has no sign-up and no mailer, this is the only way in. A PolHunter entry in the dashboard nav. The PolHunter embed on the public ledger with one slot beside the payout feed; CSP allows polhunter.com. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -269,6 +269,9 @@ textarea{resize:vertical;font:inherit}
|
||||
.bo-menu button{display:flex;align-items:center;gap:11px;background:transparent;border:0;color:var(--muted);
|
||||
font:600 14px var(--disp);padding:11px 12px;border-radius:11px;cursor:pointer;text-align:left;
|
||||
border-left:3px solid transparent}
|
||||
.bo-menu a{display:flex;align-items:center;gap:11px;color:var(--muted);font:600 14px var(--disp);padding:11px 12px;border-radius:11px;text-align:left;border-left:3px solid transparent;text-decoration:none}
|
||||
.bo-menu a:hover{color:var(--ink);background:rgba(67,232,195,.05)}
|
||||
.bo-menu a svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}
|
||||
.bo-menu button svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}
|
||||
.bo-menu button:hover{color:var(--ink);background:rgba(67,232,195,.05)}
|
||||
.bo-menu button.on{color:var(--mint);background:rgba(67,232,195,.09);border-left-color:var(--mint)}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
</section>
|
||||
<div class="card" id="adSlotBanner" hidden></div>
|
||||
<div class="card" style="padding:0">
|
||||
<div data-ph-slot="0" style="margin:0 0 10px"></div>
|
||||
<div class="feed" id="feed"><div class="row muted">Loading recent history…</div></div>
|
||||
</div>
|
||||
<div class="card small" id="adSlotText" hidden></div>
|
||||
@@ -39,6 +40,7 @@
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260916a"></script>
|
||||
<script src="/assets/ledger.js?v=20260906m"></script>
|
||||
<script src="https://polhunter.com/embed.js" data-slots="1" async></script>
|
||||
<script src="/assets/chat.js?v=20260906m"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+2
-1
@@ -5,7 +5,7 @@
|
||||
<title>Member area | InstantAdPay</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="icon" type="image/png" href="/logo-icon.png">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260919a">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260919b">
|
||||
</head>
|
||||
<body class="bo-body">
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
<button data-pane="earnings" type="button"><svg viewBox="0 0 24 24"><path d="M4 17l5-5 4 3 7-8"/><path d="M14 7h6v6"/></svg>Earnings</button>
|
||||
<button data-pane="promo" type="button"><svg viewBox="0 0 24 24"><path d="M7 10s5-1 9-5v14c-4-4-9-5-9-5H5a2 2 0 0 1-2-2v0a2 2 0 0 1 2-2h2z"/><path d="M8 15l1 5h2l-1-5"/></svg>Promo tools</button>
|
||||
<button data-pane="training" type="button"><svg viewBox="0 0 24 24"><path d="M3 5l9-2 9 2v11l-9 2-9-2z"/><path d="M12 3v13"/><path d="M9 11l3 2 3-2"/></svg>Training</button>
|
||||
<a href="/api/my/polhunter" id="polhunterNav" title="Missions that pay POL"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/><circle cx="12" cy="12" r="3"/></svg>PolHunter</a>
|
||||
<button data-pane="wallet" type="button"><svg viewBox="0 0 24 24"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18"/><circle cx="16.5" cy="14.5" r="1.4"/></svg>Wallet</button>
|
||||
<button data-pane="profile" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="8.5" r="3.6"/><path d="M4.5 20c1.6-3.6 4.2-5.2 7.5-5.2s5.9 1.6 7.5 5.2"/></svg>Profile</button>
|
||||
</nav>
|
||||
|
||||
@@ -483,7 +483,7 @@ const MIME = { '.html': 'text/html; charset=utf-8', '.css': 'text/css', '.js': '
|
||||
'.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.webp': 'image/webp',
|
||||
'.ico': 'image/x-icon', '.json': 'application/json', '.mp4': 'video/mp4', '.woff2': 'font/woff2',
|
||||
'.gif': 'image/gif', '.webm': 'video/webm', '.txt': 'text/plain; charset=utf-8', '.xml': 'application/xml; charset=utf-8' };
|
||||
const CSP = "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://www.networkadspace.com https://networkadspace.com 'sha256-NzvNrqk5jB9YZATwo5BF4JoRlJ02HsnFikbKXgEPdaQ='; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https:; media-src 'self' https: blob:; connect-src 'self' https://*.walletconnect.com wss://*.walletconnect.com https://*.walletconnect.org wss://*.walletconnect.org https://*.reown.com wss://*.reown.com https://*.reown.org wss://*.reown.org https://*.web3modal.org https://*.drpc.org https://*.publicnode.com https://*.coinbase.com; font-src 'self' data: https://fonts.gstatic.com https://fonts.reown.com; form-action 'self'; frame-src https: http:";
|
||||
const CSP = "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://www.networkadspace.com https://networkadspace.com https://polhunter.com 'sha256-NzvNrqk5jB9YZATwo5BF4JoRlJ02HsnFikbKXgEPdaQ='; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https:; media-src 'self' https: blob:; connect-src 'self' https://*.walletconnect.com wss://*.walletconnect.com https://*.walletconnect.org wss://*.walletconnect.org https://*.reown.com wss://*.reown.com https://*.reown.org wss://*.reown.org https://*.web3modal.org https://*.drpc.org https://*.publicnode.com https://*.coinbase.com; font-src 'self' data: https://fonts.gstatic.com https://fonts.reown.com; form-action 'self'; frame-src https: http:";
|
||||
function baseHeaders(extra) {
|
||||
return Object.assign({ 'Content-Security-Policy': CSP, 'X-Content-Type-Options': 'nosniff',
|
||||
'Referrer-Policy': 'strict-origin-when-cross-origin' }, extra || {});
|
||||
@@ -2100,6 +2100,20 @@ const server = http.createServer(async (req, res) => {
|
||||
earnTokens.delete(s.email); // single use
|
||||
return json(res, 200, await ads.recordView(s.email));
|
||||
}
|
||||
// -- PolHunter hand-off (2026-09-19): sign the member across; polhunter.com verifies with the shared secret
|
||||
if (p === '/api/my/polhunter' && req.method === 'GET') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) { res.writeHead(302, { Location: '/my' }); return res.end(); }
|
||||
const secret = String(process.env.HUNT_SSO_SECRET || '').trim(); const huntUrl = String(process.env.HUNT_URL || 'https://polhunter.com').replace(/\/+$/, '');
|
||||
if (secret.length < 32) return json(res, 503, { error: 'PolHunter is not connected yet.' });
|
||||
const acct = await accounts.byEmail(s.email);
|
||||
const memberId = (acct && acct.memberId) || s.memberId || 0;
|
||||
if (!memberId) { res.writeHead(302, { Location: '/my#wallet' }); return res.end(); }
|
||||
const b64u = b => Buffer.from(b).toString('base64').replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||
const payload = JSON.stringify({ iat: Date.now(), exp: Date.now() + 5 * 60000, nonce: crypto.randomBytes(12).toString('hex'), memberId: Number(memberId), email: s.email, wallet: (acct && acct.address) || s.address || null, username: (acct && acct.username) || null });
|
||||
const tok = b64u(payload) + '.' + b64u(crypto.createHmac('sha256', secret).update(payload).digest());
|
||||
res.writeHead(302, { Location: huntUrl + '/auth?t=' + tok, 'Cache-Control': 'no-store' }); return res.end();
|
||||
}
|
||||
if (p === '/api/my/claim' && req.method === 'POST') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
|
||||
Reference in New Issue
Block a user