Settings — OTP Verification
ภาพรวม
OTP Verification คือที่เก็บข้อมูลรับรองและเทมเพลตข้อความสำหรับส่งรหัส OTP ระดับ LINE OA หนึ่งบัญชี หน้านี้ ไม่ได้กำหนดว่าฟอร์มใดต้องยืนยันตัวตน (ตั้งค่านั้นอยู่ในแท็บการยืนยันตัวตนของ Form builder) แต่เป็นชั้นล่างที่ตอบว่า OA นี้ส่ง OTP ได้ทางช่องทางใดบ้าง — รองรับ 2 ช่องทาง คือ SMS (ผ่าน ThaiBulkSMS) และอีเมล (ผ่าน SMTP ระดับ channel)
จุดที่ควรรู้ก่อนตั้งค่าคือ สองช่องทางนี้ทำงานต่างกันโดยพื้นฐาน — ช่องทาง SMS ให้ ThaiBulkSMS เป็นผู้สร้างและส่งรหัสเอง ระบบเราเพียงขอให้เขาส่งแล้วเก็บ token ไว้ถามกลับว่ารหัสที่ผู้ใช้พิมพ์ถูกหรือไม่ ดังนั้นข้อความ SMS ถูกตั้งค่าที่ฝั่ง ThaiBulkSMS ไม่ใช่ในหน้านี้ — หน้านี้จึงมีช่องเทมเพลตให้เฉพาะอีเมล ซึ่งเป็นช่องทางที่ระบบเราสร้างรหัส 6 หลักด้วยตัวเอง
บทบาทที่ใช้งาน — ผู้ดูแลระบบที่ตั้งค่าครั้งเดียวให้ทั้งบัญชี ก่อนทีมที่สร้างฟอร์มจะเปิดใช้ OTP ในฟอร์มของตนได้ (โมดูลนี้ ไม่มี ModuleGate ที่ backend — ดูหัวข้อ สิทธิ์การเข้าถึง)
เส้นทางผู้ใช้งาน
- เปิดเมนู Settings > OTP Verification — ระบบแสดงฟอร์มได้เสมอแม้ยังไม่เคยตั้งค่ามาก่อน (
GET /otp-configตอบค่าว่างเปล่า/ค่าเริ่มต้น ไม่เคยตอบ 404) และเติมหัวข้อ/เนื้อหาอีเมลเริ่มต้นให้ - การ์ด SMS — ThaiBulkSMS มี 3 ช่อง
- สวิตช์เปิดใช้งาน OTP ทาง SMS
- ThaiBulkSMS Key
- ThaiBulkSMS Secret — เป็นช่องรหัสผ่าน ค่าจะว่างเสมอตอนโหลดหน้า โดยแสดงค่าที่ปิดบังไว้เป็น placeholder ("ปัจจุบัน: ab****yz — เว้นว่างไว้เพื่อคงค่าเดิม") เพื่อให้รู้ว่ามี secret เก็บอยู่แล้ว เว้นว่างไว้หมายถึงคงค่าเดิม ไม่ใช่ล้างค่า
- การ์ดนี้ไม่มีการตรวจฝั่งหน้าเว็บเลย — เปิดสวิตช์แล้วไม่กรอกอะไรก็กดบันทึกได้ แล้วรับข้อความ error จาก backend
- การ์ดอีเมล มี 3 ช่อง
- สวิตช์เปิดใช้งาน OTP ทางอีเมล
- หัวข้ออีเมล — ค่าเริ่มต้น
รหัสยืนยันของคุณ(EN:Your verification code) - เนื้อหาอีเมล (HTML) — ค่าเริ่มต้น
รหัส OTP ของคุณคือ <b>{otp}</b> จะหมดอายุใน 3 นาที(EN:Your OTP code is <b>{otp}</b>. It expires in 3 minutes.) ต้องมี{otp}เสมอ ใส่{ref}เพิ่มได้ถ้าต้องการโชว์รหัสอ้างอิง
- กดบันทึก — ป้ายสถานะบนหัวการ์ดจะอัปเดตเป็น "กำหนดค่าแล้ว" เมื่อช่องทางนั้นครบเงื่อนไข (ป้ายนี้อ่านค่าที่บันทึกแล้ว ไม่ได้อ่านสถานะฟอร์มปัจจุบัน จึงอัปเดตหลัง refetch)
- ไปที่เมนู Form builder เปิดฟอร์มที่ต้องการ เปิด Profile Mapping พร้อมเลือก Member Database (จำเป็น เพราะรหัส OTP จะถูกส่งไปยังคอลัมน์ของระเบียนที่ค้นหาเจอ ไม่ใช่ค่าที่ผู้ใช้กรอก) แล้วไปที่แท็บการยืนยันตัวตนเพื่อเปิดสวิตช์ OTP ของฟอร์มนั้น — ช่องทางที่ยัง "ยังไม่ได้กำหนดค่า" จะถูก disable ไว้พร้อมลิงก์กลับมาหน้านี้
รายละเอียดสำคัญที่ตรวจสอบจากซอร์สโค้ดจริง
ยืนยันจาก internal/modules/otpconfig/{controller,service,dto,crypto}.go ของ line-management-cms-api-go และ internal/otp/{service,session,mask}.go + internal/otp/{code,thaibulksms} ของ line-management-client-api-go:
- secret ถูกเข้ารหัสก่อนเก็บด้วย AES-256-GCM และไม่มีทางอ่านค่าจริงกลับออกมาผ่าน API — คีย์ได้จาก SHA-256 ของ
APP_ENCRYPT_SECRETมีการสุ่ม nonce ใหม่ทุกครั้ง และเก็บเป็นbase64(nonce ‖ ciphertext ‖ tag)ลงคอลัมน์thaibulksms_secret_encเวลาอ่านออกมา ระบบถอดรหัสไว้ในหน่วยความจำเพื่อคำนวณค่าที่ปิดบัง (maskSecretเก็บอักขระ 2 ตัวหน้าและ 2 ตัวท้าย ที่เหลือแทนด้วย*; secret ยาวไม่เกิน 4 ตัวถูกปิดทั้งหมด) แล้วส่งออกเพียงthaibulksmsSecretMaskedเท่านั้น — plaintext ไม่เคยอยู่ในโครงสร้างที่ส่งกลับ นี่ต่างจากเมนู BigQuery Sync อย่างชัดเจน ซึ่งเก็บ service account key เป็นข้อความธรรมดาและส่งกลับมาทั้งก้อน - ถ้าถอดรหัส secret ไม่สำเร็จ ระบบจะไม่ตอบ 500 — เช่นกรณี
APP_ENCRYPT_SECRETถูกเปลี่ยน/ตั้งค่าผิด ระบบเขียน log แล้วคืนค่าที่ปิดบังเป็นค่าว่างไปแทน (ท่าที "ไม่เชื่อ config ที่เสีย") อาการที่เห็นคือ placeholder ของช่อง secret กลับไปเป็น "กรอกรหัสลับ ThaiBulkSMS" ราวกับไม่มี secret เก็บอยู่ ทั้งที่ในฐานข้อมูลยังมี — และเพราะsmsConfiguredตรวจจาก ค่าที่เข้ารหัสไว้ ไม่ใช่ค่าที่ถอดรหัสได้ ป้ายสถานะจึงยังขึ้นว่า "กำหนดค่าแล้ว" อยู่ ทั้งที่ตอนส่งจริงจะใช้ secret ไม่ได้ - "เว้นว่าง = คงค่าเดิม" ถูกจัดการที่ฝั่ง backend ในฟังก์ชันแยกที่ตรวจสอบได้ (
resolveSecretEnc) — ส่ง secret มาไม่ว่างจะเข้ารหัสใหม่ทับของเดิม, ส่งมาว่างจะคงของเดิมไว้เสมอ, และไม่มีของเดิมกับไม่ส่งมาด้วยจะได้ "ไม่มี secret" ผลข้างเคียงที่ควรรู้: ไม่มีช่องทางใดเลยในหน้าเว็บที่จะ "ลบ" secret ที่เก็บไว้ เพราะการเว้นว่างหมายถึงคงค่าเดิมเสมอ - กฎข้ามฟิลด์ถูกตรวจ หลัง ผสานกับ secret เดิมแล้ว — จึงเปิดสวิตช์ SMS โดยไม่ต้องพิมพ์ secret ซ้ำได้ ถ้ามี secret เก็บอยู่แล้ว กฎที่ตรวจคือ
smsEnabledต้องมี key ที่ไม่ว่าง และ secret ที่ผสานแล้วไม่ว่าง ไม่ครบได้ 400ThaiBulkSMS key and secret are required when SMS OTP is enabledemailEnabledต้องมีหัวข้อที่ไม่ว่าง และ เนื้อหาที่มี{otp}ไม่ครบได้ 400Email subject and a body containing {otp} are required when email OTP is enabled
- ป้าย "กำหนดค่าแล้ว" หมายถึง "เปิดสวิตช์ไว้ และ ข้อมูลครบ" ไม่ใช่แค่ "บันทึกข้อมูลรับรองไว้แล้ว" —
smsConfigured = smsEnabled && key != "" && secretEnc != ""และemailConfigured = emailEnabled && subject != "" && strings.Contains(body, "{otp}")การปิดสวิตช์จึงทำให้ป้ายกลับไปเป็น "ยังไม่ได้กำหนดค่า" ทันทีทั้งที่ข้อมูลยังอยู่ครบ — และเพราะ Form builder อ่านสองค่านี้ไปตัดสินว่าจะให้เลือกช่องทางได้หรือไม่ การปิดสวิตช์ในหน้านี้จึงมีผลกับทุกฟอร์มที่ใช้ช่องทางนั้นทันที - ทุกฟิลด์ใน
PUT /otp-configเป็น optional และสตริงถูก trim — ค่าที่ไม่ส่งมาหรือส่งเป็น null จะได้false/""ไม่ใช่ "ไม่แตะต้องค่าเดิม" ดังนั้น การ PUT แบบบางส่วน (partial update) จะล้างค่าที่ไม่ได้ส่งมา ยกเว้น secret ที่มีกฎ "เว้นว่าง = คงค่าเดิม" เป็นข้อยกเว้นเดียว หน้าเว็บส่งครบทุกฟิลด์ทุกครั้งจึงไม่เจอปัญหานี้ แต่การเรียก API ตรงต้องระวัง {otp}ถูกตรวจว่าเป็นสตริงตรงตัวและตรวจซ้ำสามชั้น — หน้าเว็บตรวจ (value.includes('{otp}')แบบสนตัวพิมพ์เล็ก/ใหญ่), cms-api ตรวจตอนบันทึก, และ client-api ตรวจอีกครั้งตอนกำลังส่ง OTP จริง (Email OTP template is missing the {otp} placeholder.) ส่วน{ref}ไม่มีการตรวจในชั้นใดเลย ใส่หรือไม่ใส่ก็ได้ — และหัวข้ออีเมลไม่ถูกตรวจ placeholder เลย ถ้าใส่{otp}ในหัวข้อจะถูกส่งออกไปตามตัวอักษร เพราะการแทนที่ทำเฉพาะกับเนื้อหา- ค่าที่ควบคุมพฤติกรรม OTP จริงไม่ได้อยู่ในหน้านี้เลย — ตัวเลขทั้งชุดถูกกำหนดเป็นค่าคงที่ในซอร์สของ
line-management-client-api-goไม่มีที่ใดใน CMS ให้แก้- อายุรหัส 3 นาที (TTL ของ session ใน Redis)
- กรอกผิดได้ 5 ครั้ง ครั้งที่ 5 ทำลาย session
- ส่งได้รวม 3 ครั้งต่อ session (นับครั้งแรกด้วย)
- ต้องรอ 60 วินาทีก่อนส่งซ้ำ เกินได้ 429
OTP_RESEND_COOLDOWNตัวเลข "3 นาที" ในเนื้อหาอีเมลเริ่มต้นเป็นเพียงข้อความ ไม่ได้อ่านจากค่าคงที่ — ถ้าค่าคงที่ถูกแก้ในอนาคต เนื้อหาอีเมลจะไม่ตามไปด้วย
- SMS กับอีเมลใช้กลไกรหัสไม่เหมือนกันเลย
- SMS — ระบบเรียก
POST /v2/otp/requestของ ThaiBulkSMS (form-urlencoded: key, secret, msisdn) แล้วเก็บtokenที่ได้ไว้ใน session เราไม่เคยรู้ค่ารหัส ตอนตรวจก็ส่ง token+pin ไปถามPOST /v2/otp/verifyเบอร์โทรถูกแปลงเป็นรูปแบบท้องถิ่นก่อนส่ง (+66/66…→0…, ตัดอักขระที่ไม่ใช่ตัวเลขทั้งหมด) - อีเมล — ระบบสุ่มรหัส 6 หลักเองด้วย
crypto/randเก็บเป็น bcrypt hash ใน session (ไม่เคยเก็บ plaintext) และเทียบด้วย bcrypt แบบ constant-time ตอนตรวจ แล้วส่งอีเมลผ่าน SMTP ระดับ channel โดยแทนที่{otp}และ{ref}ในเนื้อหา
- SMS — ระบบเรียก
- session ของ OTP ไม่เก็บเบอร์/อีเมลจริงไว้เลย — เก็บเฉพาะค่าที่ปิดบัง (
0819188051→08xxxxx051,[email protected]→a***@e***.com) และเส้นทางส่งจะค้นหาระเบียนสมาชิกใหม่ทุกครั้งรวมถึงตอนส่งซ้ำ — ค่าปลายทางจึงไม่เคยถูกเชื่อจากฝั่งผู้ใช้ และไม่มีอะไรที่รั่วเบอร์/อีเมลอยู่ใน Redis - client-api ตรวจความพร้อมของช่องทางซ้ำอีกครั้งตอนส่งจริง — ตรวจสวิตช์, key, secret สำหรับ SMS และตรวจสวิตช์, หัวข้อ, เนื้อหาที่ไม่ว่าง, และ
{otp}สำหรับอีเมล ตอบเป็นรหัสข้อผิดพลาดที่ผู้กรอกเห็นได้ (OTP_NOT_CONFIGURED) และมีรหัสแยกสำหรับกรณีระเบียนที่จับคู่ได้ไม่มีข้อมูลติดต่อในช่องทางที่เลือก (OTP_NO_CONTACT) - การอ่าน/เขียน config จำกัดขอบเขตด้วย
line_oa_idเพียงค่าเดียว ไม่ได้กรองorganization_id— ปลอดภัยเพราะคอลัมน์line_oa_idมี unique constraint (หนึ่งแถวต่อหนึ่ง OA) และline_oa_idมาจาก claim ใน token แต่ต่างจากโมดูลอื่นในระบบที่กรองสองชั้น ค่าorganization_idถูกใช้แค่ตอน INSERT แถวใหม่ - ไม่มีเส้นทางลบ — โมดูลนี้มีแค่ 2 เส้นทาง (
GETและPUT) และเป็น upsert แบบ singleton ต่อ OA
เมนูที่เกี่ยวข้องกันในเส้นทางนี้
- Form builder — เป็นผู้ใช้ค่าการตั้งค่านี้เพียงรายเดียว แท็บการยืนยันตัวตนอ่าน
smsConfigured/emailConfiguredจากที่นี่มาตัดสินว่าจะเปิดให้เลือกได้หรือไม่ และเปิดได้เพียง 1 ช่องยืนยันต่อฟอร์ม นอกจากนี้ backend ของ Form builder ยังอ่านตารางotp_configตรง ๆ อีกชั้น (validateOtpConfigGuard) เพื่อปฏิเสธ 400 ถ้าฟอร์มขอเปิดช่องทางที่ยังไม่พร้อม — การเรียก API ตรงเพื่อข้ามหน้าเว็บจึงเปิด OTP ไม่ได้ - Member Database — เป็นเงื่อนไขบังคับของฝั่งฟอร์มที่เปิด OTP เพราะรหัสถูกส่งไปยังคอลัมน์ของระเบียนสมาชิกที่จับคู่ได้ ทั้งตัวเลือก "คอลัมน์ที่จะยืนยัน" ในแท็บนั้นก็ดึงมาจากคอลัมน์ของฐานข้อมูลที่เลือก ไม่ใช่จากคำถามในฟอร์ม
- สามเมนูนี้ผูกกันแบบต่อเนื่องเป็นลูกโซ่ — ปิด Profile Mapping หรือถอด Member Database ออก แท็บยืนยันตัวตนใน Form builder จะเปิดไม่ได้ทันที (และระบบจะปิดสวิตช์ให้อัตโนมัติ); ปิดสวิตช์ช่องทางในหน้านี้ ฟอร์มที่ใช้ช่องทางนั้นจะขึ้นสถานะ error ในแท็บและกดบันทึกใหม่ไม่ผ่าน
จุดที่ควรระวัง
- ไม่มีปุ่มทดสอบส่ง OTP จริงและไม่มีหน้าประวัติการส่งในเมนูนี้ — ตรวจแล้วโมดูลนี้มีเพียง 2 endpoint (GET/PUT) และปุ่มเดียวคือปุ่มบันทึก ต้องทดลองส่งฟอร์มจริงเพื่อยืนยันว่าข้อมูลรับรองใช้งานได้
- ปิดสวิตช์ช่องทางใดช่องทางหนึ่งมีผลทันทีกับทุกฟอร์มที่เปิดใช้ช่องทางนั้นอยู่ — ตรวจสอบก่อนว่ามีฟอร์มที่กำลังใช้งานอยู่หรือไม่
- ไม่มีทางอ่านค่า Secret เดิมกลับมาดูได้ และไม่มีทางลบมันด้วย — ถ้าจำไม่ได้ต้องออก Key/Secret ชุดใหม่จากผู้ให้บริการแล้วกรอกทับ
- ข้อความ SMS ไม่ได้ตั้งค่าที่นี่ — ThaiBulkSMS เป็นผู้สร้างและส่งรหัสเอง ถ้าต้องการแก้ข้อความหรือชื่อผู้ส่งของ SMS ต้องไปทำที่ระบบของ ThaiBulkSMS ช่องเทมเพลตในหน้านี้มีผลกับ อีเมลเท่านั้น
- การ์ด SMS ไม่มีการตรวจฝั่งหน้าเว็บเลย — เปิดสวิตช์แล้วปล่อยช่องว่างก็กดบันทึกได้ จะเห็น error จาก backend หลังกดเท่านั้น (ต่างจากการ์ดอีเมลที่ตรวจ
{otp}ให้ก่อน) - ตัวเลข 3 นาทีในเนื้อหาอีเมลเริ่มต้นเป็นข้อความล้วน — ถ้าเปลี่ยนเนื้อหาแล้วอ้างเวลาอื่น จะไม่ตรงกับพฤติกรรมจริง (TTL คงที่ 3 นาทีในซอร์สของ client-api)
- ถ้า
APP_ENCRYPT_SECRETถูกเปลี่ยน ป้ายสถานะจะยังบอกว่า "กำหนดค่าแล้ว" แต่ placeholder ของ secret จะกลับไปเป็นค่าว่างและการส่ง SMS จริงจะล้มเหลว — สังเกตอาการนี้ให้ดี วิธีแก้คือกรอก secret ใหม่ทับเพื่อเข้ารหัสด้วยคีย์ปัจจุบัน - เรียก
PUT /otp-configแบบบางส่วนจะล้างค่าที่ไม่ได้ส่งมา (ยกเว้น secret) — ควรอ่านค่าปัจจุบันด้วย GET แล้วส่งกลับให้ครบเสมอถ้าเรียก API ตรง
สิทธิ์การเข้าถึง (ตรวจสอบจากซอร์สโค้ดจริง)
- ฝั่งหน้าเว็บ — ถูกครอบด้วย
PermissionGuardตรวจ actionVIEWของ subjectotp-configและรายการในเมนูข้าง (รวมถึง quick access) ก็ถูกกรองด้วยเงื่อนไขเดียวกัน โดย subject ที่ปลดล็อกให้คือform-builderซึ่งใช้ร่วมกับเมนู Form builder (mapping:'form-builder' → [FORM_BUILDER, OTP_CONFIG]) — ผู้ที่ดู Form builder ได้จึงเห็นเมนูนี้ด้วยเสมอ นี่คือการควบคุมที่แน่นกว่าเมนู User Attribute Setup และ BigQuery Sync ซึ่งไม่มีPermissionGuardเลย - ฝั่งเซิร์ฟเวอร์ — ยืนยันแล้วว่า ไม่มี
ModuleGateตรวจแล้วทั้ง repository:modulegate.ModuleGate(...)ถูกเรียกใช้ใน 15 โมดูล และotpconfigไม่ใช่หนึ่งในนั้น ผู้ดูแลระดับแพลตฟอร์มจึงไม่สามารถปิดโมดูลนี้ให้องค์กรใดองค์กรหนึ่งได้ (แม้ปิดโมดูลform-builderไปแล้ว หน้าตั้งค่า OTP ก็ยังเรียกได้อยู่) - policy annotation มีประกาศไว้แต่เป็น no-op —
CheckPolicies(read/update, otp-config)ถูกผูกไว้ทั้งสองเส้นทาง และคอมเมนต์ในไฟล์ระบุตรงว่า "auth.CheckPoliciesเป็น no-op ในวันนี้ — เก็บ metadata ไว้เผื่อวันที่เปิดการบังคับใช้" ดังนั้น ในทางปฏิบัติผู้ใช้คนใดก็ตามที่ล็อกอินอยู่และมี token ที่ผูก OA ไว้ สามารถอ่านและแก้การตั้งค่า OTP ของ OA นั้นได้ทั้งหมด ไม่ว่าจะมีบทบาทใด - ทั้งสองเส้นทางลงทะเบียนบน
authedgroup จึงยังอยู่หลัง JwtAuthGuard ระดับ global ซึ่งบังคับให้ token ต้องมี claimlineOaId— นี่คือจุดที่โมดูลนี้ควบคุมแน่นกว่า BigQuery Sync ซึ่งลงทะเบียนบนpublicgroup ด้วย@Public()แล้วใช้ strategyjwt-loginที่ไม่บังคับ claim นั้น - สิ่งที่ชดเชยการไม่มี ModuleGate อยู่บ้าง — ข้อมูลรับรองถูกเข้ารหัสก่อนเก็บและคืนออกมาเพียงค่าที่ปิดบัง จึงต่างจาก BigQuery Sync ที่ผู้ล็อกอินคนใดก็อ่าน service account key ได้ทั้งก้อน ในโมดูลนี้ผู้ที่เข้าถึงได้จะแก้หรือทำลายการตั้งค่าได้ แต่ไม่สามารถขโมยข้อมูลรับรองที่เก็บไว้ไปใช้ต่อได้
- ข้อสรุปเทียบกับเอกสารเชิงเทคนิคชุดเดิม — คำกล่าวที่ว่า OTP Verification และ BigQuery Sync "ไม่มี ModuleGate และไม่มีการบังคับ policy จริงเลย เปิดให้ผู้ใช้ที่ล็อกอินคนใดก็ได้" ตรวจแล้วว่าถูกต้อง โดยมีรายละเอียดเพิ่มสองข้อ: (1) BigQuery Sync อ่อนกว่าอีกขั้นเพราะอยู่บน
publicgroup +jwt-login, และ (2) OTP Verification ยังมีPermissionGuardฝั่งหน้าเว็บและการเข้ารหัสข้อมูลรับรองเป็นชั้นป้องกันที่เหลืออยู่ - ไม่มีเส้นทางใดต้องเป็น Super Admin และไม่มีเส้นทางลบในโมดูลนี้
ไฟล์อ้างอิงจากซอร์สโค้ด
Frontend (cms-web-2026)
src/app/otp-config/page.tsx— จุดเข้าและPermissionGuard(subjectotp-config)src/components/otp-config/otp-config-form.tsx— การ์ด SMS/อีเมล, ป้ายสถานะ, placeholder ของ secret, ตัวตรวจ{otp}src/services/otp-config.service.ts,src/services/types/otp-config.type.ts— endpoint ทั้งสองและความหมายของ*Configuredsrc/components/ability/permission-map.ts— mapping'form-builder' → [FORM_BUILDER, OTP_CONFIG]src/components/form-builder/form/verification/verification-form.tsx— ฝั่งผู้บริโภคที่อ่านสถานะความพร้อมของช่องทาง
Backend (line-management-cms-api-go)
internal/modules/otpconfig/controller.go— 2 เส้นทางบนauthedgroup,CheckPoliciesที่เป็น no-op, ไม่มี ModuleGateinternal/modules/otpconfig/service.go— upsert แบบ singleton,resolveSecretEnc, การถอดรหัสเพื่อคำนวณค่าที่ปิดบังinternal/modules/otpconfig/crypto.go— AES-256-GCM และmaskSecretinternal/modules/otpconfig/dto.go— กฎข้ามฟิลด์และตรรกะของsmsConfigured/emailConfiguredinternal/modules/formbuilder/otp.go— ด่านตรวจotp_configฝั่ง Form builder ที่อ่านตารางนี้โดยตรง
Client API (line-management-client-api-go) — เป็นผู้ส่งและตรวจ OTP จริง
internal/otp/session.go— TTL 3 นาที, กรอกผิดได้ 5 ครั้ง, ส่งได้ 3 ครั้ง, รอ 60 วินาทีinternal/otp/service.go— การโหลด config ต่อ OA, การตรวจความพร้อมซ้ำ, การแทนที่{otp}/{ref}internal/otp/code/code.go— การสุ่มรหัส 6 หลักและ bcrypt (ช่องทางอีเมลเท่านั้น)internal/otp/thaibulksms/thaibulksms.go—/v2/otp/requestและ/v2/otp/verify(ThaiBulkSMS เป็นผู้สร้างรหัส)internal/otp/mask.go— การแปลงเบอร์เป็นรูปแบบท้องถิ่นและการปิดบังเบอร์/อีเมล
ดูรายละเอียดเชิงเทคนิคเพิ่มเติมที่ ตั้งค่ายืนยัน OTP