Companion bot requires its own dedicated token (companionBotToken) - never the shared CTBRewards_Bot token; allowlist the new config key

This commit is contained in:
martbost
2026-08-22 19:42:58 -05:00
parent 6ac1bcab55
commit 1f95025587
2 changed files with 7 additions and 3 deletions
+1 -1
View File
@@ -771,7 +771,7 @@ async function handleApi(req,res,pathname){
const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors}); const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors});
} }
if(req.method==='PATCH'&&pathname==='/api/admin/config'){ if(req.method==='PATCH'&&pathname==='/api/admin/config'){
const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next}); const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','companionBotToken','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next});
} }
const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/); const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/);
if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'}); if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});
+6 -2
View File
@@ -2,7 +2,7 @@
// payout pings. Deliberately additive — private-chat updates only (the same // payout pings. Deliberately additive — private-chat updates only (the same
// bot keeps posting group feeds untouched), no wallet actions ever, all state // bot keeps posting group feeds untouched), no wallet actions ever, all state
// in one JSON file on the volume. Uses the raw Bot API via fetch; token comes // in one JSON file on the volume. Uses the raw Bot API via fetch; token comes
// from config (companionBotToken overrides telegramBotToken when set). // from config key companionBotToken ONLY (see hard rule below).
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@@ -14,7 +14,11 @@ const FILE = () => path.join(DATA_DIR, 'tg-links.json');
function load() { try { return JSON.parse(fs.readFileSync(FILE(), 'utf8')); } catch (e) { return { u: '', members: {}, chats: {}, codes: {}, rmap: {} }; } } function load() { try { return JSON.parse(fs.readFileSync(FILE(), 'utf8')); } catch (e) { return { u: '', members: {}, chats: {}, codes: {}, rmap: {} }; } }
function save(d) { try { fs.writeFileSync(FILE(), JSON.stringify(d)); } catch (e) { console.error('tgbot save', e.message); } } function save(d) { try { fs.writeFileSync(FILE(), JSON.stringify(d)); } catch (e) { console.error('tgbot save', e.message); } }
function token() { const c = getConfig(); return String(c.companionBotToken || c.telegramBotToken || '').trim(); } // HARD RULE: the companion runs ONLY on its own dedicated bot token. The
// shared telegramBotToken belongs to @CTBRewards_Bot (group feeds + the CTB
// Rewards project) — registering a webhook on it breaks CTB's bot. Until
// companionBotToken is set in config, every companion feature is a no-op.
function token() { const c = getConfig(); return String(c.companionBotToken || '').trim(); }
function webhookSecret() { const t = token(); return t ? crypto.createHash('sha1').update('tg-hook:' + t).digest('hex').slice(0, 24) : ''; } function webhookSecret() { const t = token(); return t ? crypto.createHash('sha1').update('tg-hook:' + t).digest('hex').slice(0, 24) : ''; }
async function api(method, payload) { async function api(method, payload) {