UDENTIVA แพลตฟอร์มการจัดการทันตกรรมอัจฉริยะ
ความปลอดภัย

โมเดลความปลอดภัยของ API

ความปลอดภัยของเราไม่ใช่ชั้นที่เพิ่มเข้ามาภายหลัง แต่เป็นศูนย์กลางของสถาปัตยกรรม การแยกข้อมูล การแบ่งแยกตัวตน และการเขียนแบบ idempotent ถูกฝังไว้ในชั้นล่างสุดของโค้ด — ไม่ใช่การตั้งค่าที่ปิดได้ แต่เป็นวิธีการทำงานของระบบ

การแยกระดับแถว การเขียนแบบ idempotent คำสั่งแบบมีพารามิเตอร์ การแยกแบบ Loopback
การแยกข้อมูล

การแยกระดับแถว — fail-closed

ทุกตารางที่มีขอบเขตแยกตามผู้เช่า (tenant) จะเปิดใช้ PostgreSQL Row-Level Security แบบ FORCE โดยนโยบายจะผูกทุกแถวไว้กับเงื่อนไข "tenant_id = current_setting('app.current_tenant')"

คำขอที่มาโดยไม่ได้ตั้งค่า tenant context จะคืนศูนย์แถว — ไม่ใช่แม้แต่ข้อผิดพลาด แต่ว่างเปล่าอย่างเงียบ ๆ การเห็นข้อมูลของคลินิกหนึ่งด้วยเซสชันของอีกคลินิกเป็นไปไม่ได้ในเชิงสถาปัตยกรรม

การทดสอบการแยกข้ามผู้เช่า (cross-tenant) ต้องผ่านเป็นสีเขียวใน CI เสมอ หากเป็นสีแดง โค้ดจะไม่ถูกรวม

การเขียนแบบอะตอมมิก

ตัวตนแบบ transaction-local, ธุรกรรมแบบอะตอมมิก

รหัสประจำตัวผู้เช่าจะถูกกำหนดไว้ตั้งแต่จุดเริ่มต้นที่สุดของทุก transaction ที่มีการเขียนข้อมูล และมีผลเฉพาะภายใน transaction นั้นเท่านั้น จึงไม่ติดค้างอยู่ในคอนเนกชันพูล และไม่รั่วไหลไปยังคำขออื่น

ธุรกรรมแบบหลายเอนทิตี (ใบแจ้งหนี้ + รายการ + การชำระเงิน + การตัดสต็อก) เป็นอะตอมมิกภายใน transaction เดียว: เขียนพร้อมกันทั้งหมดหรือไม่เขียนเลย ไม่มีบันทึกที่ค้างคาหรือไม่สอดคล้อง

สิทธิ์

สิทธิ์ตามบทบาท (RBAC) ถูกบังคับใช้ที่เซิร์ฟเวอร์

การควบคุมสิทธิ์ทำที่ชั้น API ไม่มีวันไว้ใจไคลเอนต์หรือฝั่งหน้าจอ การซ่อนเมนูในเบราว์เซอร์เกี่ยวข้องกับรูปลักษณ์เท่านั้น — การตัดสินใจจริงอยู่ที่เซิร์ฟเวอร์

การกลายพันธุ์ (mutation) ของแกนกลาง (เฟส 1) ถูกปกป้องตามบทบาท admin, พนักงานต้อนรับ และแพทย์ แม้บทบาทที่ไม่มีสิทธิ์จะเรียกเอนด์พอยต์ คำขอก็จะถูกปฏิเสธ

การป้องกันการทำซ้ำ

การเขียนแบบ idempotent — ไม่มีการดำเนินการซ้ำ

ทุกคำขอเขียนพก Idempotency-Key การล็อกฝั่งเซิร์ฟเวอร์ป้องกันรายการที่สองที่มากับคีย์เดียวกัน

แม้เมื่อเครือข่ายขาดหรือไคลเอนต์ส่งคำขอเดิมซ้ำ ก็จะไม่เกิดการเก็บเงินหรือออกใบแจ้งหนี้เป็นครั้งที่สอง ผลลัพธ์จะเป็นหนึ่งเดียวและคาดการณ์ได้เสมอ

ความปลอดภัยของคำสั่ง (query)

SQL injection เป็นไปไม่ได้ — คำสั่งแบบมีพารามิเตอร์

คำสั่งฐานข้อมูลทั้งหมดเป็นข้อความที่สร้างในเวลาคอมไพล์ด้วย sqlc และมีพารามิเตอร์อย่างสมบูรณ์ ในโค้ดเบสไม่มีแม้แต่บรรทัดเดียวที่สร้าง SQL ด้วยการต่อสตริงจากอินพุตผู้ใช้ด้วยมือ

สิ่งนี้กำจัดการโจมตีประเภท SQL injection ในเชิงโครงสร้าง — โดยอาศัยวิธีการสร้างคำสั่ง (query) ไม่ใช่ความพยายามในการ "กรอง" หรือ "escape"

พื้นผิวเครือข่าย

การแยกแบบ Loopback — แกนกลางปิดต่อภายนอก

แกน API จะรับฟังเฉพาะที่ 127.0.0.1 (loopback) เท่านั้น ความพยายามผูกกับ 0.0.0.0 หรือ IP สาธารณะจะถูกปฏิเสธตั้งแต่เริ่มต้น แกนหลักจะไม่เปิดสู่อินเทอร์เน็ตไม่ว่ากรณีใด

หน้าสาธารณะเพียงหน้าเดียวคือประตูบางเบา (BFF) ซึ่งไม่เก็บกฎธุรกิจใด ๆ นอกจากเซสชัน การเรนเดอร์ และ CSRF เบราว์เซอร์ไม่สามารถมองเห็นหรือเข้าถึงแกน API โดยตรงได้เลย

ตัวตนและเซสชัน

รหัสผ่าน argon2id, คีย์เซสชันแบบ opaque

รหัสผ่านถูกแฮชด้วย argon2id ที่เป็นมาตรฐานอุตสาหกรรมและใช้หน่วยความจำหนัก (memory-hard) ไม่มีการใช้แฮชแบบธรรมดาหรืออ่อนแอ

คีย์เซสชันเป็นค่า opaque แบบสุ่มด้วยการเข้ารหัสขนาด 32 ไบต์ เก็บไว้ใน Redis และหมดอายุด้วย TTL เบราว์เซอร์จะได้รับเพียงคุกกี้แบบ HttpOnly + Secure + SameSite เท่านั้น คีย์ไม่เคยเปิดเผยให้ JavaScript เข้าถึง

การตรวจสอบ

บันทึกตรวจสอบที่แก้ไขไม่ได้ (append-only)

การดำเนินการสำคัญจะถูกเขียนลงในบันทึกตรวจสอบแบบ append-only ภายใน transaction เดียวกันกับ mutation ที่ทริกเกอร์มัน หากธุรกรรมถูกย้อนกลับ บันทึกตรวจสอบก็จะไม่ถูกเขียน — ทั้งสองแยกจากกันไม่ได้

สิทธิ์ UPDATE/DELETE ถูกเพิกถอนจากตารางบันทึกตรวจสอบ บันทึกจึงไม่สามารถแก้ไขหรือลบภายหลังได้ ประวัติยังคงอยู่ตามเดิม

ความเป็นส่วนตัว

การปิดบังข้อมูลที่ละเอียดอ่อน

TCKN รหัสผ่าน คีย์เซสชัน และข้อมูลสุขภาพจะไม่เข้าสู่บันทึก (log) ในรูปแบบใด ๆ แม้ในเอาต์พุตดีบัก ฟิลด์เหล่านี้ก็ถูกปิดบัง

เมื่อจำเป็น ฟิลด์ที่ละเอียดอ่อนจะถูกเข้ารหัสในระดับคอลัมน์ ข้อมูลสุขภาพที่มีลักษณะพิเศษได้รับการปกป้องด้วยกฎที่วางไว้ตั้งแต่ต้นในการออกแบบ

ความสมบูรณ์ของจำนวนเงิน

ความปลอดภัยของจำนวนเงิน — numeric, การกลับรายการ

จำนวนเงินถูกเก็บเป็น numeric(14,2) ทุกที่ ไม่มีการคำนวณเงินด้วย float การปัดเศษเกิดขึ้นที่จุดเดียวบนเซิร์ฟเวอร์ — จึงไม่เกิดการเคลื่อนของเศษสตางค์หรือความคลาดเคลื่อนจากการปัดเศษ

การยกเลิกและคืนเงินถูกประมวลผลเป็นการกลับรายการ แทนการลบบันทึก ยอดคงเหลือของบัญชีไม่ได้เขียนด้วยมือ แต่คำนวณจากยอดรวมของใบแจ้งหนี้และการชำระเงินเสมอ ประวัติไม่ถูกทำลาย

การส่งออกข้อมูล

การส่งออกข้อมูลและความปลอดภัยของเบราว์เซอร์

การรับส่งข้อมูลทั้งหมดผ่าน HTTPS/TLS และเปิด HSTS เบราว์เซอร์สร้างการเชื่อมต่อในรูปแบบเข้ารหัสเท่านั้น

นโยบายความปลอดภัยของเนื้อหา (CSP) ที่เข้มงวดจะบล็อกสคริปต์แบบ inline; X-Frame-Options DENY ป้องกัน clickjacking และ X-Content-Type-Options nosniff ป้องกันการสับสนของชนิดข้อมูล มีการบังคับใช้ Referrer-Policy และ Permissions-Policy ด้วยเช่นกัน

สำคัญ

การแบ่งแยก tier ของตัวตน + control plane แยกต่างหาก

ผู้ใช้คลินิก พอร์ทัลผู้ป่วย และผู้ดูแลแพลตฟอร์มเป็นระดับตัวตน (tier) ที่แยกจากกัน มีส่วนหัวแยก มีพื้นที่เซสชันแยก คีย์ของ tier หนึ่งใช้กับอีก tier ไม่ได้ ขอบเขตสิทธิ์ถูกแยกออกจากกันอย่างชัดเจน

ปลายทาง (endpoint) ด้านแพลตฟอร์มและการจัดการ (การจัดเตรียมผู้เช่า การออกใบแจ้งหนี้ การจัดการระบบอัตโนมัติ/ปลั๊กอิน การสร้างคีย์ API) จะไม่ปรากฏใน public API

ฟังก์ชันเหล่านี้ทำงานบน control plane ที่แยกต่างหาก อยู่ภายใน และมีรูปแบบการยืนยันตัวตนที่แตกต่าง (คีย์การจัดการเฉพาะ) ไม่สามารถเข้าถึงหรือค้นพบได้จากภายนอก จึงลดพื้นผิวการโจมตีลงอย่างชัดเจน

ทำงานอย่างไร

transaction เดียว การแยกแบบ fail-closed

โฟลว์เชิงแนวคิดด้านล่างแสดงให้เห็นว่าแต่ละการเขียนถูกล็อกกับผู้เช่า (tenant) และคง idempotent ไว้อย่างไร มีวัตถุประสงค์เพื่อการอธิบายเท่านั้น

# 1) กำหนดบริบทของผู้เช่า (tenant context) ไว้ตั้งแต่จุดเริ่มต้นที่สุดของ transaction (เฉพาะ tx นี้เท่านั้น)
BEGIN;
  SELECT set_config('app.current_tenant', :session_tenant_id, true);

  # 2) การล็อกด้วย Idempotency-Key — คีย์เดียวกันจะไม่สร้างรายการซ้ำเป็นครั้งที่สอง
  SELECT lock_idempotency(:idempotency_key);

  # 3) การเขียนหลายเอนทิตี — ทั้งหมดเป็น atomic ภายใน tx เดียว
  INSERT INTO invoices  ...;   # RLS: tenant_id ครอบคลุมโดยอัตโนมัติ
  INSERT INTO payments  ...;
  INSERT INTO audit_log ...;   # append-only, tx เดียวกัน
COMMIT;

# หากไม่ได้ตั้งค่า context นโยบาย RLS จะไม่คืนแถวข้อมูลใด ๆ
SELECT * FROM patients;  --> 0 แถว (ไม่ใช่ข้อผิดพลาด ว่างเปล่าอย่างเงียบ ๆ)
ตรวจสอบได้อย่างอิสระ

ความปลอดภัยของการส่งข้อมูลบางส่วนสามารถตรวจสอบได้จากภายนอก คุณสามารถตรวจสอบใบรับรอง HTTPS/TLS ของการเชื่อมต่อ ตลอดจนส่วนหัวความปลอดภัยของการตอบกลับ เช่น HSTS และนโยบายความปลอดภัยของเนื้อหา ได้ด้วยตนเองจากเครื่องมือสำหรับนักพัฒนาในเบราว์เซอร์ ข้อยืนยันของเราอยู่บนพื้นฐานของการควบคุมเชิงสถาปัตยกรรม ไม่ใช่ประโยคทางการตลาด

มีคำถามเกี่ยวกับสถาปัตยกรรมความปลอดภัยหรือไม่
ทีมงานของเรายินดีที่จะพูดคุยกับคุณเกี่ยวกับรายละเอียดทางเทคนิคและข้อกำหนดด้านการปฏิบัติตามกฎ
ติดต่อเรา