Menu
CoddyTech

التحقق من كلمة المرور

جزء من قسم المصادقة والأمان في رحلة Node.js على Coddy. الدرس 6 من 24.

يشتق تسجيل الدخول مفتاحًا من كلمة المرور المُدخلة باستخدام الملح المحفوظ وطول المفتاح الأصلي. سيؤدي استخدام ملح جديد إلى مفتاح مختلف، حتى عند إدخال كلمة المرور الصحيحة. لا تُفكّ كلمة المرور الأصلية من التخزين مطلقًا.

// تم تخزين رمز دعوة بصيغة salt.keyHex
const [salt, keyHex] = savedInvite.split('.');
const expected = Buffer.from(keyHex, 'hex');
const attempted = await scryptAsync(inviteCode, salt, 32);

قارن تسلسلي البايتات الناتجين باستخدام timingSafeEqual، بعد التأكد من تطابق طولهما. فهذا يتجنب التوقف المبكر المعتمد على المحتوى، والذي قد يكشف معلومات عبر التوقيت. يتحقق === مع Buffer من الهوية، ولا يضمن إجراء مقارنة السلاسل النصية العادية بزمن ثابت. تستخدم دالة كلمة المرور لديك الفاصل النقطي في سجل كلمة المرور ومفتاحًا بطول 64 بايتًا. يستخدم المسار POST /login الذي تضيفه النتيجة لإرجاع 200 أو 401.

challenge icon

التحدي

سهل

تفتح هذه الخطوة مشروع واجهة API للملاحظات الخاصة المُعدّ مسبقًا في الدورة، بدلًا من ملفات مشروعك من درس لا تخزّن كلمات المرور أبدًا (بما فيها passwords.js). يظل عملك السابق في درسه الخاص. اقرأ الملفات المقدّمة قبل تعديل passwords.js. تحتوي هذه النسخة على دوال مساعدة لتجزئة كلمات المرور ومسارات تصحيح الأخطاء المقدّمة.

يحفظ التسجيل الآن تجزئات مملّحة. أضف تسجيل دخول يتحقق من كلمة المرور بمقارنتها بما حفظه التسجيل.

  1. في passwords.js، أضف دالة async باسم verifyPassword(password, stored) وصدّرها. stored قيمة حفظها التسجيل، مثل f2bb...f065:c3f7...dbd2. أعد true عندما تكون password هي كلمة المرور التي أنشأت هذه القيمة، وfalse خلاف ذلك:
    • خذ الملح من الجزء الذي يسبق النقطتين.
    • جزّئ password باستخدام ذلك الملح إلى مفتاح طوله 64 بايت، بالطريقة نفسها التي تستخدمها hashPassword.
    • فك ترميز المفتاح المحفوظ بعد النقطتين من النظام الست عشري. إذا اختلف طول المفتاحين بالبايت، فأعد false من دون استدعاء timingSafeEqual، كما في مقارنة بايتات المفاتيح. وإلا فأعد نتيجة المقارنة.
  2. في server.js، استورد verifyPassword بجوار hashPassword. أضف POST /login. يستقبل هذا المسار جسم الطلب نفسه الذي يستقبله التسجيل، مثل {"username": "ada", "password": "correct horse"}.
    • اعثر على المستخدم الذي يحمل اسم المستخدم هذا. الدالة verifyPassword بطيئة وتعيد وعدًا، لذا انتظر نتيجتها.
    • عندما يكون المستخدم موجودًا وكلمة المرور صحيحة، أرسل استجابة بالحالة 200 و{"username": "ada"}.
    • وإلا، فأرسل استجابة بالحالة 401 و{"error": "Wrong username or password"}. تعني 401 غير مصرّح به أن الطلب لم يثبت هوية صاحب الطلب.
    • أرسل استجابة 401 نفسها تمامًا عند إدخال كلمة مرور خاطئة وعند استخدام اسم مستخدم لم يسجّل أحد به. عندئذٍ، لن يكشف نص الاستجابة أبدًا لأي شخص أسماء المستخدمين الموجودة.

جرّب ذلك: اضغط على «العب». أرسل POST /signup مع {"username": "ada", "password": "correct horse"}: الحالة 201. أرسل POST /login مع الجسم نفسه: الحالة 200 و{"username": "ada"}. أرسل POST /login مع {"username": "ada", "password": "wrong horse"}: الحالة 401 و{"error": "Wrong username or password"}. أرسل POST /login مع {"username": "nobody", "password": "correct horse"}: ستظهر استجابة 401 نفسها.

جرّب بنفسك

import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';

const scryptAsync = promisify(scrypt);

// يحوّل كلمة المرور إلى النص الذي تخزّنه signup.
export async function hashPassword(password) {
    const salt = randomBytes(16).toString('hex');
    const key = await scryptAsync(password, salt, 64);
    return `${salt}:${key.toString('hex')}`;
}

جميع دروس المصادقة والأمان

تدرّب بنفسك: Playground لـ Express