Skip to main content

ยืนยันสิทธิ์ LINE OA

ภาพรวม

POST /api/line-oa/verify เป็น endpoint สำหรับให้ระบบภายนอกที่ถือ api-key ใช้ตรวจสอบว่า credential ที่ตนถืออยู่ผูกกับ LINE OA ตัวใด และตรงกับ OA ที่คาดหวังไว้หรือไม่

ผู้เรียกส่ง channelId ของ LINE OA เข้ามา (จะแนบ channelSecret มาด้วยก็ได้) ระบบจะค้นหา ในคอลัมน์ jsonb line_oa.line_login_info แล้วเปรียบเทียบว่า OA ที่พบเป็นตัวเดียวกับ api_key.line_oa_id ของผู้เรียกหรือไม่ หากตรงกันถือว่าผ่าน หากไม่ตรงหรือไม่พบจะตอบ 404

ฟีเจอร์นี้มีผลข้างเคียงที่สำคัญคือ ทุกครั้งที่ถูกเรียก ระบบจะพยายาม seed audience ตั้งต้น 4 กลุ่มให้ผู้เรียกโดยอัตโนมัติแบบ best-effort ด้วยเหตุนี้ endpoint นี้จึงมักถูกใช้เป็น "endpoint เริ่มต้นใช้งาน" ที่พาร์ตเนอร์เรียกเป็นครั้งแรกในขั้นตอน onboarding

Business Flow

  1. คำขอผ่าน api-key middleware ก่อน (ดู การยืนยันตัวตนด้วย API Key) เพื่อให้ได้ค่า apiClientId และ apiKey
  2. bind body แบบหลวม (ต้นฉบับไม่มี DTO) โดยรับฟิลด์ channelId และ channelSecret (ไม่บังคับ) หาก body เสียหายหรือว่างเปล่า ค่าจะเป็น zero value แล้วเดินหน้าต่อ ซึ่งท้ายที่สุดจะจบที่ 404
  3. Step 1 — resolve row ของ api_key ด้วย SELECT ... FROM api_key WHERE api_client_id=$1 AND key=$2 AND status='active' หากเกิด error จริงจากฐานข้อมูลจะตอบ 500 ไม่ใช่ 404
  4. Step 2 — ค้นหา line_oa จากคอลัมน์ jsonb ด้วย SELECT ... FROM line_oa WHERE line_login_info->>'channelId' = $1 หาก body ส่ง channelSecret มาด้วย จะเพิ่มเงื่อนไข AND line_login_info->>'channelSecret' = $2 ทั้งนี้ใช้ text extraction แทน object-equality แบบ TypeORM เดิม เพื่อให้ใช้ index ได้
  5. Step 3 — seed audience template แบบ best-effort โดยเรียก audience.Service.CreateAudienceTemplate(apiClientId, apiKey) ซึ่งจะสร้าง audience slug mookept-1 ถึง mookept-4 หากยังไม่มี (ดู จัดการสมาชิก Audience ผ่าน Public API) error ในขั้นตอนนี้ถูกกลืนทิ้งตาม try/catch ของต้นฉบับ โดยฟิลด์ data จะเป็น null
  6. Step 4 — เปรียบเทียบ id
    • หาก lineOa.ID เท่ากับ apiKeyEntity.LineOaID จะตอบ 200 พร้อม {code:"RES_SUCCESS_001", message:"verify success", data:(รายการ audience)}
    • หากไม่ตรงกัน หรือหาไม่พบฝั่งใดฝั่งหนึ่ง จะตอบ 404 พร้อมข้อความ "Channel ID and Channel Secret is required" (code เดิมคือ LINE_OA_001)

:::note ข้อสังเกต 2 ประการ

  1. การ seed audience เกิดขึ้นก่อนการตรวจสอบว่า verify ผ่านหรือไม่ ดังนั้นแม้เรียกด้วย channelId ที่ผิด ระบบก็ยัง seed audience ให้ผู้เรียกอยู่ดี แล้วค่อยตอบ 404 พฤติกรรมนี้เป็น parity ตามลำดับการทำงานของต้นฉบับ
  2. envelope ของ error ในเวอร์ชัน Go เป็นรูปแบบ NestJS มาตรฐาน คือ {statusCode, message, error} โดยค่า code ที่ระบุใน NewException ถูกตัดทิ้ง ซึ่งมี comment กำกับไว้ที่ httpx.NewException :::

ไฟล์และฟังก์ชันหลัก

MethodRouteAuthHandler
POST/api/line-oa/verifyapi-keyHandler.verify

ซอร์สโค้ดทั้งหมดอยู่ภายใต้ internal/lineoa/

ไฟล์ของสำคัญ
handler.goRegister (ครอบด้วย deps.APIKeyAuth()), Handler.verify, caller, type verifyBody, audienceTemplateAdapter
service.goService.Verify(ctx, apiClientID, apiKey, channelID, channelSecret), type VerifyResult, interface scopeResolver / lineOaFinder / templateCreator
repository.goRepository.FindByLineLoginInfo(ctx, channelID, channelSecret *string) — jsonb predicate
entity.gostruct LineOa (22 คอลัมน์) และ LineLoginInfo (jsonb Scanner/Valuer)

พารามิเตอร์ channelSecret ถูกประกาศเป็น *string โดยตั้งใจ การไม่ส่งค่ามาหมายถึงการตัด predicate ตัวที่สองทิ้ง ซึ่งต่างจากการส่งค่าว่างเข้ามาที่จะไปเปรียบเทียบกับสตริงว่าง

จุดเชื่อมต่อกับ Service อื่น

  • Postgres — ตาราง line_oa (คอลัมน์ jsonb line_login_info), api_key, api_client และ audience เข้าถึงผ่าน pgx simple protocol ซึ่งสำคัญมากสำหรับการ bind jsonb และ enum ดูรายละเอียดที่ แกนกลาง HTTP และการเชื่อมฐานข้อมูล
  • โมดูล audience ภายในบริการเดียวกันlineoa.Register สร้าง audience.Service ขึ้นมาเอง เพื่อเรียก CreateAudienceTemplate ด้วยเหตุนี้จึงต้องมี config ของ AMQP และ exchange แม้เส้นทางนี้จะไม่ได้ publish ข้อความใด ๆ
  • cms-api — โมดูล line-oa-management เป็นเจ้าของข้อมูลใน line_oa.line_login_info (channelId, channelSecret, lineLiffId, formLiffId) ส่วนโมดูล api-key เป็นผู้ออก key ให้ผู้เรียก
  • โดเมนฐานข้อมูลที่เกี่ยวข้อง: line-oa-channel, api-client-key และ audience
  • ไม่มีการใช้ Redis หรือ RabbitMQ ในเส้นทาง verify เอง ยกเว้น dependency ที่ audience service พกติดตัวมา