fix: clear consumed Telegram challenges after link changes
This commit is contained in:
@@ -1124,3 +1124,10 @@ Taslak türü seçicisine erişilebilir ad eklendi. Üretim derlemesi geçti. Ö
|
|||||||
- Genel veri yazma API'siyle doğrudan Telegram chatId atama yolu kapatıldı. UI sunucudan yeni kod oluşturuyor, son geçerliliği gösteriyor, kod oluşmadan doğrulama sunmuyor. Mevcut bağlı hesaplar korunuyor; otomatik bağlantı-onay mesajı yerine uygulama sonucu gösteriliyor.
|
- Genel veri yazma API'siyle doğrudan Telegram chatId atama yolu kapatıldı. UI sunucudan yeni kod oluşturuyor, son geçerliliği gösteriyor, kod oluşmadan doğrulama sunmuyor. Mevcut bağlı hesaplar korunuyor; otomatik bağlantı-onay mesajı yerine uygulama sonucu gösteriliyor.
|
||||||
- Doğrulama: 266 test ve build başarılı. Gerçek kullanıcı deposunda hesap ayrımı, hash saklama, yenileme, süre sınırı, tüketim ve devam eden doğrulamanın iptali; sahte Telegram yanıtlarında özel/taze/tam eşleşme; HTTP üzerinden doğrudan yazma ve eski kod reddi test edildi. Gerçek Telegram ağına test mesajı gönderilmedi.
|
- Doğrulama: 266 test ve build başarılı. Gerçek kullanıcı deposunda hesap ayrımı, hash saklama, yenileme, süre sınırı, tüketim ve devam eden doğrulamanın iptali; sahte Telegram yanıtlarında özel/taze/tam eşleşme; HTTP üzerinden doğrudan yazma ve eski kod reddi test edildi. Gerçek Telegram ağına test mesajı gönderilmedi.
|
||||||
- Açık kabul: yeni kod ekranının tarayıcı testi, izinli gerçek hesapla bağlama; getUpdates son 100 güncelleme penceresinin yoğun botta yetersiz kalması; genel Telegram bağlantı sürüm/geri dönüş akışları. Genel plan henüz tamamlanmadı.
|
- Açık kabul: yeni kod ekranının tarayıcı testi, izinli gerçek hesapla bağlama; getUpdates son 100 güncelleme penceresinin yoğun botta yetersiz kalması; genel Telegram bağlantı sürüm/geri dönüş akışları. Genel plan henüz tamamlanmadı.
|
||||||
|
|
||||||
|
### 2026-09-06 — Telegram kod yenileme ve kaldırma tarayıcı kabulü
|
||||||
|
|
||||||
|
- İzole QA sunucusuna gerçek Telegram'a çıkmayan, üretimdeki createTelegramLinker ve geçici kullanıcı deposunu kullanan sağlayıcı taklidi eklendi. Görünür test kontrolleri son kodun sentetik sohbete ulaştığını ve sağlayıcı saatini yönetiyor; üretime dahil edilmiyor.
|
||||||
|
- Tarayıcı testinde bağlantı kaldırıldıktan sonra tüketilmiş kodun ekranda kaldığı ve Doğrula düğmesinin açık olduğu bulundu. Bağlama/kaldırma tamamlandığında kod artık temizleniyor. Kod yenileme başlarken eski kod kaldırılıyor. Sunucunun geçersiz/süresi dolmuş kod hatası ayrı `TELEGRAM_CHALLENGE_INVALID` koduyla taşınıyor; istemci bu durumda eski kodu temizliyor.
|
||||||
|
- Dokuz tarayıcı kontrolü geçti: koddan önce doğrulama kapalı, sunucu kodu görünür, süre açıklaması, farklı yeni kod, önceki mesajın reddi, başarılı sentetik bağlama, kaldırmada eski kodun temizlenmesi, doğrulamanın kapanması ve kaldırma onayı. Kanıt `rapor/qa-telegram-link-flow.json`. Test sekmeleri ve bu çalışmanın geçici sunucusu kapatıldı.
|
||||||
|
- 266 test ve build başarılı; son hata-kodu doğrulaması için üç Telegram birim testi ayrıca geçti. Süre dolması sunucu testinde doğrulandı; süre hatası sonrası kod temizleme kolu bu tarayıcı koşusunda ayrıca tetiklenmedi. Gerçek sağlayıcıda bağlama/gönderim yapılmadı. Genel planın diğer kabul maddeleri açık.
|
||||||
|
|||||||
+1
-1
@@ -921,7 +921,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
}
|
}
|
||||||
const bulunan=await linker.link(user.email,body.kod,kodIleBagla);
|
const bulunan=await linker.link(user.email,body.kod,kodIleBagla);
|
||||||
return json(res, 200, { success: true, ad: bulunan.ad });
|
return json(res, 200, { success: true, ad: bulunan.ad });
|
||||||
} catch (e) { return json(res,e.kod==='VERSION_CONFLICT'?409:e.status||502,{success:false,error:e.message}); }
|
} catch (e) { return json(res,e.kod==='VERSION_CONFLICT'?409:e.status||502,{success:false,error:e.message,kod:e.kod}); }
|
||||||
}
|
}
|
||||||
return json(res,405,{success:false,error:'Desteklenmeyen metot.'});
|
return json(res,405,{success:false,error:'Desteklenmeyen metot.'});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export function createTelegramLinker(store,clock=Date.now) {
|
|||||||
const snapshot=store.getDataSnapshot(email),value=snapshot.veri.telegram;
|
const snapshot=store.getDataSnapshot(email),value=snapshot.veri.telegram;
|
||||||
if(typeof code!=='string'||!/^[a-f0-9]{48}$/.test(code)||value?.challengeHash!==digest(code)||
|
if(typeof code!=='string'||!/^[a-f0-9]{48}$/.test(code)||value?.challengeHash!==digest(code)||
|
||||||
!Number.isFinite(value.expiresAt)||clock()>=value.expiresAt)
|
!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});
|
throw Object.assign(new Error('Bağlama kodu geçersiz veya süresi dolmuş. Yeni kod oluşturun.'),{status:400,kod:'TELEGRAM_CHALLENGE_INVALID'});
|
||||||
return {value,revision:snapshot.revisions.telegram ?? null};
|
return {value,revision:snapshot.revisions.telegram ?? null};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const TelegramPanel = ({isAdmin}) => {
|
|||||||
const calistir = async (ad, fn, basari) => {
|
const calistir = async (ad, fn, basari) => {
|
||||||
if(locked.current)return;locked.current=true;
|
if(locked.current)return;locked.current=true;
|
||||||
setBusy(ad);setMesaj(null);
|
setBusy(ad);setMesaj(null);
|
||||||
try {await fn();if(!alive.current)return;if(ad!=='load')await yenile();if(alive.current&&basari)setMesaj({tur:'ok',metin:basari});}
|
try {await fn();if(!alive.current)return;if(ad==='bagla'||ad==='sil')setChallenge(null);if(ad!=='load')await yenile();if(alive.current&&basari)setMesaj({tur:'ok',metin:basari});}
|
||||||
catch(e){if(alive.current)setMesaj({tur:'hata',metin:e.message});}
|
catch(e){if(alive.current){if(e.kod==='TELEGRAM_CHALLENGE_INVALID')setChallenge(null);setMesaj({tur:'hata',metin:e.message});}}
|
||||||
finally{locked.current=false;if(alive.current)setBusy(null);}
|
finally{locked.current=false;if(alive.current)setBusy(null);}
|
||||||
};
|
};
|
||||||
useEffect(()=>{alive.current=true;void calistir('load',yenile);return()=>{alive.current=false;};},[]);
|
useEffect(()=>{alive.current=true;void calistir('load',yenile);return()=>{alive.current=false;};},[]);
|
||||||
@@ -95,6 +95,7 @@ const TelegramPanel = ({isAdmin}) => {
|
|||||||
<li>Sonra buraya dönüp <strong>Doğrula</strong>'ya basın.</li>
|
<li>Sonra buraya dönüp <strong>Doğrula</strong>'ya basın.</li>
|
||||||
</ol>
|
</ol>
|
||||||
<button disabled={!!busy} className="border rounded px-3 py-2" onClick={()=>calistir('kod',async()=>{
|
<button disabled={!!busy} className="border rounded px-3 py-2" onClick={()=>calistir('kod',async()=>{
|
||||||
|
setChallenge(null);
|
||||||
const data=await telegramKodOlustur();
|
const data=await telegramKodOlustur();
|
||||||
if(typeof data.kod!=='string'||!/^[a-f0-9]{48}$/.test(data.kod)||!Number.isFinite(data.expiresAt))throw new Error('Bağlama kodu doğrulanamadı.');
|
if(typeof data.kod!=='string'||!/^[a-f0-9]{48}$/.test(data.kod)||!Number.isFinite(data.expiresAt))throw new Error('Bağlama kodu doğrulanamadı.');
|
||||||
if(alive.current)setChallenge(data);
|
if(alive.current)setChallenge(data);
|
||||||
|
|||||||
+25
-2
@@ -44,10 +44,33 @@ const qaDelivery=qaSubscriptionStore.reserveDelivery(qaReviewSub.id,{to:user.ema
|
|||||||
qaSubscriptionStore.failDelivery(qaDelivery,'Sentetik belirsiz sağlayıcı sonucu.');
|
qaSubscriptionStore.failDelivery(qaDelivery,'Sentetik belirsiz sağlayıcı sonucu.');
|
||||||
qaSubscriptionStore.reserveDelivery(qaReviewSub.id,null,[]);
|
qaSubscriptionStore.reserveDelivery(qaReviewSub.id,null,[]);
|
||||||
let subscriptionFailure='read';
|
let subscriptionFailure='read';
|
||||||
let telegramFailure=true;
|
let telegramFailure=true,telegramFixture=false,telegramInbox=null,telegramLatestCode=null,telegramClockOffset=0;
|
||||||
|
const qaTelegramStore=await import('../server/user-store.mjs');
|
||||||
|
const {createTelegramLinker}=await import('../server/telegram-link.mjs');
|
||||||
|
const qaTelegramLinker=createTelegramLinker(qaTelegramStore,()=>Date.now()+telegramClockOffset);
|
||||||
const ui=await createServer({server:{host:'127.0.0.1',port:0,proxy:{'/api':{target:'http://127.0.0.1:'+port}}},plugins:[{name:'qa-fixtures',configureServer(server){server.middlewares.use(async(req,res,next)=>{
|
const ui=await createServer({server:{host:'127.0.0.1',port:0,proxy:{'/api':{target:'http://127.0.0.1:'+port}}},plugins:[{name:'qa-fixtures',configureServer(server){server.middlewares.use(async(req,res,next)=>{
|
||||||
if(req.url==='/qa-responsive'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel ekran boyutu testi</h1><p>Gerçek uygulama ayrı bir görünüm alanında yüklenir.</p><button data-width="390">390 piksel</button><button data-width="768">768 piksel</button><button data-width="1440">1440 piksel</button><pre id="metrics" aria-label="Görünüm ölçümleri"></pre><iframe title="İhaleist test görünümü" src="/" style="display:block;width:390px;height:900px;border:0"></iframe><script>document.querySelectorAll('button').forEach(button=>button.onclick=()=>document.querySelector('iframe').style.width=button.dataset.width+'px');setInterval(()=>{const doc=document.querySelector('iframe').contentDocument;if(!doc?.body)return;const width=doc.documentElement.clientWidth;const groups=[...doc.querySelectorAll('nav[aria-label="Çalışma alanları"] button')].map(el=>({label:el.textContent.trim(),left:Math.round(el.getBoundingClientRect().left),right:Math.round(el.getBoundingClientRect().right)}));document.getElementById('metrics').textContent=JSON.stringify({width,scrollWidth:doc.documentElement.scrollWidth,groups},null,2);},400);</script></html>`);return;}
|
if(req.url==='/qa-responsive'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel ekran boyutu testi</h1><p>Gerçek uygulama ayrı bir görünüm alanında yüklenir.</p><button data-width="390">390 piksel</button><button data-width="768">768 piksel</button><button data-width="1440">1440 piksel</button><pre id="metrics" aria-label="Görünüm ölçümleri"></pre><iframe title="İhaleist test görünümü" src="/" style="display:block;width:390px;height:900px;border:0"></iframe><script>document.querySelectorAll('button').forEach(button=>button.onclick=()=>document.querySelector('iframe').style.width=button.dataset.width+'px');setInterval(()=>{const doc=document.querySelector('iframe').contentDocument;if(!doc?.body)return;const width=doc.documentElement.clientWidth;const groups=[...doc.querySelectorAll('nav[aria-label="Çalışma alanları"] button')].map(el=>({label:el.textContent.trim(),left:Math.round(el.getBoundingClientRect().left),right:Math.round(el.getBoundingClientRect().right)}));document.getElementById('metrics').textContent=JSON.stringify({width,scrollWidth:doc.documentElement.scrollWidth,groups},null,2);},400);</script></html>`);return;}
|
||||||
if(req.url==='/qa-telegram-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel Telegram testi</h1><button id="recover">Telegram durum hatasını kapat</button><p role="status"></p><script>document.getElementById('recover').onclick=async()=>{await fetch('/qa-telegram-recover',{method:'POST'});document.querySelector('[role=status]').textContent='Hata kapatıldı';};</script></html>`);return;}
|
if(req.url==='/qa-telegram-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel Telegram testi</h1><p>Gerçek Telegram mesajı gönderilmez.</p><button id="recover">Telegram durum hatasını kapat</button><button id="enable">Sentetik bağlamayı aç</button><button id="receive">Son kodu sentetik sohbete ulaştır</button><button id="expire">Kod süresini bitir</button><p role="status"></p><script>for(const mode of ['recover','enable','receive','expire'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-telegram-fixture?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
|
||||||
|
if(req.url?.startsWith('/qa-telegram-fixture?')&&req.method==='POST'){
|
||||||
|
const mode=new URL(req.url,'http://localhost').searchParams.get('mode');
|
||||||
|
if(mode==='recover')telegramFailure=false;
|
||||||
|
if(mode==='enable'){telegramFixture=true;telegramFailure=false;}
|
||||||
|
if(mode==='receive')telegramInbox=telegramLatestCode;
|
||||||
|
if(mode==='expire')telegramClockOffset+=11*60000;
|
||||||
|
res.end('Sentetik Telegram durumu: '+mode);return;
|
||||||
|
}
|
||||||
|
if(req.url==='/api/me/telegram'&&telegramFixture&&['GET','POST'].includes(req.method)){
|
||||||
|
const reply=(status,data)=>{res.writeHead(status,{'Content-Type':'application/json'});res.end(JSON.stringify(data));};
|
||||||
|
if(req.headers.authorization!=='Bearer '+token){reply(401,{success:false,error:'Test hesabı gerekli.'});return;}
|
||||||
|
try {
|
||||||
|
if(req.method==='GET'){const tg=qaTelegramStore.getData(user.email,'telegram');reply(200,{success:true,yapilandirilmis:true,bagli:!!tg?.chatId,bot:null,ad:tg?.ad||null});return;}
|
||||||
|
let raw='';for await(const chunk of req)raw+=chunk;const body=JSON.parse(raw);
|
||||||
|
if(body.islem==='kod'){const challenge=qaTelegramLinker.issue(user.email);telegramLatestCode=challenge.kod;reply(200,{success:true,...challenge});return;}
|
||||||
|
if(body.islem==='test'){reply(400,{success:false,error:'Bu test gerçek mesaj göndermez.'});return;}
|
||||||
|
const found=await qaTelegramLinker.link(user.email,body.kod,async code=>code===telegramInbox?{chatId:'123456789',ad:'QA Sentetik Sohbet'}:null);
|
||||||
|
reply(200,{success:true,ad:found.ad});
|
||||||
|
}catch(error){reply(error.status||400,{success:false,error:error.message,kod:error.kod});}return;
|
||||||
|
}
|
||||||
if(req.url==='/qa-telegram-recover'&&req.method==='POST'){telegramFailure=false;res.end('ok');return;}
|
if(req.url==='/qa-telegram-recover'&&req.method==='POST'){telegramFailure=false;res.end('ok');return;}
|
||||||
if(req.url==='/api/me/telegram'&&req.method==='GET'&&telegramFailure){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Telegram durumu alınamadı.'}));return;}
|
if(req.url==='/api/me/telegram'&&req.method==='GET'&&telegramFailure){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Telegram durumu alınamadı.'}));return;}
|
||||||
if(req.url==='/qa-subscription-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel abonelik testi</h1><button id="read">Abonelik liste hatası</button><button id="delete">Abonelik silme hatası</button><button id="none">Abonelik hatalarını kapat</button><p role="status"></p><script>for(const mode of ['read','delete','none'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-subscription-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
|
if(req.url==='/qa-subscription-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel abonelik testi</h1><button id="read">Abonelik liste hatası</button><button id="delete">Abonelik silme hatası</button><button id="none">Abonelik hatalarını kapat</button><p role="status"></p><script>for(const mode of ['read','delete','none'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-subscription-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ test('server challenge belongs to its account, expires, rotates and is consumed
|
|||||||
let time=1000000;const linker=createTelegramLinker(store,()=>time),email='link@example.test';
|
let time=1000000;const linker=createTelegramLinker(store,()=>time),email='link@example.test';
|
||||||
const first=linker.issue(email);assert.match(first.kod,/^[a-f0-9]{48}$/);
|
const first=linker.issue(email);assert.match(first.kod,/^[a-f0-9]{48}$/);
|
||||||
assert.ok(!JSON.stringify(store.getData(email,'telegram')).includes(first.kod));
|
assert.ok(!JSON.stringify(store.getData(email,'telegram')).includes(first.kod));
|
||||||
await assert.rejects(linker.link('other@example.test',first.kod,async()=>found),{status:400});
|
await assert.rejects(linker.link('other@example.test',first.kod,async()=>found),{status:400,kod:'TELEGRAM_CHALLENGE_INVALID'});
|
||||||
const second=linker.issue(email);assert.notEqual(second.kod,first.kod);
|
const second=linker.issue(email);assert.notEqual(second.kod,first.kod);
|
||||||
await assert.rejects(linker.link(email,first.kod,async()=>found),{status:400});
|
await assert.rejects(linker.link(email,first.kod,async()=>found),{status:400,kod:'TELEGRAM_CHALLENGE_INVALID'});
|
||||||
time=second.expiresAt;
|
time=second.expiresAt;
|
||||||
await assert.rejects(linker.link(email,second.kod,async()=>found),{status:400});
|
await assert.rejects(linker.link(email,second.kod,async()=>found),{status:400,kod:'TELEGRAM_CHALLENGE_INVALID'});
|
||||||
const current=linker.issue(email);
|
const current=linker.issue(email);
|
||||||
assert.deepEqual(await linker.link(email,current.kod,async()=>found),found);
|
assert.deepEqual(await linker.link(email,current.kod,async()=>found),found);
|
||||||
await assert.rejects(linker.link(email,current.kod,async()=>found),{status:400});
|
await assert.rejects(linker.link(email,current.kod,async()=>found),{status:400,kod:'TELEGRAM_CHALLENGE_INVALID'});
|
||||||
assert.equal(store.getData(email,'telegram').chatId,'123');
|
assert.equal(store.getData(email,'telegram').chatId,'123');
|
||||||
assert.throws(()=>linker.issue(email),{status:409});
|
assert.throws(()=>linker.issue(email),{status:409});
|
||||||
});
|
});
|
||||||
@@ -30,10 +30,10 @@ test('server challenge belongs to its account, expires, rotates and is consumed
|
|||||||
test('disconnect or expiration during provider lookup cannot restore a removed link',async()=>{
|
test('disconnect or expiration during provider lookup cannot restore a removed link',async()=>{
|
||||||
let time=1000000;const linker=createTelegramLinker(store,()=>time),email='disconnect@example.test';
|
let time=1000000;const linker=createTelegramLinker(store,()=>time),email='disconnect@example.test';
|
||||||
const challenge=linker.issue(email);
|
const challenge=linker.issue(email);
|
||||||
await assert.rejects(linker.link(email,challenge.kod,async()=>{store.setData(email,'telegram',null);return found;}),{status:400});
|
await assert.rejects(linker.link(email,challenge.kod,async()=>{store.setData(email,'telegram',null);return found;}),{status:400,kod:'TELEGRAM_CHALLENGE_INVALID'});
|
||||||
assert.equal(store.getData(email,'telegram'),null);
|
assert.equal(store.getData(email,'telegram'),null);
|
||||||
const next=linker.issue(email);
|
const next=linker.issue(email);
|
||||||
await assert.rejects(linker.link(email,next.kod,async()=>{time=next.expiresAt;return found;}),{status:400});
|
await assert.rejects(linker.link(email,next.kod,async()=>{time=next.expiresAt;return found;}),{status:400,kod:'TELEGRAM_CHALLENGE_INVALID'});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('matching accepts only a fresh exact code from a private non-bot sender',async()=>{
|
test('matching accepts only a fresh exact code from a private non-bot sender',async()=>{
|
||||||
|
|||||||
Reference in New Issue
Block a user