Quiero crear un Express Middleware para realizar una verificación básica si el par de usuario/contraseña en el encabezado de autorización existe en un archivo JSON (propósito educativo). Lo agregué en una aplicación de conversión de unidades muy simple.
El problema es que recibo un 403 en lugar del recurso cuando el nombre de usuario y la contraseña son correctos.
Descubrí que cuando realizo una solicitud, Promise.then en el middleware se ejecuta antes de que se cumpla Promise en mi función findUserByCredentials. Vea una ilustración del problema en el tercer fragmento de código a continuación.
índice.js
const express = require('express') const app = express() const port = process.env.PORT || 3000 const findUserByCredentials = require("./lib/find-user"); app.use(function (req, res, next) { if (req.headers) { let header = req.headers.authorization || ''; let [type, payload] = header.split(' '); if (type === 'Basic') { let credentials = Buffer.from(payload, 'base64').toString('ascii'); let [username, password] = credentials.split(':'); findUserByCredentials({username, password}).then(() => { console.log("next") next(); }).catch(() => { console.log("403") res.sendStatus(403); }); } } else { next(); } }); app.get('/inchtocm', (req, res) => { const cm = parseFloat(req.query.inches) * 2.54; res.send({"unit": "cm", "value": cm}); }); app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) }) module.exports = app;./lib/buscar-usuario.js
const bcrypt = require('bcrypt'); const jsonfile = require('../users.json'); let findUserByCredentials = () => (object) => { const username = object.username; const password = object.password; return new Promise((resolve, reject) => { jsonfile.forEach(user => { if (user.username === username) { bcrypt.compare(password, user.password).then((buffer) => { if (buffer) { console.log("resolve") resolve(); } else { console.log("reject") reject(); } }); } }); reject(); }); }; module.exports = findUserByCredentials();Consola del servidor después de enviar una solicitud
Example app listening at http://localhost:3000 403 resolve¿Cómo puedo obligar a Express a esperar a que finalice la primera Promesa antes de realizar la segunda operación?
Para tener un mejor control sobre el orden de tus promesas y también tener un código menos anidado, debes usar la sintaxis async/await . Puedes leer más sobre esto aquí .
Básicamente, lo que hace es permitirle... bueno, esperar a que finalice una operación asíncrona antes de continuar. Si usa await antes de algo que devuelve una Promise (como su findUserByCredentials ), asignará a la variable con qué resuelve su promesa, por ejemplo:
const myPromise = () => { return new Promise(resolve => resolve(3)); } const myFunc = async () => { const number = await myPromise(); console.log(number); // Output: 3 } Yo reformularía su función findUserByCredentials así:
const bcrypt = require('bcrypt'); const jsonfile = require('../users.json'); const findUserByCredentials = async (object) => { const username = object.username; const password = object.password; // This assumes there's only a single unique user with a specific username const potentialUser = jsonfile.find(user => user.username === username); if (!potentialUser) { throw new Error('wrong credentials') } const passHashCompare = await bcrypt.compare(password, potentialUser.password); if (!passHashCompare) { throw new Error('wrong credentials') } }; module.exports = findUserByCredentials; De esta manera, está menos anidado, es más legible y funciona en el orden que necesita. Puede ir aún más lejos con este principio y hacer que su middleware (la función que está pasando a app.use ) también sea una async function y use la palabra clave await en lugar de .then() y .catch()
Cambiaría el código para usar las últimas características del lenguaje javascript relacionado con el código async/await de esa manera, puede tener un mejor control de su flujo de ejecución.
Voy a modificar su código de la siguiente manera:
En primer lugar para la función findUserByCredentials :
const bcrypt = require('bcrypt'); const jsonfile = require('../users.json'); const findUserByCredentials = async (object) => { const username = object.username; const password = object.password; const user = jsonfile.find(user => user.username == username); if (user) await bcrypt.compareSync(user.password, password); return false; }; module.exports = findUserByCredentials;En segundo lugar para el index.js
const express = require('express') const app = express() const port = process.env.PORT || 3000 const findUserByCredentials = require("./lib/find-user"); app.use(async (req, res, next) => { if (req.headers) { let header = req.headers.authorization || ''; let [type, payload] = header.split(' '); if (type === 'Basic') { const credentials = Buffer.from(payload, 'base64').toString('ascii'); const [username, password] = credentials.split(':'); const result = await findUserByCredentials({username, password}) if(result) { return next() } return res.sendStatus(403); } return res.sendStatus(403); } return next(); }); app.get('/inchtocm', (req, res) => { const cm = parseFloat(req.query.inches) * 2.54; res.send({"unit": "cm", "value": cm}); }); app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) }) module.exports = app; Es muy importante aprender a usar código asíncrono en Nodejs porque es un solo hilo y si no se usa correctamente, puede bloquearlo y el rendimiento de su aplicación no será óptimo, también async/await es azúcar sintáctico de lenguajes javascript que permite Para escribir código asincrónico limpio, intente usar las últimas novedades de la especificación del lenguaje porque están creadas para facilitarnos la vida.