إنشاء واجهة برمجة تطبيقات آمنة لـ REST في Node.js

نشرت: 2022-03-11

واجهات برمجة التطبيقات (APIs) موجودة في كل مكان. إنها تمكن البرامج من الاتصال بأجزاء أخرى من البرامج - داخلية أو خارجية - بشكل متسق ، وهو عنصر أساسي في قابلية التوسع ، ناهيك عن إعادة الاستخدام.

من الشائع جدًا في الوقت الحاضر أن تحتوي الخدمات عبر الإنترنت على واجهات برمجة تطبيقات عامة. يتيح ذلك للمطورين الآخرين دمج الميزات بسهولة مثل عمليات تسجيل الدخول إلى الوسائط الاجتماعية ومدفوعات بطاقات الائتمان وتتبع السلوك. يُطلق على المعيار الواقعي الذي يستخدمونه لهذا الغرض نقل الحالة التمثيلية (REST).

بينما يمكن استخدام العديد من الأنظمة الأساسية ولغات البرمجة للمهمة - على سبيل المثال ، ASP.NET Core أو Laravel (PHP) أو Bottle (Python) - في هذا البرنامج التعليمي ، سنبني واجهة REST API أساسية ولكن آمنة باستخدام المكدس التالي:

  • Node.js ، والتي يجب أن يكون القارئ على دراية بها بالفعل
  • Express ، الذي يبسط إلى حد كبير بناء مهام خادم الويب الشائعة ضمن Node.js وهو أجرة قياسية في بناء الواجهة الخلفية لـ REST API
  • Mongoose ، والذي سيربط نهايتنا الخلفية بقاعدة بيانات MongoDB

يجب أن يكون المطورون الذين يتبعون هذا البرنامج التعليمي مرتاحين أيضًا مع المحطة (أو موجه الأوامر).

ملاحظة: لن نغطي هنا قاعدة كود الواجهة الأمامية ، ولكن حقيقة أن نهايتنا الخلفية مكتوبة بجافا سكريبت تجعل من الملائم مشاركة الكود - نماذج الكائنات ، على سبيل المثال - عبر المكدس الكامل.

تشريح واجهة برمجة تطبيقات REST

تُستخدم واجهات برمجة تطبيقات REST للوصول إلى البيانات ومعالجتها باستخدام مجموعة مشتركة من العمليات عديمة الحالة. تعد هذه العمليات جزءًا لا يتجزأ من بروتوكول HTTP وتمثل وظائف الإنشاء والقراءة والتحديث والحذف الأساسية (CRUD) ، على الرغم من أنها ليست بطريقة فردية نظيفة:

  • POST (إنشاء مورد أو توفير البيانات بشكل عام)
  • GET (استرداد فهرس الموارد أو مورد فردي)
  • PUT (إنشاء أو استبدال مورد)
  • PATCH (تحديث / تعديل مورد)
  • DELETE (إزالة مورد)

باستخدام عمليات HTTP واسم المورد كعنوان ، يمكننا بناء واجهة برمجة تطبيقات REST عن طريق إنشاء نقطة نهاية لكل عملية. ومن خلال تنفيذ النمط ، سيكون لدينا أساس مستقر وسهل الفهم يمكننا من تطوير الكود بسرعة والحفاظ عليه بعد ذلك. كما ذكرنا سابقًا ، سيتم استخدام الأساس نفسه لدمج ميزات الجهات الخارجية ، والتي يستخدم معظمها أيضًا واجهات برمجة تطبيقات REST ، مما يجعل هذا التكامل أسرع.

في الوقت الحالي ، لنبدأ في إنشاء واجهة برمجة تطبيقات REST الآمنة الخاصة بنا باستخدام Node.js!

في هذا البرنامج التعليمي ، سننشئ واجهة برمجة تطبيقات REST شائعة جدًا (وعملية جدًا) لمورد يسمى users .

سيكون لموردنا الهيكل الأساسي التالي:

  • id (UUID تم إنشاؤه تلقائيًا)
  • firstName
  • lastName
  • email
  • password
  • permissionLevel ، مستوى (ما الذي يسمح لهذا المستخدم أن يفعل؟)

وسننشئ العمليات التالية لهذا المورد:

  • POST على نقطة النهاية /users (إنشاء مستخدم جديد)
  • GET على نقطة النهاية /users (قائمة جميع المستخدمين)
  • GET على نقطة النهاية /users/:userId (احصل على مستخدم معين)
  • PATCH على نقطة النهاية /users/:userId (تحديث البيانات لمستخدم معين)
  • DELETE في نقطة النهاية /users/:userId (إزالة مستخدم معين)

سنستخدم أيضًا رموز الويب JSON (JWTs) للوصول إلى رموز الوصول. لتحقيق هذه الغاية ، سننشئ موردًا آخر يسمى auth والذي يتوقع البريد الإلكتروني وكلمة المرور للمستخدم ، وفي المقابل ، سننشئ الرمز المميز المستخدم للمصادقة في عمليات معينة. (تقدم مقالة Dejan Milosevic الرائعة عن JWT لتطبيقات REST الآمنة في Java مزيدًا من التفاصيل حول هذا الموضوع ؛ المبادئ هي نفسها.)

إعداد البرنامج التعليمي REST API

بادئ ذي بدء ، تأكد من تثبيت أحدث إصدار من Node.js. بالنسبة لهذه المقالة ، سأستخدم الإصدار 14.9.0 ؛ قد يعمل أيضًا على الإصدارات الأقدم.

بعد ذلك ، تأكد من تثبيت برنامج MongoDB. لن نشرح تفاصيل Mongoose و MongoDB المستخدمة هنا ، ولكن لتشغيل الأساسيات ، ما عليك سوى تشغيل الخادم في الوضع التفاعلي (أي من سطر الأوامر مثل mongo ) بدلاً من الخدمة. هذا لأنه ، في مرحلة ما من هذا البرنامج التعليمي ، سنحتاج إلى التفاعل مع MongoDB مباشرةً بدلاً من التفاعل عبر كود Node.js الخاص بنا.

ملاحظة: مع MongoDB ، ليست هناك حاجة لإنشاء قاعدة بيانات محددة كما هو الحال في بعض سيناريوهات RDBMS. ستؤدي أول مكالمة إدراج من كود Node.js إلى إنشائها تلقائيًا.

لا يحتوي هذا البرنامج التعليمي على جميع التعليمات البرمجية اللازمة لمشروع عمل. من المفترض بدلاً من ذلك استنساخ الريبو المصاحب ومتابعة النقاط البارزة ببساطة أثناء قراءتك - ولكن يمكنك أيضًا النسخ في ملفات ومقتطفات معينة من الريبو حسب الحاجة ، إذا كنت تفضل ذلك.

انتقل إلى المجلد rest-api-tutorial/ الناتج في جهازك الطرفي. سترى أن مشروعنا يحتوي على ثلاثة مجلدات وحدة:

  • common (التعامل مع جميع الخدمات المشتركة ، والمعلومات المشتركة بين وحدات المستخدم)
  • users (كل ما يتعلق بالمستخدمين)
  • auth (التعامل مع إنشاء JWT وتدفق تسجيل الدخول)

الآن ، قم بتشغيل npm install (أو yarn إذا كان لديك.)

تهانينا ، لديك الآن كل التبعيات والإعداد المطلوب لتشغيل الواجهة الخلفية REST API البسيطة.

إنشاء وحدة المستخدم

سنستخدم Mongoose ، وهي مكتبة لنمذجة بيانات الكائن (ODM) لـ MongoDB ، لإنشاء نموذج المستخدم داخل مخطط المستخدم.

أولاً ، نحتاج إلى إنشاء مخطط النمس في /users/models/users.model.js :

 const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });

بمجرد تحديد المخطط ، يمكننا بسهولة إرفاق المخطط بنموذج المستخدم.

 const userModel = mongoose.model('Users', userSchema);

بعد ذلك ، يمكننا استخدام هذا النموذج لتنفيذ جميع عمليات CRUD التي نريدها ضمن نقاط نهاية Express الخاصة بنا.

لنبدأ بعملية "إنشاء مستخدم" بتحديد المسار في users/routes.config.js :

 app.post('/users', [ UsersController.insert ]);

يتم سحب هذا إلى تطبيق Express في ملف index.js الرئيسي. يتم استيراد كائن UsersController من وحدة التحكم الخاصة بنا ، حيث نقوم بتجزئة كلمة المرور بشكل مناسب ، كما هو محدد في /users/controllers/users.controller.js :

 exports.insert = (req, res) => { let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512',salt) .update(req.body.password) .digest("base64"); req.body.password = salt + "$" + hash; req.body.permissionLevel = 1; UserModel.createUser(req.body) .then((result) => { res.status(201).send({id: result._id}); }); };

في هذه المرحلة ، يمكننا اختبار نموذج Mongoose الخاص بنا عن طريق تشغيل الخادم ( npm start ) وإرسال طلب POST إلى /users مع بعض بيانات JSON:

 { "firstName" : "Marcos", "lastName" : "Silva", "email" : "[email protected]", "password" : "s3cr3tp4sswo4rd" }

هناك العديد من الأدوات التي يمكنك استخدامها لهذا الغرض. الأرق (المغطى أدناه) و Postman من أدوات واجهة المستخدم الرسومية الشائعة ، و curl هو خيار CLI شائع. يمكنك حتى استخدام JavaScript فقط ، على سبيل المثال ، من وحدة تحكم أدوات التطوير المضمنة في المتصفح الخاص بك:

 fetch('http://localhost:3600/users', { method: 'POST', headers: { "Content-type": "application/json" }, body: JSON.stringify({ "firstName": "Marcos", "lastName": "Silva", "email": "[email protected]", "password": "s3cr3tp4sswo4rd" }) }) .then(function(response) { return response.json(); }) .then(function(data) { console.log('Request succeeded with JSON response', data); }) .catch(function(error) { console.log('Request failed', error); });

في هذه المرحلة ، ستكون نتيجة المشاركة الصالحة هي المعرف فقط من المستخدم الذي تم إنشاؤه: { "id": "5b02c5c84817bf28049e58a3" } . نحتاج أيضًا إلى إضافة التابع createUser إلى النموذج في users/models/users.model.js :

 exports.createUser = (userData) => { const user = new User(userData); return user.save(); };

تم تعيين كل شيء ، والآن نحتاج إلى معرفة ما إذا كان المستخدم موجودًا. لذلك ، سنقوم بتنفيذ ميزة "get user by id" لنقطة النهاية التالية: users/:userId .

أولاً ، نقوم بإنشاء مسار في /users/routes/config.js :

 app.get('/users/:userId', [ UsersController.getById ]);

بعد ذلك ، نقوم بإنشاء وحدة التحكم في /users/controllers/users.controller.js :

 exports.getById = (req, res) => { UserModel.findById(req.params.userId).then((result) => { res.status(200).send(result); }); };

وأخيرًا ، أضف التابع findById إلى النموذج في /users/models/users.model.js :

 exports.findById = (id) => { return User.findById(id).then((result) => { result = result.toJSON(); delete result._id; delete result.__v; return result; }); };

سيكون الرد على هذا النحو:

 { "firstName": "Marcos", "lastName": "Silva", "email": "[email protected]", "password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==", "permissionLevel": 1, "id": "5b02c5c84817bf28049e58a3" }

لاحظ أنه يمكننا رؤية كلمة المرور المجزأة. في هذا البرنامج التعليمي ، نعرض كلمة المرور ، ولكن أفضل الممارسات الواضحة هي عدم الكشف عن كلمة المرور مطلقًا ، حتى لو تم تجزئتها. شيء آخر يمكننا رؤيته هو مستوى permissionLevel ، والذي سنستخدمه للتعامل مع أذونات المستخدم لاحقًا.

بتكرار النمط الموضح أعلاه ، يمكننا الآن إضافة وظيفة لتحديث المستخدم. سنستخدم عملية PATCH لأنها ستمكننا من إرسال الحقول التي نريد تغييرها فقط. وبالتالي ، سيكون المسار PATCH to /users/:userid ، وسنرسل أي حقول نريد تغييرها. سنحتاج أيضًا إلى تنفيذ بعض عمليات التحقق الإضافية نظرًا لأن التغييرات يجب أن تقتصر على المستخدم المعني أو المسؤول ، ويجب أن يكون المسؤول فقط قادرًا على تغيير مستوى permissionLevel . سنتخطى ذلك الآن ونعود إليه بمجرد تنفيذ وحدة المصادقة. في الوقت الحالي ، ستبدو وحدة التحكم الخاصة بنا كما يلي:

 exports.patchById = (req, res) => { if (req.body.password){ let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64"); req.body.password = salt + "$" + hash; } UserModel.patchUser(req.params.userId, req.body).then((result) => { res.status(204).send({}); }); };

بشكل افتراضي ، سنرسل رمز HTTP 204 بدون نص استجابة للإشارة إلى نجاح الطلب.

وسنحتاج إلى إضافة التابع patchUser إلى النموذج:

 exports.patchUser = (id, userData) => { return User.findOneAndUpdate({ _id: id }, userData); };

سيتم تنفيذ قائمة المستخدمين باعتبارها GET at /users/ بواسطة وحدة التحكم التالية:

 exports.list = (req, res) => { let limit = req.query.limit && req.query.limit <= 100 ? parseInt(req.query.limit) : 10; let page = 0; if (req.query) { if (req.query.page) { req.query.page = parseInt(req.query.page); page = Number.isInteger(req.query.page) ? req.query.page : 0; } } UserModel.list(limit, page).then((result) => { res.status(200).send(result); }) };

ستكون طريقة النموذج المقابلة:

 exports.list = (perPage, page) => { return new Promise((resolve, reject) => { User.find() .limit(perPage) .skip(perPage * page) .exec(function (err, users) { if (err) { reject(err); } else { resolve(users); } }) }); };

سيكون لاستجابة القائمة الناتجة الهيكل التالي:

 [ { "firstName": "Marco", "lastName": "Silva", "email": "[email protected]", "password": "z4tS/DtiH+0Gb4J6QN1K3w==$al6sGxKBKqxRQkDmhnhQpEB6+DQgDRH2qr47BZcqLm4/fphZ7+a9U+HhxsNaSnGB2l05Oem/BLIOkbtOuw1tXA==", "permissionLevel": 1, "id": "5b02c5c84817bf28049e58a3" }, { "firstName": "Paulo", "lastName": "Silva", "email": "[email protected]", "password": "wTsqO1kHuVisfDIcgl5YmQ==$cw7RntNrNBNw3MO2qLbx959xDvvrDu4xjpYfYgYMxRVDcxUUEgulTlNSBJjiDtJ1C85YimkMlYruU59rx2zbCw==", "permissionLevel": 1, "id": "5b02d038b653603d1ca69729" } ]

والجزء الأخير الذي سيتم تنفيذه هو DELETE at /users/:userId .

ستكون وحدة التحكم الخاصة بنا للحذف:

 exports.removeById = (req, res) => { UserModel.removeById(req.params.userId) .then((result)=>{ res.status(204).send({}); }); };

كما كان من قبل ، ستعيد وحدة التحكم كود HTTP 204 ولا يوجد نص محتوى كتأكيد.

يجب أن تبدو طريقة النموذج المقابلة كما يلي:

 exports.removeById = (userId) => { return new Promise((resolve, reject) => { User.deleteMany({_id: userId}, (err) => { if (err) { reject(err); } else { resolve(err); } }); }); };

لدينا الآن جميع العمليات اللازمة للتعامل مع مورد المستخدم ، وقد انتهينا من وحدة تحكم المستخدم. الفكرة الرئيسية لهذا الكود هي تزويدك بالمفاهيم الأساسية لاستخدام نمط REST. سنحتاج إلى العودة إلى هذا الرمز لتنفيذ بعض عمليات التحقق من الصحة والأذونات الخاصة به ، ولكن أولاً ، سنحتاج إلى البدء في بناء الأمان الخاص بنا. لنقم بإنشاء وحدة المصادقة.

إنشاء وحدة المصادقة

قبل أن نتمكن من تأمين وحدة users من خلال تنفيذ البرامج الوسيطة للإذن والتحقق ، سنحتاج إلى أن نكون قادرين على إنشاء رمز مميز صالح للمستخدم الحالي. سننشئ JWT ردًا على المستخدم الذي يقدم بريدًا إلكترونيًا وكلمة مرور صالحين. JWT هو رمز ويب JSON مميز يمكنك استخدامه لجعل المستخدم يقوم بعدة طلبات بأمان دون التحقق من صحته بشكل متكرر. عادة ما يكون لها وقت انتهاء الصلاحية ، ويتم إعادة إنشاء رمز جديد كل بضع دقائق للحفاظ على الاتصال آمنًا. في هذا البرنامج التعليمي ، على الرغم من ذلك ، سنتخلى عن تحديث الرمز المميز ونبقيه بسيطًا باستخدام رمز مميز واحد لكل تسجيل دخول.

أولاً ، سننشئ نقطة نهاية لطلبات POST إلى /auth على المورد. سيحتوي نص الطلب على البريد الإلكتروني وكلمة المرور للمستخدم:

 { "email" : "[email protected]", "password" : "s3cr3tp4sswo4rd2" }

قبل أن نشرك وحدة التحكم ، يجب علينا التحقق من صحة المستخدم في /authorization/middlewares/verify.user.middleware.js :

 exports.isPasswordAndUserMatch = (req, res, next) => { UserModel.findByEmail(req.body.email) .then((user)=>{ if(!user[0]){ res.status(404).send({}); }else{ let passwordFields = user[0].password.split('$'); let salt = passwordFields[0]; let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64"); if (hash === passwordFields[1]) { req.body = { userId: user[0]._id, email: user[0].email, permissionLevel: user[0].permissionLevel, provider: 'email', name: user[0].firstName + ' ' + user[0].lastName, }; return next(); } else { return res.status(400).send({errors: ['Invalid email or password']}); } } }); };

بعد القيام بذلك ، يمكننا الانتقال إلى وحدة التحكم وإنشاء JWT:

 exports.login = (req, res) => { try { let refreshId = req.body.userId + jwtSecret; let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64"); req.body.refreshKey = salt; let token = jwt.sign(req.body, jwtSecret); let b = Buffer.from(hash); let refresh_token = b.toString('base64'); res.status(201).send({accessToken: token, refreshToken: refresh_token}); } catch (err) { res.status(500).send({errors: err}); } };

على الرغم من أننا لن نقوم بتحديث الرمز المميز في هذا البرنامج التعليمي ، فقد تم إعداد وحدة التحكم لتمكين هذا الجيل لتسهيل تنفيذه في التطوير اللاحق.

كل ما نحتاجه الآن هو إنشاء المسار واستدعاء البرامج الوسيطة المناسبة في /authorization/routes.config.js :

 app.post('/auth', [ VerifyUserMiddleware.hasAuthValidFields, VerifyUserMiddleware.isPasswordAndUserMatch, AuthorizationController.login ]);

ستحتوي الاستجابة على JWT الذي تم إنشاؤه في حقل accessToken:

 { "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YjAyYzVjODQ4MTdiZjI4MDQ5ZTU4YTMiLCJlbWFpbCI6Im1hcmNvcy5oZW5yaXF1ZUB0b3B0YWwuY29tIiwicGVybWlzc2lvbkxldmVsIjoxLCJwcm92aWRlciI6ImVtYWlsIiwibmFtZSI6Ik1hcmNvIFNpbHZhIiwicmVmcmVzaF9rZXkiOiJiclhZUHFsbUlBcE1PakZIRG1FeENRPT0iLCJpYXQiOjE1MjY5MjMzMDl9.mmNg-i44VQlUEWP3YIAYXVO-74803v1mu-y9QPUQ5VY", "refreshToken": "U3BDQXBWS3kyaHNDaGJNanlJTlFkSXhLMmFHMzA2NzRsUy9Sd2J0YVNDTmUva0pIQ0NwbTJqOU5YZHgxeE12NXVlOUhnMzBWMGNyWmdOTUhSaTdyOGc9PQ==" }

بعد إنشاء الرمز المميز ، يمكننا استخدامه داخل رأس Authorization باستخدام نموذج Bearer ACCESS_TOKEN .

إنشاء البرامج الوسيطة للأذونات وعمليات التحقق من الصحة

أول شيء يجب أن نحدده هو من يمكنه استخدام موارد users . هذه هي السيناريوهات التي سنحتاج إلى التعامل معها:

  • عام لإنشاء المستخدمين (عملية التسجيل). لن نستخدم JWT لهذا السيناريو.
  • خاص للمستخدم الذي قام بتسجيل الدخول وللمسؤولين لتحديث هذا المستخدم.
  • خاص للمسؤول فقط لإزالة حسابات المستخدمين.

بعد تحديد هذه السيناريوهات ، سنطلب أولاً برمجية وسيطة تتحقق دائمًا من صحة المستخدم إذا كان يستخدم JWT صالحًا. يمكن أن تكون البرامج الوسيطة في /common/middlewares/auth.validation.middleware.js بسيطة مثل:

 exports.validJWTNeeded = (req, res, next) => { if (req.headers['authorization']) { try { let authorization = req.headers['authorization'].split(' '); if (authorization[0] !== 'Bearer') { return res.status(401).send(); } else { req.jwt = jwt.verify(authorization[1], secret); return next(); } } catch (err) { return res.status(403).send(); } } else { return res.status(401).send(); } };

سنستخدم رموز خطأ HTTP للتعامل مع أخطاء الطلب:

  • HTTP 401 لطلب غير صالح
  • HTTP 403 لطلب صالح برمز مميز غير صالح ، أو رمز مميز صالح بأذونات غير صالحة

يمكننا استخدام عامل التشغيل bitwise AND (bitmasking) للتحكم في الأذونات. إذا قمنا بتعيين كل إذن مطلوب كقوة 2 ، فيمكننا التعامل مع كل بت من العدد الصحيح 32 بت كإذن واحد. يمكن للمسؤول بعد ذلك الحصول على جميع الأذونات من خلال تعيين قيمة الإذن الخاصة به إلى 2147483647. ويمكن لهذا المستخدم بعد ذلك الوصول إلى أي مسار. كمثال آخر ، سيكون لدى المستخدم الذي تم تعيين قيمة إذنه إلى 7 أذونات للأدوار المميزة بتات للقيم 1 و 2 و 4 (اثنان إلى قوة 0 و 1 و 2).

قد تبدو البرامج الوسيطة الخاصة بذلك كما يلي:

 exports.minimumPermissionLevelRequired = (required_permission_level) => { return (req, res, next) => { let user_permission_level = parseInt(req.jwt.permission_level); let user_id = req.jwt.user_id; if (user_permission_level & required_permission_level) { return next(); } else { return res.status(403).send(); } }; };

الوسيطة عامة. إذا تزامن مستوى إذن المستخدم ومستوى الإذن المطلوب في بت واحد على الأقل ، فستكون النتيجة أكبر من الصفر ، ويمكننا السماح للإجراء بالمضي قدمًا ؛ خلاف ذلك ، سيتم إرجاع رمز HTTP 403.

الآن ، نحتاج إلى إضافة برمجية المصادقة الوسيطة إلى مسارات الوحدة النمطية للمستخدم في /users/routes.config.js :

 app.post('/users', [ UsersController.insert ]); app.get('/users', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(PAID), UsersController.list ]); app.get('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.getById ]); app.patch('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.patchById ]); app.delete('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(ADMIN), UsersController.removeById ]);

هذا يختتم التطوير الأساسي لـ REST API الخاص بنا. كل ما تبقى لفعله هو اختبار كل شيء.

الجري والاختبار مع الأرق

Insomnia هو عميل REST لائق مع نسخة مجانية جيدة. تتمثل أفضل الممارسات ، بالطبع ، في تضمين اختبارات الكود وتنفيذ تقارير الأخطاء المناسبة في المشروع ، ولكن عملاء REST من الأطراف الثالثة رائعون لاختبار وتنفيذ حلول الطرف الثالث عندما لا يتوفر الإبلاغ عن الأخطاء وتصحيح أخطاء الخدمة. سنستخدمه هنا للعب دور التطبيق والحصول على نظرة ثاقبة لما يجري مع واجهة برمجة التطبيقات الخاصة بنا.

لإنشاء مستخدم ، نحتاج فقط إلى POST الحقول المطلوبة في نقطة النهاية المناسبة وتخزين المعرّف الذي تم إنشاؤه لاستخدامه لاحقًا.

طلب مع البيانات المناسبة لإنشاء مستخدم

ستستجيب API بمعرف المستخدم:

استجابة تأكيد مع معرف المستخدم

يمكننا الآن إنشاء JWT باستخدام /auth/ endpoint:

طلب مع بيانات تسجيل الدخول

يجب أن نحصل على رمز باعتباره ردنا:

تأكيد يحتوي على رمز ويب JSON المقابل

احصل على accessToken ، وابدأه بـ Bearer (تذكر المساحة) ، وأضفه إلى رؤوس الطلبات ضمن Authorization :

تحتوي عملية إعداد الرؤوس للنقل على JWT للمصادقة

إذا لم نفعل ذلك الآن بعد أن قمنا بتنفيذ البرامج الوسيطة للأذونات ، فسيتم إرجاع رمز HTTP 401 في كل طلب بخلاف التسجيل. ومع وجود الرمز المميز في مكانه الصحيح ، نحصل على الاستجابة التالية من /users/:userId :

الرد يسرد البيانات للمستخدم المشار إليه

كما ذكرنا سابقًا ، نحن نعرض جميع المجالات ، للأغراض التعليمية ولأغراض التبسيط. يجب ألا تظهر كلمة المرور (مجزأة أو غير ذلك) في الرد مطلقًا.

دعنا نحاول الحصول على قائمة المستخدمين:

طلب قائمة بجميع المستخدمين

مفاجئة! حصلنا على استجابة 403.

تم رفض الإجراء بسبب نقص مستوى الإذن المناسب

لا يمتلك مستخدمنا الأذونات للوصول إلى نقطة النهاية هذه. سنحتاج إلى تغيير مستوى permissionLevel الخاص بمستخدمنا من 1 إلى 7 (أو حتى 5 سيفعل ذلك ، نظرًا لأن مستويات الأذونات المجانية والمدفوعة يتم تمثيلها على أنها 1 و 4 ، على التوالي.) يمكننا القيام بذلك يدويًا في MongoDB ، في موجهها التفاعلي ، مثل هذا (مع تغيير المعرف إلى نتيجتك المحلية):

 db.users.update({"_id" : ObjectId("5b02c5c84817bf28049e58a3")},{$set:{"permissionLevel":5}})

بعد ذلك ، نحتاج إلى إنشاء JWT جديد.

بعد ذلك نحصل على الرد المناسب:

الاستجابة لجميع المستخدمين وبياناتهم

بعد ذلك ، دعنا نختبر وظيفة التحديث عن طريق إرسال طلب PATCH مع بعض الحقول إلى /users/:userId endpoint:

طلب يحتوي على بيانات جزئية ليتم تحديثها

نتوقع استجابة 204 كتأكيد لعملية ناجحة ، ولكن يمكننا أن نطلب من المستخدم مرة أخرى للتحقق.

الاستجابة بعد التغيير الناجح

أخيرًا ، نحتاج إلى حذف المستخدم. سنحتاج إلى إنشاء مستخدم جديد كما هو موضح أعلاه (لا تنس تدوين معرف المستخدم) والتأكد من أن لدينا JWT المناسب لمستخدم مسؤول. سيحتاج المستخدم الجديد إلى تعيين أذوناته على 2053 (أي 2048 - ADMIN - بالإضافة إلى 5 السابقة) ليتمكن أيضًا من إجراء عملية الحذف. بعد القيام بذلك وإنشاء JWT جديد ، سيتعين علينا تحديث عنوان طلب Authorization الخاص بنا:

طلب الإعداد لحذف مستخدم

إرسال طلب DELETE إلى /users/:userId ، يجب أن نحصل على استجابة 204 كتأكيد. يمكننا ، مرة أخرى ، التحقق من خلال الطلب /users/ لإدراج جميع المستخدمين الحاليين.

الخطوات التالية لواجهة برمجة تطبيقات REST الخاصة بك

باستخدام الأدوات والطرق التي تم تناولها في هذا البرنامج التعليمي ، يجب أن تكون قادرًا الآن على إنشاء واجهات برمجة تطبيقات REST بسيطة وآمنة على Node.js. تم تخطي الكثير من أفضل الممارسات غير الضرورية للعملية ، لذلك لا تنسَ:

  • تنفيذ عمليات التحقق المناسبة (على سبيل المثال ، تأكد من أن البريد الإلكتروني للمستخدم فريد)
  • تنفيذ اختبار الوحدة والإبلاغ عن الخطأ
  • منع المستخدمين من تغيير مستوى الأذونات الخاصة بهم
  • منع المشرفين من إزالة أنفسهم
  • منع الكشف عن المعلومات الحساسة (مثل كلمات المرور المجزأة)
  • انقل سر JWT من common/config/env.config.js إلى آلية توزيع سرية خارج الريبو وغير قائمة على البيئة

يمكن أن يكون التمرين الأخير للقارئ هو تحويل مصدر الشفرة من استخدام جافا سكريبت إلى أسلوب غير متزامن / انتظار.

بالنسبة لأولئك الذين قد يكونون مهتمين ، هناك الآن أيضًا إصدار TypeScript من المشروع متاح.

الموضوعات ذات الصلة: 5 أشياء لم تفعلها من قبل مع مواصفات REST