Sin categoría

Servidores de juegos y Couchbase con Node.js - Parte 2

En esta parte de la serie, implementaremos la gestión de sesiones y los endpoints autenticados (endpoints que requieren que hayas iniciado sesión). Empecemos.

Si aún no ha leído Parte 1 de esta serie, le sugiero que lo haga, ya que establece el diseño básico del proyecto, así como la gestión básica de usuarios, y es un requisito previo para la Parte 2.

Gestión de sesiones - El modelo

El primer paso para construir nuestros puntos finales de gestión de sesiones es configurar un modelo para utilizar desde estos puntos finales para manipular la base de datos. A diferencia del modelo de cuenta, este modelo no necesita documentos referenciales ni ninguna lógica complicada, por lo que es bastante sencillo. El primer paso es importar los diversos módulos que necesitaremos, así como obtener una referencia a nuestra conexión de base de datos desde nuestro módulo de base de datos.

var db = requiere('./../base de datos').cubo principal;
var couchbase = requiere(couchbase);
var uuid = requiere(uuid);

A continuación, de forma similar a nuestro modelo de cuenta, creamos una pequeña función para eliminar las propiedades de nuestra base de datos. 'type' es la única que necesitamos eliminar también en este caso.

función cleanSessionObj(obj) {
borrar obj.tipo;
devolver obj;
}

Ahora vamos a empezar a trabajar en la propia clase de modelo de sesión. Primero viene nuestro constructor en blanco. Me gustaría mencionar en este punto que nuestras clases modelo son actualmente totalmente estáticas en este ejemplo, pero una buena práctica a seguir es devolver tus clases modelo de las operaciones CRUD estáticas, simplemente no estamos en el punto en que esto sería útil todavía.

función SessionModel() {
}
módulo.exportaciones = SessionModel;

Ahora, nuestra primera función del modelo de sesión que realmente hará algún trabajo.

SessionModel.crear = función(uid, devolución de llamada) {
};

Y dentro de esa función necesitamos crear nuestro documento que vamos a insertar y su clave asociada (sólo almacenamos el uid, y luego accedemos al resto de información del usuario directamente desde su documento de cuenta a través de nuestro modelo de cuenta, que verás más adelante.

var sessDoc = {
tipo: sesión,
sid: uuid.v4(),
uid: uid
};
var sessDocName = "sess- + sessDoc.sid;

A continuación, almacenamos nuestro documento de sesión recién creado en nuestro clúster. Usted también notará que llamamos a nuestra función de desinfección de arriba aquí, así como el establecimiento de un valor de caducidad de 60 minutos. Esto hará que el clúster 'expire' la sesión eliminándola después de esa cantidad de tiempo.

db.añada(sessDocName, sessDoc, {caducidad: 3600}, función(err, resultado) {
devolución de llamada(err, cleanSessionObj(sessDoc), resultado.cas);
});

A continuación tenemos que construir una función en nuestro modelo que nos permita recuperar la información de sesión que hemos almacenado previamente. Para ello, generamos una clave que coincida con la que tendríamos almacenada, y a continuación ejecutamos una petición get, devolviendo el uid de usuario de la sesión a nuestro callback.

SessionModel.consiga = función(sid, devolución de llamada) {
var sessDocName = "sess- + sid;db.consiga(sessDocName, función(err, resultado) {
si (err) {
devolver devolución de llamada(err);
}

devolución de llamada(null, resultado.valor.uid);
});
};

Y aquí está el sessionmodel.js finalizado:

var db = requiere('./../base de datos').cubo principal;
var couchbase = requiere(couchbase);
var uuid = requiere(uuid);función cleanSessionObj(obj) {
borrar obj.tipo;
devolver obj;
}

función SessionModel() {
}

SessionModel.crear = función(uid, devolución de llamada) {
var sessDoc = {
tipo: sesión,
sid: uuid.v4(),
uid: uid
};
var sessDocName = "sess- + sessDoc.sid;

db.añada(sessDocName, sessDoc, {caducidad: 3600}, función(err, resultado) {
devolución de llamada(err, cleanSessionObj(sessDoc), resultado.cas);
});
};

SessionModel.consiga = función(sid, devolución de llamada) {
var sessDocName = "sess- + sid;

db.consiga(sessDocName, función(err, resultado) {
si (err) {
devolver devolución de llamada(err);
}

devolución de llamada(null, resultado.valor.uid);
});
};

módulo.exportaciones = SessionModel;

Gestión de la sesión - Búsqueda de cuentas

Antes de empezar a escribir nuestro gestor de peticiones, necesitamos crear un método que nos permita buscar un usuario basándonos en su nombre de usuario. No queremos que los usuarios tengan que recordar sus nombres de usuario. En la parte 1 construimos nuestro modelo de cuenta en accountmodel.js. Volvamos a ese archivo y agreguemos un nuevo método.

Modelo de cuenta.getByUsername = función(nombre de usuario, devolución de llamada) {
};

La forma en que manejamos este método es que construiremos una clave utilizando el nombre de usuario proporcionado que buscará uno de los documentos referenciales que creamos en AccountModel.create. Si no somos capaces de localizar este documento referencial, asumimos que el usuario no existe y devolvemos un error. Si se localiza el nombre de usuario y se encuentra el documento de referencia, se ejecuta un comando AccountModel.get para localizar el propio documento de usuario y reenviar la llamada de retorno a través de él. Esto significa que las llamadas a AccountModel.getByUsername devolverá el objeto de usuario completo como si hubiera llamado directamente a AccountModel.get con el uid.

Aquí está toda la función:

Modelo de cuenta.getByUsername = función(nombre de usuario, devolución de llamada) {
var refdocName = nombre de usuario + nombre de usuario;
db.consiga(refdocName, función(err, resultado) {
si (err && err.código === couchbase.errores.keyNotFound) {
devolver devolución de llamada(Nombre de usuario no encontrado);
} si no si (err) {
devolver devolución de llamada(err);
}// Extraer el UID que encontramos
var foundUid = resultado.valor.uid;

// Reenviar a un get normal
Modelo de cuenta.consiga(foundUid, devolución de llamada);
});
};

Gestión de sesiones - Gestión de solicitudes

El último paso para habilitar la creación de sesiones es escribir el propio manejador de peticiones. Afortunadamente, la mayor parte de la lógica importante se ha ido en las secciones anteriores, y nuestro controlador de peticiones simple reenvía la información a cada uno para su procesamiento. Primero validamos las entradas del usuario para asegurarnos de que se ha proporcionado todo lo necesario. A continuación, intentamos localizar la cuenta por el nombre de usuario proporcionado. A continuación, validamos que la contraseña coincide con lo que el usuario proporcionó por hash de la contraseña proporcionada y la comparación. Y finalmente creamos la sesión con nuestro recién creado modelo de sesión y devolvemos los detalles al usuario. Puedes notar que el identificador de sesión no se proporciona directamente al usuario, sino que se pasa en la cabecera. Esto es por coherencia, ya que la cabecera también se utiliza para autenticar cada solicitud más tarde.

app.Correo electrónico:(/sesiones, función(consulte, res, siguiente) {
si (!req.cuerpo.nombre de usuario) {
devolver res.enviar(400, Debe especificar un nombre de usuario);
}
si (!req.cuerpo.contraseña) {
devolver res.enviar(400, Debe especificar una contraseña);
}accountModel.getByUsername(req.cuerpo.nombre de usuario, función(err, usuario) {
si (err) {
devolver siguiente(err);
}

si (cripta.sha1(req.cuerpo.contraseña) !== usuario.contraseña) {
devolver res.enviar(400, Las contraseñas no coinciden);
}

sessionModel.crear(usuario.uid, función(err, sesión) {
si (err) {
devolver siguiente(err);
}

res.setHeader(Autorización, 'Portador' + sesión.sid);

// Borrar la contraseña por razones de seguridad
borrar usuario.contraseña;
res.enviar(usuario);
});
});
});

Ahora que existe nuestra creación de sesión, vamos a añadir un método para autenticar al usuario en una base por solicitud. Esto comprobará la sesión del usuario, por ahora he colocado esto en nuestra app.js, sin embargo puede ser mejor ponerlo en su propio archivo separado más adelante una vez que las rutas comiencen a ser separadas en archivos separados. Para autenticar al usuario, comprobamos la cabecera estándar HTTP Authorization, obtenemos el identificador de sesión y lo buscamos utilizando nuestro modelo de sesión. Si todo va según lo previsto, almacenamos el identificador de usuario recién encontrado en la solicitud para posteriores gestores de rutas.

función authUser(consulte, res, siguiente) {
req.uid = null;
si (req.cabeceras.autorización) {
var authInfo = req.cabeceras.autorización.dividir(‘ ‘);
si (authInfo[0] === Portador) {
var sid = authInfo[1];
sessionModel.consiga(sid, función(err, uid) {
si (err) {
siguiente(Su identificador de sesión no es válido);
} si no {
req.uid = uid;
siguiente();
}
});
} si no {
siguiente(Debe estar autorizado para acceder a este punto final".);
}
} si no {
siguiente(Debe estar autorizado para acceder a este punto final".);
}
}

Principalmente con el propósito de mostrar el método authUser en acción he implementado un método /yo que devuelve el documento de usuario. Simplemente hacemos un get a través del modelo de cuenta basado en el uid que fue almacenado en la solicitud por el manejador authInfo, y devolvemos esto al cliente, por supuesto quitando la contraseña primero.

app.consiga("/me, authUser, función(consulte, res, siguiente) {
accountModel.consiga(req.uid, función(err, usuario) {
si (err) {
devolver siguiente(err);
}borrar usuario.contraseña;
res.enviar(usuario);
});
});

Finale

Llegados a este punto, ya deberías poder crear cuentas, iniciar sesión en ellas y solicitar información almacenada sobre el usuario. He aquí un ejemplo utilizando el usuario que creamos en la Parte 1.

> POST /sesiones
{
"nombre de usuario": "brett19",
"contraseña": "¡éxito!"
}
< 200 OK
Cabecera(Autorización): Bearer 0e9dd36c-5e2c-4f0e-9c2c-bffeea72d4f7> GET /me
Cabecera(Autorización): Bearer 0e9dd36c-5e2c-4f0e-9c2c-bffeea72d4f7
< 200 OK
{
"uid": “b836d211-425c-47de-9faf-5d0adc078edc”,
"name": "Brett Lawson",
"nombre de usuario": "brett19"
}

La fuente completa de esta aplicación está disponible aquí: https://github.com/brett19/node-gameapi

¡Que aproveche! Brett

Comparte este artículo
Recibe actualizaciones del blog de Couchbase en tu bandeja de entrada
Este campo es obligatorio.

Author

Posted by Brett Lawson

Brett Lawson es Ingeniero de Software Principal en Couchbase. Brett es responsable del diseño y desarrollo de los clientes Node.js y PHP de Couchbase, además de participar en el diseño y desarrollo de la biblioteca C, libcouchbase.

10 Comentarios

  1. Brendon Murphy octubre 7, 2013 a 3:03 pm

    Gracias por compartir esto, ¡la serie parece que va a ser interesante durante un tiempo!

    Pregunta sobre la función SessionModel.get: ¿Debería (o alguna otra cosa) actualizar la expiración en el documento de sesión a 3600 en las búsquedas? De lo contrario, una vez que te conectes, tu sesión morirá automáticamente después de una hora, incluso si has estado activo durante ese tiempo.

    Espero con impaciencia la continuación de la serie, sigan así.

    1. ¡Hey Brendon! Tienes toda la razón. Dentro de mi función authUser debería estar tocando la sesión para renovar el tiempo de expiración. Voy a añadir esto a la siguiente parte.

      Saludos, Brett

      1. Podrías eliminar una llamada adicional a la BD (ya que Couchbase soporta get-and-touch en una única petición) simplemente añadiendo {expiry: 3600} a la llamada db.get existente dentro de SessionModel.get

        Gran serie, por cierto, y espero con impaciencia el informe sobre el Universo.

        1. Ben, ¿a qué artículo del Universo te refieres?

  2. Seillier Xavier octubre 8, 2013 a 6:31 pm

    Gracias por sus artículos. Tengo una pregunta sobre \ 'referencial
    por qué has utilizado "documento referencial" en lugar de "vista".

    1. Hola Seillier, la razón por la que elegí usar un documento referencial en este caso fue para asegurar la consistencia de la base de datos. Las vistas tienen un retraso en la actualización de sus índices, esto significa que potencialmente un usuario podría registrar un nombre de usuario en particular, y luego antes de que la vista sea reindexada, otro usuario podría registrar el mismo nombre de usuario (ya que la vista no devolverá ese nombre de usuario como usado todavía). Usando un documento referencial, podemos asegurar la consistencia ya que este retraso no existe.

      Saludos, Brett

  3. Referencia a "finalized accountmodel.js\" debería decir "finalized sessionmodel.js\"

    Además, es posible que desee incluir una copia del fragmento para AccountModel.get() también.

    En cuanto a la implementación de la función authUser, la puse en su propio archivo \'lib/httpauth.js\' de la siguiente manera:

    Y luego pasarlo a la ruta:

    1. ¡Gracias rdev5! He arreglado el texto como sugeriste.

  4. No puedo probar el endpoint en postman con la autorización de las cabeceras

    POST /sesiones

Deja un comentario

¿Listo para empezar con Couchbase Capella?

Empezar a construir

Consulte nuestro portal para desarrolladores para explorar NoSQL, buscar recursos y empezar con tutoriales.

Utilizar Capella gratis

Ponte manos a la obra con Couchbase en unos pocos clics. Capella DBaaS es la forma más fácil y rápida de empezar.

Póngase en contacto

¿Quieres saber más sobre las ofertas de Couchbase? Permítanos ayudarle.