Una de las tareas que siempre se presenta es la generación de contraseñas seguras para todos los sistemas tanto en producción como en desarrollo, y usaré esa necesidad propia como escusa para crear un pequeño proyecto angular de generación de contraseñas, y al mismo tiempo ofrecer un servicio más en la web Greenborn.
Manos a la obra
Repo en GitHub
Lo primero que necesitaremos es un repositorio de Github, claramente no es estrictamente necesario pero es una buena practica mantener el código versionado y en este caso está bueno para a quien le interese chusmear el proyecto, si recién comienza a trabajar con dicho Framework.
Enlace a repositorio: https://github.com/Greenborn/generador_contrase-as_web
Prerequisitos
Antes de comenzar es necesario tener instalado:
- NodeJS en su última versión estable.
- NPM para la gestión de paquetes.
- Angular-cli para el desarrollo del proyecto Angular, al proporcionar el comando ng
- Git (opcional) para la gestión del repositorio.
Creación de proyecto base
El proyecto Angular base se deberá crear utilizado el comando ng de la siguiente manera:
$ ng new GeneraPass
Dicho comando preguntará si se desea utilizar el Router del Framework, a lo cual responderemos que si, ya que si bien puede que no lo necesitemos en un principio, luego puede ser que se necesite si se agregan varias páginas a la navegación.
Luego nos preguntará si deseamos usar CSS, SCSS, SASS o LESS, aquí no me complicaría mucho y elegiría CSS, igualmente cualquiera de las opciones anteriores son muy buenas.
Bien, ya tenemos el proyecto generado, ahora solo resta probarlo, para lo cual es esencial ir al directorio del proyecto y ejecutar ng serve:
$ cd GeneraPass
$ ng serve
Si todo va bien, ya debería abrirse una nueva pestaña en el navegador predeterminado con el proyecto funcionando, si no es así hay que mirar en la terminal en que ubicación se sirve el proyecto, en general por defecto es: http://localhost:4200
El comando ng serve se usa para montar un servidor de desarrollo el cual se encarga de actualizar el proyecto automáticamente, cada vez que se detectan cambios, de forma que siempre veremos la versión actualizada del proyecto.
Primeras modificaciones
Bien, ahora ya podremos modificar el proyecto a nuestra conveniencia
Lo primero que podremos hacer es cambiar el favicon, por el que utilizaríamos en el proyecto, para lo que hay que reemplazar el archivo:
src/favicon.ico
El siguiente paso es la eliminación del código del template base, osea la vista que vemos al iniciar el proyecto.
Para eso deberemos editar el archivo: src/app/app.component.html
En este caso borramos todo su contenido, por lo cual obtendremos una vista en blanco.
Elección de Framework de estilos
En este punto uno puede o no utilizar un Framework como Bootstrap, para el maquetado de las vistas, en este caso, la decisión es de utilizarlo, por lo cual lo agregaremos al proyecto con el siguiente comando:
$ ng add @ng-bootstrap/ng-bootstrap
Dicho comando ya nos instala todas las librerías necesarias, y nos deja todo listo para ya poder utilizar Bootstrap en las vistas!
Puedes ver el sitio oficial de ng-bootstrap en: https://ng-bootstrap.github.io
Otra librería que utilizaremos, será la de iconos de Bootstrap, la cual podremos instalar con el comando:
npm i ngx-bootstrap-icons
Recomiendo leer la documentación de la misma en: https://www.npmjs.com/package/ngx-bootstrap-icons
Una vez que la dependencia se haya instalado correctamente, procederemos a incluir la misma en el archivo: src/app/app.module.ts
Dicho archivo ahora debería verse así:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { GeneratePasswordComponent } from './components/generate-password/generate-password.component';
import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
AppComponent,
GeneratePasswordComponent
],
imports: [
BrowserModule,
AppRoutingModule,
NgbModule,
FormsModule,ReactiveFormsModule,
NgxBootstrapIconsModule.pick(allIcons)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
En dicho Módulo, también se puede ver que se importan los módulos FormsModule, ReactiveFormsModule que son necesarios para poder bindear los inputs con el modelo.
Creación del componente PasswordGenerator
Los componentes son la base de cualquier proyecto Angular, y lo ideal sería separar lo mejor posible cada una de las funcionalidades del proyecto en componente que luego se puedan reutilizar.
En este caso por motivos de simplicidad y por que el proyecto en si no es muy complejo, se creará un solo componente.
Para crear nuevos componentes, lo mejor es usar el comando especifico para dicha función:
$ ng g c components/GeneratePassword
En dicho comando el argumento “g” indica que se debe generar un nuevo elemento, “c” indica que dicho elemento es un componente y components/GeneraPassword es la ruta en la cual se debe generar el mismo.
Ahora ya podremos incluir dicho componente en cualquier vista, en el nuevo archivo src/app/components/generate-password/generate-password.component.ts se especifica el selector “app-generate-password“.
Lo que significa que podremos incluir dicho componente en cualquier vista referenciandolo de la siguiente manera:
<app-generate-password></app-generate-password>
Por ej lo incluiremos en src/app/app.component.html
Que es el archivo en el cual anteriormente borramos todo su contenido, por lo que ahora solo debería contener:
<div class="container-fluid">
<app-generate-password></app-generate-password>
</div>
Maquetado de la vista
Ahora ya podremos modificar la vista, la cual se encuentra en el archivo: src/app/components/generate-password/generate-password.component.html
La cual actualmente solo contiene el siguiente código:
<p>generate-password works!</p>
Y lo reemplazaremos por el nuevo código HTML:
<div class="row">
<div class="col col-sm-8 offset-sm-4 col-lg-6 offset-lg-3 col-xxl-4 offset-xxl-4">
<div class="row mt-5">
<div class="col text-center">
<h2 class="title p-2">Generador de Contraseñas</h2>
</div>
</div>
<div class="row">
<div class="col mb-5"> <p>Puede crear una nueva contraseña segura, con nuestra herramienta On-line, solo complete el formulario y se generarán diferentes contraseñas aleatorias.</p>
</div>
</div>
<div class="row">
<div class="col m-3">
<div class="row mb-4">
<div class="col password-cont p-0">
<input class="p-2" id="passCont" value="{{password}}" placeholder="*************" />
</div>
<div class="col-auto p-2">
<i-bs name="clipboard" class="app-btn" (click)="copyToClipboard()"></i-bs>
<i-bs name="arrow-clockwise" class="app-btn" (click)="generatePassword()"></i-bs>
</div>
</div>
<div class="row options-content">
<div class="col">
<div class="row">
<div class="col text-center">
<h4 class="p-2">Opciones de generación Contraseña</h4>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-6 mb-2">
<label for="longInput" class="form-label">Longitud</label>
<input [(ngModel)]="passwordOptions.long" type="number" min="0" class="form-control" id="longInput" placeholder="25">
</div>
</div>
<div class="row">
<div class="col-12 col-sm-6 mb-2">
<div class="form-check">
<input [(ngModel)]="passwordOptions.mayus" class="form-check-input" type="checkbox" value="" id="mayusInput" checked>
<label class="form-check-label" for="mayusInput"> Mayúsculas </label>
</div>
</div>
<div class="col-12 col-sm-6 mb-2">
<div class="form-check">
<input [(ngModel)]="passwordOptions.minus" class="form-check-input" type="checkbox" value="" id="minusInput" checked>
<label class="form-check-label" for="minusInput"> Minusculas </label>
</div>
</div>
<div class="col-12 col-sm-6 mb-2">
<div class="form-check">
<input [(ngModel)]="passwordOptions.symbol" class="form-check-input" type="checkbox" value="" id="simbolInput" checked>
<label class="form-check-label" for="simbolInput"> Símbolos </label>
</div>
</div>
<div class="col-12 col-sm-6 mb-2">
<div class="form-check">
<input [(ngModel)]="passwordOptions.numbers" class="form-check-input" type="checkbox" value="" id="numberInput" checked>
<label class="form-check-label" for="numberInput"> Números </label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Sobre el HTML, resaltaría los [(ngModel)] que definen el binding entre los valores de los inputs y el modelo (a definir) en donde se almacenarían los valores del formulario.
Y los (click) que definen la llamada a la función definida en el mismo, que será ejecutada al producirse el evento onClick.
Definición de modelo
La configuración usada para la generación de la contraseña se almacenará en un nuevo modelo que crearemos a tal propósito.
Para lo cual, dentro del directorio del componente, se creará un directorio models y dentro del mismo un archivo: password.options.ts
Dentro del cual se agregaría el siguiente contenido:
export class PasswordOptions {
public long:number = 8;
public mayus:boolean = true;
public minus:boolean = true;
public symbol:boolean = true;
public numbers:boolean = true;
}
Programación del componente
Luego deberemos editar el archivo correspondiente al código del componente: src/app/components/generate-password/generate-password.component.ts
Dicho archivo nos quedaría de la siguiente manera:
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { PasswordOptions } from './models/password.options';
@Component({
selector: 'app-generate-password',
templateUrl: './generate-password.component.html',
styleUrls: ['./generate-password.component.css']
})
export class GeneratePasswordComponent implements OnInit {
constructor() { }
public password:string = '';
public passwordOptions:PasswordOptions = new PasswordOptions();
private passCont!: HTMLInputElement;
ngOnInit(): void {
}
copyToClipboard(){
if (this.password == '') {
alert('Para poder copiar una contraseña primero es necesario generar una nueva');
return false;
}
this.passCont = document.getElementById("passCont") as HTMLInputElement;
this.passCont.select();
document.execCommand('copy');
alert("¡Contraseña copiada al portapapeles!");
return true;
}
generatePassword(){
if (this.passwordOptions.long < 0){
alert('La longitud de la contraseña debe ser mayor a 0');
return false;
}
if (!(this.passwordOptions.minus || this.passwordOptions.mayus || this.passwordOptions.numbers || this.passwordOptions.symbol)){
alert('Es necesario elegir al menos una opcion de tipo de contraseña');
return false;
}
if (this.passwordOptions.long >= 10000){
alert('La cantidad de caracteres ingresada es demasiado grande!');
return false;
}
this.password = '';
for (let c=0; c < this.passwordOptions.long; c++){
this.password += this.getPasswordChar();
}
return true;
}
getPasswordChar():string{
//se genera un numero al azar entre 1 y 4, dicho numero representa el tipo de caracter a insertar
let option:number = Math.round( Math.random() * (4 - 1) + 1 );
let ch:string = '';
switch (option){
case 1:
if (!this.passwordOptions.mayus) { //si no se seleccionaron las mayusculas
return this.getPasswordChar();
}
return this.getCaracter(this.letras).toUpperCase();
break;
case 2:
if (!this.passwordOptions.minus) { //si no se seleccionaron las minusculas
return this.getPasswordChar();
}
return this.getCaracter(this.letras);
break;
case 3:
if (!this.passwordOptions.numbers) { //si no se seleccionaron numeros
return this.getPasswordChar();
}
return this.getNumero();
break;
case 4:
if (!this.passwordOptions.symbol) { //si no se seleccionaron simbolos
return this.getPasswordChar();
}
return this.getCaracter(this.simbolos);
break;
}
return ch;
}
private letras:string = 'abcdefghijklmnñopqrstuvwxyz';
private simbolos:string= '!$%&/()=?^_-:;@*º|#~{[]}';
getCaracter(chs:string):string{
let chsCount = chs.length -1;
let chsSelect = Math.round( Math.random() * (chsCount - 0) + 0 );
return chs[chsSelect];
}
private getNumero():string{
return String(Math.round( Math.random() * (9 - 0) + 0 ));
}
}
Sobre el código, podría resaltar algunas cuestiones, como por ej el prestar atención al ámbito de las variables, es decir si son de tipo private, solo podran ser accedidas desde dentro del componente y no se podrá hacer binding con la vista, puede funcionar mientras se realizan pruebas con ng serve, pero al hacer el ng build va a saltar un error indicando la situación.
Definicón de estilos
Y el archivo CSS de nuestro componente (src/app/components/generate-password/generate-password.component.css):
.title{
background-color: rgb(176, 255, 131);
font-weight: bolder;
}
.password-cont{
border: 1px solid #555;
}
.password-cont input{
width:100%;
border:none;
color: rgb(0, 43, 22);
font-weight: bolder;
}
.options-content{
box-shadow: 0px 1px 6px #888;
border-radius: 0.2rem;
}
Hay que tener en cuenta que los estilos definidos en este archivo solo se aplicarán al componente en cuestión, lo ideal es que en el mismo no haya nada que aplique a varios componentes, en esa situación los estilos que pudieran repetirse deberìan definirse el style.css.
En el archivo CSS de estilos global del template (src/styles.css) deberemos agregar:
.app-btn{
cursor: pointer;
padding: .5rem;
border: 1px solid #555;
}
.app-btn:hover{
background-color: rgb(161, 192, 255);
}
La clase app-btn se define en el archivo de estilos globales, por que se prevee que pueda ser utilizada en caualquier sección del código de la APP, y se utiliza para definir el comportamiento mínimo del botón en cuanto a lo visual, definiendo su borde, el cursor y el mouse hover.
Probando
Si no hay ningún error, deberíamos ver la siguiente pantalla en el navegador:
Generando el build
El último paso sería hacer el build del proyecto, para tener lista la versión optimizada para producción, por lo cual bastará con ejecutar el comando:
$ ng build
El cual generá dicha versión dentro del directorio: dist
https://demos.greenborn.com.ar/pass-generator/