แอปเป็น SPA ที่เรียก Firebase จากเบราว์เซอร์โดยตรง ดังนั้น firestore.rules คือขอบเขตความปลอดภัยเพียงชั้นเดียว โค้ดฝั่งแอปตรวจสิทธิ์เพื่อ UX เท่านั้น ไม่ใช่การป้องกัน
- ปฏิเสธเป็นค่าเริ่มต้น — เอกสารที่ไม่มีกฎรองรับเข้าถึงไม่ได้
- สมาชิกเท่านั้นที่อ่านสมุดได้ — ไม่มีรหัสผ่านเก็บในฐานข้อมูล การเข้าร่วมต้องผ่านการอนุมัติของเจ้าของ
- Query ต้องสอดคล้องกับกฎ — แอปดึงสมุดด้วย
where("memberIds","array-contains",uid)เสมอ query กว้างกว่านั้นถูกปฏิเสธ - ตรวจรูปแบบข้อมูลฝั่ง server — ชนิด ช่วงตัวเลข ความยาว รูปแบบวันที่ URL รูป และห้ามมีฟิลด์เกิน
- ฟิลด์ที่เปลี่ยนไม่ได้ —
createdAt,createdByUidของรายการ;ownerUid,createdAtของสมุด - สิทธิ์น้อยที่สุด — แต่ละบทบาททำได้เฉพาะที่จำเป็น (ตารางด้านล่าง)
| ทรัพยากร | อ่าน | สร้าง | แก้ไข | ลบ |
|---|---|---|---|---|
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ในผลรันเป็นเรื่องปกติ — คือกรณีที่เทสต์คาดหวังให้ถูกปฏิเสธ
- รัน
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 แก้/ลบรายการของสมาชิกคนอื่นในสมุดได้ (เป็นพฤติกรรมที่ตั้งใจให้ทีมแก้ไขร่วมกัน)