Skip to content

Latest commit

 

History

History
59 lines (43 loc) · 8.27 KB

SECURITY.md

File metadata and controls

59 lines (43 loc) · 8.27 KB

ความปลอดภัยของข้อมูล (Security model)

แอปเป็น SPA ที่เรียก Firebase จากเบราว์เซอร์โดยตรง ดังนั้น firestore.rules คือขอบเขตความปลอดภัยเพียงชั้นเดียว โค้ดฝั่งแอปตรวจสิทธิ์เพื่อ UX เท่านั้น ไม่ใช่การป้องกัน

หลักการ

  1. ปฏิเสธเป็นค่าเริ่มต้น — เอกสารที่ไม่มีกฎรองรับเข้าถึงไม่ได้
  2. สมาชิกเท่านั้นที่อ่านสมุดได้ — ไม่มีรหัสผ่านเก็บในฐานข้อมูล การเข้าร่วมต้องผ่านการอนุมัติของเจ้าของ
  3. Query ต้องสอดคล้องกับกฎ — แอปดึงสมุดด้วย where("memberIds","array-contains",uid) เสมอ query กว้างกว่านั้นถูกปฏิเสธ
  4. ตรวจรูปแบบข้อมูลฝั่ง server — ชนิด ช่วงตัวเลข ความยาว รูปแบบวันที่ URL รูป และห้ามมีฟิลด์เกิน
  5. ฟิลด์ที่เปลี่ยนไม่ได้ — createdAt, createdByUid ของรายการ; ownerUid, createdAt ของสมุด
  6. สิทธิ์น้อยที่สุด — แต่ละบทบาททำได้เฉพาะที่จำเป็น (ตารางด้านล่าง)

ตารางสิทธิ์

ทรัพยากร อ่าน สร้าง แก้ไข ลบ
users/{uid}/transactions/* เจ้าของ เจ้าของ (ผ่านการตรวจรูปแบบ) เจ้าของ เจ้าของ
books/{id} สมาชิก ผู้ล็อกอิน (เป็นเจ้าของ+สมาชิกคนเดียว ไม่มี joinPassword) เจ้าของ (เปลี่ยนชื่อ/สมาชิก/soft-delete) · สมาชิกออกจากสมุดได้เฉพาะตัวเอง เจ้าของ
books/{id}/transactions/* สมาชิก Owner/Editor (สมุดต้องไม่ถูก soft-delete) Owner/Editor Owner/Editor · เจ้าของลบได้เสมอ
users/{uid}/joinRequestRefs/{bookId} เจ้าของ เจ้าของ (เฉพาะ requestedAt) เจ้าของ เจ้าของ
books/{id}/joinRequests/{uid} ผู้ขอ + เจ้าของ คนนอก ขอได้เฉพาะ uid ของตัวเอง, role = viewer, status = pending เจ้าของ เปลี่ยน status เป็น rejected เท่านั้น ผู้ขอ · เจ้าของ

การอนุมัติ = เจ้าของแก้สมุดเพิ่มสมาชิก และ ลบคำขอ ใน writeBatch เดียว (atomic)

ช่องโหว่ที่เคยพบและวิธีปิด

ช่องโหว่ (เวอร์ชันก่อน) วิธีปิด เทสต์ที่ป้องกัน
รหัสเข้าร่วมเก็บ plaintext ในเอกสารสมุด และผู้ล็อกอินทุกคนอ่านได้ เลิกใช้รหัส, อ่านได้เฉพาะสมาชิก members can read; strangers cannot
กฎไม่ตรวจรหัสตอนเข้าร่วม (ตรวจแค่ฝั่ง client) เข้าร่วมต้องผ่านคำขอ + เจ้าของอนุมัติ a stranger cannot add themself … (no password bypass)
list ทุกสมุดในระบบได้ บังคับ query ด้วย memberIds listing is members-only
สมาชิกออกจากสมุดไม่ได้ / ลบสมุดแล้วยังโผล่ กฎ leave ที่จำกัดเฉพาะตัวเอง + soft-delete ที่กรองและล็อกการเขียน a member can leave, only the owner can soft-delete …
ข้อมูลสมุดเก่ามี joinPassword ค้าง แอปลบให้อัตโนมัติเมื่อเจ้าของเปิด; กฎอนุญาตให้ “ลบ” ได้อย่างเดียว a legacy joinPassword can be removed … but never written

การทดสอบ

npm test                # unit test (ตรรกะฟอร์ม/โปรไฟล์/CSV)
npm run test:rules      # ต้องมี JDK 21+; สคริปต์เปิด Firestore Emulator ให้เอง

ชุดทดสอบ tests/rules/firestore.rules.test.ts มี 35 กรณี แบ่งเป็น: รายการส่วนตัว · อ่านสมุด · สร้างสมุด · รายการในสมุด · คำขอเข้าร่วม · การเปลี่ยนสมาชิก · soft-delete/purge/ข้อมูลเก่า · วันที่ · การทำให้รายการไม่ระบุตัวตน · รายการคำขอของผู้ใช้ ทุกกรณีมีทั้งฝั่ง “ต้องทำได้” และ “ต้องถูกปฏิเสธ” เพื่อกันไม่ให้กฎผ่อนปรนเกินไปโดยไม่รู้ตัว

ข้อความ PERMISSION_DENIED ในผลรันเป็นเรื่องปกติ — คือกรณีที่เทสต์คาดหวังให้ถูกปฏิเสธ

ข้อควรระวังเมื่อแก้ rules

  • รัน npm run test:rules ทุกครั้งและเพิ่มเทสต์สำหรับกฎที่เปลี่ยน
  • ค่าที่ rules จำกัด (ความยาวชื่อ รูปแบบ URL วันที่ ช่วงจำนวนเงิน) มีตัวตรวจฝั่งแอปใน src/lib/ คอยกันไม่ให้ส่งข้อมูลที่จะถูกปฏิเสธ — แก้ที่ใดต้องแก้อีกที่ให้ตรงกัน (มี unit test คุม)
  • deploy firestore.rules ก่อน deploy hosting เสมอ
  • rules ใช้ get() ตรวจสิทธิ์ในสมุด (คิดเป็นการอ่านเพิ่ม 1 ครั้งต่อคำสั่งเขียนรายการ) ซึ่งยอมรับได้ที่ขนาดปัจจุบัน

ข้อจำกัดที่ทราบ

  • API key ของ Firebase ฝั่ง web เป็นค่าสาธารณะโดยออกแบบ ความปลอดภัยอยู่ที่ rules ไม่ใช่การซ่อน key
  • ไม่มี rate limit ต่อผู้ใช้ — ผู้ล็อกอินที่ทราบ ID สมุดส่งคำขอเข้าร่วมได้ (ID เป็นค่าสุ่มยาว เดายาก) หากต้องการป้องกันการสแปมเพิ่ม ควรเพิ่ม Firebase App Check
  • ผู้ใช้ที่เป็น Editor แก้/ลบรายการของสมาชิกคนอื่นในสมุดได้ (เป็นพฤติกรรมที่ตั้งใจให้ทีมแก้ไขร่วมกัน)