Anuncios

Bienvenidos sean a este post, hoy veremos como aplicar passport a nuestra para aplicacion Notas.

Anuncios

En el post anterior implementamos el modelo que nos sera util para poder acceder a todas las acciones para manipular los usuarios de nuestro servidor pero hoy veremos como mediante el modulo passport podemos darle la capacidad de login y logout para los usuarios en la aplicacion, para ello necesitaremos el codigo del post anterior, sino lo poseen les dejo un link para descargarlo:

Anuncios

Una vez descargado simplemente extraigan los dos directorios en el PC y ya tenemos todo listo para continuar, de aqui ingresaremos al directorio notas y ejecutaremos el siguiente comando:

$ npm install passport@^0.4.x passport-local@1.x --save
Anuncios

Este se encargara de instalarlos en nuestra aplicacion a los dos modulos para utilizar passport y poder permitir el ingreso de los usuarios, una vez terminado debemos ejecutar el siguiente comando:

$ npm install express-session@1.17.x session-file-store@1.4.x --save
Anuncios
Anuncios

Estos dos modulos que instalamos son para poder manejar las sesiones para Express y el segundo es para almacenar informacion de la sesion en archivos de sesiones, volviendo a los modulos passport el que usaremos en este caso sera a passport-local dado que este nos permite utilizar un servicio local para poder realizar el login, en este caso el servidor que creamos, sobre el otro se utiliza para poder brindar el uso de OAuth con twitter o X pero de eso hablaremos en otro post.

Anuncios

Nuestro primer paso sera crear el archivo enrutador para poder habilitar a los usuarios, para ello deben ir al directorio routes y crearemos un nuevo archivo con el nombe de users.mjs y le agregaremos el siguiente codigo:

routes/users.mjs

import path from 'path';
import util from 'util';
import { default as express } from 'express';
import { default as passport } from 'passport';
import { default as passportLocal } from 'passport-local';
const EstrategiaLocal = passportLocal.Strategy;
import * as modeloUsers from '../models/users-admin.mjs';
import { sesionCookie } from '../app.mjs';
import DBG from 'debug';
const debug=DBG('notas:router-users');
const error=DBG('notas:error-users');

export const router = express.Router();
Anuncios
Anuncios

Como en todos los casos primero importaremos todos los modulos necesarios, entre ellos tenemos el de express para nuestra aplicacion, passport, passport-local para poder ingresar, asi como tambien el modelo de usuarios que vimos en el post anterior, una propiedad de app.mjs pero de eso hablaremos en un rato, observen que definimos un objeto para utilizar al metodo Strategy de passport-local, y al final definimos nuestro bloque de depuracion que hemos implementado a partir de este post, pasemos a agregar las funciones necesarias:

export function iniciaPass(app) {
app.use(passport.initialize());
app.use(passport.session());
}
Anuncios

Esta funcion se encargara de iniciar a passport para ser utilizado con las funciones middleware, en este caso utilizaremos dos metodos para iniciarlos e iniciar la sesion, esto lo usaremos en un momento, agreguemos la siguiente funcion:

export function validaAutenticado(req, res, next) {
try {
if (req.user) next();
else res.redirect('/users/login');
} catch(e) { next(e); }
}
Anuncios

Este se encargara de certificar de que el usuario autenticado es valido, en un momento veremos donde se implementa, cabe mencionar que el user de req es entregado por el metodo deserialize de passport, donde si existe usara un next pero en caso contrario nos mandara al login de usuarios, y ante algun error lo informaremos, nuestro siguiente paso sera agregar algunos escuchadores:

router.get('/login', function(req, res, next) {
try {
res.render('login', { title: 'Ingreso a Notas',
user: req.user });
} catch(e) { next(e); }
});

router.post('/login',
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: 'login'
})
);
Anuncios
Anuncios

Estos dos en particular son para interceptar los eventos enviados por el formulario de ingreso que usaremos, en el caso de get nos mostrara el modelo de login donde le pasaremos dos datos, y el catch sera para mostrar un error en caso de suceder, pero de esto hablaremos en un rato, el siguiente escuchador sera para cuando ingresemos en si donde lo autenticaremos y le indicaremos el tipo de autenticacion, y tendremos dos propiedades donde succesRedirect sera para cuando fue exitoso el ingreso y nos enviara al raiz de la aplicacion y en caso de fallo nos enviara nuevamente al login para volver a intentar, en ambos casos solo usamos a /login porque al encontrarse en el archivo users.mjs, el encargado de ruteo establece el nombre del archivo como el directorio /users y este lo agregara automaticamente al url dando como resultado que al momento de acceder lo haremos como si fuera /users/login permitiendo que se ejecute el escuchador, pasemos a agregar el siguiente escuchador:

router.get('/logout', function(req, res, next) {
try {
req.session.destroy();
req.logout();
res.clearCookie(sesionCookie);
res.redirect('/');
} catch(e) { next(e); }
});
Anuncios

Este sera para el logout o cierre de sesion, observen que principalmente sera para destruir la sesion que hayamos creado para nuestro ingreso, asi como la eliminacion de la cookie generada y nos redireccionara a la raiz de nuestra aplicacion, agreguemos el siguietne manejador:

passport.use(new EstrategiaLocal({
usernameField: 'usuario',
passwordField: 'clave'
},
async (usuario, clave, done) => {
try {
var chk = await modeloUsers.chequeaClave(usuario,
clave);
if (chk.check) {
done(null, {
id: chk.usuario,
usuario: chk.usuario
});
} else {
done(null, false, chk.message);
}
} catch(e) { done(e); }
}
));
Anuncios
Anuncios

Esta la usaremos para el passport, donde crearemos un nuevo objeto para la estrategia local, aqui tendremos principalmente tres datos como son el usuario, la clave o contraseña y otro para devolver el resultado exitoso o no, lo primero que haremos sera definir una variable donde almacenaremos el resultado que nos devuelve la funcion de chequeo de clave, de la cual hablamos en este post, despues tenemos un condicional donde verificamos si el valor de check es true, en caso de ser verdadero devolveremos el valor de usuario para indicar de que se ingreso correctamente, en caso contrario indicaremos porque fallo, pasemos a agregar los siguientes manejadores:

passport.serializeUser(function(user, done) {
try {
console.log("Serial");
done(null, user.usuario);
} catch(e) { done(e); }
});

passport.deserializeUser(async (usuario, done) => {
try {
var user = await modeloUsers.buscar(usuario);
done(null, user);
} catch(e) { done(e); }
});
Anuncios

Estos dos se encargaran de codificar y decodificar la informacion para la autenticacion del usuario, el caso de serializar adjunta el usuario a la sesion, en el caso de deserializar es llamado cuando se procesa una solicitud de HTTP y ahi buscaremos los datos del usuario para luego adjuntarlo al objeto solicitante, con esto ya tenemos el archivo para el ruteo, nuestro siguiente paso sera ir al archivo app.mjs y buscaremos estas dos lineas:

import { router as indexRouter } from './routes/index.mjs';
import { router as notasRouter } from './routes/notas.mjs';
Anuncios

Debajo de estas dos lineas agregaremos la siguiente:

import { router as usersRouter, iniciaPass } from './routes/users.mjs';
Anuncios

Esta sera la encargada de importar al router que usaremos para los usuarios asi como tambien la funcion encargada de iniciar a passport, nuestro siguiente paso sera agregar las siguientes lineas detras de la anterior:

import session from 'express-session';
import sessionFileStore from 'session-file-store';
const FileStore = sessionFileStore(session);
export const sesionCookie = 'notascookie.sid';
Anuncios

Primero importaremos los modulos necesarios para manejar las sesiones, el primero es para la sesion y el segundo es para almacenarlo, despues definimos la variable que manejara la sesion y la ultima sera la encargada de identificar el archivo que usaremos para la sesion, esto lo especificamos para identificarlo mejor pero de manera predeterminada utiliza el archivo connect.sid, nuestro siguiente paso sera agregar un bloque de codigo despues de la siguiente linea:

app.use(cookieParser());
Anuncios

Una vez encontrada agregaran el siguiente codigo:

app.use(session({
store: new FileStore({ path: "sessions" }),
secret: 'keyboard cat',
resave: true,
saveUninitialized: true,
name: sesionCookie
}));
iniciaPass(app);
Anuncios
Anuncios

Este se encargara de crear nuestra sesion, observen como establecemos el path, el «secret» que usaremos para conectarnos, el siguiente es para indicar que podemos re-grabarlo, si esta inicialmente la opcion sin grabar asi como el nombre que establecimos en la variable sesionCookie, y finalmente con la sesion establecida se llamara a la funcion iniciaPass la cual iniciara al passport pero en algunas ocasiones en Windows puede ocasionarnos errores debido principalmente al momento de usar el almancenamiento en archivos mediante session-file-store, suponiendo que tienen ese inconveniente pueden instalar el paquete memorystore, el cual lo instalara en memoria en lugar de un archivo, despues al momento de iniciarlo deben hacerlo de la siguiente manera:

import sessionMemoryStore from 'memorystore';
const MemoryStore = sessionMemoryStore(sesion);
Anuncios

En este caso en lugar de hacerlo mediante FileStore lo haremos por MemoryStore y el bloque anterior lo cambiaremos de la siguiente manera:

app.use(session({
store: new MemoryStore({}),
secret: 'teclado mouse',
resave: true,
saveUninitialized: true,
name: sesionCookie
}));
Anuncios

Es muy similar al anterior pero en lugar de utilizar a FileStore lo hace mediante MemoryStore, el resto sigue siendo lo mismo pero recuerden que esto es para Windows y en el caso de que ocurran errores al momento de grabar la sesion, retomando lo anterior nuestro ultimo cambio en este archivo sera agregar una linea nueva, para ello debemos buscar la siguiente linea:

app.use('/notas', notasRouter);
Anuncios

Y debajo de esta agregaremos la siguiente:

app.use('/users', usersRouter);
Anuncios

Esta sera la encargada de habilitarnos a nuestro router de los usuarios, con esto ya tenemos por el momento todas las modificaciones, nuestro siguiente paso sera ir al archivo index.mjs en el directorio routes y buscaremos este bloque de codigo:

res.render('index', { title: 'Notas',
listanotas: listanotas
});
Anuncios

Lo modificaremos de la siguiente manera:

res.render('index', { title: 'Notas',
listanotas: listanotas,
user: req.user ? req.user : undefined
});
Anuncios

En este caso basicamente le agregamos la propiedad de user para identificar que hay un usuaro «logueado» en la aplicacion, en caso contrario usara undefined para identificar que no lo esta, con esto hemos terminado aqui pero seguiremos en el mismo directorio, aqui iremos al archivo notas.mjs y agregaremos la siguiente linea:

import { validaAutenticado } from './users.mjs';
Anuncios

Este nos permite importar la funcion que usaremos para verificar que el usuario esta ingresado correctamente desde nuestro archivo de ruteo para los usuarios, nuestro siguiente paso sera trabajar con la funcion que maneja el ingreso de las notas, para ello tomaremos su bloque:

router.get('/add', (req, res, next) => {
res.render('editarnota', {
title: "Agregar una nota",
creador: true,
clavenota: '',
nota: undefined
});
});
Anuncios

Y lo modificaremos de la siguiente manera:

router.get('/add', validaAutenticado, (req, res, next) => {
res.render('editarnota', {
title: "Agregar una nota",
creador: true,
clavenota: '',
user: req.user,
nota: undefined
});
});
Anuncios

En este caso primero agregaremos la funcion de verificaAutenticado para saber si el usuairo esta ingresado correctamente, recuerden que sino es asi nos redirecciona a la pagina de ingreso, y en caso de estar ingresado le pasaremos el valor del usuario junto al resto tal como haciamos antes, tomemos el bloque encargado de mostrar las notas y lo modificaremos de la siguiente manera:

router.get('/ver', async(req, res, next) => {
try {
let nota = await notas.read(req.query.clave);
res.render('vistanota', {
title: nota ? nota.titulo : "",
clavenota: req.query.clave,
nota: nota,
user: req.user ? req.user : undefined
});
} catch(err) { next(err); }
});
Anuncios

Aqui basicamente respetamos el mismo codigo pero al igual que en el caso del bloque anterior agregamos al usuario para que lo pasemos como dato, ya veremos como lo mostraremos, siguiendo con los bloques de codigo pasemos a la encargada de editar y lo modificaremos de la siguiente manera:

router.get('/editar', validaAutenticado, async(req, res, next) => {
try {
const nota = await notas.read(req.query.clave);
res.render('editarnota', {
title: nota ? ("Editando " + nota.titulo) : "Agregar nota",
creador: false,
clavenota: req.query.clave,
nota: nota,
user: req.user
});
} catch(err) { next(err); }
});
Anuncios

En este caso volvimos a repetir el tema de usar la funcion de verificacion de autenticacion del usuario, asi como el dato del usuario lo volvemos a pasar, tomemos el bloque encargado de borrar y hagamos la siguiente modificacion:

router.get('/borrar', validaAutenticado, async(req, res, next) => {
try {
let nota = await notas.read(req.query.clave);
res.render('borrarnota', {
title: nota ? `Borrar ${nota.titulo}` : "",
clavenota: req.query.clave,
nota: nota,
user: req.user
});
} catch(err) { next(err); }
});
Anuncios

En este caso volvemos a repetir la misma modificacion donde verificamos el usuario y lo pasamos en caso de ser exitoso, recuerden que la funcion de verificacion si no esta ingresado procede a llevarnos a la pagina de login, solo nos queda dos modificaciones y la primera sera tomar esta linea:

router.post('/save', async (req, res, next) => {
Anuncios

Y la modificaremos de la siguiente manera:

router.post('/save', validaAutenticado,  async (req, res, next) => {
Anuncios

En este caso solo modificaremos el encabezado del bloque encargado de guardar nuestras notas, donde aplicaremos la funcion de verificacion, el resto del codigo queda exactamente de la misma forma, la ultima modificacion sera en esta linea:

router.post('/borrar/confirmar', async(req, res, next) => {
Anuncios

Y la modificaremos de la siguiente forma:

router.post('/borrar/confirmar', validaAutenticado, async(req, res, next) => {
Anuncios

Al igual que en los casos anteriores volvemos a aplicar la funcion de verificacion y en este caso del encargado de la confirmacion de borrado de una nota y al igual que en el caso anterior el resto de codigo no se debe modificar, con esto terminamos con el archivo notas.mjs podemos pasar con las vistas para visualizar la informacion.

Anuncios

Nuestro primer cambio sera en el archivo header.hbs en el directorio partials, en este modificaremos el codigo existente de la siguiente manera:

partials/header.hbs

<header class="page-header">
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" href='/'><i data-feather="home"></i></a>
<button class="navbar-toggler" type="button"
data-toggle="collapse" data-target="#navbarLogin"
aria-controls="navbarLogin"
aria-expanded="false" aria-label="Alternar navegacion">
<span clas="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarLogin">
<span class="navbar-text col">{{ title }}</span>
<div class="navbar-nav col">
{{#if breadcrumb}}
<a class="nav-item nav-link" href="{{breadcrumb.url}}">
{{breadcrumb.title}}</a>
{{/if}}
</div>
{{#if user}}
<a class="btn btn-dark col-auto" href="/users/logout">
Salir <span class="badge badge-light">{{user.usuario}}
</span></a>
<a class="nav-item nav-link btn btn-light col-auto"
href='/notas/add'>Agregar Nota</a>
{{else}}
<a class="btn btn-primary" href="/users/login">Ingreso</a>
{{/if}}

</div>
</nav>
</header>
Anuncios
Anuncios

En este caso les resalto los cambios que realizamos en el archivo, en esta ocasion, cambiamos el nombre de la capa que mostraremos como barra de navegacion, mantendremos el titulo y el tema de las migas de pan, de la cual hablamos en este post, pero despues agregamos el boton de ingreso y salida, para ello usamos un condicional donde verificamos si existe user, en caso de ser verdadero se considera que el usuario esta ingresado correctamente por lo tanto daremos la opcion de salir y a su vez de poder agregar una nota, en caso contrario solo mostraremos la posibilidad de ingresar a la aplicacion, nuestro siguiente paso sera crear la vista encargada de mostrar la posibilidad de ingreso, nuestro siguiente paso sera ir al directorio views y crearemos un nuevo archivo con el nombre de login.hbs y le agregaremos el siguiente codigo:

views/login.hbs

<div class="container-fluid">
<div class="row">
<div class="col-12 btn-group-vertical role="group">
<form method='POST' action='/users/login'>
<div class="form-group">
<label for="usuario">Usuario</label>
<input class='form-control' type='text' id='usuario'
name='usuario' value='' placeholder='Usuario' />
</div>
<div class="form-group">
<label for="clave">Clave</label>
<input class='form-control' type='password' id='clave'
name='clave' value='' placeholder='Clave' />
</div>
<button type="submit" class="btn btn-default">Ingresar</button>
</form>
</div>
</div>
</div>
Anuncios

Este es un formulario simple para ingresar un usuario y contraseña donde lo enviaremos para procesarlo en caso de confirmarse permitira el ingreso a la aplicacion, en este caso aplicamos el diseño de movil-primero para que se ajuste a nuestros dispositivos automaticamente, aqui tenemos unas etiquetas para identificarlas y los campos relacionados, con esto comentado nuestro siguiente paso sera ir al archivo borrarnota.hbs en el directorio views y lo modificaremos de la siguiente manera:

views/borrarnota.hbs

<form method='POST' action='/notas/borrar/confirmar'>
<div class="container-fluid">
{{#if user}}
<input type='hidden' name='clavenota'
value='{{#if nota}}{{clavenota}}{{/if}}'>
<p class="form-text">¿ Borrar {{nota.titulo}} ?</p>
<div class="btn-group">
<button type="submit" value="Borrar"
class="btn btn-outline-dark">Borrar</button>
<a class="btn btn-outline-dark"
href="/notas/ver?clave={{#if nota}}{{clavenota}}{{/if}}"
role="button">Cancelar</a>
</div>
{{else}}
{{> no-ingresado}}
{{/if}}

</div>
</form>
Anuncios
Anuncios

Tecnicamente es el mismo codigo que teniamos anteriormente pero agregamos un condicional para verificar si existe user, es decir que este «logueado», y en ese caso mostrara el formulario para borrar la nota, en caso contrario llamaremos a la vista no-ingresado, de la cual hablaremos en un momento, nuestro siguiente paso sera ir al archivo editarnota.hbs, tambien en el directorio views, donde modificaremos el codigo de la siguiente manera:

views/editarnota.hbs

<form method='POST' action='/notas/save'>
<div class="container-fluid">
{{#if user}}
<input type='hidden' name='creador'
value='{{#if creador }}crear{{else}}cambiar{{/if}}'>
<div class="form-group row align-items-center">
<label for="clavenota" class="col-1 col-form-label">Clave</label>
{{#if creador }}
<div class="col">
<input type='text' class='form-control'
name='clavenota' value=''/>
</div>
{{else}}
{{#if nota }}
<span class="input-group-text">{{clavenota}}</span>
{{/if}}
<input type='hidden' name='clavenota'
value='{{#if nota }}{{clavenota}}{{/if}}'/>
{{/if}}
</div>

<div class="form-group row">
<label for="titulo" class="col-1 col-form-label">Titulo</label>
<div class="col">
<input type='text' name='titulo'
value='{{#if nota }}{{nota.titulo}}{{/if}}' />
</div>
</div>

<div class="form-group row">
<textarea class="form-control" rows=5 cols=40
name='cuerpo'>{{#if nota }}{{nota.cuerpo}}{{/if}}</textarea>
</div>
<input type='submit' class="btn btn-default" value='Subir' />
{{else}}
{{> no-ingresado}}
{{/if}}

</div>
</form>
Anuncios
Anuncios

En este caso volvemos a aplicar unicamente un condicional donde verifica si existe el usuario «logueado» y en caso de ser verdadero procede a mostrarnos todo el contenido que teniamos antes para poder crear o editar una nota, asi como tambien ahora al igual que en el caso anterior donde sino esta «logueado» a la aplicacion llamaremos a una vista para indicarlo, nuestro siguiente paso sera crear la vista para indicar esto y para ello debemos ir al directorio partials y crearemos un nuevo archivo con el nombre de no-ingresado.hbs y le agregaremos el siguiente codigo:

partials/no-ingresado.hbs

<div class="jumbotron">
<h1>Usuario no logueado</h1>
<p>Se necesita que estes ingresado para realizar esta accion pero no lo estas.
No deberias ver este mensaje, es un bug si este mensaje aparece.</p>
<p><a class="btn btn-primary" href="/users/login">Ingreso</a></p>
</div>
Anuncios
Anuncios

Como indica el mensaje este deberia aparecer solamente si ocurre un bug o error pero de forma natural no deberia verse porque en todos los casos verificamos si esta autenticado, en caso de no estarlo procede a enviarnos al login, salvo para ver las notas, pero es bueno tenerlo para estas eventualidades, con todo esto ya tenemos nuestra aplicacion casi terminada solo nos resta el iniciarlo con las nuevas modificaciones, para ello debemos volver al archivo package.json y en la seccion de scripts modificaremos el script start de la siguiente manera:

"start": "cross-env DEBUG=notas:* SEQUELIZE_CONNECT=models/sequelizar-sqlite.yaml MODELO_NOTAS=sequelizar URL_SERVICIO_USER=http://localhost:5858 node ./app.mjs",
Anuncios

Basicamente modificamos nuestro script inicial para iniciar la aplicacion para que ahora utilice a sqlite3 para almacenar la informacion, asi como que solo muestre el debug de notas, y le pasamos el URL para conectarse al servidor de usuarios que tenemos, con esto ya tenemos nuestra aplicacion finalmente modificada, pasemos a probarlo.

Anuncios

Para ello debemos abrir primero una terminal donde iremos hasta el directorio notas y en este ejecutaremos el comando npm start para iniciarlo, deberia quedar de la sigueinte manera:

notas$ npm start

> notas@0.0.0 start

> cross-env DEBUG=notas:* SEQUELIZE_CONNECT=models/sequelizar-sqlite.yaml MODELO_NOTAS=sequelizar URL_SERVICIO_USER=http://localhost:5858 node ./app.mjs

notas:debug Escuchando en port 3000 +0ms
Anuncios

En este caso iniciara como siempre y no tendremos mayores inconvenientes, nuestro siguente paso sera iniciar al servidor de usuarios, para ello deben ejecutar en otra terminal desde el directorio users al comando npm start y debe quedar de la siguiente manera:

users$ npm start

> user-auth-server@1.0.0 start
> cross-env DEBUG=users:* PORT=5858 SEQUELIZE_CONNECT=sequelizar-sqlite.yaml node ./user-server.mjs

(node:2170) [DEP0111] DeprecationWarning: Access to process.binding('http_parser') is deprecated.
(Use `node --trace-deprecation ...` to show where the warning was created)
users:service user-auth-server escuchando en http://127.0.0.1:5858 +0ms
Anuncios

No se preocupen por el momento del aviso, dado que seguira funcionando pero en algun momento puede dejar de hacerlo, con esto tenemos nuestro servidor de usuario habilitado, si quedo asi en ambos casos quiere decir que ya podemos hacerr algunas pruebas, pasemos a ver algunas mediante el siguiente video

Anuncios
Anuncios

En el video podemos ver como se ingresa y sale con el usuario asi como tambien muchas de las opciones que hemos estado trabajando libremente ahora se encuentran restringuidas y solamente con la validacion del usuario podemos hacerlo, este caso es para los usuarios locales de la aplicacion (mas alla de ser mediante un servidor) pero tambien podemos dar la posibilidad de poder ingresar con otro tipo de cuentas pero eso sera un tema que veremos en el proximo post, antes de finalizar les dejo un link a un archivo que contiene ambas aplicaciones con todos los archivos necesarios asi como los trabajados en este post:

Anuncios

En resumen, hoy hemos creado una forma de autenticarnos en nuestra aplicacion mediante una forma local, es decir con medios que proveemos nosotros, en este caso un «servidor» con los usuarios creados en este, hemos visto como conectarnos, como hacer unas operaciones, en este caso el chequeador de clave, para poder usar a passport para realizar esta tarea y crear una sesion con la cual trabajaremos hasta finalizarlo, hemos visto como pudimos restringuir el acceso mediante este metodo, espero les haya sido de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios
pp258

Donación

Es para mantenimento del sitio, gracias!

$1.50