สารบัญ

ต่อระบบ

สิทธิ์ระดับรายการ (resource)

เจ้าของ ทีม การแชร์ต่อรายการ — Pi Auth เก็บและตรวจ แอปไม่ต้องทำหน้าทีม/แชร์เอง

สิทธิ์ระดับรายการ (เช่น โปรเจกต์ เอกสาร) — ใครเป็นเจ้าของ ทีมไหนเข้าได้ แชร์ให้ใคร — ให้ Pi Auth เป็นคนเก็บและตรวจ แอปไม่ต้องสร้างระบบทีม คำเชิญ และหน้าแชร์เอง

Pi Auth เก็บแค่ (องค์กร, แอป, ชนิด, externalId) เจ้าของ ทีม สมาชิก คำเชิญ และการแชร์ ไม่เก็บไฟล์หรือข้อมูลธุรกิจของแอป ต้องใช้ @suphark/auth-client ≥ 0.4.0

ประกาศ resource

ประกาศ resources ใน definePermissions() ครั้งเดียว (ดู Permission manifest) ระบุ action และสิ่งที่ owner, ทีม, การแชร์ และ permission ของแอปได้รับ

ใช้ในโค้ด

ตัวอย่าง
const projects = piAuth.resource(P.resources.project);

// สร้าง: ลงทะเบียน PENDING → สร้างแถวในฐานแอป → ACTIVE (fn ล้มเหลว SDK ยกเลิก PENDING ให้)
const project = await projects.create(id, { displayName: title }, () =>
  prisma.project.create({ data: { id, tenantId, title } }),
);
await projects.require(projectId, "edit"); // 404 ถ้าไม่มีสิทธิ์

// รายการ: แอป query/sort เองเป็นช่วง แล้วกรองแบบชุด (คงลำดับ) จนได้ครบหน้า
const candidates = await prisma.project.findMany({
  where: { tenantId, deletedAt: null }, orderBy: { updatedAt: "desc" }, take: 200,
});
const allowed = await projects.filterAllowed(candidates.map((p) => p.id), "read");
const actions = await projects.allowedActions(allowed, ["edit", "manage"]);

await projects.trash(projectId); // ย้ายลงถัง (สิทธิ์ purge) — restore() คืน
const manageUrl = await projects.manageUrl(projectId, `/projects/${projectId}`);
  • SDK ไม่ผูกกับ ORM: แอปเป็นเจ้าของ query/sort/page แล้วส่ง ID ไปตรวจครั้งละ 100 รายการ (พร้อมกันไม่เกิน 4 คำขอ) — ดึง candidate ทีละช่วงและหยุดเมื่อได้ครบหน้า ไม่ต้องตรวจทั้งองค์กร
  • ติดต่อ Pi Auth ไม่ได้ → SDK ส่ง AUTH_UNAVAILABLE (503)
  • คำสั่งเปลี่ยนสถานะ (create/activate/delete/trash/restore/pending) ต้องมี client secret ของแอปฝั่ง server เพิ่มจาก token ผู้ใช้

ทีม (หน้าที่ Pi Auth ทำให้)

เมนู "ทีม" ในแอปให้ redirect ไป await piAuth.resources.teamsUrl(returnPath) ลิงก์ไปทีมเดียวใช้ teamUrl(teamId, returnPath) — ไม่มี sidebar มีปุ่มกลับแอป สมาชิกที่เข้าแอปได้สร้างทีมได้ อีเมลของสมาชิก active เข้าทีมทันที อีเมลอื่นได้คำเชิญ 7 วัน การผูกทีมกับรายการทำในหน้าจัดการของรายการ (manageUrl) ป้ายทีมในรายการใช้ resource.teamsOf(ids) (คืน null เมื่อไม่มีทีมหรืออ่านรายการไม่ได้)

เปิดเป็น dialog ในแอป

ส่ง { embed: true } ให้ teamsUrl/teamUrl/manageUrl แล้วเปิดใน iframe หน้าจะมีปุ่ม "ปิด" ที่ส่ง postMessage({ type: "pi-auth:close" }) แอปรับเฉพาะ message จาก origin ของ issuer แล้วปิด dialog และ refresh ข้อมูล

เงื่อนไขรายละเอียด
origin ของแอปต้องอยู่ในรายการ embed origin ของ Pi Auth (ตอนนี้ https://*.suphark.com)
same-siteแอปต้อง same-site กับ Pi Auth เพราะ cookie เป็น SameSite=Lax
CSPCSP ของแอปต้องมี frame-src <issuer>

ควรมีลิงก์ "เปิดในแท็บใหม่" (URL เดียวกันไม่มี embed) เผื่อ iframe ใช้ไม่ได้

เลือกองค์กรใน dialog: piAuth.resources.selectTenantUrl({ embed: true, next }) เปิด /select-tenant ใน iframe — เลือกแล้วหน้าไม่ redirect ในเฟรม แต่ส่ง postMessage({ type: "pi-auth:select-tenant", tenant: slug }) ไปที่ origin ของ redirect URI ที่ลงทะเบียนเท่านั้น จากนั้นแอปเข้า /auth/login?tenant=<slug> ที่ระดับบนเอง

เจ้าของรายการ

Pi Auth เป็นแหล่งจริงของเจ้าของ (โอนได้จากหน้าจัดการของรายการ หรือผู้ดูแลองค์กรโอนจากเมนู "ทีมและเจ้าของ" ของแอปในคอนโซล) ค่าเจ้าของในฐานของแอปเป็นแค่สำเนา:

  • resource.describe(ids) → { ownerId, team } ของแต่ละรายการ
  • resource.ownedIds() → ID ทั้งหมดที่ผู้ใช้ปัจจุบันเป็นเจ้าของ (ใช้คิดโควตา)

วงจรชีวิตและการคืนสภาพ

  • PENDING ไม่ผ่านการตรวจสิทธิ์ ถ้าเปิดใช้ล้มเหลวหลังฐานแอปสร้างแถวแล้ว ให้เรียก projects.reconcilePending(async (ids) => idsที่ยังมีในฐานแอป) — SDK เปิดใช้เฉพาะ ID ที่คืนมา
  • ถังขยะ: trash()/restore() ด้วย token ผู้ใช้ — Pi Auth เปลี่ยนรายการที่อยู่ในถังเกิน 180 วันเป็น DELETED เอง ระยะถังของแอปจึงต้องสั้นกว่า 180 วัน (cron ล้างถาวรของแอปไม่ต้องเรียก Pi Auth)
  • ลบถาวรทันทีใช้ remove(id, fn)
  • นำเข้าข้อมูลเดิม: piAuth.resources.importExisting([{ type, externalId, ownerId, displayName?, shares? }]) ครั้งละ ≤ 100 รายการ ใช้ token ผู้ดูแลองค์กรและ client secret เรียกซ้ำได้ไม่สร้างซ้ำ — owner ต้องเคยเป็นสมาชิก และผู้รับ share ต้องเป็นสมาชิก active