# DMS Backend – คู่มือ Auth & RBAC/ABAC ## ภาพรวมระบบ Backend ใช้ **Bearer Token** สำหรับการยืนยันตัวตน และตรวจสอบสิทธิ์ด้วย **RBAC (Role-Based Access Control)** ร่วมกับ **ABAC (Attribute-Based Access Control)** โครงหลักคือ: 1. **authJwt()** → ตรวจสอบ JWT ใน header `Authorization: Bearer ...` 2. **loadPrincipalMw()** → โหลดข้อมูลผู้ใช้ + บทบาท + สิทธิ์ + ขอบเขตโปรเจ็ค/องค์กร 3. **requirePerm()** → ตรวจสอบ `perm_code` จากตาราง `permissions` และบังคับ ABAC (ORG/PROJECT scope) --- ## การยืนยันตัวตน (Authentication) ### Frontend ส่งอย่างไร ```http GET /api/projects Authorization: Bearer ``` - **ไม่มีการใช้ cookie** (Bearer-only) - ถ้า token หมดอายุ ให้ใช้ `refresh_token` ไปขอใหม่ที่ `/api/auth/refresh` ### Middleware `authJwt()` - อ่าน `Authorization: Bearer ...` - ตรวจสอบด้วย `JWT_SECRET` - เติม `req.auth = { user_id, username }` --- ## การโหลด Principal ### Middleware `loadPrincipalMw()` - ใช้ `user_id` ไป query DB: - users, roles, permissions, project_ids, org_ids - สร้าง `req.principal`: ```js { user_id, username, email, first_name, last_name, org_id, roles: [{ role_id, role_code, role_name }], permissions: Set, project_ids: [..], org_ids: [..], is_superadmin: true/false, // helper functions can(code), canAny(codes[]), canAll(codes[]), inProject(pid), inOrg(oid) } ``` --- ## การตรวจสอบสิทธิ์ (RBAC + ABAC) ### Middleware `requirePerm(permCode, { projectParam?, orgParam? })` 1. ตรวจว่า user มี `permCode` หรือเป็น superadmin 2. อ่าน `scope_level` จากตาราง `permissions` - `GLOBAL` → มีสิทธิ์ก็พอ - `ORG` → ต้องมีสิทธิ์ + อยู่ใน org scope - `PROJECT` → ต้องมีสิทธิ์ + อยู่ใน project scope 3. อ่าน `project_id` / `org_id` จาก request (`params`, `query`, `body`) 4. ถ้าไม่ผ่าน → คืน `403 FORBIDDEN` ### Error response ตัวอย่าง ```json { "error": "FORBIDDEN", "need": "projects.manage" } { "error": "FORBIDDEN_PROJECT", "project_id": 12 } { "error": "FORBIDDEN_ORG", "org_id": 5 } ``` --- ## รูปแบบที่แนะนำ ### List (PROJECT scope) ```js r.get("/", requirePerm("documents.view", { projectParam: "project_id" }), async (req, res) => { const P = req.principal; const { project_id } = req.query; const cond = [], params = []; if (!P.is_superadmin) { if (project_id) { if (!P.inProject(+project_id)) return res.status(403).json({ error: "FORBIDDEN_PROJECT" }); cond.push("project_id=?"); params.push(+project_id); } else if (P.project_ids?.length) { cond.push(`project_id IN (${P.project_ids.map(()=>"?").join(",")})`); params.push(...P.project_ids); } } const where = cond.length ? `WHERE ${cond.join(" AND ")}` : ""; const [rows] = await sql.query(`SELECT * FROM documents ${where} ORDER BY created_at DESC LIMIT 50`, params); res.json(rows); }); ``` ### Item (PROJECT scope) ```js r.delete("/:id", requirePerm("drawings.delete"), async (req, res) => { const id = +req.params.id; const [[row]] = await sql.query("SELECT project_id FROM drawings WHERE id=?", [id]); if (!row) return res.status(404).json({ error: "Not found" }); if (!req.principal.is_superadmin && !req.principal.inProject(row.project_id)) { return res.status(403).json({ error: "FORBIDDEN_PROJECT" }); } await sql.query("DELETE FROM drawings WHERE id=?", [id]); res.json({ ok: 1 }); }); ``` --- ## การแมปสิทธิ์ (perm_code) | หมวด | สิทธิ์ (perm_code) | scope | |--------------|---------------------------------------------|------------| | Organizations| `organizations.view`, `organizations.manage`| GLOBAL | | Projects | `projects.view`, `projects.manage` | ORG | | Drawings | `drawings.view`, `drawings.upload`, `drawings.delete` | PROJECT | | Documents | `documents.view`, `documents.manage` | PROJECT | | RFAs | `rfas.view`, `rfas.create`, `rfas.respond`, `rfas.delete` | PROJECT | | Correspondences | `corr.view`, `corr.manage` | PROJECT | | Transmittals | `transmittals.manage` | PROJECT | | Reports | `reports.view` | GLOBAL | | Settings | `settings.manage` | GLOBAL | | Admin | `admin.access` | ORG | --- ## Checklist สำหรับเพิ่ม Endpoint ใหม่ 1. เลือก `perm_code` ที่ตรงกับ seed 2. ใส่ `requirePerm("", { projectParam?: "...", orgParam?: "..." })` 3. ถ้าเป็น GET/PUT/DELETE record เดี่ยว → ตรวจสอบซ้ำด้วย `inProject`/`inOrg` 4. ใช้ `callProc("sp_name", [...])` ถ้า endpoint เรียก Stored Procedure 5. ฝั่ง FE ต้องส่ง `Authorization: Bearer ...` และ parameter `project_id`/`org_id` ที่จำเป็น --- ## Pitfalls ที่พบบ่อย - ลืมส่ง `project_id` ในคำขอ → 403 - อ้าง perm_code ผิด (เช่น `document.view` แทน `documents.view`) - ไม่กรอง project/org scope ใน query → ข้อมูลรั่ว - ลืมเช็ค item-level ABAC → ข้ามขอบเขตได้ - ปน cookie-auth เข้ามา → backend จะไม่รองรับแล้ว --- ## TL;DR - ทุกคำขอ → Bearer Token - `authJwt()` → ใส่ `req.auth` - `loadPrincipalMw()` → ใส่ `req.principal` (roles, perms, scope) - `requirePerm()` → บังคับ RBAC + ABAC อัตโนมัติ - เพิ่ม endpoint ใหม่ → ใช้ checklist ข้างบน