Mundarija (35)
- Bu darsda
- 1. Nega bu kerak?
- 2. Oqim
- 3. Kodni yasash
- 3.1 Math.random emas
- 3.2 Modulo bias
- 3.3 Uzunlik va muddat
- 4. Kodni saqlash: hash yetmaydi
- 4.1 Kanon o'lchovi
- 4.2 HMAC ichida nima bor
- 5. Urinishlar chegarasi
- 5.1 Onlayn taxmin
- 5.2 "5 dan keyin to'g'ri kod ham o'ladi"
- 5.3 NIST bilan solishtirish
- 6. Yuborish chastotasi
- 7. Telefon raqamni normallash
- 8. SMS pumping
- 8.1 Firibgarlik qanday ishlaydi
- 8.2 Himoya qatlamlari
- 9. SMS'ning o'zi qanchalik ishonchli
- 10. Hujumchi nigohi
- 11. Ko'p uchraydigan xatolar
- 12. Mashqlar
- 1-mashq (oson): to'g'ri yoki noto'g'ri
- 2-mashq (o'rta): beshinchi urinishdan keyin
- 3-mashq (qiyin): chastota cheklovi
- 13. Loyiha qadami: telefon va SMS kod
- 13.1 Migratsiyalar
- 13.2 src/auth/otp.ts
- 13.3 xizmat.ts va yo'llar
- 13.4 Testlar
- 13.5 Tekshiruv va commit
- 14. Real ishda
- Xulosa
- Manbalar
OTP orqali login: SMS kod, HMAC bilan saqlash, urinishlar chegarasi va SMS pumping
Qisqacha: SMS kod (OTP) — atigi 10⁶ variant. Shuning uchun uning xavfsizligi kodning murakkabligida emas, cheklovlarda: 5 daqiqa muddat, bitta kodga 5 urinish, bir marta ishlatish, raqamga soatiga 5 va kuniga 10 SMS. Kod
crypto.randomIntbilan yasaladi (Math.randomemas) va bazada HMAC-SHA256(server siri) ko'rinishida turadi: kanon o'lchovida sizib chiqqan oddiy SHA-256 dan kod 564 ms da topildi, HMAC'dan esa sirsiz — topilmadi. SMS'ni faqat ruxsat etilgan mamlakat raqamlariga yuboramiz — bu SMS pumping firibgarligiga qarshi birinchi to'siq.
Bu darsda
- 6 xonali kodni nega
Math.randombilan emas, CSPRNG bilan va bias'siz yasash kerakligini ko'rasiz. - Kanon o'lchovi bilan oddiy hash va HMAC farqini tushuntirasiz.
- Urinishlar chegarasi, muddat va yuborish chastotasini atomik qilib yozasiz.
- Telefon raqamni E.164 ga normallash va SMS pumping himoyasini qo'shasiz.
vazifalar-apiga 115–116 migratsiyalarni,otp.tsni, to'rt yo'lni va sakkizta testni qo'shasiz.
Oldin bilishingiz kerak: Email tasdiqlash va parol tiklash oqimlari, crypto 1: tasodif, hash va HMAC, String, TTL va kalit nomlash, Qulflar: row, table va advisory, SMS provayderlar: Eskiz va PlayMobile.
Oldingi darsda xat orqali kelgan uzun kalit bilan ishladik — 256 bit tasodif. Bugun xuddi shu g'oya, lekin kalit qisqa: odam uni SMS'dan ko'chirib, qo'lda yozadi. Uzunlik o'zgarishi bilan butun xavfsizlik hisobi o'zgaradi.
1. Nega bu kerak?
O'zbekistonda ko'p ilova telefon raqam bilan kiradi: taksi, bank, yetkazib berish, davlat xizmatlari. Foydalanuvchi parol o'ylab topmaydi — raqamini yozadi, SMS'da 6 ta raqam keladi, kiradi. Bu qulay va tanish.
Lekin 6 raqam — atigi bir million variant. Parol tiklash havolasini taxmin qilib bo'lmasdi (2²⁵⁶). SMS kodni esa — kerakli shart-sharoitda — topish mumkin. Ikkinchi xavf — pul: har SMS sizga pul turadi va firibgarlar shundan foydalanadi. Bugun ikkala xavfni ham yopamiz.
2. Oqim
vazifalar-api da hisob baribir email bilan ochiladi. Telefon — keyin, profil orqali ulanadi va shundan keyingina u bilan kirish mumkin:
sequenceDiagram
participant B as Brauzer
participant A as vazifalar-api
participant S as SMS provayder
B->>A: POST /auth/telefon (sessiya bilan)
A->>S: kod (telefon_ulash)
B->>A: POST /auth/telefon/tasdiq {telefon, kod}
A-->>B: raqam ulandi
B->>A: POST /auth/otp/yubor {telefon}
A-->>B: 202 (har doim bir xil)
A->>S: kod (kirish), fonda
B->>A: POST /auth/otp/kirish {telefon, kod}
A-->>B: 200 + sessiya cookieNimaga qarang: raqam faqat o'zi tasdiqlagan foydalanuvchiga ulanadi. Aks holda kimdir begona raqamni o'z hisobiga yozib qo'yardi. "Telefon bilan ro'yxatdan o'tish" (emailsiz) — boshqa variant; uning uchun sxemada email NULL bo'lishi kerak edi va kanon uni qo'shmadi.
3. Kodni yasash
3.1 Math.random emas
Math.random() — tez, lekin kriptografik emas. V8 uni xorshift128+ algoritmi bilan hisoblaydi: bir nechta chiqishni ko'rgan hujumchi ichki holatni tiklab, keyingi qiymatlarni oldindan aytishi mumkin. Kanon tajribasida 100 000 ta Math.random kodi taqsimot bo'yicha "tasodifiyga o'xshash" chiqdi — muammo sifatda emas, oldindan aytib bo'lishida. Shuning uchun Node'da crypto.randomInt, brauzerda crypto.getRandomValues (crypto 1: tasodif, hash va HMAC).
3.2 Modulo bias
crypto.getRandomValues baytlar beradi. Ularni raqamga aylantirishning sodda yo'li — qoldiq: bayt % 10. Unda yashirin tuzoq bor:
// Bayt (0–255) ni 10 ga bo'lib qoldiq olsak — qaysi raqam necha?
const sanoq = Array(10).fill(0);
for (let b = 0; b < 256; b++) sanoq[b % 10]++;
console.log(sanoq.join(" "));
// To'g'ri yo'l: chegaradan oshganini tashlab, qayta olish
const CHEGARA = 4_294_000_000; // 10^6 ga bo'linadigan eng katta son
function kodYasa() {
const bir = new Uint32Array(1);
do {
crypto.getRandomValues(bir);
} while (bir[0] >= CHEGARA);
return String(bir[0] % 1_000_000).padStart(6, "0");
}
const kodlar = Array.from({ length: 1000 }, kodYasa);
console.log(kodlar.every((k) => /^\d{6}$/.test(k)));
console.log(CHEGARA % 1_000_000, 2 ** 32 - CHEGARA);Konsolda:
26 26 26 26 26 26 25 25 25 25
true
0 967296Birinchi qator: 256 ta bayt 10 ga teng bo'linmaydi, shuning uchun 0–5 raqamlari 6–9 dan ko'proq chiqadi (26 va 25). Bu modulo bias (qoldiq og'ishi). Hujumchi uchun "ehtimolroq" kodlar paydo bo'ladi. Yechim — rad etish (rejection sampling): 32 bitli son 10⁶ ga teng bo'linadigan chegaradan oshsa, uni tashlab, yangisini olamiz. 4.29 milliarddan atigi 967 296 tasi tashlanadi — kamdan-kam takrorlash.
padStart(6, "0") ham muhim: 4217 emas, 004217. Nol bilan boshlanadigan kodlar ham to'liq 6 xonali bo'lsin. Node'ning crypto.randomInt(0, 10 ** 6) bu ishni ichida o'zi qiladi — kanon shuni ishlatadi.
3.3 Uzunlik va muddat
Kanon: 6 raqam, 5 daqiqa. Nega 4 emas? 4 raqam — 10 000 variant; 5 urinish bilan bitta kodni topish ehtimoli 1/2000. Nega 8 emas? SMS'dan ko'chirish qiyinlashadi, xatolar ko'payadi. 6 — sanoatdagi odatiy muvozanat. 5 daqiqa — SMS kechikib kelsa ham yetadi, lekin hujumchiga uzoq oyna bermaydi.
4. Kodni saqlash: hash yetmaydi
4.1 Kanon o'lchovi
Oldingi darsda tokenni SHA-256 bilan saqlash yetarli edi — 2²⁵⁶ variant. Bu yerda variantlar 10⁶. Kanon tekshirdi (Node 24.21.0, bitta yadro):
1) sizib chiqqan sha256(telefon:kod) dan kod topildi: true — 564 ms (bitta yadro, eng ko'pi 10^6 urinish)
2) HMAC(OTP_SIRI) — sirsiz 10^6 urinish: topildi false (67105 ms) — sir bazada emas, config'daBirinchi qator: baza sizsa, oddiy hash'dan kod yarim soniyada topiladi — u hali 5 daqiqa amal qilib turgan bo'ladi. Ikkinchi qator: HMAC'da kalit sifatida server siri (OTP_SIRI) ishlatiladi. U bazada emas, server sozlamasida. Sirsiz hujumchi hamma 10⁶ variantni sinab chiqdi (67 soniya) — birortasi ham mos kelmadi, chunki u to'g'ri hisoblay olmaydi.
Bu — oldingi darslardagi pepper g'oyasining ishlatilishi (Parolni xavfsiz saqlash). Parol uchun kanon pepper'ni qo'shmagan edi — aylantirish qimmat. OTP uchun esa qo'shdi: kod 5 daqiqa yashaydi, sir almashsa eng ko'pi bilan faol kodlar o'ladi va foydalanuvchi yangi SMS so'raydi.
4.2 HMAC ichida nima bor
Kanon HMAC'ga faqat kodni emas, maqsad:telefon:kod ni beradi. Brauzerda ko'ramiz (namuna sir bilan):
const enc = new TextEncoder();
const hex = (b) => Array.from(new Uint8Array(b), (x) =>
x.toString(16).padStart(2, "0")).join("");
// namuna sir — haqiqiysi .env da (OTP_SIRI, kamida 32 belgi)
const kalit = await crypto.subtle.importKey(
"raw", enc.encode("namuna-otp-siri-faqat-dars-uchun-0001"),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"],
);
async function kodHmac(maqsad, telefon, kod) {
const b = await crypto.subtle.sign(
"HMAC", kalit, enc.encode(`${maqsad}:${telefon}:${kod}`));
return hex(b);
}
const TEL = "+998900000000";
const kirish = await kodHmac("kirish", TEL, "004217");
const ulash = await kodHmac("telefon_ulash", TEL, "004217");
console.log(kirish.length, kirish.slice(0, 16));
console.log(ulash.slice(0, 16));
console.log("bir xilmi:", kirish === ulash);Konsolda:
64 0b7f788946ca76d7
1f896e3301bada50
bir xilmi: falseBir xil kod, bir xil raqam — lekin maqsad boshqa, HMAC boshqa. Natija: "raqamni ulash" uchun kelgan SMS kodi bilan kirib bo'lmaydi. Telefon ham ichida — bir raqamning kodi boshqa raqamga yaramaydi. Bazaga 64 hex belgi, ya'ni 32 bayt tushadi.
Tekshirib ko'ring: Nega OTP uchun argon2 kabi sekin hash ishlatmaymiz, axir variantlar kam?
Javob
Sekin hash ham 10⁶ variantni qutqarmaydi: argon2 bilan bitta urinish 30 ms bo'lsa, million urinish — 8 soatdan kam, bir nechta yadroda — undan ham tez. Muammo — variantlar soni, sekinlik uni yetarlicha qoplamaydi. HMAC'da esa hujumchi bitta ham to'g'ri hisob qila olmaydi — siri yo'q. Bundan tashqari argon2 har SMS so'rovida serverni 30 ms band qilardi.
5. Urinishlar chegarasi
5.1 Onlayn taxmin
Baza sizmasa ham, hujumchi POST /otp/kirish ga ketma-ket taxmin yuborishi mumkin. Kanon hisobi:
const VARIANT = 10 ** 6;
const foiz = (x) => `${(x * 100).toPrecision(2)} %`;
// bitta kodga 5 urinish
console.log("5 urinish:", foiz(5 / VARIANT));
// kuniga 10 SMS × 5 urinish
console.log("kuniga 50:", foiz(50 / VARIANT));
// cheklovsiz: 1000 so'rov/s × 300 s, hammasi har xil taxmin
console.log("cheklovsiz:", foiz((1000 * 300) / VARIANT));Konsolda:
5 urinish: 0.00050 %
kuniga 50: 0.0050 %
cheklovsiz: 30 %Uchinchi qator — darsning eng muhim raqami. Urinishlar cheklanmasa, sekundiga 1000 so'rov yuborgan hujumchi 5 daqiqada har uchinchi hisobni oladi. Cheklov bilan — bir kunda 0.005 %. 6 raqam kuchsiz emas, cheksiz urinish kuchsiz.
5.2 "5 dan keyin to'g'ri kod ham o'ladi"
Kanon qoidasi qattiq: beshinchi noto'g'ri urinishdan keyin kod o'ladi — hatto to'g'ri kod kiritilsa ham. Aks holda hujumchi 5 ta taxmin qiladi, kutadi va yana davom etadi. Foydalanuvchi uchun natija oddiy: "kod noto'g'ri — yangisini so'rang".
Urinishni hisoblash ham atomik bo'lishi kerak. Agar server avval urinishlar ni o'qib, keyin oshirsa, 50 ta parallel so'rov hammasi "4 urinish" ni ko'radi va hammasi tekshiriladi. Kanon bitta UPDATE … SET urinishlar = urinishlar + 1 … AND urinishlar < 5 RETURNING bilan hisoblaydi: hisoblagich avval oshadi, keyin kod solishtiriladi. 5 tadan keyin UPDATE hech qator qaytarmaydi.
Taqqoslash — timingSafeEqual bilan: ikkala HMAC ham 32 bayt, uzunlik farqi bo'lmaydi (Parolni xavfsiz saqlash).
5.3 NIST bilan solishtirish
NIST SP 800-63B-4 (AQSh standarti, 2025) SMS kod uchun aniq talablar qo'yadi. Kanon ularning ko'pchiligiga mos, bittasida farq bor:
| NIST talabi | Kanon |
|---|---|
| kamida 6 raqam, kriptografik tasodif | 6 raqam, crypto.randomInt |
| 10 daqiqadan ko'p amal qilmasin | 5 daqiqa |
| bir kod — bir marta | ishlatilgan_vaqt |
| ketma-ket xato urinishlar hisobga ko'pi bilan 100 ta; yangi kod hisoblagichni nolga tushirmaydi | hisoblagich har kodda alohida (5 ta); yangi SMS bilan yangi 5 urinish |
Oxirgi qator — halol farq. Kanonda bir kunda ko'pi bilan 10 SMS × 5 = 50 urinish bor. Bir kunlik ehtimol kichik (0.005 %), lekin kunlar ko'paygan sari u yig'iladi. NIST esa hisob bo'yicha umumiy hisoblagich talab qiladi: 100 ta ketma-ket xatodan keyin bu usul o'chadi. Buni kanonda kunlik chegara qisman qoplaydi; to'liq yechim — hisob bo'yicha xato urinishlar sanog'i (Rate limiting darsida ko'riladigan cheklovlar bilan birga).
6. Yuborish chastotasi
Har "kodni yuborish" bosilishi — bitta SMS va bitta to'lov. Kanon raqam bo'yicha uch qoida qo'yadi:
| Qoida | Qiymat | Nimadan |
|---|---|---|
| oraliq | 60 s | tugmani qayta-qayta bosish |
| soatiga | 5 SMS | qo'lda yoki bot bilan to'ldirish |
| kuniga | 10 SMS | uzoq davom etadigan suiiste'mol |
Har yangi kod shu raqamning eski faol kodlarini bekor qiladi — faqat oxirgi SMS'dagi kod ishlaydi.
Tekshiruv yana poygaga uchraydi: ikki parallel so'rov ikkalasi ham "oxirgi SMS 60 s dan eski" ni ko'radi. Oldingi darsda foydalanuvchi qatorini FOR UPDATE bilan qulflagandik. Bu yerda raqam bazada bo'lmasligi ham mumkin, shuning uchun kanon advisory lock oladi — raqamning o'zidan hisoblangan son bo'yicha qulf: pg_advisory_xact_lock(hashtextextended(telefon, 28)) (Qulflar: row, table va advisory). Bir raqamga kelgan so'rovlar navbat bilan o'tadi, boshqa raqamlar bir-birini kutmaydi. Qulf tranzaksiya tugashi bilan o'zi bo'shaydi.
Bu cheklovlar raqam bo'yicha. IP va umumiy so'rovlar bo'yicha cheklov — Rate limiting darsida.
7. Telefon raqamni normallash
Bitta raqamni foydalanuvchi o'n xil yozadi: +998 90 000-00-00, 998900000000, 90 000 00 00. Bazada esa bitta shakl kerak — aks holda chastota cheklovi va "bitta raqam — bitta hisob" qoidasi aylanib o'tiladi. Xalqaro standart shakl — E.164: +, mamlakat kodi va raqam, bo'sh joysiz, jami 15 raqamgacha.
function telefonNormallash(x) {
const r = x.replace(/[\s\-()]/g, "");
const t = /^\d{9}$/.test(r) ? `+998${r}`
: /^998\d{9}$/.test(r) ? `+${r}`
: r;
return /^\+[1-9]\d{7,14}$/.test(t) ? t : undefined;
}
const KIRISH = [
"+998 90 000-00-00",
"998900000000",
"90 000 00 00",
"(90) 000-00-00",
"+7 912 000 00 00",
"8 90 000 00 00",
"abc",
];
for (const x of KIRISH) {
console.log(x.padEnd(18), "→", telefonNormallash(x));
}Konsolda:
+998 90 000-00-00 → +998900000000
998900000000 → +998900000000
90 000 00 00 → +998900000000
(90) 000-00-00 → +998900000000
+7 912 000 00 00 → +79120000000
8 90 000 00 00 → undefined
abc → undefined9 raqam — O'zbekiston raqami deb olinadi va oldiga +998 qo'shiladi. Eski "8 …" shakli tanilmadi — kanon uni ataylab qo'llab-quvvatlamaydi. +7 … — to'g'ri E.164, lekin u boshqa mamlakat: normallash uni qabul qiladi, mamlakat tekshiruvi esa keyingi bo'limda rad etadi. Raqamlar namunaviy (+998 90 000 00 00 — kursdagi standart namuna).
Murakkab holatlar uchun (har mamlakat qoidalari, raqam turi) libphonenumber-js kabi kutubxona bor. Kanonga bitta mamlakat uchun 5 qatorli funksiya yetdi.
8. SMS pumping
8.1 Firibgarlik qanday ishlaydi
SMS pumping (SMS'ni "haydash", toll fraud) — eng qimmat OTP xatosi. Firibgar qimmat yo'nalishdagi raqamlar egasi (yoki operator bilan til biriktirgan) bo'ladi. U sizning "kodni yuborish" formangizga shu raqamlarni minglab yuboradi. Siz har SMS uchun to'laysiz, operator esa daromadning bir qismini firibgarga qaytaradi. Hech kim tizimga kirmaydi — maqsad kirish emas, sizning hisobingizdan pul.
8.2 Himoya qatlamlari
| Qatlam | Kanonda |
|---|---|
| faqat ruxsat etilgan mamlakatlar | OTP_MAMLAKATLAR (sukut +998) |
| raqam bo'yicha chastota | 60 s / soatiga 5 / kuniga 10 |
| noma'lum raqamga SMS yo'q | kirishda faqat ulangan raqamga |
| kunlik umumiy SMS byudjeti, CAPTCHA | keyinroq: CAPTCHA tokenini serverda tekshirish, honeypot va SMS pumping |
Birinchi qatlam — eng arzon va eng kuchli: ruxsat ro'yxati (allowlist). Ilovangiz faqat O'zbekistonda ishlasa, +998 dan boshqa raqamga SMS yuborishga hech qanday sabab yo'q. Ro'yxatdan tashqari raqam — 422. Bu raqam haqida hech narsa oshkor qilmaydi: hisobda bor-yo'qligidan qat'i nazar, bir xil xato.
Uchinchi qatlam ham muhim: kirish uchun SMS faqat hisobga ulangan raqamga ketadi. Noma'lum raqamga javob bir xil (202), lekin SMS yuborilmaydi — firibgar o'z raqamlarini ishlata olmaydi.
9. SMS'ning o'zi qanchalik ishonchli
SMS — eng qulay, lekin eng kuchli usul emas:
- SIM swap — firibgar operatorni aldab, sizning raqamingizga yangi SIM karta oladi va SMS'laringiz unga keladi.
- SS7 — operatorlar orasidagi eski signal protokoli; uning zaifliklari orqali SMS'ni tutib olish holatlari ma'lum.
- Ijtimoiy muhandislik — "kodni ayting, bankdan qo'ng'iroq qilyapmiz". Shuning uchun SMS matnida: "Kodni hech kimga aytmang".
NIST SP 800-63B-4 telefon tarmog'i (SMS yoki ovozli qo'ng'iroq) orqali kodni cheklangan (restricted) autentifikator deb belgilaydi. Hujjat chiqqan paytda bu — yagona cheklangan autentifikator. Uni ishlatish mumkin, lekin shartlari bor:
- tashkilot xavfni baholaydi va qabul qiladi; xavf qabul qilinmasa — boshqa usul;
- foydalanuvchiga kamida bitta cheklanmagan muqobil beriladi;
- foydalanuvchi SMS xavfi va muqobillar haqida ogohlantiriladi;
- kelajakda SMS taqiqlansa — ko'chish rejasi tayyor turadi.
Hujjat yana tavsiya qiladi: SMS yuborishdan oldin xavf belgilariga qarang — SIM karta almashgani, raqam boshqa operatorga ko'chgani. Muqobillar — TOTP ilovasi va passkey — Ikki faktorli autentifikatsiya va passkeys darsida.
Kanon haqiqiy SMS provayderini sinamadi — hisob ochilmadi, pul sarflanmadi. SmsYuboruvchi interfeysi bor, testlarda XotiraSms, dev'da LogSms (kod log'ga yoziladi). Eskiz va PlayMobile bilan ulanish — SMS provayderlar: Eskiz va PlayMobile darsida.
10. Hujumchi nigohi
Har qatorda: hujum, himoya va uni tekshiradigan kanon testi (yoki o'lchovi).
| Hujum | Nima uchun ishlaydi | Himoya | Qanday tekshirilgan |
|---|---|---|---|
Math.random chiqishidan keyingi kodni topish |
PRNG oldindan aytiladi | crypto.randomInt |
yangiKod testi (10 000 kod) |
sizgan bazadan sha256(kod) ni sanab chiqish |
10⁶ variant, 564 ms | HMAC(server siri) | kanon o'lchovi; test: bazada kod ochiq emas |
| ketma-ket 300 000 taxmin | urinish cheklanmagan | 5 urinish, keyin kod o'ladi | "5 ta noto'g'ridan keyin to'g'ri kod ham 401" |
| 50 ta parallel taxmin | o'qish va oshirish alohida | atomik UPDATE … RETURNING |
3 parallel to'g'ri so'rov — bittasi 200 |
| ulash kodi bilan kirish | maqsad ajratilmagan | maqsad HMAC ichida | "maqsad ajratilgan" testi |
+998 90… va 90… bilan chastotani aylanib o'tish |
raqam normallanmagan | E.164 | telefonNormallash testi, baza CHECK |
| qimmat xalqaro raqamlarga SMS (pumping) | mamlakat cheklanmagan | OTP_MAMLAKATLAR, chastota |
boshqa mamlakat — 422; chastota testi |
| "bu raqam ro'yxatda bormi?" | javob farqli | bir xil 202, ish fonda | noma'lum raqam — o'sha 202 tanasi |
| SIM swap | SMS — raqamga, odamga emas | TOTP / passkey muqobili | keyingi darslarda |
11. Ko'p uchraydigan xatolar
| Xato | Nega xavfli | To'g'risi |
|---|---|---|
Math.floor(Math.random() * 1e6) |
oldindan aytib bo'ladi | crypto.randomInt |
bayt % 10 |
modulo bias | rad etish yoki randomInt |
String(kod) — 4217 |
6 xonali emas | padStart(6, "0") |
| kodni ochiq yoki SHA-256 bilan saqlash | sizsa — darhol topiladi | HMAC + sir config'da |
| urinishni hisoblamaslik | 5 daqiqada 30 % | 5 urinish |
| xato urinishda urinishni oshirmaslik | cheksiz taxmin | har urinish hisoblanadi |
| hamma mamlakatga SMS | SMS pumping | ruxsat ro'yxati |
| "bunday raqam yo'q" javobi | enumeration | bir xil 202 |
12. Mashqlar
Mashqlar kurs/mashqlar/28/13-otp/ da.
1-mashq (oson): to'g'ri yoki noto'g'ri
- 6 xonali SMS kodni SHA-256 bilan saqlash xavfsiz — u bir tomonlama funksiya.
crypto.randomInt(0, 1_000_000)modulo bias'dan xoli.- Beshta noto'g'ri urinishdan keyin to'g'ri kod qabul qilinishi kerak — foydalanuvchi uni kiritdi-ku.
- SMS pumping'da firibgar boshqa odamning hisobiga kirishga harakat qiladi.
+998 90 000 00 00va998900000000bazada bitta qiymat bo'lishi kerak.
Yechim
- Noto'g'ri — 10⁶ variantni sanab chiqish uchun yarim soniya yetadi (kanon: 564 ms). HMAC va server siri kerak.
- To'g'ri — Node uni ichida rad etish usuli bilan yasaydi.
- Noto'g'ri — aks holda hujumchi 5 tadan bo'lib cheksiz taxmin qiladi. Kod o'ladi, yangisi so'raladi.
- Noto'g'ri — maqsad kirish emas, sizning SMS hisobingizdan pul.
- To'g'ri — E.164
+998900000000; aks holda chastota cheklovi aylanib o'tiladi.
2-mashq (o'rta): beshinchi urinishdan keyin
Bitta kod uchun xotiradagi model yozing: { qiymat: "004217", urinishlar: 0, ishlatilgan: false } va tekshir(kiritilgan). Qoidalar: kod ishlatilgan yoki urinishlar 5 ga yetgan bo'lsa — "o'lik"; aks holda avval urinish oshadi, keyin solishtiriladi: mos kelmasa — "xato", mos kelsa — "kirdi" va kod ishlatilgan. Besh xato kod va ikki marta to'g'ri kodni sinang, har qadamda natija va urinishlar sonini chiqaring.
Yechim
const MAKS = 5;
// Bazadagi qatorning soddalashtirilgan modeli
const kod = { qiymat: "004217", urinishlar: 0, ishlatilgan: false };
function tekshir(kiritilgan) {
if (kod.ishlatilgan || kod.urinishlar >= MAKS) return "o'lik";
kod.urinishlar++; // avval hisoblanadi — to'g'ri bo'lsa ham
if (kiritilgan !== kod.qiymat) return "xato";
kod.ishlatilgan = true;
return "kirdi";
}
const urinishlar = ["111111", "222222", "333333", "444444",
"555555", "004217", "004217"];
for (const k of urinishlar) {
console.log(k, tekshir(k), kod.urinishlar);
}Konsolda:
111111 xato 1
222222 xato 2
333333 xato 3
444444 xato 4
555555 xato 5
004217 o'lik 5
004217 o'lik 5Oltinchi qator — qoidaning mohiyati: to'g'ri kod keldi, lekin kech. Kanonda bu hol 401 otp-xato bo'ladi — "noto'g'ri" va "o'lik" foydalanuvchiga bir xil ko'rinadi, hujumchiga ham. Kanon testi xuddi shu ketma-ketlikni HTTP orqali tekshiradi.
3-mashq (qiyin): chastota cheklovi
cheklovSababi(yuborilgan, hozir) funksiyasini yozing: yuborilgan — shu raqamga oldin yuborilgan SMS vaqtlari (ms, o'sib boradi). Natija kanon tartibida: oxirgi SMS 60 s ichida bo'lsa — "oraliq"; oxirgi soatda 5 ta bo'lsa — "soatiga"; oxirgi kunda 10 ta bo'lsa — "kuniga"; aks holda "yuborilsin". Olti holatni sinang. Birinchi uchtasi: hech narsa yuborilmagan; 30 s oldin bitta; har 2 daqiqada 5 ta, 10-daqiqada. Keyingilari: o'sha 5 ta, 2 soatdan keyin; ikki to'lqin (0 va 3-soatda 5 tadan), 6-soatda; o'sha, 25-soatda. Ishora: filter, at(-1), spread bilan massivlarni birlashtirish (Spread va rest operatori).
Yechim
const S = 1000;
const SOAT = 3600 * S;
const QOIDA = { oraliq: 60 * S, soatiga: 5, kuniga: 10 };
function cheklovSababi(yuborilgan, hozir) {
const kun = yuborilgan.filter((t) => t > hozir - 24 * SOAT);
const soat = kun.filter((t) => t > hozir - SOAT);
const oxirgi = kun.at(-1);
if (oxirgi !== undefined && hozir - oxirgi < QOIDA.oraliq) {
return "oraliq";
}
if (soat.length >= QOIDA.soatiga) return "soatiga";
if (kun.length >= QOIDA.kuniga) return "kuniga";
return "yuborilsin";
}
const har2daq = (n, boshi) =>
Array.from({ length: n }, (_, i) => boshi + i * 120 * S);
console.log(cheklovSababi([], 0));
console.log(cheklovSababi([0], 30 * S));
console.log(cheklovSababi(har2daq(5, 0), 10 * 60 * S));
console.log(cheklovSababi(har2daq(5, 0), 2 * SOAT));
const kunBoyi = [...har2daq(5, 0), ...har2daq(5, 3 * SOAT)];
console.log(cheklovSababi(kunBoyi, 6 * SOAT));
console.log(cheklovSababi(kunBoyi, 25 * SOAT));Konsolda:
yuborilsin
oraliq
soatiga
yuborilsin
kuniga
yuborilsinBeshinchi qator: oxirgi soatda SMS yo'q, lekin kun bo'yi 10 ta — kunlik chegara ishladi. Oltinchi: 25 soatdan keyin eski SMS'lar "kun" oynasidan chiqdi. Kanonda xuddi shu hisobni Postgres bitta so'rovda qiladi (count(*) FILTER (WHERE …)), advisory lock ostida.
13. Loyiha qadami: telefon va SMS kod
13.1 Migratsiyalar
sql/115-telefon-otp.sql (izohlar qisqartirilgan, SQL kanondagidek):
ALTER TABLE foydalanuvchilar ADD COLUMN telefon text;
ALTER TABLE foydalanuvchilar
ADD CONSTRAINT foydalanuvchilar_telefon_check
CHECK (telefon ~ '^\+[1-9][0-9]{7,14}$');
-- Bitta raqam — bitta hisob (NULL lar cheklanmaydi)
CREATE UNIQUE INDEX foydalanuvchilar_telefon_key
ON foydalanuvchilar (telefon);
CREATE TABLE otp_kodlar (
id bigint GENERATED ALWAYS AS IDENTITY,
foydalanuvchi_id bigint NOT NULL,
maqsad text NOT NULL,
telefon text NOT NULL,
kod_hmac bytea NOT NULL,
yaratilgan_vaqt timestamptz NOT NULL DEFAULT now(),
tugash_vaqti timestamptz NOT NULL,
urinishlar int NOT NULL DEFAULT 0,
ishlatilgan_vaqt timestamptz,
ip inet,
CONSTRAINT otp_kodlar_pkey PRIMARY KEY (id),
CONSTRAINT otp_kodlar_foydalanuvchi_id_fkey
FOREIGN KEY (foydalanuvchi_id)
REFERENCES foydalanuvchilar (id) ON DELETE CASCADE,
CONSTRAINT otp_kodlar_maqsad_check
CHECK (maqsad IN ('kirish', 'telefon_ulash')),
CONSTRAINT otp_kodlar_telefon_check
CHECK (telefon ~ '^\+[1-9][0-9]{7,14}$'),
CONSTRAINT otp_kodlar_kod_hmac_check
CHECK (octet_length(kod_hmac) = 32),
CONSTRAINT otp_kodlar_urinishlar_check
CHECK (urinishlar BETWEEN 0 AND 100)
);
-- oxirgi faol kod va chastota: raqam + vaqt
CREATE INDEX otp_kodlar_telefon_idx
ON otp_kodlar (telefon, yaratilgan_vaqt DESC);sql/116-namuna-telefon.sql — Azizga namuna raqam (faqat lokal kurs bazasi uchun):
UPDATE foydalanuvchilar SET telefon = '+998900000000'
WHERE email = 'aziz@example.com';Prisma tomoni: prisma/migrations/20261027150000_telefon_otp/migration.sql, schema.prisma da Foydalanuvchi.telefon (@unique) va OtpKod modeli.
Lokal bazada (115–116 bilan) tekshirdik — biz ishga tushirdik, PostgreSQL 17.6:
SELECT id, email, telefon
FROM foydalanuvchilar
WHERE telefon IS NOT NULL;Natija:
id | email | telefon
----+------------------+---------------
1 | aziz@example.com | +998900000000
(1 row)Normallashtirilmagan raqamni baza qabul qilmaydi:
\set VERBOSITY terse
UPDATE foydalanuvchilar SET telefon = '90 000 00 00'
WHERE email = 'dilnoza@example.com';Natija:
ERROR: new row for relation "foydalanuvchilar" violates check constraint "foydalanuvchilar_telefon_check"Band raqamni boshqa hisobga yozish:
\set VERBOSITY terse
UPDATE foydalanuvchilar SET telefon = '+998900000000'
WHERE email = 'dilnoza@example.com';Natija:
ERROR: duplicate key value violates unique constraint "foydalanuvchilar_telefon_key""Takroriy kalit qiymati foydalanuvchilar_telefon_key noyoblik cheklovini buzadi". Ilova bu holatni 409 telefon-band ga aylantiradi. Va bitta halol ogohlantirish — baza nimani tekshira olmasligi:
\set VERBOSITY terse
INSERT INTO otp_kodlar (foydalanuvchi_id, maqsad, telefon, kod_hmac,
tugash_vaqti)
VALUES (1, 'kirish', '+998900000000', sha256('004217'),
now() + interval '5 minutes');Natija:
INSERT 0 1Baza faqat "32 bayt" ni tekshiradi. U kod_hmac ustuniga HMAC emas, oddiy sha256('004217') yozilganini bilmaydi — bu xatoni faqat kod va test ushlaydi (kanon testi bazadagi qatorda kod ochiq turmasligini tekshiradi).
13.2 src/auth/otp.ts
Telefon uchun bo'lingan; uzun tiplarni alohida nom bilan chiqardik, izohlar qisqartirilgan, kod ma'nosi kanondagidek (tip, lint va 8 test o'tdi — biz tekshirdik). Yuboruvchilar (XotiraSms, LogSms) va tiplarning bir qismini tushirib qoldirdik:
import { createHmac, randomInt, timingSafeEqual } from "node:crypto";
export type OtpMaqsadi = "kirish" | "telefon_ulash";
export const OTP = Object.freeze({
uzunlik: 6,
muddatMs: 5 * 60_000,
maksUrinish: 5,
oraliqMs: 60_000,
soatiga: 5,
kuniga: 10,
});
/** SMS yuborish interfeysi (Eskiz, PlayMobile …) */
export interface SmsYuboruvchi {
yubor(telefon: string, matn: string): Promise<void>;
}
/** Kod: CSPRNG, oldidagi nollar bilan ("004217") */
export const yangiKod = (): string =>
String(randomInt(0, 10 ** OTP.uzunlik)).padStart(OTP.uzunlik, "0");
export class OtpOmbori {
readonly #pool: Pool;
readonly #siri: Buffer;
/** @param siri OTP_SIRI (≥ 32 bayt) — bazada emas, config'da */
constructor(pool: Pool, siri: Buffer) {
if (siri.length < 32) {
throw new Error("OTP siri kamida 32 bayt bo'lsin");
}
this.#pool = pool;
this.#siri = siri;
}
/** maqsad va telefon ham HMAC ichida — bir maqsadning kodi
* boshqasiga yaramaydi */
#hmac(maqsad: OtpMaqsadi, telefon: string, kod: string): Buffer {
return createHmac("sha256", this.#siri)
.update(`${maqsad}:${telefon}:${kod}`)
.digest();
}
/** Yangi kod (chastota cheklovlari bilan); eski faol kodlar bekor.
* Telefon bo'yicha advisory lock — parallel so'rovlar ikki SMS
* yubormasin */
async yarat(
foydalanuvchiId: number,
maqsad: OtpMaqsadi,
telefon: string,
ip?: string,
): Promise<YuborishNatijasi> {
const c = await this.#pool.connect();
try {
await c.query("BEGIN");
await c.query(
"SELECT pg_advisory_xact_lock(hashtextextended($1, 28))",
[telefon],
);
const { rows: [q] } = await c.query<Chastota>(
`SELECT coalesce(max(yaratilgan_vaqt)
> now() - $2 * interval '1 millisecond', false)
AS oraliq,
count(*) FILTER (WHERE yaratilgan_vaqt
> now() - interval '1 hour')::int AS soat,
count(*) FILTER (WHERE yaratilgan_vaqt
> now() - interval '1 day')::int AS kun
FROM otp_kodlar
WHERE telefon = $1
AND yaratilgan_vaqt > now() - interval '1 day'`,
[telefon, OTP.oraliqMs],
);
const sabab = q?.oraliq === true ? "oraliq"
: (q?.soat ?? 0) >= OTP.soatiga ? "soatiga"
: (q?.kun ?? 0) >= OTP.kuniga ? "kuniga"
: undefined;
if (sabab !== undefined) {
await c.query("ROLLBACK");
return { tur: "cheklov", sabab };
}
await c.query(
`UPDATE otp_kodlar SET ishlatilgan_vaqt = now()
WHERE telefon = $1 AND ishlatilgan_vaqt IS NULL`,
[telefon],
);
const kod = yangiKod();
await c.query(
`INSERT INTO otp_kodlar (foydalanuvchi_id, maqsad, telefon,
kod_hmac, tugash_vaqti, ip)
VALUES ($1, $2, $3, $4,
now() + $5 * interval '1 millisecond', $6)`,
[
foydalanuvchiId,
maqsad,
telefon,
this.#hmac(maqsad, telefon, kod),
OTP.muddatMs,
ip ?? null,
],
);
await c.query("COMMIT");
return { tur: "yuborildi", kod };
} catch (x) {
await c.query("ROLLBACK");
throw x;
} finally {
c.release();
}
}
/** Har urinish hisoblanadi (atomik); 5 dan keyin kod o'ladi,
* hatto to'g'ri kod ham. Solishtirish — timingSafeEqual */
async tekshir(
maqsad: OtpMaqsadi,
telefon: string,
kod: string,
foydalanuvchiId?: number,
): Promise<TekshiruvNatijasi> {
const [q] = await this.#sorov<KodQatori>(
`UPDATE otp_kodlar SET urinishlar = urinishlar + 1
WHERE id = (
SELECT id FROM otp_kodlar
WHERE telefon = $1 AND maqsad = $2
AND ishlatilgan_vaqt IS NULL AND tugash_vaqti > now()
AND ($3::bigint IS NULL OR foydalanuvchi_id = $3)
ORDER BY yaratilgan_vaqt DESC LIMIT 1)
AND urinishlar < $4
RETURNING id, foydalanuvchi_id AS fid, kod_hmac AS hmac,
urinishlar`,
[telefon, maqsad, foydalanuvchiId ?? null, OTP.maksUrinish],
);
if (q === undefined || !/^\d{6}$/.test(kod)) {
return { tur: "xato" };
}
const kutilgan = this.#hmac(maqsad, telefon, kod);
if (!timingSafeEqual(q.hmac, kutilgan)) return { tur: "xato" };
const r = await this.#pool.query(
`UPDATE otp_kodlar SET ishlatilgan_vaqt = now()
WHERE id = $1 AND ishlatilgan_vaqt IS NULL`,
[q.id],
);
return r.rowCount === 1
? { tur: "ok", foydalanuvchiId: q.fid }
: { tur: "xato" };
}
}Uch tafsilot:
hashtextextended($1, 28)— matndan 64 bitli son yasaydigan Postgres funksiyasi;28— "urug'" (seed), bu qulflar boshqa modullarning advisory qulflari bilan to'qnashmasligi uchun.tekshirda urinish kod formatini tekshirishdan oldin hisoblanadi:abcyuborish ham bitta urinish. Aks holda hujumchi noto'g'ri formatli so'rovlar bilan tizimni bepul sinab ko'rardi.- Oxirgi
UPDATE … AND ishlatilgan_vaqt IS NULL— ikkita to'g'ri parallel so'rovdan faqat bittasirowCount === 1oladi. Kanon testi: 3 parallel to'g'ri so'rovdan bittasi 200.
telefonNormallash — «Telefon raqamni normallash» bo'limidagi funksiyaning TypeScript varianti.
13.3 xizmat.ts va yo'llar
Kod so'rash — javob bir xil, ish fonda (oldingi darsdagi FonIshlar):
async kodSora(
maqsad: OtpMaqsadi,
xomTelefon: string,
mijoz: Mijoz,
foydalanuvchiId?: number,
): Promise<void> {
const telefon = raqam(xomTelefon);
let fid = foydalanuvchiId;
if (maqsad === "kirish") {
fid = (await ombor.telefonBilan(telefon))?.id;
} else if ((await ombor.telefonBilan(telefon)) !== undefined) {
// raqam band — SMS yubormaymiz (javob baribir bir xil)
fid = undefined;
}
await ombor.hodisa({
tur: "otp_sorov", foydalanuvchiId: fid ?? foydalanuvchiId,
...mijoz, tafsilot: { maqsad, topildi: fid !== undefined },
});
if (fid === undefined) return;
const egasi = fid;
d.fon.qosh("sms-kod", async () => {
const n = await d.otp.yarat(egasi, maqsad, telefon, mijoz.ip);
if (n.tur === "cheklov") {
await ombor.hodisa({
tur: "otp_sorov", foydalanuvchiId: egasi, ...mijoz,
tafsilot: { maqsad, cheklov: n.sabab },
});
return;
}
const daqiqa = String(OTP.muddatMs / 60_000);
await d.sms.yubor(telefon, `vazifalar: tasdiqlash kodi ` +
`${n.kod}. ${daqiqa} daqiqa amal qiladi. ` +
"Kodni hech kimga aytmang.");
});
},raqam(x) — normallash va mamlakat ruxsat ro'yxati; ikkalasida ham xato — 422 (ValidatsiyaXatosi). Cheklov sababi (oraliq, soatiga) foydalanuvchiga aytilmaydi, faqat jurnalga yoziladi.
| Yo'l | Tana | Javob |
|---|---|---|
POST /v1/auth/otp/yubor |
{ telefon } |
202 har doim; 422 |
POST /v1/auth/otp/kirish |
{ telefon, kod, eslabQol? } |
200 + sessiya; 401 otp-xato |
POST /v1/auth/telefon |
{ telefon } (sessiya kerak) |
202; 401 |
POST /v1/auth/telefon/tasdiq |
{ telefon, kod } (sessiya) |
200; 401; 409 telefon-band |
Sxemalar (Zod): telefon — ko'pi bilan 30 belgi, kod — 10 belgi (formatni otp.ts tekshiradi). Config'ga ikki o'zgaruvchi:
OTP_SIRI— kamida 32 belgi. Berilmasa, har ishga tushishda tasodifiy yasaladi (dev uchun qulay: server qayta yongach eski kodlar yaroqsiz). Haqiqiy qiymatni.envga yozasiz, kodga va git'ga — hech qachon.OTP_MAMLAKATLAR— vergul bilan prefikslar, sukut+998.
13.4 Testlar
test/otp.test.ts — 8 ta test. Urinishlar testining yadrosi (bo'lingan):
await request(app).post("/v1/auth/otp/yubor")
.send({ telefon: TEL }).expect(202);
const k = await kod();
const notogri = k === "000000" ? "000001" : "000000";
for (let i = 0; i < 5; i++) {
await request(app).post("/v1/auth/otp/kirish")
.send({ telefon: TEL, kod: notogri }).expect(401);
}
// beshtadan keyin to'g'ri kod ham — 401
await request(app).post("/v1/auth/otp/kirish")
.send({ telefon: TEL, kod: k }).expect(401);notogri — tasodifan to'g'ri kodga teng bo'lib qolmasligi uchun (kod 000000 bo'lishi ham mumkin!). Biz ishga tushirdik — lokal PostgreSQL 17.6, kanon nusxasi va yuqoridagi bo'lingan fayllar bilan, Node 24.21.0:
node --test test/otp.test.tsChiqishda har qatorning boshidagi belgiga va oxirgi pass / fail soniga qarang:
✔ telefonNormallash — E.164; 9 raqam → +998; yaroqsizlar (1.9991ms)
✔ yangiKod — har doim 6 raqam, oldidagi nollar bilan, takrorlar kam (10 000 ta) (5.6756ms)
✔ raqamni ulash → SMS kod bilan kirish → sessiya; kod bazada ochiq emas (HMAC) (248.8826ms)
✔ noma'lum raqam — o'sha 202 tanasi, SMS yuborilmaydi; boshqa mamlakat va noto'g'ri raqam — 422 (238.7198ms)
✔ 5 ta noto'g'ri urinishdan keyin TO'G'RI kod ham ishlamaydi; muddati o'tgan kod — 401 (227.8304ms)
✔ chastota: 60 s ichida ikkinchi SMS yo'q; soatiga 5 tadan ortiq yo'q (jurnalda cheklov) (215.0049ms)
✔ maqsad ajratilgan: telefon ulash kodi bilan kirib bo'lmaydi; parallel 3 ta to'g'ri so'rov — bittasi 200 (214.9031ms)
✔ band raqam: boshqa hisob ulamoqchi bo'lsa — 202, lekin SMS yuborilmaydi; sessiyasiz /telefon — 401 (217.4993ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3922.5924Nima bo'ldi? Sakkizta test ham o'tdi (pass 8, fail 0). Har test nomi darsdagi bitta qoidaga mos: normallash, kod formati, HMAC, bir xil 202, 5 urinish, chastota, maqsad va band raqam. Qavs ichidagi son — test vaqti (millisekund).
13.5 Tekshiruv va commit
Kanon tekshiruvi (2026-10-27, PostgreSQL 18.6): tip va lint xatosiz, testlar 165 (164 + 1 o'tkazilgan) — sakkizta yangi; e2e 4/4; Prisma migratsiyalari va sql/ bir xil.
M="feat(auth): telefon raqamni ulash va SMS kod bilan kirish —"
M+=" HMAC, 5 urinish, 5 daqiqa, chastota cheklovi, mamlakat"
M+=" ro'yxati; db: 115 (+116 namuna)"
git add src test sql prisma
git commit -m "$M"14. Real ishda
- O'zbek ilovalarida telefon + SMS kod — eng keng tarqalgan kirish usuli. Ko'pchilik SMS provayderi (Eskiz, PlayMobile) shablon matnini oldindan tasdiqlashni talab qiladi — kod matni ham shablon bo'ladi (SMS provayderlar: Eskiz va PlayMobile).
- Telegram orqali kod — SMS'dan arzon va SIM swap'ga chidamliroq; kursda Telegram bilan kirish alohida darsda: Telegram Login va Mini App initData tekshiruvi.
- Xarajat monitoringi — production'da SMS soni va xarajati uchun ogohlantirish qo'ying: SMS pumping'ni ko'pincha birinchi bo'lib hisob-faktura ko'rsatadi.
- Intervyuda: "OTP'ni qanday saqlaysiz?", "Nega 6 raqam yetadi?", "SMS pumping nima?", "SMS 2FA nega zaif hisoblanadi?".
Xulosa
- 6 xonali kod — 10⁶ variant; xavfsizlikni muddat (5 daqiqa), urinishlar (5) va chastota (60 s, soatiga 5, kuniga 10) beradi.
- Kod —
crypto.randomInt(modulo bias'siz),padStart(6, "0");Math.randomemas. - Bazada HMAC-SHA256(server siri,
maqsad:telefon:kod); oddiy SHA-256 dan kanon kodni 564 ms da topdi. - Urinish atomik
UPDATE … RETURNINGbilan hisoblanadi va kodni solishtirishdan oldin; 5 dan keyin to'g'ri kod ham o'ladi. - Raqam E.164 ga normallanadi, faqat ruxsat etilgan mamlakatlarga va faqat ulangan raqamga SMS ketadi — SMS pumping'ga qarshi.
- SMS — "cheklangan" autentifikator (SIM swap); kuchliroq muqobillar — TOTP va passkey.
Keyingi dars: OAuth 2.0 va OpenID Connect nazariyasi — "Google bilan kirish" ortidagi protokol: authorization code, PKCE, state va nonce.
Manbalar
- OWASP Multifactor Authentication Cheat Sheet (SMS va OTP): https://cheatsheetseries.owasp.org/cheatsheets/Multifactor_Authentication_Cheat_Sheet.html (2026-10-28 da o'qildi)
- NIST SP 800-63B-4, out-of-band autentifikatorlar va PSTN cheklovi: https://pages.nist.gov/800-63-4/sp800-63b.html (2026-10-28 da o'qildi)
- ITU-T E.164 (xalqaro raqam formati): https://www.itu.int/rec/T-REC-E.164
- Node.js 24
crypto.randomInt,createHmac,timingSafeEqual: https://nodejs.org/docs/latest-v24.x/api/crypto.html - PostgreSQL 18: Advisory Locks: https://www.postgresql.org/docs/18/explicit-locking.html#ADVISORY-LOCKS
- NIST SP 800-63B-4: §3.1.3 (out-of-band, 10 daqiqa, 6 raqam), §3.1.3.3 (PSTN), §3.2.2 (100 ta xato urinish), §3.2.9 (cheklangan autentifikatorlar) — o'sha havola (2026-10-28 da o'qildi)
- Kanon o'lchovi (IlmHamroh, 2026-10-27): OTP hash va HMAC tajribasi (Node 24.21.0, i5-12500H), loyiha tekshiruvi. Darsdagi brauzer misollari, SQL (PostgreSQL 17.6),
otp.test.tsva bo'lingan fayllar — biz ishga tushirdik (2026-10-28). Haqiqiy SMS provayderlari (Eskiz, PlayMobile) — sinalmagan.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!