Firebase নিরাপত্তা নিয়মের মাধ্যমে আপনার Firestore ডেটা সুরক্ষিত করুন

1. আপনি শুরু করার আগে

ক্লাউড ফায়ারস্টোর, ফায়ারবেসের জন্য ক্লাউড স্টোরেজ, এবং রিয়েলটাইম ডেটাবেস আপনার লেখা কনফিগারেশন ফাইলগুলির উপর নির্ভর করে যা পড়ার এবং লেখার অ্যাক্সেস দেওয়ার জন্য। সেই কনফিগারেশন, যাকে বলা হয় সিকিউরিটি রুলস, আপনার অ্যাপের জন্য এক ধরনের স্কিমা হিসেবেও কাজ করতে পারে। এটি আপনার অ্যাপ্লিকেশন বিকাশের সবচেয়ে গুরুত্বপূর্ণ অংশগুলির মধ্যে একটি। এবং এই কোডল্যাব আপনাকে এর মধ্য দিয়ে হেঁটে যাবে।

পূর্বশর্ত

  • একটি সাধারণ সম্পাদক যেমন ভিজ্যুয়াল স্টুডিও কোড, এটম বা সাবলাইম টেক্সট
  • Node.js 8.6.0 বা উচ্চতর (Node.js ইনস্টল করতে, nvm ব্যবহার করুন ; আপনার সংস্করণ পরীক্ষা করতে, node --version চালান)
  • Java 7 বা উচ্চতর (জাভা ইনস্টল করতে এই নির্দেশাবলী ব্যবহার করুন ; আপনার সংস্করণ পরীক্ষা করতে, java -version চালান)

আপনি কি করবেন

এই কোডল্যাবে, আপনি Firestore-এ নির্মিত একটি সাধারণ ব্লগ প্ল্যাটফর্ম সুরক্ষিত করবেন। নিরাপত্তা বিধির বিরুদ্ধে ইউনিট পরীক্ষা চালানোর জন্য আপনি Firestore এমুলেটর ব্যবহার করবেন, এবং নিশ্চিত করুন যে নিয়মগুলি আপনার প্রত্যাশিত অ্যাক্সেসের অনুমতি দেয় এবং না দেয়।

আপনি কিভাবে শিখবেন:

  • দানাদার অনুমতি দিন
  • তথ্য প্রয়োগ এবং বৈধতা টাইপ
  • অ্যাট্রিবিউট ভিত্তিক অ্যাক্সেস কন্ট্রোল প্রয়োগ করুন
  • প্রমাণীকরণ পদ্ধতির উপর ভিত্তি করে অ্যাক্সেস মঞ্জুর করুন
  • কাস্টম ফাংশন তৈরি করুন
  • সময় ভিত্তিক নিরাপত্তা নিয়ম তৈরি করুন
  • একটি অস্বীকার তালিকা এবং নরম মুছে ফেলার প্রয়োগ করুন
  • একাধিক অ্যাক্সেস প্যাটার্ন পূরণ করতে কখন ডেটাকে অস্বাভাবিক করতে হবে তা বুঝুন

2. সেট আপ করুন

এটি একটি ব্লগিং অ্যাপ্লিকেশন। এখানে অ্যাপ্লিকেশন কার্যকারিতার একটি উচ্চ স্তরের সারসংক্ষেপ রয়েছে:

খসড়া ব্লগ পোস্ট:

  • ব্যবহারকারীরা খসড়া ব্লগ পোস্ট তৈরি করতে পারে, যা drafts সংগ্রহে থাকে।
  • প্রকাশিত হওয়ার জন্য প্রস্তুত না হওয়া পর্যন্ত লেখক একটি খসড়া আপডেট করা চালিয়ে যেতে পারেন।
  • এটি প্রকাশিত হওয়ার জন্য প্রস্তুত হলে, একটি Firebase ফাংশন ট্রিগার হয় যা published সংগ্রহে একটি নতুন নথি তৈরি করে।
  • খসড়া লেখক বা সাইট মডারেটর দ্বারা মুছে ফেলা যেতে পারে

প্রকাশিত ব্লগ পোস্ট:

  • প্রকাশিত পোস্টগুলি ব্যবহারকারীদের দ্বারা তৈরি করা যায় না, শুধুমাত্র একটি ফাংশনের মাধ্যমে৷
  • এগুলিকে শুধুমাত্র সফ্ট-মুছে ফেলা যেতে পারে, যা একটি visible বৈশিষ্ট্যকে মিথ্যাতে আপডেট করে৷

মন্তব্য

  • প্রকাশিত পোস্টগুলি মন্তব্যের অনুমতি দেয়, যা প্রতিটি প্রকাশিত পোস্টে একটি উপ-সংগ্রহ।
  • অপব্যবহার কমাতে, ব্যবহারকারীদের অবশ্যই একটি যাচাইকৃত ইমেল ঠিকানা থাকতে হবে এবং একটি মন্তব্য করার জন্য অস্বীকারকারীতে থাকবেন না।
  • মন্তব্য পোস্ট হওয়ার এক ঘণ্টার মধ্যেই আপডেট করা যাবে।
  • মন্তব্যগুলি মন্তব্য লেখক, মূল পোস্টের লেখক বা মডারেটর দ্বারা মুছে ফেলা যেতে পারে।

অ্যাক্সেসের নিয়মগুলি ছাড়াও, আপনি সুরক্ষা নিয়ম তৈরি করবেন যা প্রয়োজনীয় ক্ষেত্র এবং ডেটা বৈধতা প্রয়োগ করে৷

ফায়ারবেস এমুলেটর স্যুট ব্যবহার করে সবকিছু স্থানীয়ভাবে ঘটবে।

সোর্স কোড পান

এই কোডল্যাবে, আপনি নিরাপত্তা বিধিগুলির জন্য পরীক্ষা দিয়ে শুরু করবেন, তবে নিরাপত্তা বিধিগুলি নিজেরাই কম, তাই আপনাকে প্রথমে যা করতে হবে তা হল পরীক্ষা চালানোর জন্য উত্সটি ক্লোন করুন:

$ git clone https://github.com/FirebaseExtended/codelab-rules.git

তারপরে প্রারম্ভিক-স্টেট ডিরেক্টরিতে যান, যেখানে আপনি এই কোডল্যাবের অবশিষ্টাংশের জন্য কাজ করবেন:

$ cd codelab-rules/initial-state

এখন, নির্ভরতাগুলি ইনস্টল করুন যাতে আপনি পরীক্ষা চালাতে পারেন। আপনি যদি ধীর গতির ইন্টারনেট সংযোগে থাকেন তবে এটি এক বা দুই মিনিট সময় নিতে পারে:

# Move into the functions directory, install dependencies, jump out.
$ cd functions && npm install && cd -

Firebase CLI পান

আপনি পরীক্ষা চালানোর জন্য যে এমুলেটর স্যুটটি ব্যবহার করবেন সেটি Firebase CLI (কমান্ড-লাইন ইন্টারফেস) এর অংশ যা আপনার মেশিনে নিম্নলিখিত কমান্ডের মাধ্যমে ইনস্টল করা যেতে পারে:

$ npm install -g firebase-tools

এরপর, নিশ্চিত করুন যে আপনার কাছে CLI এর সর্বশেষ সংস্করণ রয়েছে। এই কোডল্যাবটি 8.4.0 বা উচ্চতর সংস্করণের সাথে কাজ করা উচিত তবে পরবর্তী সংস্করণগুলিতে আরও বাগ সংশোধন অন্তর্ভুক্ত রয়েছে।

$ firebase --version
9.10.2

3. পরীক্ষা চালান

এই বিভাগে, আপনি স্থানীয়ভাবে পরীক্ষা চালাবেন। এর মানে হল এমুলেটর স্যুট বুট করার সময়।

এমুলেটরগুলি শুরু করুন

আপনি যে অ্যাপ্লিকেশনটির সাথে কাজ করবেন তাতে তিনটি প্রধান ফায়ারস্টোর সংগ্রহ রয়েছে: drafts ব্লগ পোস্টগুলি রয়েছে যা চলছে, published সংগ্রহে প্রকাশিত ব্লগ পোস্টগুলি রয়েছে এবং comments প্রকাশিত পোস্টগুলির একটি উপসংগ্রহ৷ রেপো নিরাপত্তা বিধিগুলির জন্য ইউনিট পরীক্ষার সাথে আসে যা ব্যবহারকারীর বৈশিষ্ট্য এবং অন্যান্য শর্তগুলিকে সংজ্ঞায়িত করে যা একজন ব্যবহারকারীর drafts , published এবং comments সংগ্রহে নথি তৈরি, পড়তে, আপডেট করতে এবং মুছে ফেলার জন্য প্রয়োজনীয়। এই পরীক্ষাগুলি পাস করার জন্য আপনি নিরাপত্তা নিয়ম লিখবেন।

শুরু করার জন্য, আপনার ডাটাবেস লক করা হয়েছে: ডাটাবেসে পড়া এবং লেখা সর্বজনীনভাবে অস্বীকার করা হয়েছে, এবং সমস্ত পরীক্ষা ব্যর্থ হয়েছে। আপনি নিরাপত্তা নিয়ম লিখুন, পরীক্ষা পাস হবে. পরীক্ষাগুলি দেখতে, আপনার সম্পাদকে functions/test.js খুলুন।

কমান্ড লাইনে, emulators emulators:exec ব্যবহার করে এমুলেটরগুলি শুরু করুন এবং পরীক্ষাগুলি চালান:

$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test"

আউটপুটের শীর্ষে স্ক্রোল করুন:

$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test"
i  emulators: Starting emulators: functions, firestore, hosting
⚠  functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: auth, database, pubsub
⚠  functions: Unable to fetch project Admin SDK configuration, Admin SDK behavior in Cloud Functions emulator may be incorrect.
i  firestore: Importing data from /Users/user/src/firebase/rules-codelab/initial-state/.seed/firestore_export/firestore_export.overall_export_metadata
i  firestore: Firestore Emulator logging to firestore-debug.log
⚠  hosting: Authentication error when trying to fetch your current web app configuration, have you run firebase login?
⚠  hosting: Could not fetch web app configuration and there is no cached configuration on this machine. Check your internet connection and make sure you are authenticated. To continue, you must call firebase.initializeApp({...}) in your code before using Firebase.
i  hosting: Serving hosting files from: public
✔  hosting: Local server: http://localhost:5000
i  functions: Watching "/Users/user/src/firebase/rules-codelab/initial-state/functions" for Cloud Functions...
✔  functions[publishPost]: http function initialized (http://localhost:5001/codelab/us-central1/publishPost).
✔  functions[softDelete]: http function initialized (http://localhost:5001/codelab/us-central1/softDelete).
i  Running script: pushd functions; npm test
~/src/firebase/rules-codelab/initial-state/functions ~/src/firebase/rules-codelab/initial-state

> functions@ test /Users/user/src/firebase/rules-codelab/initial-state/functions
> mocha

(node:76619) ExperimentalWarning: Conditional exports is an experimental feature. This feature could change at any time


  Draft blog posts
    1) can be created with required fields by the author
    2) can be updated by author if immutable fields are unchanged
    3) can be read by the author and moderator

  Published blog posts
    4) can be read by everyone; created or deleted by no one
    5) can be updated by author or moderator

  Comments on published blog posts
    6) can be read by anyone with a permanent account
    7) can be created if email is verfied and not blocked
    8) can be updated by author for 1 hour after creation
    9) can be deleted by an author or moderator


  0 passing (848ms)
  9 failing

...

এই মুহূর্তে 9টি ব্যর্থতা রয়েছে। আপনি নিয়ম ফাইল তৈরি করার সাথে সাথে, আপনি আরও পরীক্ষা পাস দেখে অগ্রগতি পরিমাপ করতে পারেন।

4. ব্লগ পোস্টের খসড়া তৈরি করুন।

যেহেতু খসড়া ব্লগ পোস্টের অ্যাক্সেস প্রকাশিত ব্লগ পোস্টের অ্যাক্সেস থেকে এতটাই আলাদা, এই ব্লগিং অ্যাপ খসড়া ব্লগ পোস্টগুলিকে একটি পৃথক সংগ্রহে সঞ্চয় করে, /drafts । খসড়া শুধুমাত্র লেখক বা একজন মডারেটর দ্বারা অ্যাক্সেস করা যেতে পারে, এবং প্রয়োজনীয় এবং অপরিবর্তনীয় ক্ষেত্রের জন্য বৈধতা আছে।

firestore.rules ফাইলটি খুললে, আপনি একটি ডিফল্ট নিয়ম ফাইল পাবেন:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

ম্যাচ স্টেটমেন্ট, match /{document=**} , উপসংগ্রহের সমস্ত নথিতে পুনরাবৃত্তিমূলকভাবে প্রয়োগ করতে ** সিনট্যাক্স ব্যবহার করছে। এবং যেহেতু এটি শীর্ষ স্তরে রয়েছে, এই মুহূর্তে একই কম্বল নিয়ম সমস্ত অনুরোধের ক্ষেত্রে প্রযোজ্য, কে অনুরোধ করছে বা তারা কোন ডেটা পড়ার বা লেখার চেষ্টা করছে তা বিবেচনা না করে।

সবচেয়ে অভ্যন্তরীণ-ম্যাচ স্টেটমেন্টটি সরিয়ে দিয়ে শুরু করুন match /drafts/{draftID} দিয়ে প্রতিস্থাপন করুন। (নথির কাঠামোর মন্তব্যগুলি নিয়মগুলিতে সহায়ক হতে পারে এবং এই কোডল্যাবে অন্তর্ভুক্ত করা হবে; সেগুলি সর্বদা ঐচ্ছিক৷)

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /drafts/{draftID} {
      // `authorUID`: string, required
      // `content`: string, optional
      // `createdAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, optional
    }
  }
}

ড্রাফ্টের জন্য আপনি যে প্রথম নিয়মটি লিখবেন তা নিয়ন্ত্রণ করবে কে নথি তৈরি করতে পারবে। এই অ্যাপ্লিকেশনে, ড্রাফ্টগুলি শুধুমাত্র লেখক হিসাবে তালিকাভুক্ত ব্যক্তি দ্বারা তৈরি করা যেতে পারে। যে ব্যক্তি অনুরোধ করছেন তার UID নথিতে তালিকাভুক্ত একই UID কিনা তা পরীক্ষা করুন।

তৈরির জন্য প্রথম শর্ত হবে:

request.resource.data.authorUID == request.auth.uid

এরপরে, নথিগুলি শুধুমাত্র তখনই তৈরি করা যেতে পারে যদি তারা তিনটি প্রয়োজনীয় ক্ষেত্র, authorUID , createdAt এবং title অন্তর্ভুক্ত করে। (ব্যবহারকারী createdAt ক্ষেত্র সরবরাহ করে না; এটি প্রয়োগ করছে যে অ্যাপটিকে অবশ্যই একটি নথি তৈরি করার চেষ্টা করার আগে এটি যোগ করতে হবে।) যেহেতু আপনাকে কেবলমাত্র বৈশিষ্ট্যগুলি তৈরি করা হচ্ছে কিনা তা পরীক্ষা করতে হবে, আপনি যে request.resource সব আছে তা পরীক্ষা করতে পারেন। ঐ চাবিগুলো:

request.resource.data.keys().hasAll([
  "authorUID",
  "createdAt",
  "title"
])

একটি ব্লগ পোস্ট তৈরির জন্য চূড়ান্ত প্রয়োজনীয়তা হল শিরোনামটি 50 অক্ষরের বেশি লম্বা হতে পারে না:

request.resource.data.title.size() < 50

যেহেতু এই সমস্ত শর্ত অবশ্যই সত্য হতে হবে, এইগুলিকে লজিক্যাল এবং অপারেটর, && এর সাথে একত্রিত করুন। প্রথম নিয়ম হয়ে যায়:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /drafts/{draftID} {
      // `authorUID`: string, required
      // `content`: string, optional
      // `createdAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, optional

      allow create: if
        // User creating document is draft author
        request.auth.uid == request.resource.data.authorUID &&
        // Must include title, author, and url fields
        request.resource.data.keys().hasAll([
          "authorUID",
          "createdAt",
          "title"
        ]) &&
        // Title must be < 50 characters long
        request.resource.data.title.size() < 50;
    }
  }
}

টার্মিনালে, পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে প্রথম পরীক্ষাটি পাস হয়েছে।

5. ব্লগ পোস্টের খসড়া আপডেট করুন।

পরবর্তীতে, লেখকরা তাদের খসড়া ব্লগ পোস্টগুলিকে পরিমার্জন করার সাথে সাথে, তারা খসড়া নথিগুলি সম্পাদনা করবে৷ যখন একটি পোস্ট আপডেট করা যেতে পারে তখন শর্তগুলির জন্য একটি নিয়ম তৈরি করুন৷ প্রথমত, শুধুমাত্র লেখক তাদের খসড়া আপডেট করতে পারেন। মনে রাখবেন যে এখানে আপনি ইতিমধ্যেই লেখা UID চেক করুন, resource.data.authorUID :

resource.data.authorUID == request.auth.uid

একটি আপডেটের জন্য দ্বিতীয় প্রয়োজনীয়তা হল যে দুটি বৈশিষ্ট্য, authorUID এবং createdAt পরিবর্তন করা উচিত নয়:

request.resource.data.diff(resource.data).unchangedKeys().hasAll([
    "authorUID",
    "createdAt"
]);

এবং অবশেষে, শিরোনামটি 50 অক্ষর বা তার কম হওয়া উচিত:

request.resource.data.title.size() < 50;

যেহেতু এই সমস্ত শর্তগুলি পূরণ করা প্রয়োজন, সেগুলিকে && এর সাথে একত্রিত করুন:

allow update: if
  // User is the author, and
  resource.data.authorUID == request.auth.uid &&
  // `authorUID` and `createdAt` are unchanged
  request.resource.data.diff(resource.data).unchangedKeys().hasAll([
    "authorUID",
    "createdAt"
  ]) &&
  // Title must be < 50 characters long
  request.resource.data.title.size() < 50;

সম্পূর্ণ নিয়ম হয়ে যায়:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /drafts/{draftID} {
      // `authorUID`: string, required
      // `content`: string, optional
      // `createdAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, optional

      allow create: if
        // User creating document is draft author
        request.auth.uid == request.resource.data.authorUID &&
        // Must include title, author, and url fields
        request.resource.data.keys().hasAll([
          "authorUID",
          "createdAt",
          "title"
        ]) &&
        // Title must be < 50 characters long
        request.resource.data.title.size() < 50;

      allow update: if
        // User is the author, and
        resource.data.authorUID == request.auth.uid &&
        // `authorUID` and `createdAt` are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "createdAt"
        ]) &&
        // Title must be < 50 characters long
        request.resource.data.title.size() < 50;
    }
  }
}

আপনার পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে অন্য পরীক্ষা পাস হয়েছে।

6. খসড়া মুছুন এবং পড়ুন: অ্যাট্রিবিউট ভিত্তিক অ্যাক্সেস কন্ট্রোল

লেখক যেমন খসড়া তৈরি এবং আপডেট করতে পারেন, তেমনি তারা খসড়া মুছতেও পারেন।

resource.data.authorUID == request.auth.uid

উপরন্তু, তাদের প্রমাণীকরণ টোকেনে একটি isModerator বৈশিষ্ট্য সহ লেখকদের খসড়া মুছে ফেলার অনুমতি দেওয়া হয়:

request.auth.token.isModerator == true

যেহেতু এই শর্তগুলির যেকোন একটি মুছে ফেলার জন্য যথেষ্ট, সেগুলিকে একটি যৌক্তিক বা অপারেটরের সাথে সংযুক্ত করুন, || :

allow delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true

একই শর্ত পড়ার ক্ষেত্রে প্রযোজ্য, যাতে নিয়মে অনুমতি যোগ করা যায়:

allow read, delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true

সম্পূর্ণ নিয়ম এখন:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /drafts/{draftID} {
      // `authorUID`: string, required
      // `content`: string, optional
      // `createdAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, optional

      allow create: if
        // User creating document is draft author
        request.auth.uid == request.resource.data.authorUID &&
        // Must include title, author, and url fields
        request.resource.data.keys().hasAll([
          "authorUID",
          "createdAt",
          "title"
        ]) &&
        // Title must be < 50 characters long
        request.resource.data.title.size() < 50;

      allow update: if
        // User is the author, and
        resource.data.authorUID == request.auth.uid &&
        // `authorUID` and `createdAt` are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "createdAt"
        ]) &&
        // Title must be < 50 characters long
        request.resource.data.title.size() < 50;

      allow read, delete: if
        // User is draft author
        resource.data.authorUID == request.auth.uid ||
        // User is a moderator
        request.auth.token.isModerator == true;
    }
  }
}

আপনার পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে অন্য একটি পরীক্ষা এখন পাস করেছে।

7. প্রকাশিত পোস্টের জন্য পড়ে, তৈরি করে এবং মুছে দেয়: বিভিন্ন অ্যাক্সেস প্যাটার্নের জন্য অস্বাভাবিককরণ

যেহেতু প্রকাশিত পোস্ট এবং ড্রাফ্ট পোস্টগুলির অ্যাক্সেসের ধরণগুলি এতই আলাদা, এই অ্যাপটি পোস্টগুলিকে আলাদা draft এবং published সংগ্রহে পরিণত করে৷ উদাহরণস্বরূপ, প্রকাশিত পোস্টগুলি যে কেউ পড়তে পারে কিন্তু কঠিনভাবে মুছে ফেলা যাবে না, যখন খসড়াগুলি মুছে ফেলা যেতে পারে তবে শুধুমাত্র লেখক এবং মডারেটররা পড়তে পারেন৷ এই অ্যাপে, যখন একজন ব্যবহারকারী একটি খসড়া ব্লগ পোস্ট প্রকাশ করতে চায়, তখন একটি ফাংশন ট্রিগার হয় যা নতুন প্রকাশিত পোস্ট তৈরি করবে।

এরপর, আপনি প্রকাশিত পোস্টের নিয়ম লিখবেন। লেখার সহজ নিয়ম হল প্রকাশিত পোস্ট যে কেউ পড়তে পারে, এবং কেউ তৈরি বা মুছে ফেলতে পারে না। এই নিয়ম যোগ করুন:

match /published/{postID} {
  // `authorUID`: string, required
  // `content`: string, required
  // `publishedAt`: timestamp, required
  // `title`: string, < 50 characters, required
  // `url`: string, required
  // `visible`: boolean, required

  // Can be read by everyone
  allow read: if true;

  // Published posts are created only via functions, never by users
  // No hard deletes; soft deletes update `visible` field.
  allow create, delete: if false;
}

বিদ্যমান নিয়মগুলিতে এগুলি যোগ করলে, সম্পূর্ণ নিয়ম ফাইল হয়ে যায়:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /drafts/{draftID} {
      // `authorUID`: string, required
      // `content`: string, optional
      // `createdAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, optional

      allow create: if
        // User creating document is draft author
        request.auth.uid == request.resource.data.authorUID &&
        // Must include title, author, and url fields
        request.resource.data.keys().hasAll([
          "authorUID",
          "createdAt",
          "title"
        ]) &&
        // Title must be < 50 characters long
        request.resource.data.title.size() < 50;

      allow update: if
        // User is the author, and
        resource.data.authorUID == request.auth.uid &&
        // `authorUID` and `createdAt` are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "createdAt"
        ]) &&
        // Title must be < 50 characters long
        request.resource.data.title.size() < 50;

      allow read, delete: if
        // User is draft author
        resource.data.authorUID == request.auth.uid ||
        // User is a moderator
        request.auth.token.isModerator == true;
    }

    match /published/{postID} {
      // `authorUID`: string, required
      // `content`: string, required
      // `publishedAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, required
      // `visible`: boolean, required

      // Can be read by everyone
      allow read: if true;

      // Published posts are created only via functions, never by users
      // No hard deletes; soft deletes update `visible` field.
      allow create, delete: if false;
    }
  }
}

পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে অন্য একটি পরীক্ষা পাস হয়েছে।

8. প্রকাশিত পোস্ট আপডেট করা: কাস্টম ফাংশন এবং স্থানীয় ভেরিয়েবল

এএ প্রকাশিত পোস্ট আপডেট করার শর্ত হল:

  • এটা শুধুমাত্র লেখক বা মডারেটর দ্বারা করা যেতে পারে, এবং
  • এটিতে অবশ্যই সমস্ত প্রয়োজনীয় ক্ষেত্র থাকতে হবে।

যেহেতু আপনি ইতিমধ্যে একজন লেখক বা একজন মডারেটর হওয়ার শর্তাবলী লিখেছেন, আপনি শর্তগুলি অনুলিপি এবং পেস্ট করতে পারেন, কিন্তু সময়ের সাথে সাথে এটি পড়া এবং বজায় রাখা কঠিন হয়ে উঠতে পারে। পরিবর্তে, আপনি একটি কাস্টম ফাংশন তৈরি করবেন যা লেখক বা মডারেটর হওয়ার যুক্তিকে এনক্যাপসুলেট করে। তারপর, আপনি একাধিক শর্ত থেকে এটি কল করবেন।

একটি কাস্টম ফাংশন তৈরি করুন

ড্রাফ্টের জন্য ম্যাচ স্টেটমেন্টের উপরে, isAuthorOrModerator নামে একটি নতুন ফাংশন তৈরি করুন যা আর্গুমেন্ট হিসেবে পোস্ট ডকুমেন্ট নেয় (এটি ড্রাফ্ট বা প্রকাশিত পোস্টের জন্য কাজ করবে) এবং ব্যবহারকারীর প্রমাণীকরণ বস্তু:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Returns true if user is post author or a moderator
    function isAuthorOrModerator(post, auth) {

    }

    match /drafts/{postID} {
      allow create: ...
      allow update: ...
      ...
    }

    match /published/{postID} {
      allow read: ...
      allow create, delete: ...
    }
  }
}

স্থানীয় ভেরিয়েবল ব্যবহার করুন

ফাংশনের ভিতরে, isAuthor এবং isModerator ভেরিয়েবল সেট করতে let কীওয়ার্ড ব্যবহার করুন। সমস্ত ফাংশন অবশ্যই একটি রিটার্ন স্টেটমেন্ট দিয়ে শেষ করতে হবে, এবং আমাদের একটি বুলিয়ান রিটার্ন করবে যা নির্দেশ করে যে কোন একটি পরিবর্তনশীল সত্য কিনা:

function isAuthorOrModerator(post, auth) {
  let isAuthor = auth.uid == post.authorUID;
  let isModerator = auth.token.isModerator == true;
  return isAuthor || isModerator;
}

ফাংশন কল

এখন আপনি সেই ফাংশনটিকে কল করার জন্য ড্রাফ্টের নিয়ম আপডেট করবেন, প্রথম যুক্তি হিসেবে resource.data এ পাস করতে সতর্ক থাকবেন:

  // Draft blog posts
  match /drafts/{draftID} {
    ...
    // Can be deleted by author or moderator
    allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
  }

এখন আপনি প্রকাশিত পোস্টগুলি আপডেট করার জন্য একটি শর্ত লিখতে পারেন যা নতুন ফাংশন ব্যবহার করে:

allow update: if isAuthorOrModerator(resource.data, request.auth);

বৈধতা যোগ করুন

একটি প্রকাশিত পোস্টের কিছু ক্ষেত্র পরিবর্তন করা উচিত নয়, বিশেষ করে url , authorUID , এবং publishedAt ক্ষেত্রগুলি অপরিবর্তনীয়৷ অন্য দুটি ক্ষেত্র, title এবং content এবং visible একটি আপডেটের পরেও উপস্থিত থাকতে হবে৷ প্রকাশিত পোস্টগুলিতে আপডেটের জন্য এই প্রয়োজনীয়তাগুলি প্রয়োগ করতে শর্ত যুক্ত করুন:

// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
  "authorUID",
  "publishedAt",
  "url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
  "content",
  "title",
  "visible"
])

আপনার নিজের উপর একটি কাস্টম ফাংশন তৈরি করুন

এবং অবশেষে, একটি শর্ত যোগ করুন যে শিরোনামটি 50 অক্ষরের কম হবে। কারণ এটি পুনরায় ব্যবহার করা যুক্তি, আপনি একটি নতুন ফাংশন, titleIsUnder50Chars তৈরি করে এটি করতে পারেন। নতুন ফাংশনের সাথে, একটি প্রকাশিত পোস্ট আপডেট করার শর্ত হল:

allow update: if
  isAuthorOrModerator(resource.data, request.auth) &&
  // Immutable fields are unchanged
  request.resource.data.diff(resource.data).unchangedKeys().hasAll([
    "authorUID",
    "publishedAt",
    "url"
  ]) &&
  // Required fields are present
  request.resource.data.keys().hasAll([
    "content",
    "title",
    "visible"
  ]) &&
  titleIsUnder50Chars(request.resource.data);

এবং সম্পূর্ণ নিয়ম ফাইল হল:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Returns true if user is post author or a moderator
    function isAuthorOrModerator(post, auth) {
      let isAuthor = auth.uid == post.authorUID;
      let isModerator = auth.token.isModerator == true;
      return isAuthor || isModerator;
    }

    function titleIsUnder50Chars(post) {
      return post.title.size() < 50;
    }

    // Draft blog posts
    match /drafts/{draftID} {
      // `authorUID`: string, required
      // `content`: string, optional
      // `createdAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, optional

      allow create: if
        // User creating document is draft author
        request.auth.uid == request.resource.data.authorUID &&
        // Must include title, author, and url fields
        request.resource.data.keys().hasAll([
          "authorUID",
          "createdAt",
          "title"
        ]) &&
        titleIsUnder50Chars(request.resource.data);

      allow update: if
        // User is the author, and
        resource.data.authorUID == request.auth.uid &&
        // `authorUID` and `createdAt` are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "createdAt"
          ]) &&
        titleIsUnder50Chars(request.resource.data);

      // Can be read or deleted by author or moderator
      allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
    }

    // Published blog posts are denormalized from drafts
    match /published/{postID} {
      // `authorUID`: string, required
      // `content`: string, required
      // `publishedAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, required
      // `visible`: boolean, required

      // Can be read by everyone
      allow read: if true;

      // Published posts are created only via functions, never by users
      // No hard deletes; soft deletes update `visible` field.
      allow create, delete: if false;

      allow update: if
        isAuthorOrModerator(resource.data, request.auth) &&
        // Immutable fields are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "publishedAt",
          "url"
        ]) &&
        // Required fields are present
        request.resource.data.keys().hasAll([
          "content",
          "title",
          "visible"
        ]) &&
        titleIsUnder50Chars(request.resource.data);
    }
  }
}

পরীক্ষাগুলি পুনরায় চালান। এই মুহুর্তে, আপনার 5টি পাস করা এবং 4টি অকৃতকার্য হওয়া উচিত।

9. মন্তব্য: উপ-সংগ্রহ এবং সাইন-ইন প্রদানকারীর অনুমতি

প্রকাশিত পোস্টগুলি মন্তব্য করার অনুমতি দেয় এবং মন্তব্যগুলি প্রকাশিত পোস্টের একটি উপ-সংকলনে সংরক্ষণ করা হয় ( /published/{postID}/comments/{commentID} )। ডিফল্টভাবে একটি সংগ্রহের নিয়ম উপ-সংগ্রহের ক্ষেত্রে প্রযোজ্য নয়। প্রকাশিত পোস্টের মূল নথিতে প্রযোজ্য একই নিয়ম আপনি মন্তব্যে প্রয়োগ করতে চান না; আপনি বিভিন্ন কারুকাজ করব.

মন্তব্য অ্যাক্সেস করার নিয়ম লিখতে, ম্যাচ বিবৃতি দিয়ে শুরু করুন:

match /published/{postID}/comments/{commentID} {
  // `authorUID`: string, required
  // `comment`: string, < 500 characters, required
  // `createdAt`: timestamp, required
  // `editedAt`: timestamp, optional

মন্তব্য পড়া: বেনামী হতে পারে না

এই অ্যাপের জন্য, শুধুমাত্র ব্যবহারকারীরা যারা একটি স্থায়ী অ্যাকাউন্ট তৈরি করেছেন, একটি বেনামী অ্যাকাউন্ট নয় মন্তব্য পড়তে পারেন। সেই নিয়মটি প্রয়োগ করতে, প্রতিটি auth.token অবজেক্টে থাকা sign_in_provider বৈশিষ্ট্যটি দেখুন:

allow read: if request.auth.token.firebase.sign_in_provider != "anonymous";

আপনার পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে আরও একটি পরীক্ষা পাস হয়েছে।

মন্তব্য তৈরি করা: একটি অস্বীকার তালিকা চেক করা হচ্ছে

একটি মন্তব্য তৈরি করার জন্য তিনটি শর্ত আছে:

  • একজন ব্যবহারকারীর অবশ্যই একটি যাচাইকৃত ইমেল থাকতে হবে
  • মন্তব্যটি অবশ্যই 500 অক্ষরের কম হতে হবে এবং
  • তারা নিষিদ্ধ ব্যবহারকারীদের তালিকায় থাকতে পারে না, যা bannedUsers সংগ্রহে ফায়ারস্টোরে সংরক্ষণ করা হয়। এই শর্তগুলি একবারে নেওয়া:
request.auth.token.email_verified == true
request.resource.data.comment.size() < 500
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));

মন্তব্য তৈরি করার চূড়ান্ত নিয়ম হল:

allow create: if
  // User has verified email
  (request.auth.token.email_verified == true) &&
  // UID is not on bannedUsers list
  !(exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));

সম্পূর্ণ নিয়ম ফাইল এখন:

For bottom of step 9
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Returns true if user is post author or a moderator
    function isAuthorOrModerator(post, auth) {
      let isAuthor = auth.uid == post.authorUID;
      let isModerator = auth.token.isModerator == true;
      return isAuthor || isModerator;
    }

    function titleIsUnder50Chars(post) {
      return post.title.size() < 50;
    }

    // Draft blog posts
    match /drafts/{draftID} {
      // `authorUID`: string, required
      // `content`: string, optional
      // `createdAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, optional

      allow create: if
        // User is author
        request.auth.uid == request.resource.data.authorUID &&
        // Must include title, author, and createdAt fields
        request.resource.data.keys().hasAll([
          "authorUID",
          "createdAt",
          "title"
        ]) &&
        titleIsUnder50Chars(request.resource.data);

      allow update: if
        // User is author
        resource.data.authorUID == request.auth.uid &&
        // `authorUID` and `createdAt` are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "createdAt"
          ]) &&
        titleIsUnder50Chars(request.resource.data);

      // Can be read or deleted by author or moderator
      allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
    }

    // Published blog posts are denormalized from drafts
    match /published/{postID} {
      // `authorUID`: string, required
      // `content`: string, required
      // `publishedAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, required
      // `visible`: boolean, required

      // Can be read by everyone
      allow read: if true;

      // Published posts are created only via functions, never by users
      // No hard deletes; soft deletes update `visible` field.
      allow create, delete: if false;

      allow update: if
        isAuthorOrModerator(resource.data, request.auth) &&
        // Immutable fields are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "publishedAt",
          "url"
        ]) &&
        // Required fields are present
        request.resource.data.keys().hasAll([
          "content",
          "title",
          "visible"
        ]) &&
        titleIsUnder50Chars(request.resource.data);
    }

    match /published/{postID}/comments/{commentID} {
      // `authorUID`: string, required
      // `createdAt`: timestamp, required
      // `editedAt`: timestamp, optional
      // `comment`: string, < 500 characters, required

      // Must have permanent account to read comments
      allow read: if !(request.auth.token.firebase.sign_in_provider == "anonymous");

      allow create: if
        // User has verified email
        request.auth.token.email_verified == true &&
        // Comment is under 500 characters
        request.resource.data.comment.size() < 500 &&
        // UID is not on the block list
        !exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
    }
  }
}

পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে আরও একটি পরীক্ষা পাস হয়েছে।

10. মন্তব্য আপডেট করা: সময়-ভিত্তিক নিয়ম

মন্তব্যের ব্যবসায়িক যুক্তি হল যে সেগুলি সৃষ্টির এক ঘন্টা পরে মন্তব্য লেখক দ্বারা সম্পাদনা করা যেতে পারে। এটি বাস্তবায়ন করতে, createdAt timestamp ব্যবহার করুন।

প্রথমত, ব্যবহারকারী যে লেখক তা প্রতিষ্ঠিত করতে:

request.auth.uid == resource.data.authorUID

পরবর্তী, যে মন্তব্যটি শেষ ঘন্টার মধ্যে তৈরি করা হয়েছিল:

(request.time - resource.data.createdAt) < duration.value(1, 'h');

যৌক্তিক এবং অপারেটরের সাথে এগুলিকে একত্রিত করলে, মন্তব্য আপডেট করার নিয়মটি হয়ে যায়:

allow update: if
  // is author
  request.auth.uid == resource.data.authorUID &&
  // within an hour of comment creation
  (request.time - resource.data.createdAt) < duration.value(1, 'h');

পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে আরও একটি পরীক্ষা পাস হয়েছে।

11. মন্তব্য মুছে ফেলা: পিতামাতার মালিকানা পরীক্ষা করা

মন্তব্যগুলি মন্তব্য লেখক, একজন মডারেটর, বা ব্লগ পোস্টের লেখক দ্বারা মুছে ফেলা যেতে পারে৷

প্রথমত, যেহেতু সাহায্যকারী ফাংশনটি আপনি আগে যোগ করেছেন এমন একটি authorUID ক্ষেত্রের জন্য চেক করে যা একটি পোস্ট বা একটি মন্তব্যে বিদ্যমান থাকতে পারে, আপনি ব্যবহারকারী লেখক বা মডারেটর কিনা তা পরীক্ষা করতে সাহায্যকারী ফাংশনটি পুনরায় ব্যবহার করতে পারেন:

isAuthorOrModerator(resource.data, request.auth)

ব্যবহারকারী ব্লগ পোস্ট লেখক কিনা তা পরীক্ষা করতে, Firestore-এ পোস্টটি দেখার জন্য একটি get ব্যবহার করুন:

request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID

যেহেতু এই শর্তগুলির মধ্যে যেকোনো একটি যথেষ্ট, তাদের মধ্যে একটি যৌক্তিক বা অপারেটর ব্যবহার করুন:

allow delete: if
  // is comment author or moderator
  isAuthorOrModerator(resource.data, request.auth) ||
  // is blog post author
  request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID;

পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে আরও একটি পরীক্ষা পাস হয়েছে।

এবং সম্পূর্ণ নিয়ম ফাইল হল:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Returns true if user is post author or a moderator
    function isAuthorOrModerator(post, auth) {
      let isAuthor = auth.uid == post.authorUID;
      let isModerator = auth.token.isModerator == true;
      return isAuthor || isModerator;
    }

    function titleIsUnder50Chars(post) {
      return post.title.size() < 50;
    }

    // Draft blog posts
    match /drafts/{draftID} {
      // `authorUID`: string, required
      // `content`: string, optional
      // `createdAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, optional

      allow create: if
        // User is author
        request.auth.uid == request.resource.data.authorUID &&
        // Must include title, author, and createdAt fields
        request.resource.data.keys().hasAll([
          "authorUID",
          "createdAt",
          "title"
        ]) &&
        titleIsUnder50Chars(request.resource.data);

      allow update: if
        // User is author
        resource.data.authorUID == request.auth.uid &&
        // `authorUID` and `createdAt` are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "createdAt"
          ]) &&
        titleIsUnder50Chars(request.resource.data);

      // Can be read or deleted by author or moderator
      allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
    }

    // Published blog posts are denormalized from drafts
    match /published/{postID} {
      // `authorUID`: string, required
      // `content`: string, required
      // `publishedAt`: timestamp, required
      // `title`: string, < 50 characters, required
      // `url`: string, required
      // `visible`: boolean, required

      // Can be read by everyone
      allow read: if true;

      // Published posts are created only via functions, never by users
      // No hard deletes; soft deletes update `visible` field.
      allow create, delete: if false;

      allow update: if
        isAuthorOrModerator(resource.data, request.auth) &&
        // Immutable fields are unchanged
        request.resource.data.diff(resource.data).unchangedKeys().hasAll([
          "authorUID",
          "publishedAt",
          "url"
        ]) &&
        // Required fields are present
        request.resource.data.keys().hasAll([
          "content",
          "title",
          "visible"
        ]) &&
        titleIsUnder50Chars(request.resource.data);
    }

    match /published/{postID}/comments/{commentID} {
      // `authorUID`: string, required
      // `createdAt`: timestamp, required
      // `editedAt`: timestamp, optional
      // `comment`: string, < 500 characters, required

      // Must have permanent account to read comments
      allow read: if !(request.auth.token.firebase.sign_in_provider == "anonymous");

      allow create: if
        // User has verified email
        request.auth.token.email_verified == true &&
        // Comment is under 500 characters
        request.resource.data.comment.size() < 500 &&
        // UID is not on the block list
        !exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));

      allow update: if
        // is author
        request.auth.uid == resource.data.authorUID &&
        // within an hour of comment creation
        (request.time - resource.data.createdAt) < duration.value(1, 'h');

      allow delete: if
        // is comment author or moderator
        isAuthorOrModerator(resource.data, request.auth) ||
        // is blog post author
        request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID;
    }
  }
}

12. পরবর্তী পদক্ষেপ

অভিনন্দন! আপনি নিরাপত্তা বিধি লিখেছেন যা সমস্ত পরীক্ষায় উত্তীর্ণ হয়েছে এবং আবেদনটি সুরক্ষিত করেছে!

এখানে কিছু সম্পর্কিত বিষয় রয়েছে যা পরবর্তীতে ডুব দিতে হবে:

  • ব্লগ পোস্ট : কিভাবে কোড পর্যালোচনা নিরাপত্তা নিয়ম
  • কোডল্যাব : এমুলেটরদের সাথে স্থানীয় প্রথম বিকাশের মাধ্যমে হাঁটা
  • ভিডিও : গিটহাব অ্যাকশন ব্যবহার করে এমুলেটর-ভিত্তিক পরীক্ষার জন্য সেট আপ সিআই কীভাবে ব্যবহার করবেন