Skip to main content

ด่านเปิด/ปิดแอปรายองค์กร (App Enabled Guard)

ภาพรวม

Platform admin สามารถเปิดหรือปิด "แอป" อย่าง bulletin, loyalty และ appointment ให้แต่ละองค์กรได้ผ่านตาราง line_oa_app ฝั่ง CMS บังคับกฎนี้อยู่แล้วทั้งการซ่อนเมนูและ guard บน API แต่ ลิงก์ LIFF สาธารณะเดิมกลับไม่มีการตรวจสอบเลย ทำให้แอปที่ถูกปิดยังใช้งานได้ตามปกติจากฝั่งลูกค้า

appguard.AppEnabledGuard คือ middleware ที่ปิดช่องโหว่นี้ที่ระดับ client-api ซึ่งเป็นชั้นที่ client-web ข้ามไปไม่ได้

Business Flow

  1. middleware ถูกติดตั้งด้วย Use() บน route group ที่ผูกกับ :hash ปัจจุบันใช้กับ 2 กลุ่มคือ /bulletin/:hash (appID เป็น bulletin) และ /loyalty/:hash (appID เป็น loyalty)
  2. หากไม่มี :hash ใน path จะปล่อยผ่านทันที
  3. รัน SQL เพียงคำสั่งเดียว โดย resolve line_oa จาก line_oa_hash (ต้องมี status <> 'delete' และไม่ถูก soft-delete) แล้ว LEFT JOIN line_oa_app ด้วยคู่ (organization_id, app_id) และคืนค่า COALESCE(a.enabled, false) ทำให้แถว line_oa_app ที่ไม่มีถูกอ่านว่า "ปิด" แทนที่จะทำให้ OA หลุดจากผลลัพธ์ไปเลย
  4. ปรัชญาการตัดสินแบ่งเป็น 2 กรณี
    • เมื่อ enabled = false อย่างชัดเจน จะตอบ 403 พร้อม message APP_DISABLED (fail closed) ซึ่ง client-web จะแปลงโค้ดนี้เป็นหน้า "ฟีเจอร์นี้ยังไม่เปิดใช้งาน" ดังนั้นสตริงนี้ห้ามเปลี่ยน
    • เมื่อเกิด lookup error หรือ hash ไม่ตรงกับ OA ใด (sql.ErrNoRows) จะ fail open ปล่อยผ่านให้ handler ตัดสินเอง ซึ่งโดยปกติจะตอบ 404 เหตุผลคือ governance lookup ที่สะดุดชั่วคราวไม่ควรล็อกลูกค้าออกจากแอปที่เปิดใช้งานอยู่
  5. guard ตัวนี้ครอบทั้งฝั่งลูกค้าและฝั่งพนักงานของ loyalty กล่าวคือเมื่อปิดแอปแล้ว เครื่องของพนักงานก็ใช้งานไม่ได้เช่นกัน

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

รายการค่า
ไฟล์internal/appguard/appguard.go
ฟังก์ชันAppEnabledGuard(db *sqlx.DB, appID string) gin.HandlerFunc และ isEnabled
ค่าคงที่ErrAppDisabled = "APP_DISABLED"
SQLappEnabledSQL (LEFT JOIN ระหว่าง line_oa และ line_oa_app)
จุดติดตั้งinternal/bulletin/register.go เรียก g.Use(appguard.AppEnabledGuard(deps.DB, "bulletin")) และ internal/loyalty/register.go ใช้ค่า loyalty

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

  • ตาราง line_oa และ line_oa_app (คอลัมน์ organization_id, app_id, enabled)
  • ฝั่ง CMS มี guard คู่กันชื่อ apps.AppEnabledGuard ใน cms-api-go ซึ่งทั้งสองฝั่งต้องใช้ชุด app_id เดียวกัน
  • feature ที่ถูกครอบด้วย guard นี้ ได้แก่ กลุ่ม bulletin ทั้งหมด (feed, การเขียนโพสต์, คอมเมนต์, reaction และ report) และกลุ่ม loyalty ทั้งหมด (บัตรสะสมแต้ม, การสะสมแต้ม, การแลกรางวัล และฝั่งพนักงาน)
  • ข้อควรทราบ: เส้นทาง /appointment/public/* ยังไม่มี guard ตัวนี้ เนื่องจาก route ใช้ :token ไม่ใช่ :hash
  • ฝั่ง client-web ที่เกี่ยวข้องคือ feature bulletin-board และ loyalty-card ซึ่งต้องรองรับการแสดงหน้าจอเมื่อได้รับ APP_DISABLED