পাসকী ম্যানেজমেন্ট

এই নির্দেশিকায় বিস্তারিতভাবে বলা হয়েছে যে কীভাবে ব্যবহারকারীদের তাদের রেজিস্টার করা পাসকী দেখতে, নাম পরিবর্তন করতে এবং মুছে ফেলতে দেওয়া যায়, যেখানে Signal API ব্যবহার করে সার্ভার এবং ব্যবহারকারীর পাসওয়ার্ড ম্যানেজারের মধ্যে সেভ করা ক্রেডেনশিয়াল এবং ব্যবহারকারীর প্রোফাইলের বিবরণ সিঙ্ক করা থাকে।

সার্ভার-সাইড অপারেশন

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

  1. সব ব্যবহারকারীর ক্রেডেনশিয়ালের তালিকা তৈরি করুন: সাইন-ইন করা ব্যবহারকারীর আইডির সাথে ম্যাচ করে এমন সব StoredPasskeyCredential রেকর্ড ফেচ করুন।
  2. ক্রেডেনশিয়ালের নাম আপডেট করা: নির্দিষ্ট ক্রেডেনশিয়াল আইডির জন্য নতুন কাস্টম ডাকনাম গ্রহণ করা এবং সার্ভারে আপডেটটি সেভ করা।
  3. ক্রেডেনশিয়াল মুছে দেওয়া: ডেটাবেস থেকে নির্দিষ্ট ক্রেডেনশিয়াল আইডি মুছে দেওয়া।
  4. ব্যবহারকারীর অ্যাকাউন্টের বিবরণ আপডেট করুন: সাইন-ইন করা ব্যবহারকারীর প্রোফাইলের জন্য আপডেট করা name (ইউজারনেম) এবং displayName ভ্যালু গ্রহণ করুন যাতে ক্লায়েন্ট পাসকী প্রদানকারীর সাথে সেগুলি সিঙ্ক করতে পারে।
// Node.js routing example for credential CRUD and user profile rename
router.get('/api/credentials', checkUserAuthenticated, async (req, res) => {
  const list = await db.findCredentialsByUserId(req.user.id);
  return res.json(list);
});

router.put('/api/credential/:id', checkUserAuthenticated, async (req, res) => {
  const { id } = req.params;
  const { name } = req.body;
  const cred = await db.findCredentialById(id);
  if (!cred || cred.passkeyUserId !== req.user.id) {
    return res.status(404).json({ error: 'Credential not found.' });
  }
  cred.name = name;
  await db.saveCredential(cred);
  return res.json(cred);
});

router.delete('/api/credential/:id', checkUserAuthenticated, async (req, res) => {
  const { id } = req.params;
  const cred = await db.findCredentialById(id);
  if (!cred || cred.passkeyUserId !== req.user.id) {
    return res.status(404).json({ error: 'Credential not found.' });
  }
  await db.deleteCredential(id);
  return res.json({ success: true });
});

router.put('/api/user', checkUserAuthenticated, async (req, res) => {
  const { name, displayName } = req.body;
  const updatedUser = await db.updateUser(req.user.id, { name, displayName });
  return res.json(updatedUser);
});

ক্লায়েন্ট-সাইড ম্যানেজমেন্ট UI

একটি ডেডিকেটেড সেটিংস প্যানেল রেন্ডার করুন যা ব্যবহারকারীদের সহজেই তাদের রেজিস্টার করা যাচাইকরণ বিকল্প এবং অ্যাকাউন্ট প্রোফাইলের বিবরণ অডিট এবং ম্যানেজ করতে দেয়:

  1. সেভ করা তালিকা দেখানো: আপনার এন্ডপয়েন্ট থেকে তালিকা নিয়ে এসে ক্রেডেনশিয়ালের আলাদা আলাদা সারি রেন্ডার করা। উত্তর খালি থাকলে, একটি সহায়ক খালি-স্টেট মেসেজ দেখান (যেমন, "কোনও পাসকী পাওয়া যায়নি")।
  2. AAGUID মেটাডেটা ম্যাপ করুন: প্রতিটি পাসকী-র জন্য, এর পরিষেবা প্রদানকারীর বিবরণ রেন্ডার করতে আপনার স্থানীয় রেজিস্ট্রির বিপরীতে এর aaguid প্রপার্টি দেখুন। আরও তথ্যের জন্য AAGUID থেকে পাসকী প্রদানকারী নির্ধারণ করুন বিভাগ দেখুন।
  3. প্রতিটি আইটেমের জন্য UI সংক্রান্ত প্রয়োজনীয়তা: তালিকা কন্টেনারের মধ্যে প্রতিটি সারি অবশ্যই রেন্ডার করতে হবে:
    • পরিষেবা প্রদানকারীর আইকন: AAGUID-থেকে প্রাপ্ত ছবি বা ডেটা URI।
    • প্রদানকারী/কাস্টম নাম: AAGUID থেকে পাওয়া নাম বা ব্যবহারকারীর পরিবর্তন করা স্ট্রিং।
    • রেজিস্ট্রেশনের তারিখ: ডেটাবেসে সেভ করা র' ইপক টাইমস্ট্যাম্প registeredAt যা ক্লায়েন্টকে দেখানোর জন্য মানুষের-পঠনযোগ্য তারিখে ফর্ম্যাট করা হয়েছে।
    • শেষবার ব্যবহার করার তারিখ: ক্লায়েন্টকে দেখানোর জন্য ডেটাবেসে সেভ করা র এপোচ টাইমস্ট্যাম্প lastUsedAt মানুষের-পঠনযোগ্য তারিখে (যদি থাকে) ফর্ম্যাট করা হয়েছে।
    • নাম পরিবর্তন করার বোতাম: সার্ভারে (PUT /api/credential/:id) ক্রেডেনশিয়ালের কাস্টম ডাকনাম আপডেট করার জন্য নাম পরিবর্তন করার অ্যাকশন ট্রিগার করে। মনে রাখবেন: কোনও নির্দিষ্ট পাসকী-র ডাকনাম পরিবর্তন করা হল সার্ভার-সাইড লেবেল পরিবর্তন এবং এটি signalCurrentUserDetails()-কে ইনভোক করে না।
    • মুছে দেওয়ার বোতাম: ক্রেডেনশিয়াল (DELETE /api/credential/:id) মুছে দেওয়ার ট্রিগার করে এবং signalAllAcceptedCredentials()-এর মাধ্যমে বাকি ক্রেডেনশিয়াল আইডি সিঙ্ক করে।
  4. ব্যবহারকারীর অ্যাকাউন্ট রিনেম করার UI:
    • ব্যবহারকারীকে তার অ্যাকাউন্টের ইউজারনেম (name) বা displayName (PUT /api/user) আপডেট করার অনুমতি দেওয়ার জন্য একটি কন্ট্রোল প্রদান করুন এবং PublicKeyCredential.signalCurrentUserDetails()-এর মাধ্যমে পাসওয়ার্ড ম্যানেজারের সাথে আপডেট করা ব্যবহারকারীর বিবরণ অবিলম্বে সিঙ্ক করুন।
  5. কন্ডিশনাল "পাসকী তৈরি করুন" বোতাম:
    • ম্যানেজমেন্ট পৃষ্ঠায় "পাসকী তৈরি করুন" রেজিস্ট্রেশন ট্রিগার বোতামটি স্পষ্টভাবে দেখান। এই UI এলিমেন্ট রেন্ডার করার আগে, প্ল্যাটফর্ম যাচাইকারী কাজ করছে কিনা তা যাচাই করতে, পৃষ্ঠায় অবশ্যই PublicKeyCredential.getClientCapabilities() ব্যবহার করে ফিচার-শনাক্তকরণ ক্ষমতা থাকতে হবে। পাসকী কাজ না করলে, এই বোতামটি লুকান এবং পরিবর্তে স্ট্যান্ডার্ড MFA এনরোলমেন্টের জন্য উৎসাহিত করুন।
    • navigator.credentials.create() কলে authenticatorSelection.authenticatorAttachment বাদ দিয়ে নিরাপত্তা কী রেজিস্টার করার অনুমতি দিন।

সিগন্যাল API সিঙ্ক্রোনাইজেশন

Signal API, অ্যাপ্লিকেশনকে পাসওয়ার্ড ম্যানেজারের সাথে ক্রেডেনশিয়াল স্ট্যাটাস শেয়ার করার অনুমতি দেয়, যার ফলে ব্যবহারকারীর সিঙ্ক করা ভল্ট এবং আপনার ব্যাকএন্ড ডেটাবেস লকস্টেপে থাকে।

  • ফিচার শনাক্তকরণ ও প্যারামিটার এনকোডিং সংক্রান্ত নিয়ম:
    • সিগন্যাল API পদ্ধতি ইনভোক করার আগে সবসময় ফিচার-ডিটেক্ট করুন (if (PublicKeyCredential.signalAllAcceptedCredentials) এবং if (PublicKeyCredential.signalCurrentUserDetails))।
    • Signal API পদ্ধতিতে (signalAllAcceptedCredentials, signalCurrentUserDetails) পাস করা সমস্ত userId এবং ক্রেডেনশিয়াল আইডি প্যারামিটার অবশ্যই Base64URL-এনকোডেড স্ট্রিং হতে হবে।
  • ইনিশিয়েটিং পেজ লোড সিঙ্ক:
    • ম্যানেজমেন্ট পৃষ্ঠা লোড হওয়ার সময় অ্যাপ্লিকেশনকে অবশ্যই অটোমেটিক signalAllAcceptedCredentials() কল করতে হবে (যেমন, DOMContentLoaded ইভেন্ট লিসনার, মডিউল ইনিশিয়ালাইজেশন বা ফ্রেমওয়ার্ক কম্পোনেন্ট মাউন্ট হুক যেমন useEffect-এর মধ্যে)।
  • ম্যানেজমেন্ট আপডেট সিঙ্ক:
    • অ্যাপ্লিকেশনটি অবশ্যই আপনার মুছে ফেলা ক্রেডেনশিয়াল ক্লিক হ্যান্ডলার পোস্ট-ফেচের মধ্যে অবিলম্বে signalAllAcceptedCredentials() কল করতে হবে।
    • অ্যাপ্লিকেশনটিকে অবশ্যই আপনার ব্যবহারকারীর অ্যাকাউন্টের নাম পরিবর্তন (name বা displayName) ক্লিক হ্যান্ডলার পোস্ট-ফেচের মধ্যে অবিলম্বে signalCurrentUserDetails() কল করতে হবে।
// Client-side management synchronization ES module
import { listFetch, renameCredentialFetch, updateUserFetch, deleteFetch } from './api.js';

// Base64URL-encoded User ID string (illustration only)
const base64UrlUserId = "M2YPl-KGnA8";

async function syncAcceptedCredentials(currentCredentialsList) {
  if (!window.PublicKeyCredential || !PublicKeyCredential.signalAllAcceptedCredentials) return;
  try {
    const credentialIds = currentCredentialsList.map(c => c.id); // Array of Base64URL credential ID strings
    
    await PublicKeyCredential.signalAllAcceptedCredentials({
      rpId, // RP ID must match the one defined on the server
      userId: base64UrlUserId, // User ID Base64URL-encoded string
      allAcceptedCredentialIds: credentialIds
    });
  } catch (e) {
    console.error('SignalAllAcceptedCredentials sync failure:', e);
  }
}

async function loadManagementPanel() {
  const response = await listFetch();
  const list = await response.json();
  
  renderUI(list);
  // Sync accepted credentials on initial load
  await syncAcceptedCredentials(list);
}

async function performDelete(credentialId) {
  const response = await deleteFetch(credentialId);
  if (response.ok) {
    const updatedResponse = await listFetch();
    const updatedList = await updatedResponse.json();
    
    renderUI(updatedList);
    // Sync remaining accepted credentials after deletion
    await syncAcceptedCredentials(updatedList);
  }
}

// Renaming a passkey's nickname updates the server record only
async function performCredentialRename(credentialId, newCredentialName) {
  const response = await renameCredentialFetch(credentialId, { name: newCredentialName });
  if (response.ok) {
    const updatedResponse = await listFetch();
    renderUI(await updatedResponse.json());
  }
}

// Renaming the user's account username or displayName triggers signalCurrentUserDetails
async function performUserRename(rpId, userId, updatedName, updatedDisplayName) {
  const response = await updateUserFetch({ name: updatedName, displayName: updatedDisplayName });
  if (response.ok && window.PublicKeyCredential && PublicKeyCredential.signalCurrentUserDetails) {
    try {
      await PublicKeyCredential.signalCurrentUserDetails({
        rpId, // RP ID must match the one defined on the server
        userId, // Base64URL-encoded user ID
        name: updatedName, // Updated username
        displayName: updatedDisplayName // Updated display name
      });
    } catch (e) {
      console.error('SignalCurrentUserDetails sync failure:', e);
    }
  }
}

window.addEventListener('DOMContentLoaded', loadManagementPanel);

AAGUID থেকে পাসকী প্রদানকারী নির্ধারণ করুন

AAGUID (Authenticator Attestation Globally Unique Identifier) হল ১২৮-বিট শনাক্তকারী যা প্রমাণীকরণকারীর মডেলকে উপস্থাপন করে, কোনও নির্দিষ্ট ইনস্ট্যান্সকে নয়। পাসকী রেজিস্ট্রেশনের সময় এটি যাচাইকারীর ডেটায় অন্তর্ভুক্ত থাকে এবং কোন পাসকী প্রদানকারী (যেমন, Google Password Manager, iCloud Keychain, 1Password) ক্রেডেনশিয়াল তৈরি করেছে তা নির্ধারণ করতে ব্যবহার করা যেতে পারে।

AAGUID শুধুমাত্র পাসকী ম্যানেজমেন্টের ক্ষেত্রে ব্যবহারকারীদের সাহায্য করার জন্য ব্যবহার করা উচিত। ক্রিপ্টোগ্রাফিক্যালি অ্যাটেস্ট করা না হলে এটি পরিবর্তন করা যেতে পারে, যা প্ল্যাটফর্ম পাসকী বর্তমানে কাজ করে না।

১. AAGUID রেজিস্ট্রি

কমিউনিটি দ্বারা পরিচালিত AAGUID-এর সাথে পরিষেবা প্রদানকারীর নাম ও আইকনের JSON ম্যাপিং এখানে উপলভ্য:

https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/refs/heads/main/combined_aaguid.json

প্রতিটি এন্ট্রিতে নিম্নলিখিত স্কিমা থাকে:

{
  "<aaguid-uuid>": {
    "name": "Provider Name",
    "icon_light": "data:image/png;base64,...",
    "icon_dark": "data:image/png;base64,..."
  }
}

২. রেজিস্ট্রেশনের পরে AAGUID ব্যবহার করা

রেজিস্ট্রেশন সংক্রান্ত উত্তর যাচাই করার পরে, রেজিস্ট্রেশন ফলাফল থেকে aaguid পড়ুন এবং ক্রেডেনশিয়ালের name ও providerIcon পূরণ করতে রেজিস্ট্রি থেকে এটি খুঁজে নিন:

রেজিস্ট্রিতে AAGUID খোঁজার আগে, এটি '00000000-0000-0000-0000-000000000000'-এর সমান কিনা তা চেক করুন। যদি তাই হয়, তাহলে রেজিস্ট্রি লুকআপ এড়িয়ে যান এবং name-কে ফলব্যাক (যেমন, ইউজার-এজেন্ট থেকে ডিভাইসের নাম বা "অজানা পাসকী প্রদানকারী") এবং providerIcon-কে undefined হিসেবে সেট করুন। শুধুমাত্র নন-জিরো AAGUID-এর জন্য রেজিস্ট্রি দেখুন।

import aaguids from './aaguids.json' with { type: 'json' };

const { aaguid } = registrationInfo;
if (aaguid === '00000000-0000-0000-0000-000000000000') {
  // use the device name as the passkey provider based on
  // the information derived from the user agent string,
  // or just say "Unknown passkey provider"
} else {
  const provider = aaguids[aaguid];
  const credential = {
    // ...other fields
    aaguid,
    name: provider?.name || 'Unknown passkey provider',
    providerIcon: provider?.icon_light,
  };
}

ফলব্যাক স্ট্র্যাটেজি

পাসকী ফিচার শনাক্তকরণ ফলব্যাক

সবসময় 'webauthn-polyfills' ইনস্টল করুন এবং কনটেক্সটে এটি ইমপোর্ট করুন। যতক্ষণ PublicKeyCredential কাজ করবে, ততক্ষণ PublicKeyCredential.getClientCapabilities-ও কাজ করবে।

import 'webauthn-polyfills';

সিগন্যাল API সিঙ্ক্রোনাইজেশন ফলব্যাক

ব্রাউজারে PublicKeyCredential.parseRequestOptionsFromJSON কাজ না করলে, 'webauthn-polyfills' ব্যবহার করুন:

<script type="module"> 
  if (!PublicKeyCredential.parseRequestOptionsFromJSON) { 
     await import('https://unpkg.com/webauthn-polyfills'); 
   } 
 </script> 

এটি PublicKeyCredential.prototype.toJSON-এর জন্য সহায়তাও যোগ করবে।