১. শুরু করার আগে
ক্লাউড ফায়ারস্টোর, ফায়ারবেসের জন্য ক্লাউড স্টোরেজ এবং রিয়েলটাইম ডেটাবেস আপনার লেখা কনফিগারেশন ফাইলের উপর নির্ভর করে পঠন এবং লেখার অ্যাক্সেস দেয়। এই কনফিগারেশন, যাকে সিকিউরিটি রুলস বলা হয়, আপনার অ্যাপের জন্য এক ধরণের স্কিমা হিসেবেও কাজ করতে পারে। এটি আপনার অ্যাপ্লিকেশন তৈরির সবচেয়ে গুরুত্বপূর্ণ অংশগুলির মধ্যে একটি। এবং এই কোডল্যাব আপনাকে এটির মধ্য দিয়ে নিয়ে যাবে।
পূর্বশর্ত
- ভিজ্যুয়াল স্টুডিও কোড, অ্যাটম, অথবা সাব্লাইম টেক্সটের মতো একটি সাধারণ সম্পাদক
- Node.js 8.6.0 বা উচ্চতর (Node.js ইনস্টল করতে, nvm ব্যবহার করুন ; আপনার সংস্করণ পরীক্ষা করতে,
node --versionচালান) - জাভা ৭ বা তার উচ্চতর (জাভা ইনস্টল করতে এই নির্দেশাবলী ব্যবহার করুন ; আপনার সংস্করণ পরীক্ষা করতে,
java -versionচালান)
তুমি কি করবে
এই কোডল্যাবে, আপনি ফায়ারস্টোরের উপর নির্মিত একটি সহজ ব্লগ প্ল্যাটফর্ম সুরক্ষিত করবেন। আপনি ফায়ারস্টোর এমুলেটর ব্যবহার করে সুরক্ষা নিয়মের বিরুদ্ধে ইউনিট পরীক্ষা চালাবেন এবং নিশ্চিত করবেন যে নিয়মগুলি আপনার প্রত্যাশিত অ্যাক্সেসের অনুমতি দেয় এবং নিষিদ্ধ করে।
তুমি শিখবে কিভাবে:
- ছোট ছোট অনুমতি দিন
- ডেটা এবং টাইপ যাচাইকরণ প্রয়োগ করুন
- অ্যাট্রিবিউট ভিত্তিক অ্যাক্সেস কন্ট্রোল বাস্তবায়ন করুন
- প্রমাণীকরণ পদ্ধতির উপর ভিত্তি করে অ্যাক্সেস মঞ্জুর করুন
- কাস্টম ফাংশন তৈরি করুন
- সময়-ভিত্তিক নিরাপত্তা নিয়ম তৈরি করুন
- একটি অস্বীকার তালিকা এবং সফট ডিলিট বাস্তবায়ন করুন
- একাধিক অ্যাক্সেস প্যাটার্ন পূরণের জন্য কখন ডেটা ডিনরমালাইজ করতে হবে তা বুঝুন
2. সেট আপ করুন
এটি একটি ব্লগিং অ্যাপ্লিকেশন। অ্যাপ্লিকেশনটির কার্যকারিতার একটি উচ্চ স্তরের সারাংশ এখানে দেওয়া হল:
খসড়া ব্লগ পোস্ট:
- ব্যবহারকারীরা খসড়া ব্লগ পোস্ট তৈরি করতে পারেন, যা
draftsসংগ্রহে থাকে। - লেখক একটি খসড়া প্রকাশের জন্য প্রস্তুত না হওয়া পর্যন্ত আপডেট করতে পারবেন।
- যখন এটি প্রকাশের জন্য প্রস্তুত হয়, তখন একটি Firebase ফাংশন ট্রিগার হয় যা
publishedসংগ্রহে একটি নতুন নথি তৈরি করে। - লেখক অথবা সাইট মডারেটররা খসড়া মুছে ফেলতে পারেন।
প্রকাশিত ব্লগ পোস্ট:
- প্রকাশিত পোস্ট ব্যবহারকারীরা তৈরি করতে পারবেন না, শুধুমাত্র একটি ফাংশনের মাধ্যমে।
- এগুলি কেবল সফট-ডিলিট করা যেতে পারে, যা একটি
visibleঅ্যাট্রিবিউটকে false-এ আপডেট করে।
মন্তব্য
- প্রকাশিত পোস্টগুলিতে মন্তব্য করার অনুমতি দেওয়া হয়, যা প্রতিটি প্রকাশিত পোস্টের একটি উপ-সংগ্রহ।
- অপব্যবহার কমাতে, ব্যবহারকারীদের একটি যাচাইকৃত ইমেল ঠিকানা থাকতে হবে এবং মন্তব্য করার জন্য অস্বীকারকারীর তালিকায় থাকা যাবে না।
- মন্তব্য পোস্ট করার এক ঘন্টার মধ্যে আপডেট করা যাবে।
- মন্তব্য লেখক, মূল পোস্টের লেখক, অথবা মডারেটররা মন্তব্য মুছে ফেলতে পারেন।
অ্যাক্সেস নিয়ম ছাড়াও, আপনি সুরক্ষা নিয়ম তৈরি করবেন যা প্রয়োজনীয় ক্ষেত্র এবং ডেটা যাচাইকরণ জোরদার করবে।
ফায়ারবেস এমুলেটর স্যুট ব্যবহার করে সবকিছু স্থানীয়ভাবে ঘটবে।
সোর্স কোডটি পান
এই কোডল্যাবে, আপনি নিরাপত্তা নিয়মের পরীক্ষা দিয়ে শুরু করবেন, তবে নিরাপত্তা নিয়মগুলি খুব কম, তাই প্রথমে আপনাকে পরীক্ষাগুলি চালানোর জন্য উৎসটি ক্লোন করতে হবে:
$ git clone https://github.com/FirebaseExtended/codelab-rules.git
তারপর initial-state ডিরেক্টরিতে যান, যেখানে আপনি এই কোডল্যাবের বাকি অংশের জন্য কাজ করবেন:
$ cd codelab-rules/initial-state
এখন, পরীক্ষাগুলি চালানোর জন্য নির্ভরতাগুলি ইনস্টল করুন। যদি আপনার ইন্টারনেট সংযোগ ধীর হয় তবে এটি এক বা দুই মিনিট সময় নিতে পারে:
# Move into the functions directory, install dependencies, jump out. $ cd functions && npm install && cd -
ফায়ারবেস সিএলআই পান
পরীক্ষা চালানোর জন্য আপনি যে এমুলেটর স্যুটটি ব্যবহার করবেন তা Firebase CLI (কমান্ড-লাইন ইন্টারফেস) এর অংশ যা নিম্নলিখিত কমান্ডের সাহায্যে আপনার মেশিনে ইনস্টল করা যেতে পারে:
$ npm install -g firebase-tools
এরপর, নিশ্চিত করুন যে আপনার কাছে CLI এর সর্বশেষ সংস্করণ আছে। এই কোডল্যাবটি 8.4.0 বা তার উচ্চতর সংস্করণের সাথে কাজ করবে তবে পরবর্তী সংস্করণগুলিতে আরও বাগ সংশোধন অন্তর্ভুক্ত রয়েছে।
$ firebase --version 9.10.2
৩. পরীক্ষা চালান
এই বিভাগে, আপনি স্থানীয়ভাবে পরীক্ষাগুলি চালাবেন। এর অর্থ হল এমুলেটর স্যুটটি বুট করার সময় এসেছে।
এমুলেটরগুলি শুরু করুন
আপনি যে অ্যাপ্লিকেশনটির সাথে কাজ করবেন তাতে তিনটি প্রধান ফায়ারস্টোর সংগ্রহ রয়েছে: drafts এমন ব্লগ পোস্ট থাকে যা প্রক্রিয়াধীন থাকে, published সংগ্রহে প্রকাশিত ব্লগ পোস্ট থাকে এবং comments প্রকাশিত পোস্টের উপর একটি উপ-সংগ্রহ। রেপোতে সুরক্ষা নিয়মের জন্য ইউনিট পরীক্ষা রয়েছে যা ব্যবহারকারীর বৈশিষ্ট্য এবং drafts , published এবং comments সংগ্রহে নথি তৈরি, পড়া, আপডেট এবং মুছে ফেলার জন্য প্রয়োজনীয় অন্যান্য শর্তগুলি সংজ্ঞায়িত করে। এই পরীক্ষাগুলি পাস করার জন্য আপনাকে সুরক্ষা নিয়মগুলি লিখতে হবে।
শুরু করার জন্য, আপনার ডাটাবেসটি লক ডাউন করা হয়েছে: ডাটাবেসে পড়ার এবং লেখার বিষয়টি সর্বজনীনভাবে অস্বীকৃত, এবং সমস্ত পরীক্ষা ব্যর্থ হয়। আপনি যখন সুরক্ষা বিধি লিখবেন, পরীক্ষাগুলি পাস হবে। পরীক্ষাগুলি দেখতে, আপনার সম্পাদকে functions/test.js খুলুন।
কমান্ড লাইনে, 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
...
এই মুহূর্তে ৯টি ব্যর্থতা রয়েছে। নিয়ম ফাইল তৈরি করার সাথে সাথে, আরও পরীক্ষায় উত্তীর্ণ হওয়া দেখে আপনি অগ্রগতি পরিমাপ করতে পারবেন।
৪. ব্লগ পোস্টের খসড়া তৈরি করুন।
যেহেতু খসড়া ব্লগ পোস্টের অ্যাক্সেস প্রকাশিত ব্লগ পোস্টের অ্যাক্সেস থেকে অনেক আলাদা, তাই এই ব্লগিং অ্যাপটি খসড়া ব্লগ পোস্টগুলিকে একটি পৃথক সংগ্রহে সংরক্ষণ করে, /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
}
}
}
খসড়া তৈরির জন্য আপনি যে প্রথম নিয়মটি লিখবেন তা নিয়ন্ত্রণ করবে কে ডকুমেন্ট তৈরি করতে পারবে। এই অ্যাপ্লিকেশনে, খসড়াগুলি কেবল লেখক হিসাবে তালিকাভুক্ত ব্যক্তিই তৈরি করতে পারবেন। অনুরোধকারী ব্যক্তির ইউআইডি ডকুমেন্টে তালিকাভুক্ত একই ইউআইডি কিনা তা পরীক্ষা করুন।
তৈরির প্রথম শর্ত হবে:
request.resource.data.authorUID == request.auth.uid
এরপর, ডকুমেন্টগুলি কেবল তখনই তৈরি করা যাবে যদি সেগুলিতে তিনটি প্রয়োজনীয় ক্ষেত্র থাকে, authorUID , createdAt , এবং title । (ব্যবহারকারী createdAt ফিল্ডটি সরবরাহ করে না; এর ফলে ডকুমেন্ট তৈরি করার চেষ্টা করার আগে অ্যাপটিকে অবশ্যই এটি যোগ করতে হবে।) যেহেতু আপনাকে কেবল বৈশিষ্ট্যগুলি তৈরি করা হচ্ছে কিনা তা পরীক্ষা করতে হবে, আপনি পরীক্ষা করতে পারেন যে request.resource সেই সমস্ত কী রয়েছে:
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
])
ব্লগ পোস্ট তৈরির জন্য চূড়ান্ত শর্ত হল শিরোনামটি ৫০ অক্ষরের বেশি দীর্ঘ হওয়া উচিত নয়:
request.resource.data.title.size() < 50
যেহেতু এই সমস্ত শর্ত অবশ্যই সত্য হতে হবে, তাই এগুলিকে লজিক্যাল AND অপারেটর, && এর সাথে সংযুক্ত করুন। প্রথম নিয়মটি হল:
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;
}
}
}
টার্মিনালে, পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে প্রথম পরীক্ষাটি পাস হয়েছে।
৫. ব্লগ পোস্টের খসড়া আপডেট করুন।
এরপর, লেখকরা যখন তাদের খসড়া ব্লগ পোস্টগুলি পরিমার্জন করবেন, তখন তারা খসড়া নথিগুলি সম্পাদনা করবেন। কোন পোস্ট কখন আপডেট করা যেতে পারে তার জন্য একটি নিয়ম তৈরি করুন। প্রথমে, শুধুমাত্র লেখক তাদের খসড়াগুলি আপডেট করতে পারবেন। মনে রাখবেন যে এখানে আপনি ইতিমধ্যে লেখা UID, resource.data.authorUID , পরীক্ষা করবেন:
resource.data.authorUID == request.auth.uid
একটি আপডেটের জন্য দ্বিতীয় প্রয়োজনীয়তা হল দুটি বৈশিষ্ট্য, authorUID এবং createdAt পরিবর্তন করা উচিত নয়:
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]);
এবং পরিশেষে, শিরোনামটি ৫০ অক্ষর বা তার কম হওয়া উচিত:
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;
}
}
}
আপনার পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে অন্য একটি পরীক্ষা পাস হয়েছে।
৬. খসড়াগুলি মুছুন এবং পড়ুন: অ্যাট্রিবিউট ভিত্তিক অ্যাক্সেস নিয়ন্ত্রণ
লেখকরা যেমন খসড়া তৈরি এবং আপডেট করতে পারেন, তেমনি তারা খসড়া মুছেও ফেলতে পারেন।
resource.data.authorUID == request.auth.uid
অতিরিক্তভাবে, যাদের auth টোকেনে isModerator অ্যাট্রিবিউট আছে তারা খসড়া মুছে ফেলতে পারবেন:
request.auth.token.isModerator == true
যেহেতু এই শর্তগুলির যেকোনো একটি মুছে ফেলার জন্য যথেষ্ট, তাই একটি লজিক্যাল OR অপারেটর দিয়ে এগুলিকে সংযুক্ত করুন, || :
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;
}
}
}
আপনার পরীক্ষাগুলি পুনরায় চালান এবং নিশ্চিত করুন যে আরেকটি পরীক্ষা এখন পাস হয়েছে।
৭. প্রকাশিত পোস্টগুলি পড়ে, তৈরি করে এবং মুছে ফেলে: বিভিন্ন অ্যাক্সেস প্যাটার্নের জন্য ডিনরমালাইজেশন
প্রকাশিত পোস্ট এবং খসড়া পোস্টের অ্যাক্সেস প্যাটার্ন এত আলাদা যে, এই অ্যাপটি পোস্টগুলিকে পৃথক 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;
}
}
}
পরীক্ষাগুলি আবার চালান, এবং নিশ্চিত করুন যে আরেকটি পরীক্ষা পাস হয়েছে।
৮. প্রকাশিত পোস্ট আপডেট করা: কাস্টম ফাংশন এবং স্থানীয় ভেরিয়েবল
প্রকাশিত পোস্ট আপডেট করার শর্তাবলী হল:
- এটি শুধুমাত্র লেখক বা মডারেটর দ্বারা করা যেতে পারে, এবং
- এতে অবশ্যই সমস্ত প্রয়োজনীয় ক্ষেত্র থাকতে হবে।
যেহেতু আপনি ইতিমধ্যেই লেখক বা মডারেটর হওয়ার জন্য শর্তাবলী লিখে রেখেছেন, তাই আপনি শর্তাবলী কপি এবং পেস্ট করতে পারেন, কিন্তু সময়ের সাথে সাথে এটি পড়া এবং রক্ষণাবেক্ষণ করা কঠিন হয়ে পড়তে পারে। পরিবর্তে, আপনি একটি কাস্টম ফাংশন তৈরি করবেন যা লেখক বা মডারেটর হওয়ার যুক্তিকে অন্তর্ভুক্ত করবে। তারপর, আপনি এটিকে একাধিক শর্ত থেকে কল করবেন।
একটি কাস্টম ফাংশন তৈরি করুন
ড্রাফ্টের জন্য ম্যাচ স্টেটমেন্টের উপরে, isAuthorOrModerator নামে একটি নতুন ফাংশন তৈরি করুন যা একটি পোস্ট ডকুমেন্ট (এটি ড্রাফ্ট বা প্রকাশিত পোস্টের জন্য কাজ করবে) এবং ব্যবহারকারীর auth অবজেক্টকে আর্গুমেন্ট হিসেবে গ্রহণ করবে:
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: ...
}
}
}
স্থানীয় ভেরিয়েবল ব্যবহার করুন
ফাংশনের ভেতরে, let কীওয়ার্ড ব্যবহার করে isAuthor এবং isModerator ভেরিয়েবল সেট করুন। সমস্ত ফাংশন অবশ্যই একটি রিটার্ন স্টেটমেন্ট দিয়ে শেষ হবে, এবং আমাদের ফাংশনটি একটি বুলিয়ান রিটার্ন করবে যা নির্দেশ করবে যে দুটি ভেরিয়েবল সত্য কিনা:
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"
])
নিজে নিজে একটি কাস্টম ফাংশন তৈরি করুন
এবং পরিশেষে, একটি শর্ত যোগ করুন যে শিরোনামটি ৫০ অক্ষরের কম হবে। যেহেতু এটি পুনঃব্যবহৃত লজিক, আপনি একটি নতুন ফাংশন তৈরি করে এটি করতে পারেন, 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);
}
}
}
পরীক্ষাগুলো আবার চালান। এই মুহুর্তে, আপনার ৫টি পরীক্ষায় উত্তীর্ণ এবং ৪টি ফেল করা উচিত।
৯. মন্তব্য: উপ-সংগ্রহ এবং সাইন-ইন প্রদানকারীর অনুমতি
প্রকাশিত পোস্টগুলিতে মন্তব্য করার অনুমতি থাকে এবং মন্তব্যগুলি প্রকাশিত পোস্টের একটি উপ-সংগ্রহে সংরক্ষণ করা হয় ( /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";
তোমার পরীক্ষাগুলো আবার চালাও, এবং নিশ্চিত করো যে আরও একটি পরীক্ষায় উত্তীর্ণ হয়েছে।
মন্তব্য তৈরি করা: একটি অস্বীকৃতি তালিকা পরীক্ষা করা
মন্তব্য তৈরির জন্য তিনটি শর্ত রয়েছে:
- একজন ব্যবহারকারীর অবশ্যই একটি যাচাইকৃত ইমেল থাকতে হবে।
- মন্তব্যটি ৫০০ অক্ষরের কম হতে হবে, এবং
- নিষিদ্ধ ব্যবহারকারীদের তালিকায় তাদের নাম রাখা যাবে না, যা ফায়ারস্টোরে
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));
}
}
}
পরীক্ষাগুলি আবার চালান, এবং নিশ্চিত করুন যে আরও একটি পরীক্ষায় উত্তীর্ণ হয়েছে।
১০. মন্তব্য আপডেট করা: সময়-ভিত্তিক নিয়ম
মন্তব্যের ব্যবসায়িক যুক্তি হল, মন্তব্য লেখক তৈরির পর এক ঘন্টার জন্য সেগুলি সম্পাদনা করতে পারবেন। এটি বাস্তবায়নের জন্য, createdAt টাইমস্ট্যাম্প ব্যবহার করুন।
প্রথমত, ব্যবহারকারী যে লেখক তা প্রতিষ্ঠিত করতে:
request.auth.uid == resource.data.authorUID
এরপর, মন্তব্যটি গত এক ঘন্টার মধ্যে তৈরি করা হয়েছে:
(request.time - resource.data.createdAt) < duration.value(1, 'h');
লজিক্যাল AND অপারেটরের সাথে এগুলো একত্রিত করলে, মন্তব্য আপডেট করার নিয়মটি হয়ে যায়:
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');
পরীক্ষাগুলি আবার চালান, এবং নিশ্চিত করুন যে আরও একটি পরীক্ষায় উত্তীর্ণ হয়েছে।
১১. মন্তব্য মুছে ফেলা: পিতামাতার মালিকানা পরীক্ষা করা
মন্তব্য লেখক, মডারেটর, অথবা ব্লগ পোস্টের লেখক দ্বারা মন্তব্য মুছে ফেলা যেতে পারে।
প্রথমত, যেহেতু আপনি আগে যে হেল্পার ফাংশনটি যোগ করেছিলেন তা একটি authorUID ফিল্ড পরীক্ষা করে যা কোনও পোস্ট বা মন্তব্যে থাকতে পারে, তাই আপনি ব্যবহারকারী লেখক না মডারেটর তা পরীক্ষা করার জন্য হেল্পার ফাংশনটি পুনরায় ব্যবহার করতে পারেন:
isAuthorOrModerator(resource.data, request.auth)
ব্যবহারকারী ব্লগ পোস্টের লেখক কিনা তা পরীক্ষা করতে, Firestore-এ পোস্টটি দেখতে get ব্যবহার করুন:
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID
যেহেতু এই শর্তগুলির যেকোনো একটি যথেষ্ট, তাদের মধ্যে একটি লজিক্যাল OR অপারেটর ব্যবহার করুন:
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;
}
}
}
১২. পরবর্তী পদক্ষেপ
অভিনন্দন! আপনি নিরাপত্তা নিয়মাবলী লিখেছেন যা সমস্ত পরীক্ষায় উত্তীর্ণ হয়েছে এবং আবেদনটি সুরক্ষিত করেছে!
এখানে কিছু সম্পর্কিত বিষয় রয়েছে যা পরবর্তীতে আলোচনা করা উচিত:
- ব্লগ পোস্ট : সুরক্ষা বিধি পর্যালোচনা কোডিং কীভাবে করবেন
- কোডল্যাব : এমুলেটরগুলির সাহায্যে স্থানীয় প্রথম উন্নয়নের মধ্য দিয়ে হেঁটে যাওয়া
- ভিডিও : GitHub অ্যাকশন ব্যবহার করে এমুলেটর-ভিত্তিক পরীক্ষার জন্য সেট আপ CI কীভাবে ব্যবহার করবেন