fix: bind Telegram pairing to expiring single-use account challenges
This commit is contained in:
+11
-8
@@ -39,6 +39,7 @@ import { kalemleriCikar } from './kalem-cikar.mjs';
|
||||
import { searchInDocuments, searchByOkas, okasAra } from './ekap-v2.mjs';
|
||||
import { iknCikar } from './kalem-cikar.mjs';
|
||||
import { bonusHesapla, gommeDurumu, arkaPlandaIsit } from './uyum-gomme.mjs';
|
||||
import { createTelegramLinker } from './telegram-link.mjs';
|
||||
import { telegramConfigured, botAdi, kodIleBagla, sendTelegram } from './telegram.mjs';
|
||||
import { bildirimleriOku, okunduIsaretle, zeyilnameDurumu, zeyilnameTara, sonucTara } from './zeyilname.mjs';
|
||||
|
||||
@@ -897,7 +898,7 @@ const server = http.createServer(async (req, res) => {
|
||||
return json(res, 200, { success: true, ...gommeDurumu() });
|
||||
}
|
||||
// --- Telegram bağlantısı (kullanıcı kodu bota yazar, biz getUpdates'te görürüz) ---
|
||||
if (url.pathname.startsWith('/api/me/telegram')) {
|
||||
if (url.pathname === '/api/me/telegram') {
|
||||
const user = userFromAuthHeader(req.headers['authorization']);
|
||||
if (!user) return json(res, 401, { success: false, error: 'oturum yok' });
|
||||
const tg = store.getData(user.email, 'telegram');
|
||||
@@ -913,15 +914,16 @@ const server = http.createServer(async (req, res) => {
|
||||
await sendTelegram(tg.chatId, '✅ İhaleist bağlantı testi — bildirimler bu sohbete gelecek.');
|
||||
return json(res, 200, { success: true });
|
||||
}
|
||||
const kod = String(body.kod || '').replace(/\D/g, '');
|
||||
if (kod.length !== 6) return json(res, 400, { success: false, error: '6 haneli kod gerekli' });
|
||||
const bulunan = await kodIleBagla(kod);
|
||||
if (!bulunan) return json(res, 404, { success: false, error: 'Kod bota gönderilmiş görünmüyor. Önce bota yazın, sonra doğrulayın.' });
|
||||
store.setData(user.email, 'telegram', { ...bulunan, baglanma: new Date().toISOString() });
|
||||
await sendTelegram(bulunan.chatId, `✅ İhaleist hesabınız (${user.email}) bu sohbete bağlandı.`).catch(() => {});
|
||||
const linker=createTelegramLinker(store);
|
||||
if(body.islem==='kod'){
|
||||
if(!telegramConfigured())return json(res,503,{success:false,error:'Telegram hizmeti etkin değil.'});
|
||||
return json(res,200,{success:true,...linker.issue(user.email)});
|
||||
}
|
||||
const bulunan=await linker.link(user.email,body.kod,kodIleBagla);
|
||||
return json(res, 200, { success: true, ad: bulunan.ad });
|
||||
} catch (e) { return json(res, 502, { success: false, error: e.message }); }
|
||||
} catch (e) { return json(res,e.kod==='VERSION_CONFLICT'?409:e.status||502,{success:false,error:e.message}); }
|
||||
}
|
||||
return json(res,405,{success:false,error:'Desteklenmeyen metot.'});
|
||||
}
|
||||
// --- Takip bildirimleri (zeyilname / sonuç) ---
|
||||
if (url.pathname === '/api/me/bildirimler') {
|
||||
@@ -1217,6 +1219,7 @@ const server = http.createServer(async (req, res) => {
|
||||
const protectedKey=['firma_profil','saved_filters','ekip','favori_kurumlar','rakip_izleme','reminder','brief'].includes(k);
|
||||
if(protectedKey && (!Object.hasOwn(body,'revision') || (body.revision!==null && (typeof body.revision!=='string' || !/^[a-f0-9]{64}$/.test(body.revision)))))
|
||||
return json(res,428,{success:false,kod:'VERSION_REQUIRED',error:'Güncel kayıt sürümü gerekli. Sayfayı yenileyip tekrar deneyin.'});
|
||||
if(k==='telegram')return json(res,403,{success:false,error:'Telegram bağlantısını doğrulama ekranından yönetin.'});
|
||||
const revision=store.setData(user.email, k, body.deger, protectedKey ? body.revision : undefined);
|
||||
return json(res, 200, { success: true, ...(protectedKey ? {revision} : {}) });
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { randomBytes, createHash } from 'node:crypto';
|
||||
const digest = code => createHash('sha256').update(code).digest('hex');
|
||||
export function createTelegramLinker(store,clock=Date.now) {
|
||||
function validate(email,code) {
|
||||
const snapshot=store.getDataSnapshot(email),value=snapshot.veri.telegram;
|
||||
if(typeof code!=='string'||!/^[a-f0-9]{48}$/.test(code)||value?.challengeHash!==digest(code)||
|
||||
!Number.isFinite(value.expiresAt)||clock()>=value.expiresAt)
|
||||
throw Object.assign(new Error('Bağlama kodu geçersiz veya süresi dolmuş. Yeni kod oluşturun.'),{status:400});
|
||||
return {value,revision:snapshot.revisions.telegram ?? null};
|
||||
}
|
||||
return {
|
||||
issue(email) {
|
||||
const snapshot=store.getDataSnapshot(email);
|
||||
if(snapshot.veri.telegram?.chatId)throw Object.assign(new Error('Önce mevcut Telegram bağlantısını kaldırın.'),{status:409});
|
||||
const code=randomBytes(24).toString('hex'),issuedAt=clock(),expiresAt=issuedAt+10*60000;
|
||||
store.setData(email,'telegram',{challengeHash:digest(code),issuedAt,expiresAt},snapshot.revisions.telegram ?? null);
|
||||
return {kod:code,expiresAt};
|
||||
},
|
||||
async link(email,code,find) {
|
||||
const {value}=validate(email,code);
|
||||
const found=await find(code,{issuedAt:value.issuedAt,expiresAt:value.expiresAt,now:clock()});
|
||||
if(!found)throw Object.assign(new Error('Kod özel sohbette bulunamadı. Yeni kodu bota gönderip tekrar doğrulayın.'),{status:404});
|
||||
const current=validate(email,code);
|
||||
store.setData(email,'telegram',{chatId:found.chatId,ad:found.ad,baglanma:new Date(clock()).toISOString()},current.revision);
|
||||
return found;
|
||||
},
|
||||
};
|
||||
}
|
||||
+11
-9
@@ -2,7 +2,7 @@
|
||||
* Telegram bildirim kanalı — Bot API, bağımlılık yok.
|
||||
*
|
||||
* KURULUM (kullanıcı/yönetici): BotFather'da bot oluşturulur, tokenı
|
||||
* TELEGRAM_BOT_TOKEN env'ine girilir. Her kullanıcı uygulamadan aldığı 6 haneli
|
||||
* TELEGRAM_BOT_TOKEN env'ine girilir. Her kullanıcı uygulamadan aldığı süreli
|
||||
* kodu bota yazar; sunucu getUpdates ile kodu görüp chat id'yi kullanıcıya
|
||||
* bağlar. Token bizde tutulmaz, sohbet kimliği kullanıcı verisinde durur.
|
||||
*/
|
||||
@@ -48,22 +48,24 @@ export async function sendTelegram(chatId, text, { parseMode = 'HTML', returnRec
|
||||
}
|
||||
|
||||
/**
|
||||
* Kullanıcının bota yazdığı 6 haneli kodu son güncellemelerde arar.
|
||||
* Kullanıcının bota yazdığı süreli bağlama kodunu son güncellemelerde arar.
|
||||
* Bulursa { chatId, ad } döner. Long-polling yok; kullanıcı "doğrula" dediğinde
|
||||
* tek getUpdates çağrısı yeterli (Telegram son 24 saati tutar).
|
||||
*/
|
||||
export async function kodIleBagla(kod) {
|
||||
export async function kodIleBagla(kod, {issuedAt,expiresAt,now=Date.now()} = {}) {
|
||||
if (!telegramConfigured()) throw new Error('Sunucuda TELEGRAM_BOT_TOKEN tanımlı değil');
|
||||
if(!/^[a-f0-9]{48}$/.test(String(kod))||!Number.isFinite(issuedAt)||!Number.isFinite(expiresAt)||now>=expiresAt)return null;
|
||||
const r = await fetch(`${API()}/getUpdates?limit=100`, { signal: AbortSignal.timeout(15000) });
|
||||
const d = await r.json().catch(() => ({}));
|
||||
if (!d.ok) throw new Error(d.description || 'getUpdates başarısız');
|
||||
const aranan = String(kod).trim();
|
||||
const aranan = String(kod);
|
||||
for (const u of [...(d.result || [])].reverse()) {
|
||||
const m = u.message || u.edited_message;
|
||||
if (!m?.text) continue;
|
||||
if (m.text.replace(/\D/g, '').includes(aranan)) {
|
||||
return { chatId: String(m.chat.id), ad: [m.chat.first_name, m.chat.last_name].filter(Boolean).join(' ') || m.chat.username || null };
|
||||
}
|
||||
const m = u.message;
|
||||
if(typeof m?.text!=='string'||m.chat?.type!=='private'||m.from?.is_bot!==false||
|
||||
!Number.isSafeInteger(m.chat.id)||m.chat.id<=0||m.from.id!==m.chat.id||
|
||||
!Number.isFinite(m.date)||m.date<Math.floor(issuedAt/1000)||m.date>Math.floor(now/1000))continue;
|
||||
if(m.text.trim()===aranan || m.text.trim()===`/start ${aranan}`)
|
||||
return {chatId:String(m.chat.id),ad:[m.chat.first_name,m.chat.last_name].filter(Boolean).join(' ')||m.chat.username||null};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user