El spam ha condicionado muchas prácticas que originariamente eran tan sencillas y poderosas como enlazar tu mail en la web.
Un truqui para evitarlo que en su día lo descubrimos en 604th.net:
function nospam(name,domain){
window.location = 'mailto:' + name + '@' + domain;
}
Cópialo tal cual sin cambiar nada y súbelo como emailnospam.js. Recomendamos subirlo a una carpeta en la que guardes los javascripts como por ejemplo la carpeta js. Si no te apetece crearla, súbelo a la raíz de tu ftp.
Luego añade lo siguiente entre la línea <head> y </head> de los documentos de tu web:
Ya solo queda que escribas tu mail enlazado en la parte de la web que quieras sustituyendo la palabra ejemplo y dominio.com por lo que corresponda a tu cuenta de email:
A la hora de darle una funcionalidad más enfocada a aplicaciones de escritorio (por ejemplo con un editor WYSIWYG), suele ser necesario usar combinaciones de teclas CONTROL+tecla para ejecutar ciertas acciones.
Capturar la combinacion CTRL+tecla en javascript es muy sencillo:
var isCtrl = false;
document.onkeyup=function(e){
if(e.which == 17) isCtrl=false;
}
document.onkeydown=function(e){
if(e.which == 17) isCtrl=true;
if(e.which == 83 && isCtrl == true) {
// acción para CTRL+S y evitar que ejecute la acción propia del navegador
return false;
}
}/
La explicación es sencilla, cuando se pulsa CTRL+tecla se producen dos eventos de teclado, por lo que no es posible saber si se ha ejecutado en una sola llamada. Por ello primero comprobamos si se pulsa la tecla CTRL y se pone un flag a verdadero y luego dependiendo de la tecla que queramos, si se detecta que se pulsa y está el flag activo, se realiza la acción. Hay que devolver false para que el navegador no ejecute la acción que tiene por defecto. Cuando se deja de pulsar la tecla CTRL se pone el flag a false.
Muchas veces el diseño que creamos nos hace usar listas no ordenadas, las cuales queremos que tengan descripción, pero por la falta de espacio, queremos que esta ocupe poco espacio.
Para crear este efecto, usaremos una lista y una capa por cada descripción que queremos mostrar, a parte de un script que mostrará la descripción cuando pasamos el ratón sobre el elemento de la lista.
El otro dÃa contábamos como realizar drag&drop de elementos HTML y en el ejemplo lo que hacÃamos era recolocar los elementos dentro del HTML. Indicábamos que una buena utilidad para este efecto podrÃa ser para realizar comparativas de productos en una tienda.
Cuando un usuario se registra en una aplicación web, suele haber casos que cuando se está introduciendo la contraseña, el sistema indica si la password cumple unos mÃnimos de seguridad o no. En algunas ocasiones suele darnos solo un aviso informativo, pero en otros o cumple todos los requisitos o no admite la contraseña.
En este caso vamos a crear un script que modificará los input password para añadirle una funcionalidad que indique la calidad de la contraseña que se va introduciendo.
El script añadirá el evento de control de tecla pulsada (onkeyup) y realizará una serie de comprobaciones para conocer la calidad de la contraseña, una contraseña válida será la que cumpla todas las condiciones. Las condiciones que incluimos en el ejemplo son las tÃpicas que suelen pedir: que existan mayúsculas y minúsculas, algún número, caracteres especiales y una longitud mayor de 6.
Para chequear cada condición usaremos expresiones regulares y llevaremos un contador para saber cuántas se cumplen. Cuando hayamos finalizado las comprobaciones calcularemos el porcentaje de calidad y lo indicaremos con una barra de progreso. Esta barra se crecerá según aumente la calidad de la contraseña e irá cambiando de color desde un color rojo para la poca calidad hasta un color verde que indique mucha calidad.
El efecto de barra de progreso lo vamos a realizar de la siguiente manera, tenemos una imagen que va a funcionar como máscara, tendrá dos partes, de igual tamaño y cada parte del mismo tamaño que el ancho del input. Cada mitad tendrá una funcionalidad, la mitad de la derecha servirá para ocultar y la de la izquierda para mostrar, según queramos que se vaya mostrando la barra de progreso, iremos desplazando el fondo hacia la derecha para que se vaya viendo la barra de progreso. Inicialmente la lÃnea discontinua de la imagen que mostramos como ejemplo estará en el lado izquierdo del input.
El código serÃa el siguiente:
var __PASSWORD__ = {
colorKO: [255, 0, 0], // Color de contraseña no válida
color50: [127, 127, 0], // Color para el 50% para que no haya un cambio tan brusco
colorOK: [0, 255, 0], // Color de contraseña válida
width: 150,
// Añade el evento onkeyup a la caja input:password y le añade el evento checkpassword
init: function() {
var inputs = document.getElementsByTagName("INPUT");
for (var i=0; i<inputs.length; i++) {
if (inputs[i].type == 'password') {
inputs[i].addEventListener("keyup", __PASSWORD__.checkPassword, false);
inputs[i].className = 'password';
}
}
},
// Obtiene el color porcentual entre un color inicial y otro final, teniendo en cuenta un color intermedio
getColor: function(porc) {
var color = new Array();
var color1 = porc < 50? __PASSWORD__.colorKO:__PASSWORD__.color50;
var color2 = porc < 50? __PASSWORD__.color50:__PASSWORD__.colorOK;
color[0] = parseInt(color1[0]-((color1[0] -color2[0])*porc/100));
color[1] = parseInt(color1[1]-((color1[1] -color2[1])*porc/100));
color[2] = parseInt(color1[2]-((color1[2] -color2[2])*porc/100));
return color;
},
// Comprueba la calidad de la contraseña
checkPassword: function() {
var valor = this.value;
var cont = 0;
var comprobaciones = new Array(
/[A-Z]/, // mayúsculas
/[a-z]/, // minúsculas
/\d/, // números
/.{6}/, // más de 6 caracteres
/(\s|\\|\/|!|"|·|\$|%|&|\(|\)|=|\?|¿|\||@|#|¬|€|\^|`|\[|\]|\+|\*|¨|´|\{|\}|\-|_|\.|:|,|;|>|<)/ // Caracteres especiales
);
// Miro todas las condiciones
for (var i=0; i<comprobaciones.length; i++) {
if (valor.match(comprobaciones[i])) {
cont++;
}
}
// Valores posibles en caso de 5 condiciones: 0, 20, 40, 60, 80, 100, pero el valor mÃnimo tiene que ser 20 para que coincida con el colorKO, ya que si no, al pulsar una tecla ya cambia de color a un paso colorKO+1
var porc = parseInt((cont*100/(comprobaciones.length-1))-(1/(comprobaciones.length-1)*100));
// Desplazo el background tratándolo como una máscara
this.style.backgroundPosition = (parseInt(cont*__PASSWORD__.width/comprobaciones.length)-(__PASSWORD__.width))+"px 0px";
// Cambio el color de fondo
var color = __PASSWORD__.getColor(porc);
if (porc < 0) {
this.style.backgroundColor = '';
} else {
this.style.backgroundColor = 'rgb('+color[0]+', '+color[1]+', '+color[2]+')';
}
}
};
window.addEventListener("load", function() {__PASSWORD__.init();}, false);
Hoy toca realizar conexiones con el servidor usando llamadas autenticadas. Para eso usaremos JWT (JSON Web Token), que viene a ser el envío de un token en cada llamada que necesita autenticación. El token está formado por tres cadenas codificadas en base64 y separadas por un punto (header.payload.signature). En el payload se envía toda la info que queramos, pero sin pasarse porque el token no debería ser muy largo y sobre todo sin enviar información sensible, porque el token no está encriptado, es por ello que la comunicación navegador/servidor debe ser mediante HTTPS. Si quieres una explicación más detallada, aquí te lo explican mejor.
En el payload vamos a guardar varios claims, entre ellos el username y un uuid que usaremos para validar que el usuario es correcto. JWT asegura que el token es válido, pero no viene mal añadir cierta seguridad. Cuando el usuario se loguea un nuevo uuid es generado, por lo que se comprobará si coinciden para el username enviado.
Ya tenemos la explicación teórica, ahora vamos con la parte de programación, primero la parte servidor y luego el frontend.
Lo primero que tenemos que hacer es añadir un nuevo plugin a hapi.js que se encargue de la autenticación, registrando un nuevo strategy a server.auth que use JWT. El plugin hapi-auth-jwt2 se encargará de toda la autenticación JWT y añadiremos una capa extra de validación comprobando que el username y el uuid coinciden.
Y por último añadimos una nueva ruta para autenticar (login) el usuario, comprobamos que el usuario y la contraseña coinciden, y si es así, creamos un uuid que guardamos en la bd y generamos el JWT y lo enviamos en la cabecera Authorization:
Vale, ya tenemos la parte del servidor, ahora la parte del frontend. Primero es añadir las rutas para /login, /account y /logout. He añadido un meta que indica si la ruta tiene que ser obligatoriamente autenticada, obligatoriamente no autenticada o como sea. Para ello, para cada ruta comprobará si el JWT token está almacenado o no y según el meta redirigirá a home o continuará:
Para gestionar el almacenamiento en el navegador en vez de cookies vamos a usar sessionStorage y localStorage para guardar el token JWT. Como el formulario de login permite recordar la sesión, vamos a usar ambos storages. Si no se recuerda usaremos sessionStorage, que se borrará cuando se cierra el navegador, en caso contrario usaremos localStorage.
import Config from'@/js/config';
/**
* API methods for sesion/local storage.
* Depending on `this.session` it saves only on `sessionStorage` or also in `localStorage`
*
* @since v0.8.0
*/classstorage{
/**
* Constructor
*
* @param {boolean} session If stored only in session
*/constructor( session = false ) {
this.session = session;
}
/**
* It saves the token in the session (and local storage is this.session === false),
*
* @param {strig} token JWT token
*/
setJWTToken( token ) {
sessionStorage.setItem( Config.jwt.storageKey, token );
if ( ! this.session ) {
localStorage.setItem( Config.jwt.storageKey, token );
}
}
/**
* It gets a value from session storage or in local if session = false
*
* @returns {string}
*/
getJWTToken() {
const sessionValue = sessionStorage.getItem( Config.jwt.storageKey );
if ( sessionValue ) {
return sessionValue;
}
if ( ! this.session ) {
const storedValue = localStorage.getItem( Config.jwt.storageKey );
return storedValue;
}
returnnull;
}
/**
* Removes JWT token from session and local storage
*/
removeJWTToken() {
sessionStorage.removeItem( Config.jwt.storageKey );
localStorage.removeItem( Config.jwt.storageKey );
}
}
exportdefault storage;
También hemos creado una librería para tratar las llamadas a la API. Hay dos métodos, uno para autenticar el usuario (login) que mirará la cabecera Authorization, y otro método que obtiene los datos del usuario actual realizando una llamada autenticada enviando el JWT token en la cabecera Authorization:
import Config from'@/js/config';
import Storage from'@/js/utils/storage';
/**
* API backend methods
*
* @since 0.8.0
*/classapiFetch{
/**
* Authenticate an user, if ok, JWT token is sent by the server in Authorization header
*
* @param {string} username User name
* @param {string} password Password
*
* @returns {Promise}
*/
auth( username, password ) {
return fetch( Config.api.user.auth, {
method: 'POST',
body: JSON.stringify(
{ username, password }
),
mode: 'cors',
} ).then( response => {
const auth = response.headers.get( 'Authorization' );
if ( auth ) {
return {
response: true,
token: auth,
};
}
return {
response: false,
message: 'Username or password not valid',
};
} );
}
getUser( username ) {
return fetch( `${ Config.api.user.get }${ username }`, {
method: 'GET',
headers: {
Authorization: new Storage().getJWTToken(),
},
} ).then( response => response.json() );
}
}
exportdefault apiFetch;
Ahora solo faltan los controladores para login, account y logout. Login realizar la llamada al servidor y si se obtiene el JWT se guarda:
<template><sectionclass="section"><divclass="container"><h1class="title">
Login
</h1><divv-if="error"class="columns is-centered has-margin-bottom-2"
><b-notificationclass="column is-7-tablet is-6-desktop is-5-widescreen"type="is-danger"has-iconaria-close-label="Close notification"role="alert"size="is-small "
>
{{ error }}
</b-notification></div></div><divclass="container"><divclass="columns is-centered"><divclass="column is-5-tablet is-4-desktop is-3-widescreen has-background-light login-form"><b-fieldlabel="Username"><b-inputv-model="username"value=""maxlength="30"icon="account-circle-outline"
/></b-field><b-fieldlabel="Password"><b-inputv-model="password"value=""type="password"icon="lock-outline"
/></b-field><divclass="field"><b-checkboxv-model="remember">
Remember me
</b-checkbox></div><divclass="has-text-right"><b-buttontype="is-primary"
@click="submit"
>
Log in
</b-button></div></div></div></div></section></template><script>import ApiFectch from'@/js/utils/api';
import Storage from'@/js/utils/storage';
exportdefault {
name: 'Login',
// Form data and error messages if login fails
data() {
return {
username: '',
password: '',
remember: false,
error: '',
};
},
methods: {
// It logs in, using the backend API for authenticate the user data.// If user logs in, it saves the JWT token in the browser. If not, shows error message.
submit: function() {
const api = new ApiFectch();
api.auth( this.username, this.password )
.then( response => {
const storage = new Storage( ! this.remember );
if ( !! response.response && !! response.token ) {
storage.setJWTToken( response.token );
this.error = false;
// `go` refreshes the page, so user data is updatedthis.$router.go( '/' );
} else {
storage.removeJWTToken();
this.error = response.message;
}
} );
},
},
};
</script><stylelang="scss"scoped>.login-form {
border-radius: 4px;
}
</style>
Logout borra los datos JWT del navegador y redirige a home:
<template><sectionclass="hero is-medium is-primary is-bold"><divclass="hero-body"><divclass="container has-text-centered"><h1class="title">
{{ message }}
</h1><h2class="subtitle">
Miss you 💛
</h2></div></div></section></template><script>import User from'@/js/utils/user';
exportdefault {
name: 'Logout',
// Dummy data
data() {
return {
message: 'Bye',
};
},
// After being created it logs out and go to home
created() {
new User().logout();
// `go` instead of `push` because refreshing the page updates the user data// Maybe using vuex is a better way to do it, or not...this.$router.go( '/' );
},
};
</script>
Y Account recupera los datos del usuario una vez que ha creado el controlador:
<template><divid="account"><sectionclass="hero is-primary is-bold"><divclass="hero-body"><divclass="has-text-centered"><h1class="title">
{{ message }}
</h1><h2class="subtitle">
Your data
</h2></div></div></section><sectionclass="section"><divclass="container"><divclass="tile is-ancestor"><divclass="tile is-parent"><pclass="tile is-child notification">
Some content
</p></div><divclass="tile is-8 is-parent"><divclass="tile is-child notification is-info"><ulid="data"><liv-for="( value, key ) in user":key="key"
>
{{ key }} : {{ value }}
</li></ul></div></div></div></div></section></div></template><script>// Dummy componentimport ApiFectch from'@/js/utils/api';
import User from'@/js/utils/user';
exportdefault {
name: 'Account',
data() {
return {
message: 'Account',
user: {},
};
},
created() {
const user = new User().getCurrentUser();
new ApiFectch().getUser( user.username )
.then( response =>this.user = response );
},
};
</script>
Muchas veces los diseños no suelen ir de acuerdo con el aspecto de los radio y los checkbox que nos ofrecen los navegadores.
Crear controles que sustituyan los ya existentes puede darnos problemas de accesibilidad, salvo en este caso (bueno, y supongo que en otros), ya que lo que hace este script es aprovechar la funcionalidad de las etiquetas label para que el funcionamiento recaiga sobre estas etiquetas y no sobre las checkbox o los radio.
El script buscará los inputs radio y checkbox y los ocultará y cambiará el estilo de las label asociadas para que el funcionamiento sea el mismo. ARC – Adam’s Radio/Checkbox customisation
VÃa / Infected-FX