diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index 4198e68..e485222 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -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. - 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ı. + +### 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. diff --git a/server/index.js b/server/index.js index fa78721..69746d9 100644 --- a/server/index.js +++ b/server/index.js @@ -921,7 +921,7 @@ const server = http.createServer(async (req, res) => { } const bulunan=await linker.link(user.email,body.kod,kodIleBagla); 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.'}); } diff --git a/server/telegram-link.mjs b/server/telegram-link.mjs index dc219e1..ce04f6b 100644 --- a/server/telegram-link.mjs +++ b/server/telegram-link.mjs @@ -5,7 +5,7 @@ export function createTelegramLinker(store,clock=Date.now) { 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}); + 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 { diff --git a/src/components/tools/TelegramBaglanti.jsx b/src/components/tools/TelegramBaglanti.jsx index f251e95..72ed16b 100644 --- a/src/components/tools/TelegramBaglanti.jsx +++ b/src/components/tools/TelegramBaglanti.jsx @@ -31,8 +31,8 @@ const TelegramPanel = ({isAdmin}) => { const calistir = async (ad, fn, basari) => { if(locked.current)return;locked.current=true; 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});} - catch(e){if(alive.current)setMesaj({tur:'hata',metin:e.message});} + 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){if(e.kod==='TELEGRAM_CHALLENGE_INVALID')setChallenge(null);setMesaj({tur:'hata',metin:e.message});}} finally{locked.current=false;if(alive.current)setBusy(null);} }; useEffect(()=>{alive.current=true;void calistir('load',yenile);return()=>{alive.current=false;};},[]); @@ -95,6 +95,7 @@ const TelegramPanel = ({isAdmin}) => {
  • Sonra buraya dönüp Doğrula'ya basın.
  • `);return;}
    - if(req.url==='/qa-telegram-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`

    Yerel Telegram testi

    `);return;} + if(req.url==='/qa-telegram-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`

    Yerel Telegram testi

    Gerçek Telegram mesajı gönderilmez.

    `);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==='/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(`

    Yerel abonelik testi

    `);return;} diff --git a/tests/telegram-link.test.mjs b/tests/telegram-link.test.mjs index 6e3d53e..a349e91 100644 --- a/tests/telegram-link.test.mjs +++ b/tests/telegram-link.test.mjs @@ -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'; const first=linker.issue(email);assert.match(first.kod,/^[a-f0-9]{48}$/); 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); - 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; - 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); 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.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()=>{ let time=1000000;const linker=createTelegramLinker(store,()=>time),email='disconnect@example.test'; 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); 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()=>{