ยืนยันสิทธิ์ 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
- คำขอผ่าน api-key middleware ก่อน (ดู การยืนยันตัวตนด้วย API Key)
เพื่อให้ได้ค่า
apiClientIdและapiKey - bind body แบบหลวม (ต้นฉบับไม่มี DTO) โดยรับฟิลด์
channelIdและchannelSecret(ไม่บังคับ) หาก body เสียหายหรือว่างเปล่า ค่าจะเป็น zero value แล้วเดินหน้าต่อ ซึ่งท้ายที่สุดจะจบที่404 - Step 1 — resolve row ของ
api_keyด้วยSELECT ... FROM api_key WHERE api_client_id=$1 AND key=$2 AND status='active'หากเกิด error จริงจากฐานข้อมูลจะตอบ500ไม่ใช่404 - 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 ได้ - Step 3 — seed audience template แบบ best-effort โดยเรียก
audience.Service.CreateAudienceTemplate(apiClientId, apiKey)ซึ่งจะสร้าง audience slugmookept-1ถึงmookept-4หากยังไม่มี (ดู จัดการสมาชิก Audience ผ่าน Public API) error ในขั้นตอนนี้ถูกกลืนทิ้งตาม try/catch ของต้นฉบับ โดยฟิลด์dataจะเป็นnull - 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 ประการ
- การ seed audience เกิดขึ้นก่อนการตรวจสอบว่า verify ผ่านหรือไม่ ดังนั้นแม้เรียกด้วย
channelIdที่ผิด ระบบก็ยัง seed audience ให้ผู้เรียกอยู่ดี แล้วค่อยตอบ404พฤติกรรมนี้เป็น parity ตามลำดับการทำงานของต้นฉบับ - envelope ของ error ในเวอร์ชัน Go เป็นรูปแบบ NestJS มาตรฐาน คือ
{statusCode, message, error}โดยค่าcodeที่ระบุในNewExceptionถูกตัดทิ้ง ซึ่งมี comment กำกับไว้ที่httpx.NewException:::
ไฟล์และฟังก์ชันหลัก
| Method | Route | Auth | Handler |
|---|---|---|---|
| POST | /api/line-oa/verify | api-key | Handler.verify |
ซอร์สโค้ดทั้งหมดอยู่ภายใต้ internal/lineoa/
| ไฟล์ | ของสำคัญ |
|---|---|
handler.go | Register (ครอบด้วย deps.APIKeyAuth()), Handler.verify, caller, type verifyBody, audienceTemplateAdapter |
service.go | Service.Verify(ctx, apiClientID, apiKey, channelID, channelSecret), type VerifyResult, interface scopeResolver / lineOaFinder / templateCreator |
repository.go | Repository.FindByLineLoginInfo(ctx, channelID, channelSecret *string) — jsonb predicate |
entity.go | struct LineOa (22 คอลัมน์) และ LineLoginInfo (jsonb Scanner/Valuer) |
พารามิเตอร์ channelSecret ถูกประกาศเป็น *string โดยตั้งใจ การไม่ส่งค่ามาหมายถึงการตัด
predicate ตัวที่สองทิ้ง ซึ่งต่างจากการส่งค่าว่างเข้ามาที่จะไปเปรียบเทียบกับสตริงว่าง
จุดเชื่อมต่อกับ Service อื่น
- Postgres — ตาราง
line_oa(คอลัมน์ jsonbline_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 พกติดตัวมา