Código en GitHub: building-realtime-webapp. Release: auth-passport-providers
.
Entorno de desarrollo en Heroku: building-realtime-webapp.
User
UserController
Tras ver cómo implementar un sistema de autenticación básica y cómo utilziar Passport para autenticar usuarios vamos a dar un poso más y ver cómo autenticar usuarios usando otros proveedores como Github, Facebook o Twitter.
Como hemos dicho Passport es un sistema de autenticación modular y nos permite ir ampliando las formas de autenticar a nuestros usuarios. Puedes consultar la lista de proveedores de Passport para ver todos los que están disponibles. Instalamos los paquetes que necesitemos:
npm install --save passport-github passport-facebook passport-twitter
Una vez instalado los paquetes tenemos que configurar las estrategias. Como no quería depender de variables de entorno en local ni exponer las credenciales OAuth en un fichero de configuración, sin mencionar que necesitaremos credenciales diferentes para local y para producción. Por estas razones la solución que os planteo puede ser un poco rebuscada, pero por internet podéis encontrar formas más sencillas si no vais a hacer público vuestro código o decidis depender de variables de entorno.
Lo primero sería pensar en modificar el fichero de configuración. Pero hemos decidido dejar la configuración básica en este fichero y la declaración de estartegias externas en un servicio, debido a la necesidad de usar las variables de configuración de Sails (sails.config
). Dado que la estrategia local funciona de fomra diferente y no requiere variables de configuración la hemos mantenido en el fichero de configuración. Los únicos cambios realizados a /config/passport.js
son por temas de experiencia de usuario y para declarar los proveedores, que nos servirán para abstraer los proveedores que empleamos.
Pensando en el flujo de autenticación y que hacer cuando no se encuenta al usuario, independientemente del proveedor utilizado, la mejor solución es llevar al usuario a la página de registro, como veremos en el UserController
. Si el usuario ha introducido su usuario (username o email) y contraseña, sería un detalle dejarle ese campo del formulario de registro relleno. Esto nos supone los siguientes cambios en nuestra estrategia local cuando no se encuenta el usuario:
1 2 3 4 5 6 7 8 |
|
Hemos creado un fichero en /api/services/PassportService.js
, declarando las dependencias de proveedores de autenticación externos. Además declaramos la función, providersHandler
, la cual se ejecutará una vez se haya autenticado en el servidor del proveedor. Esta función tiene como parámetros el token, el tokenSecret, el perfil de usuario y una función de callback que definimos cuando solicitamos la autenticación en nuestro controlador. En providersHandler
se suele buscar o crear un usuario nuevo si no se encuentra, pero como nosotros tenemos nuestro sistema de usuarios y vamos a implementar varios proveedores, comprobaremos si el perfil está asociado a alguno de nuestros usuarios. Si el id del perfil está en nuestro Modelo devolvemos el usuario, en caso contraro, creamos un usuario un usuario temporal (sin guardarlo en la base de datos). Este usuario lo pasaremos al controlador mediante la función de callback para mantenerlo mientras el usuario termina de registrarse en nuestra plataforma y así poder asociar el perfil recibido sin necesidad de solicitar de nuevo la autenticación al proveedor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Por último, el servicio exporta un módulo con una función que configura los proveedores con las variables de entorno o las de configuración de sails. Lo hacemos dentro de una función para disponer del objeto sails
y por tanto de la configuración declarada en /config/local.js
(hay una copia de referencia en /config/local.ex.js
). Estas propiedades y métodos estarán disponibles en `sails.config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Esta nueva función, configProviders
debemos llamarla al iniciar la aplicaicón para que se configuren nuestros proveedores de autenticación. Para ello incluimos la llamada en /config/bootstrap.js
:
1 2 3 4 |
|
User
En el modelo solo tendremos que añadir un atributo más que guarde los perfiles del usuario. Los resultados se normalizan siguiendo el esquema de contacto establecido por Portable Contacts y puedes consultar en el perfil de usuario en Passport. De momento Sails no permite las asociaciones, disponibles en la versión 0.10, así definiremos un array y procuraremos mantener el schema. Cuando pasemos a Sails.js 0.10 implementaremos asociaciones las cuales nos permitirán crear un modelo Profile en el que definieremos este schema.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
UserController
Vamos a ampliar la funcionalidad de las acciones login
y logout
, las cuales nos permitirán realizar el login de un usuario y unir un perfil con el usuario registrado. Obtenemos el proveedor por parámetro (ver Personalización de rutas) y si es válido procederemos a la unión o eliminación del perfil. La url a la que haremos las peticiones será del tipo /user/login/github
, siendo github
lo que recibiremos como parámetro provider
.
1 2 3 4 5 6 7 8 9 10 11 |
|
Esto unido a tres funciones auxiliares resuelven de manera bastante elegante la autenticación de usuarios, enlace y desenlace de perfiles. La primera función auxiliar, isProvider
, es para comprobar si es un proveedor de authenticación válido.
1 2 3 |
|
La segunda, linkProfile
, es la encargada de gestionar el login con dicho proveedor. Tendrá en cuenta si el usuario está registrado, si hemos encontrado un usuario con dicho perfil en nuestra base de datos y actuará en consecuencia en cada uno de los 5 casos:
Un fichero que no habíamos visto hasta hora /config/routes.js
hace aparición para que definamos las url de login
y logout
. El indicar en la url :provider
establecemos provider
como un parámetro y con ?
lo hacemos opcional. De esta forma estas urls nos sirven para el login general y para el login con un proveedor concreto.
1 2 3 4 5 6 7 |
|
## Cambios en las vistas
Ya tenemos toda la parte de lógica de negocio lista. Todo lo que hemos realizado en los controladores, modelos, cofig, servicios… tenemos que refejarlo en las vistas así que vamos a listar los cambios que hay que realizar.
/view/user/auth.ejs
a /view/auth/index.ejs
./auth/login/local
./auth/login/[provider]
, siendo [provider]
el proveedor que corresponda en cada caso. Para ello utilizamos utilizamos la variable sails.config.providers
y un bucle each
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Como hemos comentado guardamos un usuario temporal cuando no hemos encontrado el usuario en nuestra base de datos. Este es el momento de mostrarle al usuario los datos que había rellenado al intentar autenticarse. Hemos omitido poner la contraseña, porque es mejor introducirla de manera consciente. Este sería un ejemplo de como introducimos el valor del usuario temporal.
1 2 3 4 5 6 7 8 9 10 |
|
En la vista del usuario ahora tendremos que informarle de qué perfiles ha unido a su cuenta y darle la posibilidad de unir nuevos o eliminar los que tenga unidos. Así que iteramos los proveedores, sails.config.providers
y si encontramos un perfil de ese proveedor entre los perfiles del usuario ofrecemos la opción de eliminar y en caso contrario la de añadir. Solo vamos a mostrar el condicional para mostrar el uso del método find:
1 2 3 4 5 6 |
|
Con esto hemos terminado nuestro sistema de autenticación con proveedores externos y creado un sistema que facilita el deshabilitar o habilitar nuevos sistemas de autenticación.
Commit en GitHub: c3e544e2ca: Auth with Passport and providers (GitHub, Facebook, Twitter).
Commit en GitHub: 7eb75b35f1: Delete verb from login and logout routes.
Commit en GitHub: 0f78d3898b: Update read me and index to show read me info.
Código en GitHub: building-realtime-webapp. Release: auth-passport-providers
.
Entorno de desarrollo en Heroku: building-realtime-webapp.
Código en GitHub: building-realtime-webapp. Release: auth-passport
.
Entorno de desarrollo en Heroku: building-realtime-webapp.
En el artículo anterior vimos cómo implementar un sistema de autenticación básica, y en esta ocasión vamos a implementar el sistema de autenticación usando Passport. Passport es un middleware de autenticación para Node nos permitirá modularizar la autenticación de usuarios en nuestra aplicación y realizar inicio de sesión único con OAuth como Facebook o Twitter.
Lo primero que tenemos que hacer es instalar Passport y los módulos que vayamos a utilizar. Empezaremos con una autenticación contra nuestra base de datos, así que necesitamos:
npm install --save passport passport-local
Passport por defecto buscará en las peticiones el parámetro username
y password
, aunque podríamos cambiarlo, hemos optado por incluir el atributo username
al modelo User
(queda mejor que mostrar el email y podremos jugar con las rutas y el user name más adelante).
Recomiendo echarle un ojo a la Documentación de Passport local.
Una vez instalado creamos un fichero de configuración en el directorio config
llamado passport.js
, el cual será cargado por Sails automáticamente al lanzar el servidor. Para no copiar todo el fichero aquí puesto que está disponible en GitHub voy a destacar lo más importante. Indicamos a passport que use una estrategia local, buscando un usuario cuyo username
o email
coincida con el campo username de nuestro fomulario de login. Una vez encontrado el usuario comparamos las contraseñas y devolvemos el usuario.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Unas lineas más abajo cerramos el fichero passport.js
declarando el middleware de Express y asignando a las variables locales el usuario que devolvimos en el fragmento de código anterior:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Login
y Logout
Ahora que tenemos configurado Sails para usar Passport realizamos los cambios en el controlador UserController
para utilizar este nuevo medio de autenticación. Nuestras acciones login
y logout
quedan mucho más reducidas puesto que gran parte del trabajo lo hace Passport con las funciones login
y logout
que trae implementadas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Como Passport nos facilita unos cuantos métodos para gestionar la autenticación del usuario vamos a aprovecharlos para usarlos en nuestras políticas. El primero de ellos es el método isAuthenticated()
, el cual utilizaremos en la política con dicho nombre:
1 2 3 4 |
|
Y otra de las tareas que automatiza es guardar el usuario registrado en la variable req.user
, la cual usaremos para comprobar si puede administrar un usuario concreto.
1 2 3 4 |
|
Y los últimos cambios que debemos realizar son en las vistas, los cuales enumero:
session.authenticated
, si no loggedUser
.loggedUser
en lugar de session.user
.username
y password
, indicando que el nombre de usuario puede ser también el correo-e.username
al formulario de registro y de edición.Commit en GitHub: 0c201f963d: Auth with Passport. Username attr added.
Commit en GitHub: 34736d85b4: Fix session.canAdminUser using req.user. And login user on create.
Código en GitHub: building-realtime-webapp. Release: auth-passport
.
Entorno de desarrollo en Heroku: building-realtime-webapp.
Código en GitHub: building-realtime-webapp. Release: auth
.
Entorno de desarrollo en Heroku: building-realtime-webapp.
Siguiendo con la saga de “Desarrollar Webapps Realtime” y depués de haber visto Cómo empezar a crear un Webapp real-time y cómo crear usuarios siguiendo el patrón MVC, le toca el turno a cómo autenticar a los usuarios en nuestra plataforma. Para ello vamos a hacerlo primero con nuestros propios medios para entender algunos conceptos y después lo haremos usando Passport for Node.
Lo primero que vamos a hacer es cambiar la barra de navegación añadiendo el botón de login cuando el usuario no esté autenticado y su correo y el boton de logout cuando si lo esté. Para ello debemos comprobar la variable session.authenticated
que estableceremos en el controlador. Estas líneas las añadimos después de nuestro menú de navegación, <ul class="nav navbar-nav">[…]</ul>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Si os fijáis para crear un nuevo usuario y para la página de autenticación he usado enlaces pero para el log out he usado un formulario con un botón. Esto es porque en los enlaces se utilizan para navegar y los botones para acciones.
Para gestionar las sesiones necesitaremos las acciones auth
, login
y logout
. Estas acciones las vamos a añadir en UserController
, aunque podrían ir en un nuevo controlador llamado SessionController
. Como posteriormente vamos a implementar la autenticación con Passport lo moveremos a un controlador especifico llamado AuthController
.
auth
Esta acción simplemente nos devolverá una vista con el formulario de autenticación. Así que habrá que crear la vista /view/user/auth.ejs
en la que le solicitaremos el email y la contraseña. Esta vista es muy similar a /view/user/new.ejs
, así que podemos copiar y pegar el contenido y hacemos un par de cambios:
/user/create
sino /user/login
.Y en el controlador copiamos la acción new sin hacer ningún cambio.
1 2 3 |
|
Ahora podemos lanzar el servidor y comprobar que la url http://localhost:1337/user/auth funciona correctamente.
Login
En esta acción hay un poco más de chicha, ya que tendremos que buscar el usuario mediante el email, comparar la contraseña y guardar una variable de sessión.
1 2 3 4 5 6 7 8 9 10 11 |
|
Si lanzamos el servidor de nuevo y probamos a rellenar el formulario con uno de los usuarios previamente creados, vemos como nos redirecciona a la página del usuario y nos reemplaza los botones de ‘Ingresar’ y ‘Resistrarse’ por el email del usuario y un botón de ‘Desconectar’.
Tenemos que tener en cuenta que cunado se crea un usuario podemos considerarlo autenticado. O bien esperar a que verifique su mail, pero como de momento no enviamos mail. Consideraremos al usuario autenticado al crear la nueva cuenta, asignando los valores a la sesión antes de enviar la respuesta:
1 2 3 4 5 6 7 8 |
|
Logout
En este caso, es mucho más facil, simplemente destruimos la sesión y redirigimos al usuario a la home.
1 2 3 4 |
|
Volvemos a lanzar el servidor, hacemos login y una vez en la página del usuario pulsamos el botón de ‘Desconectar’ para ver como salimos de la sessión y vuelven a aparecer los botones de ‘Ingresar’ y ‘Resistrarse’.
Commit en GitHub: 01e14ef420: Authentication on UserController: auth, login and logout actions.
Recomiendo echarle un ojo a la Documentación de Sails sobre Políticas.
Autenticar a un usuario tiene la misión principal de conceder o denegar el acceso a algunas partes de nuestra aplicación. Para ello haremos uso de las políticas (policies). Estas políticas las declaramos en el directorio /api/policies
.
Por defecto Sails nos incluye la política isAuthenticated.js
. La cual hace uso de la variable de sessión que establecemos a true
cuando el usuario se identifica. Una politica se declara como un módudo y ejecuta la función de callbac next
cunado se concede acceso o devuelve forbidden en caso contrario.
1 2 3 4 5 |
|
Ahora que tenemos la políca definida debemos aplicarla a alguna URL o acción del controlador. Para ello accedemos a /config/policies.js
donde definiremos las políticas que se aplcian en cada caso.
1 2 3 4 5 6 7 8 9 10 11 |
|
Con esto estamos permitiendo el acceso a todas las acciones del UserController
, y en algunos casos (find, update, destroy, edit, logut), le pedimos que estén autenticados. Personalmente prefiero aplicar las políticas partiendo de la restricción, es decir, para todas las acciones hay que estar autenticado salvo para las que definamos como abiertas. Quedaría de esta manera:
1 2 3 4 5 6 7 8 9 10 |
|
En ambos casos permitimos el acceso a las mismas url, pero si añadimos una acción al controlador, estará protegida por defecto.
Commit en GitHub: e58ea71cc3: isAuthenticated policy.
Para los casos de editar y eliminar, debemos asegurarnos de que el usuario es él mismo, para evitar que un usuario pueda editar o eliminar cuentas que no son la suya. Más adelante podremos establecer perfiles administradores que también puedan realizar esas acciones, por eso vamos a llamar a la política canAdminUser
:
1 2 3 4 5 |
|
Además tendremos que actualizar nuestras políticas:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Ahora si intentamos editar o eliminar un usuario no podremos. Así que deberíamos quitar los botones de la interfaz de usuario para reducir la generación de errores. Por lo que añadimos este condicional para mostrar las opciones sólo en caso necesario.
1 2 3 4 5 6 7 8 |
|
Una solución más elegante para no tener que aplicar esta lógica en la template sería utilizar la variable session.canAdminUser. Una forma más elegante, que delega la responsibilidad de la lógica al controlador, que es quien lo debe hacer.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
De esta forma si cambiamos las políticas de acceso a la página de listado de usuarios nos aseguramos que solo verán las acciones editar y eliminar aquellos que puedan ejecutarlas.
Commit en GitHub: 01a7287d07: Can Admin User Policy.
Código en GitHub: building-realtime-webapp. Release: auth
.
Entorno de desarrollo en Heroku: building-realtime-webapp.
Código en GitHub: building-realtime-webapp. Release: users
.
Entorno de desarrollo en Heroku: building-realtime-webapp.
Hace unos días vimos cómo crear webapps realtime y hoy vamos a implementar la gestión de usuarios. Podemos hacerlo de forma básica o usando algun middleware como Passport o Everyauth. En este artículo empezaremos creando el controlador y el modelo User
y posterior mente haremos la integración con un middleware para ampliar y mejorar la gestión de usuarios.
Si quieres empezar desde este punto y seguir el manual paso a paso puedes clonar el repositorio desde la release init:
$ git clone https://github.com/jorgecasar/building-realtime-webapp.git
$ git checkout init
Sails proporciona un ORM (Object Relational Mapping) llamado Waterline para normalizar las interacciones con modelos. De esta forma nos podemos olvidar del origen de los datos (una vez configurado, claro). Nosotros atacamos al modelo con los métods que veremos a continuación y el waterline se encargará de almacenarlos y recuperarlos del origen. De esta forma especificamos el esquema de nuestra base de datos y podremos utilizar PostgreSQL, MySQL, MongoDB, Memory, Disk, Redis, Riak, IRC, Twitter, JSDom o cualquiera que nos desarrollemos como fuente de nuestros datos.
Nosotros vamos a utiliar MongoDB para almacenar nuestros datos. Si no tenéis instalado MongoDB podéis consultar el manual de instalación de MongoDB. Después tenemos que incluir el paquete de node sails-mongo
, que nos incluirá el waterline sails-mongo y eliminar la dependencia de sails-disk
.Añadimos el flag --save
para que modifique las dependencias de package.json
, añadiendo y quitando, respectivamente:
$ npm install --save sails-mongo
$ npm uninstall --save sails-disk
Ahora tenemos que pasar por la configuración puesto que por defecto utiliza el Waterline Disk. Abrimos el fichero: confi/adapters.js
y añadimos lo siguiente:
'default': 'mongo',
[…]
mongo: {
module: 'sails-mongo',
url: process.env.DB_URL, // variable de entorno.
schema: true
}
Como véis hemos utilizado una variable de entorno para no exponer nuestros datos de configuración de producción si subimos el código a algún repositorio público.
Todo el proyecto lo vamos subiendo a un entorno en la nube, en mi caso Heroku, así que vamos a ver cómo dar de alta un Add-on:
Recomiendo echarle un ojo a la documentación de Heroku sobre variables de configuración.
Para añadir complementos a Heroku podmeos hacerlo desde el panel de control de heroku entrando en nuestra aplicación y en la sección recursos hacemos click en Get Add-ons. Aquí podemos buscar el complemento que necesitamos, en este caso buscando por Mongo nos aparen dos: MongoHQ y MongoLab. Como MongoHQ no está disponible en Europa, no nos queda otra que optar por MongoLab. La versión gratuita es muy parecida en ambos y da de sobra para entornos de desarrollo, así que nos facilitan la elección. Al final de la tabla de características encontráis la posibiliad de incluir el complemento a alguna de vuestras aplicaciones y el código para ejecutarlo en consola.
$ heroku addons:add mongolab --app building-realtime-webapp-dev
Hemos incluido el nombre de la app porque en el directorio tenemos 2 aplicaciones Heroku, la de producción y la de desarrollo, así que de momento lo incluímos en la desarrollo.
Heroku nos incluye una variable de entorno con la cadena de conexión a la base de datos llamada MONGOLAB_URI
, podemos usar esa directamente en nuestro adapter.js
o bien crearnos la que habíamos elegido, DB_URL
. Para obtener la url podemos copiar la que nos incluye Heroku y luego añadir la nuestra:
$ heroku config --app building-realtime-webapp-dev
MONGOLAB_URI: mongodb://[user]:[password]@[host]:[port]/[database]
$ heroku config:set DB_URL=mongodb://[user]:[password]@[host]:[port]/[database] --app building-realtime-webapp-dev
Podemos sobrescribir esta configuración para que nos funcione en local modificando el fichero config/local.js
. La configuración pude hacerse indicando cada una de los atributos por separado o mediante la cadena de conexión que aglutina todos los atributos en un solo script.
adapters: {
'default': 'mongo',
mongo: {
module: 'sails-mongo',
// Config by parts. Sails will generate the connection uri
host: 'localhost',
user: '',
password: '',
database: 'building-realtime-webapp',
schema: true
// Config by connection URI.
// url: mongodb://localhost/building-realtime-webapp
}
}
Antes de seguir, vamos a comprobar que todo funciona correctamente. Debemos abrir 2 consolas para:
El demonio de MongoDB:
$ mongod
all output going to: /usr/local/var/log/mongodb/mongo.log
Levantar el proyecto, como ya sabemos: $ sails lift
o `$ foreman start
Commit en GitHub: 5f34185918: Config MongoDB as default adapter.
MongoDB no incluye por defecto ningún administrador de bases de datos visual. Con el comando mongo
entramos en la consola de MongoDB y podemos realizar consultas. Como no tenemos ningún modelo, Sails todavía no ha crado la base de datos ni las colecciones, así que no os asustéis si véis la base de datos vacía.
Personalmente, opino que la línea de comandos está muy bien y recomiendo que siempre le echéis un ojo y la conozcáis antes de pasar a herramientas con Interfaz de Usuario. En Mongo Administration Interfaces tenéis unas cuantas herramientas. Para visualizar la base de datos suelo usar Genghis y si voy a tener que administrarla, haciendo consultas, exportaciones, etc… uso MongoHub.
User
Sails.js ofrece una serie de generadores como ya vimos en el artículo anterior y esta vez vamos a utilizar el generador de modelo y controlador. Si quisiéramos sólo crear el modelo o el controllador especificaríamos model
o controlller
después de generate
, respectivamente. Pero en este caso necesitaremos ambos:
$ sails generate user
info: Generating model and controller for user...
Si nos fijamos en el directorio /api
veremos que contiene las siguientes carpetas:
Como os podéis imaginar el comando anterior nos ha creado los ficheros /api/models/User.js
y /api/controllers/UserController.js
.
Sails por defecto tiene una serie de blueprints, todos activados por defecto.
/<controller>/<action>
.
find
, create
, update
, and destroy
asociados a sus respectivos verbos:
Todas estas acciones podemos dejarlas activadas en desarrollo para facilitarlos algunas tareas. Pero en el entorno de producción, las acciones y el API REST podemos dejarlos activados, pero los atajos habrá que deshabilitarlos. Esta configuración puedes modificarla en cada Controller o de manera global en config/controllers.js
.
Ahora si volvemos a lanzar nuestro servidor (sails lift
) podemos realizar algunas pruebas y ver la potencia de Sails recién salido de la caja. La primera es comprobar que la base de datos se ha crado correctamente.
Por línea de comandos:
$ mongo
> use building-realtime-webapp
switched to db building-realtime-webapp
> show collections
system.indexes
user
Con Genghis:
$ genghisapp
Starting 'genghisapp'...
'genghisapp' is already running at http://0.0.0.0:5678
Entramos en http://0.0.0.0:5678 y navegamos por nuestro servidor localhost > building-realtime-webapp > user
Parece que todo funciona, ahora le toca el turno a los blueprints. Antes de probar las siguientes URLs, debéis hacer un cambio en config/local.js
y poner a schema: false
. Esto hará que no tengamos un esquema de la base de datos definido y podamos introductir cualquier atributo a la colección. Con schema: true
, sólo se guradarán los atributos que hayamos definido en /api/models/User.js
. Os recomiendo que probéis a cambiar la configuración de los blueprints en config/controllers
y la del esquema para ver las diferentes configuraciones y podáis entender qué sucede con nuestro velero. Recordad que hay que alzar las velas (sails lift
) con cada cambio de configuración.
Commit en GitHub: 153a53883a: Create Model and Controller for User.
User
Recomiendo echarle un ojo a la Documentación de Sails sobre Modelos.
En el archivo /api/model/User.js
especificamos los atributos necesarios. Empezaremos simplemente con email y contraseña:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Salis nos permite crear nuestros propios métodos y sobrescribir los métodos toObject
y toJSON
(se especifican en attributes). Veamos un ejemplo de ambos para evitar que la contraseña llegue al cliente:
1 2 3 4 5 6 7 8 9 |
|
Ahora si volvemos a levantar el servidor y probamos introducir un nuevo usuario: http://localhost:1337/user/create?email=someone@somewhere.com&password=securePass007. Vemos que el JSON que nos devuelve el servidor no incluye el atributo password, pero si visualizamos la base de datos (http://localhost:5678/servers/localhost/databases/building-realtime-webapp/collections/user, vemos que si se ha guardado correctamente.
Guardar la contraseña directamente en la base de datos es una mala práctica, así que vamos a ver cómo encriptarla usando el paquete bcrypt
. Instalamos y guardamos la dependencia ejecutando:
$ npm install --save bcrypt
Sails nos ofrece una serie de callbacks del ciclo de vida del modelo para que podamos unirnos en cualquier parte del proceso de la consulta:
fn(values, cb)
fn(values, cb)
fn(newlyInsertedRecord, cb)
fn(valuesToUpdate, cb)
fn(valuesToUpdate, cb)
fn(updatedRecord, cb)
fn(criteria, cb)
fn(cb)
En nuestro caso necesitamos ejecutarla al crear, aunque más adelante tendremos que dar la posibilidad al usuario de actualizar su contraseña y por tanto debemos encriptarla al actualizar también:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Ahora si volvemos a levantar el servidor y probamos introducir un nuevo usuario: http://localhost:1337/user/create?email=other@somewhere.com&password=securePass007. Vemos que la contraseña es una cadena de caracteres inlegible (http://localhost:5678/servers/localhost/databases/building-realtime-webapp/collections/user.
Commit en GitHub: 153a53883a: Define User Model and include bcryp package.
En el siguiente commit hemos añadido un método para verificar la contraseña, puesto que lo utilizaremos para comprobar que es correcta cuando queremos cambiarla. También hemos mejorado la callback beforeUpdate añadiendo la lógica necesaria para cambiar la contraseña, verificando que la anterior es correcta y que la nueva se introduce correctamente 2 veces. Por último hemos mejorado el método hashPassword generando un salt previo al hash. Están todas las lineas comentadas así que es fácil ver que se hace en cada una de ellas.
Commit en GitHub: e881b5cba8: Added method validPassword, improved methods beforeUpdate and hashPassword.
La contraseña es un atributo obligatorio en en el modelo del usuario, por lo tanto al comprobar la validez de los datos nos saltará un error si no la introducimos. Por ello, las comprobaciones que hacíamos en beforeUpdate hay que hacerlas en beforeValidation para evitar que falle al no introducir contraseña. Además hay que comprobar mediante el id si no esncontramos en una cración o en una actualización de usuario.
UserController
Recomiendo echarle un ojo a la Documentación de Sails sobre Controladores.
Ahora que tenemos nuestro modelo User
listo vamos a incluir unas acciones en nuestro UserController
que nos permitirán interactuar con nuestros modelos de una forma más avanzada que las acciones por defecto. Se han mantenido las acciones estandar para estar en consonancia con la API REST y los Sortcurs CRUD, para evitar tener que cambiar routes, que ya veremos en siguientes artículos cómo hacerlo.
GET
a /user/:id?
. La ‘?’ significa que el parámetro id
es opcional. Por lo que el controlador está preparado para buscar un usuario por id o varios en función de lo especificado en el where. Además se podrán limitar, saltar y ordenar los resultados.POST
a /user
, creando un usuario y devolviendo la instancia creada.PUT
a /user/:id
. El parámetro id es obligatorio puesto que no se permite la edición múltiple de usuarios.DELETE
a /user/:id
edit
, la cual contendrá el formulario de edición con los valores precargados.new
con un formularo para la creación de un nuevo usuario.Por no copiar todo el controlador aquí, voy a destacar las parte que me parecen más interesantes, puesto que el resto lo tenéis en GitHub. Vamos a empzar con un fragmento de la acción find
que permite, filtrar y paginar los resultados. Gracias a estas 10 lineas podrás hacer consultas a urls como: http://localhost:1337/user/?where={“email”:{“contains”:”somewhere.com”}}&limit=2&sort=email DESC y obtener como máximo 2 usuarios ordenados de manera descendente cuyos emails contengan “somewhere.com”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Otra parte interesante es la forma de devolver los resultados, ya que antes de hacerlo comprobamos qué tipo de respuesta se nos está solicitando. En caso de solicitar JSON devolvemos los resultados en JSON y en caso contraro le pasamos los resultados a la vista correspondiente.
1 2 3 4 5 6 |
|
Commit en GitHub: 046d2301bf: UsarController: API REST friendly.
User
Recomiendo echarle un ojo a la Documentación de Sails sobre vistas.
Por último, tendremos que crear una vista para mostrar los resultados. Si os fijáis no hemos definido el nombre de la vista ya que Sails asocia por defecto cada acción de un controlador a la vista localizada en /views/[controlador]/[acción]
. Sabiendo esto debemos crear el directorio /view/user
y los ficheros find.ejs
, new.ejs
y edit.ejs
. El resto de acciones no se reflejarán en una vista por lo cual no las necesitamos.
Antes de enseñar las peculiaridades de las vistas he incluido Bootstrap y jQuery (dependencia de Bootstrap) en el directorio /assets/linker/js
y los estilos de Bootstrap en assets/linker/styles/
. De esta manera se incluyen automáticamente en el layout. Para asegurarnos que el orden es el correcto abrimos el fichero /Gruntfile.js
y lo modificamos indicando el orden de inclusión de los recursos:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Commit en GitHub: e3479ce836: Include jQuery and Bootstrap. Modify GruntFile to set the order.
Las vistas en si no tienen mucho misterio. Destacar que Sails utiliza el motor de templates EJS: Embebed Javascript, lo cual me recuerda mucho a PHP donde puedes mezclar código de scripting con HTML. Afortunadamente, todos sabemos que eso es una mala práctica y hay que dejarle la lógica al controlador y utilizar en las vistas la menor posible. Para este artículo, al no estar dedidaco a las vistas no he querido complicarlas mucho incluyendo partials, cambiando el layout o indicando la vista desde el controlador, pero que sepáis que se puede hacer y ya vermos cómo en otros artículos.
Commit en GitHub: 3785bbe071: Included User views. Modified index and layout. Include some styles to customize Bootstrap.
Ahora que tenemos todo subido podemos hacer un push al entorno de desarrollo:
$ git push heroku-dev develop:master
Y por último podemos ver el resultado en http://building-realtime-webapp-dev.herokuapp.com/:
]]>Código en GitHub: building-realtime-webapp. Release: init
.
Hace unos meses descubrí Sails.js, un framework MVC para Node basado en Express que te facilita la creación de aplicaciones web realtime mediante Websockets. Así que no me extraña que el slogan sea:
THE WEB FRAMEWORK OF YOUR DREAMS.
Designed for developers by Giant Squid.
Obviamente necesitamos tener Node.js. En mi caso, tengo la última versión estable, la 0.10.24, puesto que voy a desplegar en Heroku y acepta cualquier versión superior a la 0.8.5. Os recomiendo revisar qué versión hay instalada en el servidor donde vayáis a hacer despliegue de producción. Para comprobar la versión que tienes en local, ejecuta en la consola: node -v
. Para gestionar la versión de node os recomiendo el paquete n
de npm. Lo instalais globalmente para tenerlo disponible desde cualquier parte y luego podrás instalar la versión última versión estable con el comando sudo n stable
Podemos instalar Sails.js local o globalmente (flag -g)el paquete sails:
$ sudo npm -g install sails
Tenemos que llamar al commando new
que nos creará un proyecto sails en la carpeta <appName>
en el directorio desde donde lo ejecutemos.
$ sails new <appName> [--linker]
$ cd <appName>
También existe la opción de usar un enlazador de recursos automático añadiendo la flag --linker
. Esto instalará una tarea de Grunt llamada grunt-sails-linker que automatiza la adición de etiquetas html basadas en ficheros. Incluyendo la flag sails creará el Gruntfile.js
implementando esta tarea de Grunt.
Con el proyecto creado y estando en su directorio podremos lanzarlo:
$ sails lift
Una vez lanzado, vemos que el barco ha zarpado y podemos ver nuestra webapp en http://localhost:1337
Es muy recomendable tener un control de versiones aunque sea simplemente en local para poder deshacer cambios y tener un flujo de trabajo controlado.
$ git init
Además, quién sabe si mañana no eres tú solo el que toca el código. Así que, inicializamos un git y enviamos lo que llevamos. Os recomiendo echarle un ojo al modelo de ramificación Git acertado.
De momento vamos a crear únicamente las ramas master
y develop
para nuestro proyecto y según vayamos necesitando las demás las vamos creando a partir de la rama que corresponda.
$ git checkout -b master && git checkout -b develop
De esta manera nos quedamos en la rama develop
para empezar ahí nuestro proyecto y cuando tengamos algo estable lo combinaremos con la rama master
.
En mi caso, voy a usar GitHub para compartir mi código con todos vosotros. Creamos nuestro nuevo repositorio en GitHub e incluimos la url del repositorio a nuestro proyecto:
$ git remote add origin <git_url>
Ahora que tenemos nuestro repositorio git es hora de hacer el primer commit:
$ git add .
$ git commit -m "First commit"
$ git push -u origin develop
Una vez tenemos nuestro proyecto en local vamos a ver si nos funciona correctamente en el servidor de producción. Voy a describir los pasos claves, pero si tienes algún problema puedes echarle un ojo Empezar con Node.js en Heroku y Desplegando con git para obtener información adicional.
Antes de empezar necesitaremos un par de cosas:
Después de instalar las Toolbelt tendrás disponible el comando heroku
en tu consola. Así que vamos a registrarnos en Heroku y configurar nuestro entorno:
$ heroku login
Nos pedirá nuestro nombre de usuario, contraseña y si queremos crear una llave pública SSH (SSH public key) para poder publicar nuestro código posteriormente.
Es recomendable indicar en el “`package.json“ la versión de node que queremos utilizar para evitar problemas y asegurarnos que estamos usando una versión que soportamos.
~~~ json “engines”: { “node”: “0.10.x” } ~~
Heroku usa el fichero Procfile para indicar los comandos que va a ejecutar la aplicación, así que le tenemos que indicar que queremos un comando de tipo web y que ejecute nuestro script de Node.js:
web: node app.js
Ahora podemos arrancar la aplicación usando Foreman, que también lo tendrás instalado como parte de las ToolBelt:
$ foreman start
Seguramente te de un error porque no encuentra el módulo sails
, así que instala las dependencias para tener una copia de sails en node_modules, puesto que al tenerlo instalado globalmente foreman no lo encuentra.
$ npm install
Si todo funciona correctamente, subimos nuestro nuevo fichero al repositorio:
$ git add .
$ git commit -m "Added Procfile"
$ git push origin develop
Heroku utiliza git para la gestión del código, así que tendremos que crear la aplicación en Heroku y poner una referencia en nuestra lista de repositorios remotos.
$ heroku apps:create <appName> --remote <remote> --region eu
Esto creará una aplicación <appName>
en Heroku, creará un repositorio remoto llamado <remote>
(por defecto, heroku) e indicamos que nuestra región es Europa.
Si tenemos la aplicación creada en Heroku, podemos añadirla como repositorio remoto:
$ heroku git:remote -a <appName>
Debido a que tenemos la rama maestra y la de desarrollo, y Heroku sólo tiene en cuenta la rama master para los despliegues en sus servidores, recomiendo crear otra app en Heroku para desarrollo. Yo lo he hecho añadiendo -dev
tanto al nombre de la app con al nombre del repositorio remoto.
$ heroku apps:create <appName>-dev --remote <remote>-dev --region eu
Hasta ahora solo hemos subido el código a la rama develop
por lo que tendremos que desplegar en la rama master de heroku-dev
.
$ git push heroku-dev develop:master
Veremos cómo detecta que es una aplicación Node.js, instala las dependencias, cachea node_modules para futuros despliegues, construye el entorno, parsea el Procfile comprime y lanza. Ahora podemos ver nuestra webapp en el entorno de pre-producción http://building-realtime-webapp-dev.herokuapp.com
]]>Las páginas de proyectos se pueden crear automáticamente desde los ajustes (settings) de tu proyecto presionando el botón “Automatic Page Generator” y siguiendo los pasos. Esto creará una nueva rama en tu proyecto llamada gh-page la cual se desplegará cada vez que hagas un push en dicha rama. por lo que también puedes crearlas manualmente, sincronizar tu rama master con gh-pages o reemplazar la rama master por gh-pages. De una forma similar se pueden crear páginas para los usuarios y para las organizaciones, en este caso, hay que crear un repositorio llamado username.github.io dónde username es tu nombre de usuario o el nombre de la organización en GitHub. Asegurate de escribir correctamente el username, porque si no no funcionará. Lo que describo en este artículo también es aplicable a las páginas de proyecto, pero nos centraremos en las de usuario y organización.
Las GitHub Pages usan Jekyll, un generador de sitios estáticos simple, con conciencia de blog, en Ruby. Para ello se necesita un directorio donde almacenar los ficheros de texto sin procesar y mediante conversores escupe una página estática completa lista para publicar en tu servidor. Los textos sin procesar utilizan sintaxis Markdown y con ello nos olvidamos de las bases de datos pudiéndonos centrar en el contenido.
Antes de ponerse a subir contenido a las GitHub Pages es recomendable tener un entorno en local para previsualizar los cambios y así evitar hacer push innecesarios y esperar a que se actualice la GitHub Page para ver si lo hemos hecho bien. Para instalar Jekyll necesitamos:
Una vez lo tenemos, instalamos la gema jekyll con gem install jekyll
, la cual utilizará Maruku para Markdown, pero si quieres puedes utilizar LaTeX, RDiscount o Kramdown, pero no obstante, recomiendo dejar Maruku.
El siguiente paso sería crear nuestro blog, para lo cual tenemos 2 opciones, utilizar Jekyll directamente o utilizar un framework como Octopress. Veamos cada una de las opciones.
Jekyll nos ofrece la posibilidad de crear el esqueleto de nuestro blog mediante el comando jekyll new username.github.io
y creará una carpeta llamada username.github.io, recordad que username es vuestro nombre de usuario u organización de GitHub. Esto genera la estructura siguiente:
.
├── _config.yml
├── _layouts
| ├── default.html
| └── post.html
├── _posts
| └── 2013-12-18-welcome-to-jekyll.markdown
├── css
| ├── main.css
| └── syntax.css
└── index.html
Luego podemos ir creando más carpetas y ficheros, pero estos son los básicos para empezar:
Con esto es suficiente para empezar a crear tu blog, pero puedes aprender más sobre plantillas, enlaces permanentes, paginación y plugins leyendo la documentación de Jekyll.
Si no queremos empezar desde cero creando nuestras propias plantillas HTML, CSS y Javascript, podemos utilizar Octopress, un framework de blogs para hackers. Octopress viene con:
Además existen plugins creados por Octopress o por la comunidad de Jeklly con algunas mejoras. Puedes consultar el listado de plugins si quieres conocerlos.
Para empezar con Octupus debes comprobar que tienes Ruby 1.9.3 o superior, puedes comprobarlo escribiendo en la consola ruby --version
. Una vez comprabdo ejecutamos los siguientes pasos:
git clone git://github.com/imathis/octopress.git username.github.io
</div>
gem install bundler
rbenv rehash # Si usas rbenv, rehash para poder ejecutar el comando bundle
bundle install
</div>
rake install
</div>
Si queremos instalar una plantilla diferente a la que viene por defecto puedes echar un ojo a lista de plantillas en Opthemes e instalarla siguiend estos pasos:
git clone GIT_URL .themes/THEME_NAME
rake install['THEME_NAME']
rake generate
Personalmente prefiero incluir las themes como submodulos, pero tened en cuenta que gh-pages no incluye módulos de manera recursiva, únicamente los de primer nivel, así que si la template tiene submódulos no podréis incluirla como submódulo puesto que los submódulos de la template no se instalarán al desplegarla como gh-pages.
git submodule add GIT_URL .themes/THEME_NAME
rake install['THEME_NAME']
rake generate
Una vez que tenemos una primera versión de nuestro blog podemos desplegarlo en GitHub. Recuerda que primero has de crear el repositorio con nombre username.github.io
. Una vez que lo tienes tendrás que configurar tu clon de Octopress para que puedas hacer los commits a tu repositorio. Para ello exite el comando, el cual te preguntará por la url de tu repositorio y configurará todo lo necesario para usar tu blog como GitHub Page.
rake setup_github_pages
Este comando realiza lo siguiente:
origen
a octopress
origin
master
a source
master
en el directorio _deploy
para el despliegueUna vez configurado el entorno para trabajar con GitHub Pages podemos previsualizar el resultado ejecutando el comando:
rake preview
Y cuando hemos comprobado que está todo listo para subirlo a GitHub debemos generar el contenido estático y desplegarlo:
rake generate
rake deploy
Y no olvides subir el código fuente de tu blog:
git add .
git commit -m 'My new GitHub Page'
git push origin source
Ahora que tienes el entorno configurado en local y delplegado en GitHub es hora de escribir tu primer post. Todos los post se encuentan en la carpeta _posts
y deben tener el siguiente formato aaaa-mm-dd-title.MARKUP
, donde MARKUP
es la extensión del formato elegido, en nuestro caso será md
. El formato del nombre del fichero es requerimiento de Jekyll, así que a cumplirlo a rajatabla.
Si estás usando Octopres los post se encuentran en source/_posts
y puedes automatizar la creación de nuevos artículos usando el sigueinte comando:
rake new_post["title"]
El contenido será renderizado el motor de markup que hayas elegido, pero adicionalmente dispones de todas las características de las templates liquid descritas en la documentación de Jekyll.
Puedes definir el separador del estracto usando la variable de configuración excerpt_separator
, en caso de estar usando Octopress el separador por defecto es: <!-- more -->
.