Files
lcbp3.np-dms.work/backend/README2.md

6.4 KiB
Raw Permalink Blame History

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 ส่งอย่างไร

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? })

  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 ตัวอย่าง

{ "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 ใหม่

  1. เลือก perm_code ที่ตรงกับ seed
  2. ใส่ requirePerm("<perm>", { 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 ข้างบน