برنامه نویسی

Permishout: بازآفرینی توییتر با کنترل دسترسی با استفاده از Permit.io

این یک ارسال برای مجوز است. چالش مجوز: مجوزها دوباره تعریف شده اند

اگر می خواهید بلافاصله راه اندازی کنید. دستورالعمل ها را در https://github.com/ansellmaximilian/permishout دنبال کنید

آنچه من ساختم

برای این چالش ، من تصمیم گرفتم Twitter/X را با کنترل دسترسی کامل کنم. به آن Permishout گفته می شود. جایی که a Shout مثل یک است Tweet یا پست من فکر کردم که این امر برای نشان دادن مجوزها مناسب است.

در اینجا گردش کار برنامه وجود دارد:

صفحه اصلی

صفحه اصلی

ورود به سیستم/ثبت نام

ورود به سیستم منشی

نمایه کامل

تکمیل مشخصات

فریاد ایجاد

شرح تصویر

شرح تصویر

به پروفایل نگاه کنید

شرح تصویر

دنبال کردن/ناپدید شدن

شرح تصویر

پاسخ به و حذف فریادها

شرح تصویر

شرح تصویر

همانطور که می بینید برخی از فریادها متفاوت از سایرین هستند. برخی از آنها یک دکمه حذف دارند – به این دلیل که Permit.io مشخص کرده است که کاربر فعلی مجوز انجام این کار را دارد.

برخی از دکمه های پاسخ غیرفعال هستند و برخی دیگر نیستند. این همچنین مجوز است. در اینجا شرایطی وجود دارد که برای کاربر تنظیم شده است تا بتواند پاسخ دهد:

  • اگر حالت پاسخ فریاد روی “همه” تنظیم شده است ، هر کسی می تواند پاسخ دهد
  • اگر حالت پاسخ فریاد روی “فقط حساب های تأیید شده” تنظیم شده است ، فقط “سرپرستان” می توانند پاسخ دهند
  • اگر حالت پاسخ فریاد به “افراد ذکر شده” تنظیم شده باشد ، فقط افراد ذکر شده قادر به پاسخگویی خواهند بود
  • هرکسی که کاربر دنبال می کند می تواند پاسخ دهد

نسخه آزمایشی

https://www.youtube.com/watch؟v=cmgdf1q73yo

repo پروژه

repo را اینجا ببینید

سفر من

باید بگویم یادگیری همه مباحث لازم در مورد Permit.io کار ساده ای نبود. این قطعاً قوی و پر از ویژگی هایی است که از یک کتابخانه کنترل دسترسی انتظار دارید.

اما می گوید که یکی از ناامید کننده ترین اما جذاب ترین بخش یادگیری در مورد ادغام مجوز است. در قسمت جلوی با استفاده از permit-fe-sdkبشر

من سرانجام آن را به کار خود کردم و قطعاً یکی از قسمت های مورد علاقه من در مجوز است. و من یک ارائه دهنده زمینه قابل استفاده مجدد سفارشی از این مورد برای استفاده برای پروژه های آینده با استفاده از Permit.io دریافت کردم. ارائه دهنده زمینه اجازه سفارشی

به طور کلی ، این اولین بار بود که من از یک سرویس کنترل دسترسی شخص ثالث و از آنچه من دیده ام بسیار قوی و کامل است.

در ذهن من آن را با سهولت کنترل/مجوز دسترسی در لاراول مقایسه می کنم. که عالی است! این یکی از اصلی ترین دلایلی است که من عاشق استفاده از لاراول هستم حتی اگر NextJS چارچوب مورد علاقه من باشد.

با استفاده از مجوز. برای مجوز

راهی که من قصد ساخت این بخش را دارم ، با طی کردن توسعه است که گویی من از ابتدا مجاز هستم. به شما اطلاع می دهد که هنگام توسعه با مجوز ، چه تصمیماتی را باید گرفته شود. به این ترتیب ، شما قادر خواهید بود هنگام تهیه برنامه های شخصی خود با Permit.io ، سفر خود را به تنهایی انجام دهید.

در اینجا دسته ای از مواردی وجود دارد که می خواهم آن را برجسته کنم:

  • برنامه ریزی و تنظیم
  • همگام سازی کاربران
  • انجام چک در پس زمینه
  • با استفاده از permit-fe-sdk برای نمایش آسان چیزها بر اساس کنترل دسترسی

برنامه ریزی و تنظیم

داستانهای کاربر

بنابراین ابتدا می خواهم داستانهای کاربر را که برای این برنامه می خواهم شناسایی کنم:

  • احراز هویت کاربر
  • کاربران معتبر دارند profile
  • همه کاربران معتبر قادر خواهند بود create هیچ shout و همچنین آن را مشاهده کنید
  • در shouter (پوستر/نویسنده) از shout قادر خواهد بود delete وت reply به آن
  • کاربران با نقش admin قادر خواهد بود delete هیچ shout
  • کاربران معتبر می توانند یکدیگر را دنبال و یا از بین ببرند.
  • در توییتر/X می توانید تنظیم کنید که کاربران قادر به پاسخگویی به توییت/پست شما هستند. من می خواهم این ویژگی را تقلید کنم ، زیرا بسیاری از قابلیت های موجود در مجوز را نشان می دهد. در اینجا نحوه برخورد توییتر/X آن را آورده است:
    1. همه: همه می توانند به یک پاسخ دهند shout
    2. مردم ذکر کردند: فقط مردم mentioned در الف shout قوطی replyبشر
    3. افرادی که دنبال می کنید: فقط افرادی که دنبال می کنید می توانند replyبشر با این حال برای نشان دادن “مشتقات نقش” ، من می خواهم آن را بسازم تا افرادی که دنبال می کنید همیشه به هر یک از شما پاسخ دهند shouts.
    4. فقط کاربران تأیید شده: برای مجاز ، این افراد با افراد همراه خواهند بود admin نقش ها

شناسایی مؤلفه ها

اکنون که داستانهای کاربر خود را داریم ، می توانیم مجوز را شروع کنیم. مؤلفه های مورد نیاز ما.

این چیزی است که من کردم:

  1. نقش ها:
  2. admin

  3. منابع:

  4. profile
    این منبع فقط یک نقطه لنگر بسیاری از کارکردهای من است که من قصد اجرای آن را دارم. قرار نیست هیچ ویژگی را در خود جای دهد ، اما به من کمک می کند تا پیروان/زیر و همچنین مشتقات نقش را شناسایی کنم. این بعداً توضیح داده خواهد شد.

  5. shout

  6. نقش نمونه منابع:
    فقط توضیحات کمی در مورد نقش های نمونه وجود دارد: مدیر می تواند نقش سطح بالایی باشد ، جایی که مجوزهای مربوط به آن برای هر نمونه از یک منبع اعمال می شود. بنابراین اگر یک مدیر داشته باشد delete دسترسی به a shout، آن را در هر یک خواهد داشت.

با این حال ، یک نقش نمونه فقط در مورد یک نمونه واحد از یک نوع منبع اعمال می شود. در اینجا چگونه اجازه می دهد. resource:resource_key#role، به معنای اقدامات موجود در اختیار صاحب نقش فقط برای منابع با کلید اعمال می شود resource_keyبشر

در اینجا نقشهایی که من شناسایی کرده ام آورده شده است:

  • profile#owner

    فقط صاحب یک پروفایل. درست مانند توییتر قبل از اینکه صدای جیر جیر داشته باشید ، برخی از داده ها را تکمیل کرده اید.

  • profile#follower
  • profile#followed
  • shout#shouter: قوطی reply وت delete یک فریاد
  • shout#replier: قوطی reply به shout
    این نقش مهمی خواهد بود. از آنجا که من می خواهم به افرادی که از آنها پیروی می کنید اجازه دهم این توانایی را داشته باشند replyبشر من این را از این نقش استخراج خواهم کردprofile#followedبشر
  • shout#mentioned

تنظیم کردن

مجموعه ای از روش های تنظیم این کار وجود دارد. در README.md در مورد repo من آن را کاملاً توضیح می دهد ، اما من در اینجا مختصر خواهم بود و فقط مواردی را توضیح می دهم که در ابتدا درک آن دشوار است. ما از این استفاده خواهیم کرد permit شی زیر به یاد داشته باشید که از این در قسمت جلوی استفاده نکنید.

import { Permit } from "permitio";
const permit = new Permit({
  token: process.env.PERMIT_SDK_KEY,
  pdp: process.env.PERMIT_PDP_URL,
  apiUrl: process.env.PERMIT_API_URL || "https://api.permit.io",
});

export default permit;
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در اینجا آمده است که چگونه می توانید به صورت برنامه ای ایجاد کنید admin نقش:

await permit.api.createRole({
    key: "admin",
    name: "Admin",
    permissions: ["shout:delete"],
  });
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

فقط یک یادداشت ، در اینجا نحو مجوزها وجود دارد: resource#actionبشر این اساساً می گوید ، “سلام ، نقش مدیر را ایجاد کنید ، که قادر به حذف فریادها خواهد بود. از آنجا که این یک نقش سطح بالا است ، در مورد همه موارد فریادها اعمال می شود.

در اینجا نحوه ایجاد یک shout منبع ، همراه با نقش های موجود در دسترس خود:

await permit.api.createResource({
    key: "shout",
    name: "shout",
    actions: {
      delete: {},
      reply: {},
    },
    // Resource roles are used to define the permissions for each role on the resource
    roles: {
      shouter: {
        name: "Shouter",
        permissions: ["delete", "reply"],
      },
      replier: {
        name: "Replier",
        permissions: ["read", "reply"],
      },
      mentioned: {
        name: "Mentioned",
        permissions: ["read", "reply"],
      },
    },
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

همانطور که می بینید من 3 نقش منحصر به فرد را در اینجا ایجاد کرده ام. حتی اگر آنها همان مجوزها را داشته باشند ، من تصمیم گرفتم که آنها را جدا کنم فقط در صورت نیاز به اضافه کردن اقدامات بیشتر. اساساً این کد می گوید “سلام ، من می خواهم منبعی به نام” فریاد “ایجاد کنم. این می تواند 2 عمل روی آن انجام شود: حذف و پاسخ دهید. یک کاربر می تواند یک جایگزین ، ذکر شده و یک شورت باشد. هر 3 نفر از آنها قادر به انجام تمام اقدامات موجود خواهند بود.

در اینجا نحوه ایجاد رابطه بین منابع آورده شده است. این بسیار مهم است مشتقات نقش بسیار قدرتمند هستند و روابط برای آن لازم است.

await permit.api.resourceRelations.create("shout", {
    key: "parent",
    name: "Parent",
    subject_resource: "profile",
  });
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

قطعه فوق اساساً می گوید ، “سلام ، رابطه ای برای فریاد ایجاد کنید. موضوع این رابطه مشخصات است. من می خواهم پروفایل ها والدین فریاد باشند.”

من در ابتدا از این موضوع گیج شدم ، بنابراین سعی می کنم فقط در مورد شما توضیح دهم. در اصل ، در ذهن خود تصور کنید که می خواهید چه ارتباطی بین دو منبع داشته باشید.

در اینجا ما داریم shout وت profileبشر ما می خواهیم این موارد را به گونه ای وصل کنیم که a profile موارد زیادی از shoutبشر بیایید با “پروفایل والدین فریادها” برویم.

subject_resource است name از first parameter of create

در بالا نحوه ترجمه آن به پارامترها است.

ناسازگار: name پارامتر می تواند هر چیزی باشد. حتی لازم نیست که واقعاً رابطه را توصیف کند (اما باید). بنابراین می توانستم قرار دهم owns به عنوان name پارامتر دقیقاً همان.

سرانجام ، ایجاد مشتقات منابع. من می خواهم هر کسی که از یک کاربر پیروی می کند بتواند به همه آنها پاسخ دهد shoutsبشر بنابراین ، بیایید مشتق شویم profile#followed به shout#replierبشر

ناسازگار: روابط برای روابط مشتق یکپارچه است. قانون فوق که می خواهم پیاده سازی کنم به این معنی است که کاربران دارای این کار هستند shout#replier نقش فقط در صورتی که داشته باشد profile#followed در یک پروفایل و اگر آن پروفایل والدین است shout در سوال

در اینجا نحوه ایجاد آن مشتق در کد آمده است:

await permit.api.resourceRoles.update("shout", "replier", {
    granted_to: {
      users_with_role: [
        {
          linked_by_relation: "parent",
          on_resource: "profile",
          role: "followed",
        },
      ],
    },
  });
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بسیار ساده این اساساً می گوید ، “سلام ، این نقش فریاد#جایگزین را که قبلاً ایجاد کرده اید به روز کنید و این نقش را با نقش زیر در یک پروفایل به کاربران اعطا کنید.

همگام سازی کاربران

بدیهی است که اجازه می دهد. احراز هویت شما را مدیریت نمی کند. شما به شخص ثالث خود یا یک شخص خود نیاز خواهید داشت. مثل منشی با این حال ، این بدان معنی نیست که می توانید از مدیریت کاربران در مجوز غفلت کنید.

permit.io به طور خودکار کاربران خود را از سیستم تأیید اعتبار شما نمی شناسد. بیایید به عنوان مثال از Clerk استفاده کنیم زیرا این کتابخانه ای است که من در برنامه خود استفاده می کنم.

بعد از اینکه کاربر خود را در منشی ایجاد کردید. شما همچنین باید آنها را به صورت مجوز ایجاد کنید. شما می توانید این کار را در منطق ورود به سیستم/ثبت نام خود انجام دهید ، یا می توانید یک فرآیند جداگانه را به طور کامل ایجاد کنید. شما می دانید که چگونه در توییتر/X ، پس از ثبت نام با ایمیل یا حساب Google ، هنوز هم باید مواردی مانند نام کاربری ، نام و غیره را پر کنید؟ من قصد دارم از این فرصت استفاده کنم تا کاربران خود را همگام سازی کنم.

بنابراین ، اگر یک کاربر با استفاده از منشی وارد سیستم شده است ، و ورود به مجوز وجود ندارد ، من قصد دارم آنها را مجبور کنم مشخصات خود را تکمیل کنند.

میان افزار در NextJs

export default clerkMiddleware(async (auth, req) => {
  const userId = (await auth()).userId;
  const { origin, pathname } = req.nextUrl;

  if (isProtectedRoute(req)) await auth.protect();

  // get project_id and environment_id
  const { project_id, environment_id } = await permitApi
    .get("/v2/api-key/scope")
    .then((res) => res.data);

  // check if user's profile is complete (based on their existence in Permit)
  let isProfileComplete = false;

  try {
    await permitApi.get(
      `/v2/facts/${project_id}/${environment_id}/users/${userId}`
    );
    isProfileComplete = true;
  } catch {
    isProfileComplete = false;
  }

  // if it's a public route, the user is signed in and the profile is complete, redirect to home
  if (isPublicRoute(req) && isProfileComplete)
    return NextResponse.redirect(`${origin}/home`);

  // if the user has not completed their profile and is trying to access a protected route, redirect to profile creation page
  if (
    !isProfileComplete &&
    isProtectedRoute(req) &&
    !pathname.startsWith("/profile/create")
  ) {
    return NextResponse.redirect(`${origin}/profile/create`);
  }
});
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در اینجا قطعه ای از میان افزار من وجود دارد. این در بیشتر صفحات مورد نظر شما در Permishout اجرا می شود. اساساً ، آنچه انجام می دهد این است:

  • با استفاده از شناسه کاربر در حال حاضر وارد سیستم شوید (await auth()).userId
  • اگر کاربر حتی به منشی ثبت نام یا وارد سیستم نشده باشد ، ما فقط می توانیم از مسیرهای محافظت شده خود محافظت کنیم await auth.protect()
  • اگر کاربر تأیید شده است (توسط منشی) ، ما بررسی می کنیم که آیا آنها دارای یک ورودی تطبیق در مجوز هستند. ما این کار را با استفاده از کد زیر بررسی می کنیم:
// note that `permitApi` is just an instance of `axios`
await permitApi.get(
      `/v2/facts/${project_id}/${environment_id}/users/${userId}`
    );
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • اگر این تماس API موفق شود ، این بدان معنی است که کاربر مطابق است user در مجوز. ما خوب هستیم کاربر می تواند به هر مسیرهای محافظت شده و غیر عمومی دسترسی پیدا کند.
  • اگر تماس API انجام نشود ، این بدان معنی است که کاربر هیچ تطبیق ندارد userبشر permit.io در مورد این کاربر نمی داند. اکنون ما کاربر را وادار می کنیم به /profile/create مسیر. جایی که من آنها را مجبور به ایجاد یک حساب می کنم.

اکنون پس از پر کردن اطلاعات ، عمدتاً نام کاربری ، نام ، کشور و سال متولد شده ، ما اداره می کنیم

همگام سازی کاربران

در پس زمینه یا یک کنترل کننده مسیر همانطور که از NextJS استفاده می کنیم.

const POST = async (request: NextRequest) => {
  const { userId } = getAuth(request) || "";
  const user = await clerkClient.users.getUser(userId || "");
  const { username, name, yearBorn, country } = await request.json();

  const { firstName, lastName } = splitName(name);
  await clerkClient.users.updateUser(userId || "", {
    firstName,
    lastName,
  });

  const { key: createdUser } = await permit.api.syncUser({
    key: userId || "",
    first_name: firstName,
    last_name: lastName,
    email: user?.emailAddresses[0].emailAddress || "",
    attributes: {
      username,
      yearBorn: parseInt(yearBorn),
      country,
    },
  });

  await permit.api.roleAssignments.assign({
    user: createdUser,
    role: "owner",
    resource_instance: `profile:profile_${createdUser}`,
    tenant: "default",
  });

  return NextResponse.json({
    success: true,
  });
};
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

من کمی توضیح می دهم آنچه در اینجا اتفاق می افتد:

  • اول ، ما کاربر معتبر در حال حاضر را از Clerk دریافت می کنیم (یادآوری کنید که این یک کاربر از Permit.io نیست – این همان چیزی است که ما در اینجا هستیم!)
  • سپس ما تمام ویژگی های اضافی را که می خواهیم در مجوز قرار دهیم دریافت می کنیم.
  • سپس-و اجازه. این کار را برای ما بسیار آسان می کند-ما این کار را می کنیم permit.api.syncUserبشر به یاد داشته باشید که با کلید کاربر برای permit.io مطابقت داشته باشید id از کاربر منشی شما شما لزوماً مجبور نیستید با آن مطابقت داشته باشید ، اما اگر این کار را نکنید ، بی نهایت سخت تر خواهد بود. به عنوان مثال ، اگر تصمیم دارید فقط از 10 کاراکتر اول منشی خود استفاده کنید id، شما باید هر بار که به کاربر مجوز خود مراجعه کنید ، 10 مورد اول را بدست آورید.

همچنین می توانید اضافه کنید emailبا first_nameوت last_nameبشر این همچنین فرصت شما برای افزودن ویژگی های اضافی خواهد بود.

توجه: من گفتم Permit.io این کار را بسیار آسان می کند زیرا لازم نیست بین یک کاربر موجود و یکی از آنها که ایجاد نشده است ، تمایز قائل شوید. syncUser رسیدگی خواهد کرد اگر وجود داشته باشد ، آن را بازنویسی می کند (آن را همگام سازی می کند) ، و اگر این کار را نکند ، آن را ایجاد می کند.

  • حالا ، از آنجا که من profile منابع تنظیم شده من همچنین می خواهم هر کاربر را با یک کاربر مرتبط کنم. بنابراین این جایی است که این خط برای:
await permit.api.roleAssignments.assign({
    user: createdUser,
    role: "owner",
    resource_instance: `profile:profile_${createdUser}`,
    tenant: "default",
  });
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

آنچه این کد دو برابر می کند. اول ، این یک نمونه منبع ایجاد می کند profile با کلید profile_${created_user_key}بشر نکته مهمی که باید بخاطر بسپارید این است resource_instance باید یک رشته در قالب باشد resource:instance_keyبشر

ثانیا ، این کد همچنین نقش را به کاربر اختصاص می دهد createdUser کلید و نقش را تعیین می کند owner به آن ( profile نمونه)

بنابراین ، اکنون ، این کاربر خاص به طور رسمی صاحب پروفایل خود و تمام کنترل دسترسی که با آن نقش نمونه در آن پروفایل خاص و همچنین هرگونه مشتق نقش همراه است ، است.

من فقط می خواستم این را ذکر کنم زیرا ایجاد تکلیف نقش و نمونه ایجاد شده در یک عملکرد واقعاً مرتب است!

انجام چک

نکته مهمی که باید به آن توجه داشته باشید این است که همیشه باید چک ها انجام شود ، همانطور که در شما نیاز به پرس و جو از آنها دارید. permit.io در مورد کد شما و آنچه شما انجام می دهید نمی داند ، آنها فقط نوشتن کد را برای بررسی آسان برای مجوزها انجام می دهند. در اینجا یک مثال بررسی شده است که من هر زمان که کاربر در حال حذف است ، انجام می دهم shoutبشر

const isUserAllowedToDelete = await permit.check(userId || "", "delete", {
      type: "shout",
      key: shout.key,
    });
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

واقعاً ساده نیست؟ به جای بررسی چندین سناریو که در آن به کاربر اجازه داده می شود یک فریاد خاص را حذف کند ، ما فقط از مجوز استفاده می کنیم. در اینجا ، باز خواهد گشت true اگر کاربر یک باشد admin یا اگر کاربر باشد owner از shoutبشر

این دو شرط ساده است. اما تصور کنید اگر چندین مورد منحصر به فرد دارید ، باید پیگیری کنید. تصور کنید که آیا کاربر می تواند یک مجری سطح پایین برای حذف باشد. شما باید آن چک را بنویسید و آنها را به تمام قسمت های کد خود که نیاز به چک دارند اضافه کنید.

به هر حال ، اگر isUserAllowedToDelete است ، true، ما با استفاده از این موارد ، نمونه را حذف می کنیم:

await permit.api.resourceInstances.delete(`shout:${shout.key}`);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

آسان!

با استفاده از permit-fe-sdk

این قسمت مورد علاقه من است. صادقانه می فهمید که چگونه کل کار بسیار عالی است! در اصل ، این کتابخانه به شما کمک می کند تا در قسمت جلویی تعیین کنید که آیا کاربر مجاز به انجام کاری است یا خیر.

مفهوم مشابهی در دستورالعمل های تیغه لاراول وجود دارد:

@can('update', $post)
  
@endcan
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

هنگامی که permit.io با CASL کار می کند ، می توانید به همان عملکرد دست یابید. اما این بار این همه در جبهه اتفاق می افتد – با خیال راحت!

در اینجا مراحل است. درک و فهمیدن آنها برای من واقعاً سخت بود ، بنابراین امیدوارم بتوانم آن را به خوبی توضیح دهم:

  • بسته های لازم را نصب کنید
npm install @casl/ability @casl/react permit-fe-sdk permitio
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • یک کنترل کننده مسیر پست ایجاد کنید که در آن permit-fe-sdk خواستار بررسی مجوز می شود.

بر اساس تحقیقات خودم ، اگر می خواهید مجوزهای فله ای دریافت کنید ، می خواهید مسیرهای پست ایجاد کنید. به این معنی ، شما به طور هم زمان مجوزهای زیادی را بررسی می کنید.

اینجا مال من است (من روی مجوزهای فله ای تمرکز خواهم کرد):

export async function POST(req: NextRequest) {
  try {
    const { searchParams } = req.nextUrl;
    const userId = searchParams.get("user");

    const { resourcesAndActions } = await req.json();

    if (!userId) {
      return NextResponse.json(
        { error: "No userId provided." },
        { status: 400 }
      );
    }

    const checkPermissions = async (resourceAndAction: {
      resource: string;
      action: string;
      userAttributes?: PermishoutUserAttributes;
      resourceAttributes?: Record<string, any>;
    }) => {
      const { resource, action, userAttributes, resourceAttributes } =
        resourceAndAction;

      const [resourceType, resourceKey] = resource.split(":");

      return permit.check(
        {
          key: userId,
          attributes: userAttributes,
        },
        action,
        {
          type: resourceType,
          key: resourceKey,
          attributes: resourceAttributes,
          tenant: "default",
        }
      );
    };

    const permittedList = await Promise.all(
      resourcesAndActions.map(checkPermissions)
    );

    return NextResponse.json({ permittedList }, { status: 200 });
  } catch (error) {
    console.error("Permission check error:", error);
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

به نظر من ذکر هدف نهایی این مسیر مفید است. این اساساً برای بازگشت یک شیء است permittedList به عنوان مجموعه ای از بول ها. اکنون به راحتی می توانید لیستی از 100 را برگردانید trueS و شما می خواهید همه چک را پشت سر بگذارید. اما این چیزی نیست که ما در اینجا درست هستیم؟

کمی بعد به جبهه خواهیم رسید ، اما اساساً می خواهیم نقشه برداری کنیم resourcesAndActions به نتایج اجازه. این متغیر مجموعه ای از منابع و اقدامات است که از جبهه پرس و جو شده است.

هر عنصر آرایه ای خواهد داشت resouceبا actionبا userAttributes وت resourceAttributesبشر شما می خواهید به طور دقیق برای این مجوزها آزمایش کنید.

مهم: توجه داشته باشید که من کلید منبع و منبع را تقسیم می کنم؟ این امر به این دلیل است که شما آنها را از جلوی این قالب تهیه می کنید resource:resource_keyبشر و شما آن را در این مسیر همانطور که هست دریافت می کنید ، بنابراین باید آن را به صورت دستی تقسیم کنید و سپس منبع را در آن قرار دهید type و کلید در keyبشر این برای مدتی من را گرفت.

خوب ، بر اساس تمام اقدامات و منابعی که انجام می دهیم permit.check بنابراین آرایه بولی را به ترتیب صحیح بازگرداند.

مهم: به یاد داشته باشید که سفارش بسیار مهم است. شما فقط نمی توانید به طور تصادفی انجام دهید permitionList.reverse()بشر جبهه دیگر در تعیین مجوزها دیگر دقیق نخواهد بود.

خوب ، بنابراین ما آن لیست از Booleans را برمی گردانیم. حالا چی؟

  • جلوی آن را تنظیم کنید تا بتوانید از آن مسیر استفاده کنید
const defaultActionResources: ActionResourceSchema[] = [
  {
    action: "delete",
    resource: "shout",
  },
];

export const AbilityContext = createContext<
  | {
      ability: Ability<AbilityTuple, MongoQuery>;
      setActionResources: React.Dispatch<
        React.SetStateAction<ActionResourceSchema[]>
      >;
    }
  | undefined
>(undefined);

export const useAbility = () => {
  const ability = useContext(AbilityContext);
  if (!ability) {
    throw new Error("useAbility must be used within an AbilityProvider");
  }
  return ability;
};

export const AbilityLoader = ({ children }: { children: ReactNode }) => {
  const { isSignedIn, user } = useUser();
  const [ability, setAbility] = useState<Ability<AbilityTuple, MongoQuery>>(
    new Ability()
  );
  const [actionResources, setActionResources] = useState<
    ActionResourceSchema[]
  >(defaultActionResources);

  useEffect(() => {
    (async () => {
      // reset it first
      setAbility(new Ability());

      if (isSignedIn) {
        const permit = Permit({
          loggedInUser: user.id,
          backendUrl: "/api/permit/check",
        });

        permit?.reset();

        const allActionResources = [
          ...actionResources,
          ...defaultActionResources,
        ];

        await permit.loadLocalStateBulk(allActionResources);

        const caslConfig = permitState.getCaslJson();

        const caslAbility =
          caslConfig && caslConfig.length
            ? new Ability(caslConfig)
            : new Ability();
        setAbility(caslAbility);
      }
    })();
  }, [isSignedIn, user, actionResources]);

  return (
    <AbilityContext.Provider value={{ ability, setActionResources }}>
      {children}
    </AbilityContext.Provider>
  );
};

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

اساساً ما می خواهیم یک Ability شیء در کل برنامه. من استفاده می کنم Context.Provider برای این

اقدامات و منابع از مسیر را به خاطر می آورید؟ این نقشه ها به طور مستقیم به موارد موجود در این ارائه دهنده زمینه می پردازد.

مهم: چک که شما قادر خواهید بود به طور مستقیم با آنچه تهیه می کنید مطابقت داشته باشد await permit.loadLocalStateBulk(allActionResources)بشر اگر آن را با یک جفت منبع و عمل تهیه کنید ، قادر به بررسی دقیق دیگران نخواهد بود.

من یک کشور محلی به نام ایجاد کرده ام actionResources، که باعث می شود useEffect برای به روزرسانی و بارگذاری دولت محلی با آخرین مجوز.

من مجوزهای پیش فرض دارم که همیشه می خواهم بررسی کنم: مجوزهای مدیر. به همین دلیل من منابع اقدام پیش فرض دارم.

مهم: حتماً آن را مجدداً تنظیم کنید. اگر فقط تماس بگیرم مشکل پیدا کردم loadLocalStateBulk بارها و بارها

همین است! فقط مطمئن شوید که ability متغیر با جدیدترین مجوزها. اکنون می توانید استفاده کنید useAbility برای به دست آوردن این توانایی و انجام چک ها.

  • در واقع با استفاده از آن در جلوی ، بیایید روی مجوزهای فریاد تمرکز کنیم. مدیر همیشه قادر خواهد بود delete shoutبنابراین بیایید آن را در آرایه پیش فرض قرار دهیم.

اکنون ، فریادها نیاز به کنترل ریز و درشت دارند زیرا نقش ها و مجوزهای منحصر به فرد برای نمونه های منفرد وجود دارد shoutبشر

در اینجا نحوه برخورد با آن آمده است. به یاد داشته باشید که من در معرض دیدم setActionResources در آن ارائه دهنده و این که اگر آن آرایه به روز شود ، مجوزهای محلی را به روز می کند. ما این همان کاری است که من می خواهم در آن انجام دهم home صفحه

const { setActionResources } = useAbility();


useEffect(() => {
    const shoutActions: ActionResourceSchema[] = shouts.flatMap((shout) => 
    [
      { action: "reply", resource: `shout:${shout.key}` },
      { action: "delete", resource: `shout:${shout.key}` },
    ]);

    setActionResources(shoutActions);
  }, [shouts, setActionResources]);

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این است با این کار تمام مجوزهای ما در جبهه به روز می شود تا بتوانیم از آنها استفاده کنیم. در هر shout من می خواهم یک دکمه حذف برای کاربرانی که اجازه دارند داشته باشم. من می توانم از Can مؤلفه CASL:

import { Can } from "@casl/react";

<Can I="delete" a={`shout:${shout.key}`} ability={ability}>
 <Button
  style={{ padding: 0 }}
  variant="ghost"
  className="flex items-center gap-1 text-red-400 hover:text-red-500 ml-auto"
  onClick={(e) => {
   e.stopPropagation();
   if (setShoutToDeleteKey) setShoutToDeleteKey(shout.key);
  }}
 >
   <Trash size={16} />
   <span>Delete</span>
  </Button>
</Can>
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این است این تمام کاری است که من باید انجام دهم. فقط آن را با عمل و منبع و همچنین تأمین کنید ability شیء که ذکر کردم

همچنین می توانید یک چک خطی استاندارد را مانند این انجام دهید:

import { permitState } from "permit-fe-sdk";
const canReply =
    shout.replyMode === ShoutReplyType.EVERYONE ||
    permitState?.check("reply", `shout:${shout.key}`, {});
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پایان

با عرض پوزش برای مماس من بسیار هیجان زده شدم که این کار را به خودی خود انجام دهم ، به خصوص جلوی SDK. امیدوارم که آنها روی آن کار کنند زیرا من عاشق دستورالعمل های لاراول تیغه هستم. و این دقیقاً همان است React!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا