6.4 KiB
6.4 KiB
DMS Backend – คู่มือ Auth & RBAC/ABAC
ภาพรวมระบบ
Backend ใช้ Bearer Token สำหรับการยืนยันตัวตน และตรวจสอบสิทธิ์ด้วย RBAC (Role-Based Access Control) ร่วมกับ ABAC (Attribute-Based Access Control)
โครงหลักคือ:
- authJwt() → ตรวจสอบ JWT ใน header
Authorization: Bearer ... - loadPrincipalMw() → โหลดข้อมูลผู้ใช้ + บทบาท + สิทธิ์ + ขอบเขตโปรเจ็ค/องค์กร
- requirePerm() → ตรวจสอบ
perm_codeจากตารางpermissionsและบังคับ ABAC (ORG/PROJECT scope)
การยืนยันตัวตน (Authentication)
Frontend ส่งอย่างไร
GET /api/projects
Authorization: Bearer <access_token>
- ไม่มีการใช้ 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:
{
user_id, username, email, first_name, last_name, org_id,
roles: [{ role_id, role_code, role_name }],
permissions: Set<perm_code>,
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? })
- ตรวจว่า user มี
permCodeหรือเป็น superadmin - อ่าน
scope_levelจากตารางpermissionsGLOBAL→ มีสิทธิ์ก็พอORG→ ต้องมีสิทธิ์ + อยู่ใน org scopePROJECT→ ต้องมีสิทธิ์ + อยู่ใน project scope
- อ่าน
project_id/org_idจาก request (params,query,body) - ถ้าไม่ผ่าน → คืน
403 FORBIDDEN
Error response ตัวอย่าง
{ "error": "FORBIDDEN", "need": "projects.manage" }
{ "error": "FORBIDDEN_PROJECT", "project_id": 12 }
{ "error": "FORBIDDEN_ORG", "org_id": 5 }
รูปแบบที่แนะนำ
List (PROJECT scope)
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)
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 ใหม่
- เลือก
perm_codeที่ตรงกับ seed - ใส่
requirePerm("<perm>", { projectParam?: "...", orgParam?: "..." }) - ถ้าเป็น GET/PUT/DELETE record เดี่ยว → ตรวจสอบซ้ำด้วย
inProject/inOrg - ใช้
callProc("sp_name", [...])ถ้า endpoint เรียก Stored Procedure - ฝั่ง FE ต้องส่ง
Authorization: Bearer ...และ parameterproject_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.authloadPrincipalMw()→ ใส่req.principal(roles, perms, scope)requirePerm()→ บังคับ RBAC + ABAC อัตโนมัติ- เพิ่ม endpoint ใหม่ → ใช้ checklist ข้างบน