চূড়ান্ত প্রকল্প — একটি ফুল-স্ট্যাক অ্যাপ্লিকেশন ডিজাইন ও সিমুলেট করা
এই পাঠে যা শিখবেন
- কোর্সের একাধিক সিমুলেশন প্যাটার্নকে (Router, CRUD DB, ORM, JWT, Redux store) একটি সুসংগত সিস্টেমে সংযুক্ত করা
- একটি real HMAC-SHA256 JWT দিয়ে অথেন্টিকেশন মিডলওয়্যার তৈরি করা যা একটি প্রোটেক্টেড রুট রক্ষা করে
- টোকেন ছাড়া রিকোয়েস্ট কীভাবে সঠিকভাবে প্রত্যাখ্যাত হয় (401), আর সঠিক টোকেনসহ রিকোয়েস্ট কীভাবে গ্রহণযোগ্য হয় (201) — উভয়ই একই মিডলওয়্যার দিয়ে, একই কোডে
- একটি সম্পূর্ণ এন্ড-টু-এন্ড ব্যবহারকারী ফ্লো ট্রেস করা — রেজিস্ট্রেশন থেকে চূড়ান্ত ক্লায়েন্ট-সাইড স্টেট পর্যন্ত
১ · ক্যাপস্টোন পরিকল্পনা — একটি টাস্ক ম্যানেজার সিমুলেট করা
কোর্স জুড়ে আমরা একটি একটি করে ফ্রেমওয়ার্ক-স্তরের ধারণা শিখেছি — Router (M5), CRUD ডেটাবেস (M6), ORM (M7), JWT-ভিত্তিক অথেন্টিকেশন (M8), ও Redux-স্টাইল স্টেট ম্যানেজমেন্ট (M4)। এই ক্যাপস্টোনে সেগুলো আলাদা আলাদা না রেখে একসাথে জোড়া লাগিয়ে একটি ছোট্ট কিন্তু সম্পূর্ণ, কার্যকর "টাস্ক ম্যানেজার" ফুল-স্ট্যাক অ্যাপ্লিকেশন সিমুলেট করা হবে।
২ · মডিউল ১ — Task CRUD ডেটাবেস
প্রথম টুকরোটি হলো একটি ইন-মেমরি CRUD "ডেটাবেস" — একটি Python ডিকশনারি (TASKS_DB) যেখানে প্রতিটি
টাস্ক একটি অটো-ইনক্রিমেন্টিং আইডি দিয়ে সংরক্ষিত হয়, সাথে সত্যিকারের create/read/
update/delete ফাংশন (M6-M7-এ শেখা CRUD প্যাটার্ন)।
# ========== মডিউল ১: In-memory CRUD "ডেটাবেস" -- টাস্কের জন্য ==========
TASKS_DB = {} # id (int) থেকে রেকর্ড dict -- সত্যিকারের ডেটাবেস টেবিলের ভূমিকায়
_next_task_id = 1
def db_create_task(title, owner):
global _next_task_id
record = {"id": _next_task_id, "title": title, "done": False, "owner": owner}
TASKS_DB[_next_task_id] = record
_next_task_id += 1
return dict(record)
def db_read_task(task_id):
record = TASKS_DB.get(task_id)
return dict(record) if record else None
def db_list_tasks():
return [dict(r) for r in TASKS_DB.values()]
def db_update_task(task_id, **fields):
record = TASKS_DB.get(task_id)
if record is None:
return None
record.update(fields)
return dict(record)
def db_delete_task(task_id):
return TASKS_DB.pop(task_id, None) is not None
# ---- স্যানিটি-চেক: CRUD ফাংশনগুলো সরাসরি টেস্ট করা ----
demo = db_create_task("ডেমো টাস্ক (শুধু CRUD যাচাইয়ের জন্য)", owner="system")
print("তৈরি: ", demo)
print("পড়া: ", db_read_task(demo["id"]))
print("আপডেট: ", db_update_task(demo["id"], done=True))
print("তালিকা: ", db_list_tasks())
print("মোছা হলো: ", db_delete_task(demo["id"]))
print("মোছার পর: ", db_list_tasks())
৩ · মডিউল ২ — মিনি ORM Model
দ্বিতীয় টুকরোটি হলো একটি মিনি ORM (M7-এর ORM প্যাটার্ন) — একটি Task ক্লাস যা উপরের CRUD
ফাংশনগুলো ভেতরে ভেতরে কল করে, কিন্তু বাইরে থেকে একটি অনেক পরিষ্কার অবজেক্ট ইন্টারফেস দেয়
(Task.create(...), task.save(), Task.objects.filter(...))।
# ========== মডিউল ২: মিনি ORM -- Task মডেল, উপরের CRUD ডেটাবেস র্যাপ করে ==========
class Manager:
def __init__(self, model_cls, list_fn):
self.model_cls = model_cls
self.list_fn = list_fn
def filter(self, **kwargs):
rows = [r for r in self.list_fn() if all(r.get(k) == v for k, v in kwargs.items())]
return [self.model_cls(**r) for r in rows]
def all(self):
return self.filter()
class Task:
def __init__(self, id, title, done, owner):
self.id = id
self.title = title
self.done = done
self.owner = owner
@classmethod
def create(cls, title, owner):
record = db_create_task(title, owner)
return cls(**record)
def save(self):
db_update_task(self.id, title=self.title, done=self.done, owner=self.owner)
return self
def __repr__(self):
mark = "[x]" if self.done else "[ ]"
return f"{mark} #{self.id} {self.title} (owner={self.owner})"
Task.objects = Manager(Task, db_list_tasks)
# ---- মিনি-ORM যাচাই: তৈরি, ফিল্টার, আপডেট, আবার পড়া ----
task_a = Task.create(title="ডিজাইন রিভিউ", owner="rafiq")
task_b = Task.create(title="ডেপ্লয়মেন্ট চেকলিস্ট", owner="rafiq")
task_c = Task.create(title="মিটিং নোট", owner="admin")
print("সব টাস্ক:", Task.objects.all())
print("\nrafiq-এর টাস্কসমূহ:", Task.objects.filter(owner="rafiq"))
task_a.done = True
task_a.save()
print("\ntask_a সেভ করার পর, id দিয়ে আবার পড়া হলো:", Task.objects.filter(id=task_a.id))
Task ক্লাসের ভেতরে কোথাও সরাসরি TASKS_DB স্পর্শ করা হয়নি — সবসময়
db_create_task/db_update_task/db_list_tasks ফাংশনের মাধ্যমে যায়।
এটাই ORM-এর মূল সুবিধা: ভবিষ্যতে TASKS_DB-এর ভেতরের স্টোরেজ বদলে ফেললেও (যেমন একটি আসল ডেটাবেস
টেবিলে) Task ক্লাসের ব্যবহারকারী কোডের কিছু বদলাতে হয় না।
৪ · মডিউল ৩ — JWT-ভিত্তিক অথেন্টিকেশন
তৃতীয় টুকরোটি হলো real JWT — M8/L34-এ শেখা একই পদ্ধতি পুনর্ব্যবহার করে: hashlib,
hmac, base64, ও json স্ট্যান্ডার্ড লাইব্রেরি দিয়ে একটি সত্যিকারের
HMAC-SHA256-সাইন করা টোকেন তৈরি ও যাচাই করা হয় — কোনো প্লেসহোল্ডার স্ট্রিং নয়।
# ========== মডিউল ৩: JWT-ভিত্তিক অথেন্টিকেশন (real HMAC-SHA256, M8/L34-এর পদ্ধতি পুনর্ব্যবহার) ==========
import hashlib, hmac, base64, json, time
JWT_SECRET = "capstone-super-secret-key" # বাস্তবে env var/secrets manager-এ রাখা উচিত (L51)
def _b64url_encode(data):
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
def _b64url_decode(s):
padding = "=" * (-len(s) % 4)
return base64.urlsafe_b64decode(s + padding)
def create_jwt(payload, secret=JWT_SECRET):
header = {"alg": "HS256", "typ": "JWT"}
header_b64 = _b64url_encode(json.dumps(header, separators=(",", ":")).encode())
payload_b64 = _b64url_encode(json.dumps(payload, separators=(",", ":")).encode())
signing_input = f"{header_b64}.{payload_b64}".encode()
signature = hmac.new(secret.encode(), signing_input, hashlib.sha256).digest()
signature_b64 = _b64url_encode(signature)
return f"{header_b64}.{payload_b64}.{signature_b64}"
def verify_jwt(token, secret=JWT_SECRET):
try:
header_b64, payload_b64, signature_b64 = token.split(".")
except ValueError:
return None
signing_input = f"{header_b64}.{payload_b64}".encode()
expected_sig = hmac.new(secret.encode(), signing_input, hashlib.sha256).digest()
expected_sig_b64 = _b64url_encode(expected_sig)
if not hmac.compare_digest(expected_sig_b64, signature_b64):
return None
return json.loads(_b64url_decode(payload_b64))
# ---- JWT তৈরি ও সঠিক যাচাই ----
sample_token = create_jwt({"sub": "test_user", "iat": int(time.time())})
print("তৈরি হওয়া টোকেন:", sample_token)
verified_payload = verify_jwt(sample_token)
print("সঠিক টোকেন যাচাইয়ের ফলাফল:", verified_payload)
assert verified_payload is not None and verified_payload["sub"] == "test_user"
# ---- টেম্পারিং সিমুলেট করা: payload অংশের শেষ অক্ষর বদলে দেওয়া ----
header_b64, payload_b64, signature_b64 = sample_token.split(".")
tampered_last_char = "A" if payload_b64[-1] != "A" else "B"
tampered_payload_b64 = payload_b64[:-1] + tampered_last_char
tampered_token = f"{header_b64}.{tampered_payload_b64}.{signature_b64}"
tampered_result = verify_jwt(tampered_token)
print("\nটেম্পার করা টোকেন যাচাইয়ের ফলাফল:", tampered_result)
assert tampered_result is None, "টেম্পার করা টোকেন যাচাই ব্যর্থ হওয়া উচিত ছিল"
print("টেম্পার-শনাক্তকরণ কাজ করছে: স্বাক্ষর মিলছে না, তাই verify_jwt() None রিটার্ন করেছে।")
verify_jwt() কখনোই payload_b64 ডিকোড করার আগে স্বাক্ষর মিলিয়ে দেখে —
hmac.compare_digest() দিয়ে কনস্ট্যান্ট-টাইম তুলনা করা হয় (টাইমিং অ্যাটাক ঠেকাতে)। টেম্পার করা
টোকেনে সিগনেচার অপরিবর্তিত থাকলেও পরিবর্তিত payload_b64 দিয়ে পুনরায় গণনা করা প্রত্যাশিত
সিগনেচার আর মূল সিগনেচারের সাথে মেলে না — তাই যাচাই ব্যর্থ হয়, None রিটার্ন হয়।
৫ · মডিউল ৪ — Router, Auth মিডলওয়্যার ও রুট ওয়্যারিং
চতুর্থ টুকরোটি সব একসাথে জোড়া লাগায় — একটি Router (M5), একটি auth_required
মিডলওয়্যার (M5/M8) যা মডিউল ৩-এর verify_jwt() ব্যবহার করে, এবং চারটি রুট: রেজিস্ট্রেশন, লগইন,
টাস্ক তালিকা (পাবলিক), ও টাস্ক তৈরি (প্রোটেক্টেড)। শুধু টাস্ক তৈরি রুটটিই টোকেন দাবি করে —
এই সরলীকৃত টুলকিটে তালিকা দেখা একটি পাবলিক টাস্ক-বোর্ডের মতো উন্মুক্ত রাখা হয়েছে।
# ========== মডিউল ৪: ইউজার স্টোর, Router ও Auth মিডলওয়্যার ==========
import hashlib, time
USERS_DB = {} # username থেকে {"username":.., "password_hash":..}
def hash_password(password):
# সরলতার জন্য salt ছাড়া SHA-256 -- বাস্তব প্রোডাকশনে bcrypt/argon2/PBKDF2 ব্যবহার করা উচিত
return hashlib.sha256(password.encode()).hexdigest()
class Router:
def __init__(self):
self.routes = {}
def route(self, method, path):
def decorator(handler_fn):
self.routes[(method, path)] = handler_fn
return handler_fn
return decorator
def dispatch(self, method, path, request=None):
request = request or {}
handler = self.routes.get((method, path))
if handler is None:
return {"status": 404, "body": {"error": "Not Found"}}
return handler(request)
app = Router()
def auth_required(handler_fn):
"""মিডলওয়্যার -- Authorization হেডার থেকে JWT বের করে verify_jwt() দিয়ে যাচাই করে,
তবেই আসল হ্যান্ডলার কল করে -- অন্যথায় হ্যান্ডলার পর্যন্ত পৌঁছানোর আগেই 401 রিটার্ন করে"""
def wrapped(request):
auth_header = request.get("headers", {}).get("Authorization", "")
if not auth_header.startswith("Bearer "):
return {"status": 401, "body": {"error": "Unauthorized -- টোকেন পাওয়া যায়নি"}}
raw_token = auth_header[len("Bearer "):]
payload = verify_jwt(raw_token)
if payload is None:
return {"status": 401, "body": {"error": "Unauthorized -- টোকেন অবৈধ বা টেম্পার করা"}}
request["user"] = payload["sub"]
return handler_fn(request)
return wrapped
@app.route("POST", "/register")
def register(request):
username = request["body"]["username"]
password = request["body"]["password"]
if username in USERS_DB:
return {"status": 409, "body": {"error": "এই ইউজারনেম আগে থেকেই আছে"}}
USERS_DB[username] = {"username": username, "password_hash": hash_password(password)}
return {"status": 201, "body": {"username": username}}
@app.route("POST", "/login")
def login(request):
username = request["body"]["username"]
password = request["body"]["password"]
user = USERS_DB.get(username)
if user is None or user["password_hash"] != hash_password(password):
return {"status": 401, "body": {"error": "ইউজারনেম বা পাসওয়ার্ড ভুল"}}
token = create_jwt({"sub": username, "iat": int(time.time())})
return {"status": 200, "body": {"token": token}}
@app.route("GET", "/tasks")
def list_tasks_route(request):
tasks = [{"id": t.id, "title": t.title, "done": t.done, "owner": t.owner} for t in Task.objects.all()]
return {"status": 200, "body": {"tasks": tasks}}
@app.route("POST", "/tasks")
@auth_required
def create_task_route(request):
title = request["body"]["title"]
task = Task.create(title=title, owner=request["user"])
return {"status": 201, "body": {"task": {"id": task.id, "title": task.title, "done": task.done, "owner": task.owner}}}
print("রেজিস্টার করা রুটসমূহ:")
for method, path in app.routes:
print(f" {method:5s} {path}")
@app.route("POST", "/tasks")-এর নিচে @auth_required — ডেকোরেটর নিচ
থেকে উপরে প্রয়োগ হয়, তাই আসলে যা রেজিস্টার হয় তা হলো auth_required(create_task_route)-এর
রিটার্ন করা wrapped ফাংশন। মানে Router.dispatch() যখন এই রুটে পৌঁছায়, প্রথমে
টোকেন যাচাই হয় — যাচাই ব্যর্থ হলে আসল create_task_route লজিকটি একবারও কল হয় না।
৬ · মডিউল ৫ — ক্লায়েন্ট-সাইড Redux-স্টাইল স্টেট স্টোর
পঞ্চম টুকরোটি ক্লায়েন্ট-পাশে — M4-এ শেখা Redux/Flux প্যাটার্নের একটি ছোট্ট, সত্যিকারের কার্যকর সংস্করণ। সার্ভার থেকে যে রেসপন্স আসবে (মডিউল ৪-এর রুট থেকে), ক্লায়েন্ট সেটাকে একটি অ্যাকশন হিসেবে এই স্টোরে dispatch করবে, আর reducer একটি নতুন স্টেট অবজেক্ট তৈরি করবে (immutability বজায় রেখে)।
# ========== মডিউল ৫: ক্লায়েন্ট-সাইড Redux-স্টাইল স্টেট স্টোর ==========
def task_reducer(state, action):
if action["type"] == "AUTH_SUCCESS":
return {**state, "logged_in_user": action["payload"]["username"], "last_event": "AUTH_SUCCESS"}
elif action["type"] == "TASK_CREATED":
return {**state, "tasks": state["tasks"] + [action["payload"]], "last_event": "TASK_CREATED"}
elif action["type"] == "TASK_REJECTED":
return {**state, "last_event": "TASK_REJECTED", "last_error": action["payload"]}
else:
return state
class Store:
def __init__(self, reducer, initial_state):
self.reducer = reducer
self.state = initial_state
self._listeners = []
def dispatch(self, action):
self.state = self.reducer(self.state, action)
for listener in self._listeners:
listener(self.state)
return self.state
def subscribe(self, listener):
self._listeners.append(listener)
def on_state_change(state):
print(f" [সাবস্ক্রাইবার] স্টেট পরিবর্তন হয়েছে, last_event={state.get('last_event')}")
client_store = Store(task_reducer, {"logged_in_user": None, "tasks": [], "last_event": None})
client_store.subscribe(on_state_change)
print("client_store-এর প্রাথমিক স্টেট:", client_store.state)
৭ · মডিউল ৬ — সম্পূর্ণ এন্ড-টু-এন্ড ফ্লো
এখন সবগুলো টুকরো একসাথে — উপরের পাঁচটি মডিউল ব্যবহার করে একটি সম্পূর্ণ ব্যবহারকারী-যাত্রা ট্রেস করা হবে:
(a) রেজিস্ট্রেশন, (b) লগইন করে JWT পাওয়া, (c) টোকেন ছাড়া টাস্ক তৈরির চেষ্টা (প্রত্যাখ্যাত হওয়া উচিত),
(d) সঠিক টোকেন দিয়ে টাস্ক তৈরি (সফল হওয়া উচিত), (e) ক্লায়েন্ট-সাইড স্টোরে TASK_CREATED
dispatch করা, ও (f) চূড়ান্ত ক্লায়েন্ট-স্টেট প্রিন্ট করা।
# ========== মডিউল ৬: সম্পূর্ণ এন্ড-টু-এন্ড ফ্লো ==========
print("=" * 70)
print("ধাপ (a): নতুন ইউজার রেজিস্টার করা")
resp_register = app.dispatch("POST", "/register", {"body": {"username": "rafiq", "password": "sEcr3t!23"}})
print(resp_register)
assert resp_register["status"] == 201
print("\n" + "=" * 70)
print("ধাপ (b): লগইন করে সত্যিকারের সাইন করা JWT পাওয়া")
resp_login = app.dispatch("POST", "/login", {"body": {"username": "rafiq", "password": "sEcr3t!23"}})
print(resp_login)
assert resp_login["status"] == 200
token = resp_login["body"]["token"]
print(f"প্রাপ্ত টোকেন: {token}")
client_store.dispatch({"type": "AUTH_SUCCESS", "payload": {"username": "rafiq"}})
print("\n" + "=" * 70)
print("ধাপ (c): টোকেন ছাড়া টাস্ক তৈরির চেষ্টা -- প্রত্যাখ্যাত হওয়া উচিত")
resp_no_token = app.dispatch("POST", "/tasks", {"headers": {}, "body": {"title": "প্রথম টাস্ক"}})
print(resp_no_token)
assert resp_no_token["status"] == 401, "টোকেন ছাড়া প্রোটেক্টেড রুট প্রত্যাখ্যান করা উচিত ছিল"
print("\n" + "=" * 70)
print("ধাপ (d): সঠিক টোকেন দিয়ে টাস্ক তৈরি -- সফল হওয়া উচিত")
resp_with_token = app.dispatch("POST", "/tasks", {"headers": {"Authorization": f"Bearer {token}"}, "body": {"title": "প্রথম টাস্ক"}})
print(resp_with_token)
assert resp_with_token["status"] == 201, "সঠিক টোকেন দিয়ে টাস্ক তৈরি সফল হওয়া উচিত ছিল"
created_task = resp_with_token["body"]["task"]
print("\n" + "=" * 70)
print("ধাপ (e): ক্লায়েন্ট Redux-স্টাইল স্টোরে TASK_CREATED অ্যাকশন dispatch করা")
old_state = client_store.state
client_store.dispatch({"type": "TASK_CREATED", "payload": created_task})
new_state = client_store.state
print(f"পুরনো স্টেট অবজেক্ট আইডি: {id(old_state)}")
print(f"নতুন স্টেট অবজেক্ট আইডি: {id(new_state)}")
print(f"দুটো ভিন্ন অবজেক্ট (immutability বজায় আছে): {old_state is not new_state}")
print("\n" + "=" * 70)
print("ধাপ (f): চূড়ান্ত ক্লায়েন্ট-সাইড স্টেট")
print(client_store.state)
assert client_store.state["logged_in_user"] == "rafiq"
assert any(t["id"] == created_task["id"] for t in client_store.state["tasks"])
print("\nসারমর্ম: টোকেন ছাড়া 401, সঠিক টোকেনে 201, এবং নতুন টাস্কটি এখন ক্লায়েন্ট-স্টেটে সত্যিই উপস্থিত।")
app.dispatch()
(Router) USERS_DB/TASKS_DB-কে ছুঁয়েছে (CRUD), Task.create() (ORM)
দিয়ে নতুন রেকর্ড তৈরি হয়েছে, auth_required মিডলওয়্যার ধাপ (c)-এ সত্যিকারের প্রত্যাখ্যান করেছে
ও ধাপ (d)-এ সত্যিকারের অনুমতি দিয়েছে (real HMAC-SHA256 JWT যাচাই দিয়ে), এবং client_store
(Redux-স্টাইল) সেই ফলাফল প্রতিফলিত করেছে — সবকিছুই প্রকৃত কম্পিউটেড ফলাফল, কোনো হার্ডকোড করা "simulated
output" নয়।
একটি ফুল-স্ট্যাক ফ্রেমওয়ার্ক আসলে এইরকম কয়েকটি স্বাধীন কিন্তু আন্তঃসংযুক্ত ধারণার সমষ্টি — রাউটিং যা রিকোয়েস্টকে সঠিক কোডে পাঠায়, একটি ডেটা স্তর (CRUD/ORM) যা তথ্য সংরক্ষণ করে, একটি অথেন্টিকেশন স্তর যা প্রমাণ করে "কে করছে", ও একটি ক্লায়েন্ট-সাইড স্টেট স্তর যা ব্যবহারকারীকে সর্বশেষ তথ্য দেখায়। এই কোর্সে আমরা প্রতিটি ধারণা আলাদা আলাদা করে শিখেছি (M1-M12), আর এই শেষ পাঠে দেখলাম কীভাবে তারা একসাথে একটি বাস্তবসম্মত অ্যাপ্লিকেশনে রূপ নেয় — এখন থেকে যেকোনো নির্দিষ্ট ফ্রেমওয়ার্ক (Express, Django, Rails, Laravel, Spring) শেখার সময় এই একই অন্তর্নিহিত ধারণাগুলোই ভিন্ন সিনট্যাক্সে খুঁজে পাবেন।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
ধাপ (c)-এ টোকেন ছাড়া রিকোয়েস্ট 401 পায় কেন, যদিও app.routes-এ POST /tasks-এর জন্য
একটি হ্যান্ডলার ঠিকই রেজিস্টার করা আছে?
কারণ রেজিস্টার করা হ্যান্ডলারটি আসল create_task_route নয় — এটি
auth_required(create_task_route)-এর রিটার্ন করা wrapped ফাংশন।
wrapped() প্রথমে Authorization হেডার চেক করে ও verify_jwt() কল
করে; টোকেন অনুপস্থিত থাকলে এটি সাথে সাথে {"status": 401, ...} রিটার্ন করে দেয় — আসল
create_task_route লজিকটি একবারও এক্সিকিউট হয় না।
প্র ০২
JWT-এর header ও payload তো শুধু base64url-এনকোড করা — এনক্রিপ্টেড না। তাহলে payload-এ একটি অক্ষর
বদলালে কেন verify_jwt() সেটা ধরে ফেলে?
ঠিক ধরেছেন — base64 কোনো এনক্রিপশন নয়, যে কেউ একটি JWT-এর payload পড়তে (ডিকোড করতে) পারে। JWT-এর
স্বাক্ষর (signature) গোপনীয়তা (confidentiality) নয়, বরং ইন্টিগ্রিটি (অক্ষতত্ব)
নিশ্চিত করে — অর্থাৎ payload তৈরির পর কেউ সেটা বদলায়নি। যেহেতু signature গণনা হয়েছিল মূল
(অপরিবর্তিত) header_b64.payload_b64-এর উপর ভিত্তি করে, payload বদলে গেলে
verify_jwt()-এর ভেতরে পুনরায় গণনা করা expected_sig_b64 আর টোকেনের আসল
signature_b64-এর সাথে মেলে না — তাই hmac.compare_digest() False দেয়।
প্র ০৩
task_reducer-এ কেন সরাসরি state["tasks"].append(...) না করে
{**state, "tasks": state["tasks"] + [...]} দিয়ে সম্পূর্ণ নতুন dict রিটার্ন করা হয়েছে?
এটি Redux/Flux প্যাটার্নের একটি মূল নিয়ম — immutability। পুরনো স্টেট অবজেক্ট মিউটেট না
করে নতুন একটি তৈরি করলে old_state is not new_state সবসময় নির্ভরযোগ্যভাবে সত্য থাকে (কোড
সেলে id() দিয়ে এটাই যাচাই করা হয়েছে)। বাস্তব ফ্রন্ট-এন্ড ফ্রেমওয়ার্কে UI লাইব্রেরিগুলো
প্রায়ই এই "রেফারেন্স বদলেছে কি না" পরীক্ষা করেই বুঝতে পারে কখন রি-রেন্ডার করতে হবে — সরাসরি মিউটেট
করলে সেই তুলনা ব্যর্থ হয়ে যেত।
অনুশীলন
-
চিন্তা করুন: ধরুন rafiq একটি সঠিক টোকেন পেয়েছে (ধাপ b-তে), কিন্তু তারপর নিজের পাসওয়ার্ড
বদলে ফেলেছে। এই ক্যাপস্টোন টুলকিটে সেই পুরনো টোকেনটি কি এখনও
/tasks-এ টাস্ক তৈরি করতে কাজ করবে? কেন হ্যাঁ বা না?হ্যাঁ, এখনও কাজ করবে — কারণ
verify_jwt()শুধু স্বাক্ষর ও মেয়াদ (এই সরলীকৃত সংস্করণে মেয়াদ চেকই নেই) যাচাই করে, বর্তমান পাসওয়ার্ডের সাথে কোনো তুলনা করে না। এটাই দেখায় বাস্তব প্রোডাকশন সিস্টেমে কেন টোকেন এক্সপায়ারি (মেয়াদ), রিফ্রেশ টোকেন, ও প্রয়োজনে টোকেন রিভোকেশন (বাতিলকরণ) তালিকার মতো অতিরিক্ত ব্যবস্থা প্রয়োজন হয় — শুধু স্বাক্ষর যাচাই যথেষ্ট নয়। -
পরীক্ষা করুন: মডিউল ৬-এর কোড সেলে ধাপ (b)-এর ঠিক পরে একটি নতুন লাইন যোগ করুন যা ভুল
পাসওয়ার্ড দিয়ে আরেকবার লগইন করার চেষ্টা করে (
app.dispatch("POST", "/login", {"body": {"username": "rafiq", "password": "wrongpass"}})) এবং তার রেসপন্স প্রিন্ট করুন — status কি 401 দেখায়?হ্যাঁ, status 401 দেখাবে — কারণ
login()হ্যান্ডলারেuser["password_hash"] != hash_password(password)শর্তটি সত্যি হয়ে যাবে (hash_password("wrongpass")মূল পাসওয়ার্ডের হ্যাশের সাথে মিলবে না), তাই{"status": 401, "body": {"error": "ইউজারনেম বা পাসওয়ার্ড ভুল"}}রিটার্ন হবে — এটি দেখায় লগইন হ্যান্ডলারও ঠিক টাস্ক-তৈরি রুটের মতোই ভুল ইনপুট সঠিকভাবে প্রত্যাখ্যান করে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস আবার দেখুন ৫৮টি পাঠ · সম্পূর্ণ অভিনন্দন — Full-Stack Web Frameworks কোর্সের সবগুলো পাঠ এখন সম্পূর্ণ। ফাউন্ডেশন থেকে শুরু করে আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি, রিয়েল-টাইম যোগাযোগ, টেস্টিং, ডিপ্লয়মেন্ট, ও এই ক্যাপস্টোন পর্যন্ত — সম্পূর্ণ যাত্রা শেষ হলো।
- আগের পাঠ L57 কেস স্টাডি: একটি ফুল-স্ট্যাক অ্যাপ্লিকেশন স্কেল করা।
- Cybersecurity কোর্স সহোদর কোর্স এই পাঠের JWT ও পাসওয়ার্ড হ্যাশিং সহজীকৃত রাখা হয়েছে শেখার জন্য — বাস্তব প্রোডাকশন-গ্রেড সিকিউরিটি প্র্যাকটিস (bcrypt, টোকেন রিভোকেশন, রেট-লিমিটিং) সেই কোর্সে বিস্তারিত আলোচিত।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।