Commit bbaf0eae authored by Franz Huayta's avatar Franz Huayta

Merge branch 'master' into 'master'

Habilitando correo SMTP, bcrypt, configuración por archivos .env y corrección de bugs

See merge request !3
parents d193a6ad 6b47f57d
# GENERAL
# Puerto que usará la aplicación
PORT=3000
# IOP Activa el uso del módulo app-iop para la interoperabilidad
# - No funcionará la contrastación con el SEGIP
# - No funcionará el inicio de sesión con el SIN
IOP=true
# Validar con SEGIP en la creación de usuarios mediante el módulo app-iop si IOP=true
SEGIP=true
# DATABASE
DB_NAME=postgres
DB_USER=postgres
DB_PASS=postgres
DB_HOST=localhost
DB_DIALECT=postgres
DB_TIMEZONE=America/La_Paz
# LOGS
# ¿Dónde guardar los logs?
# - database: Guardar en la base de datos (se usa db.js para acceder)
# - filesystem: Guardar en sistema de archivos
LOG_STORAGE=database
# Las siguientes opciones solo se toman en cuenta si LOG_STORAGE=filesystem
# directorio con los logs
LOG_OUTPUT_DIRECTORY=./logs
# nombre de archivo de logs
LOG_OUTPUT_FILENAME=logs.log
# formato de logs
LOG_FORMAT=combined
# nivel de verbosidad, posibles: error, info, warning, debug
LOG_LEVEL=info
# MAIL SMTP (Opcional)
# - MAIL_SMTP=true activa el envió de correos mediante SMTP
# - MAIL_SMTP=false usa el módulo app-notificaciones para el envío de correos
MAIL_SMTP=false
MAIL_SENDER=info@midominio.gob.bo
MAIL_HOST=smtp.midominio.gob.bo
MAIL_PORT=587
MAIL_SECURE=false
MAIL_IGNORE_TLS=false
MAIL_TLS_REJECT_UNAUTHORIZED=false
# Solo si el servidor SMTP lo requiere
MAIL_AUTH_USER=unusuario@midominio.gob.bo 
MAIL_AUTH_PASS=password
......@@ -4,10 +4,7 @@ yarn-error.log
npm-debug.log
package-lock.json
alias.sh
src/common/config/db.js
src/common/config/mail.js
src/common/config/openid.js
src/common/config/logs.js
src/common/lib/event-bus
logs/
......@@ -19,3 +16,8 @@ deploy/roles/frontend/files/base-backend
*~
\#*\#
.\#*
.env
.env.local
.env.development
.env.production
\ No newline at end of file
......@@ -9,51 +9,68 @@ Ver [SERVER.md](SERVER.md)
1. Clonar el código fuente desde el repositorio.
```sh
$ git clone https://gitlab.agetic.gob.bo/agetic/base-backend.git
$ git clone https://gitlab.softwarelibre.gob.bo/agetic/agetic-aplicacion-base/base-backend.git
```
> Es posible que al descargar el proyecto con HTTPs, muestre siguiente error:
> ```sh
> Cloning into 'nombre-del-proyecto'...
> fatal: unable to access 'https://url-del-proyecto.git/': server certificate verification >failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none
> ```
> Configurar lo siguiente e intentar nuevamente la clonación:
> ```sh
> git config --global http.sslverify false
> ```
2. Ingresar a la carpeta.
```sh
$ cd base-backend
```
3. Verificar que se encuentra en la rama master o develop.
3. Instalar las dependencias de paquetes npm
```sh
$ git branch
$ npm install
```
4. Instalar las dependencias de paquetes npm
## Configuraciones
1. Copiar archivos de configuración y modificarlos según necesidad.
```sh
$ npm run instalar
$ cp .env.sample .env
$ cp src/common/config/openid.js.sample src/common/config/openid.js
```
## Configuraciones
2. Configurar el acceso a la base de datos en el archivo `.env`
1. Copiar archivos de configuración y modificarlos según necesidad.
```sh
# DATABASE
DB_NAME=postgres
DB_USER=postgres
DB_PASS=postgres
DB_HOST=localhost
DB_DIALECT=postgres
DB_TIMEZONE=America/La_Paz
```
3. Configurar el envío de correo electrónico en el archivo `.env` si usará un servidor SMTP *(Opcional)*
```sh
$ cp src/common/config/db.js.sample src/common/config/db.js
$ cp src/common/config/mail.js.sample src/common/config/mail.js
$ cp src/common/config/openid.js.sample src/common/config/openid.js
$ cp src/common/config/logs.js.sample src/common/config/logs.js
# MAIL SMTP
MAIL_SENDER=info@midominio.gob.bo
MAIL_HOST=smtp.midominio.gob.bo
MAIL_PORT=587
MAIL_SECURE=false
MAIL_IGNORE_TLS=false
MAIL_AUTH_USER=unusuario@midominio.gob.bo
MAIL_AUTH_PASS=password
MAIL_TLS_REJECT_UNAUTHORIZED=false
```
2. Si se usa ciudadanía digital, configurar URL, client, client_params `nano src/common/config/openid.js`
4. Configurar los logs del sistema en el archivo `.env`
```sh
# LOGS
LOG_STORAGE=database # database o filesystem
LOG_OUTPUT_DIRECTORY=./logs
LOG_OUTPUT_FILENAME=logs.log
LOG_FORMAT=combined
LOG_LEVEL=info
```
5. Si se usa ciudadanía digital, configurar URL, client, client_params `nano src/common/config/openid.js` *(Opcional)*
```js
const openid = {
......@@ -70,81 +87,42 @@ const openid = {
};
```
3. Configurar correo electrónico `nano src/common/config/mail.js` (opcional)
## Inicializar la base de datos
```js
const correoConfig = {
origen: process.env.EMAIL_SENDER || 'info@dominio.gob.bo',
host: process.env.EMAIL_HOST || 'smtp.dominio.gob.bo',
port: process.env.EMAIL_PORT || 587,
secure: false,
ignoreTLS: false,
auth: {
user: '<unusuario@dominio.gob.bo>',
pass: '<password>'
},
tls: {
rejectUnauthorized: false
},
logging: s => debug(s)
};
```
1. Crear una base de datos vacía en su gestor de base de datos con la información que configuró en el archivo `.env`
4. Configurar correo electrónico `nano src/common/config/db.js`
2. Ejecutar lo siguiente para crear las tablas, esto eliminará las tablas y los datos de estas para reescribirlos.
```js
const db = {
database: process.env.DB_NAME || 'proyecto',
username: process.env.DB_USER || 'usuario',
password: process.env.DB_PASS || 'usuario',
host: process.env.DB_HOST || 'localhost',
dialect: 'postgres',
timezone: 'America/La_Paz',
logging: s => debug(s)
};
```sh
$ npm run setup
```
5. Para configurar los logs del sistema `nano src/common/config/logs.js`
3. Ejecutar lo siguiente para poblar las tablas con datos iniciales.
```js
const logsConfig = {
// Donde guardar los logs?
// - 'database': Guardar en la base de datos (se usa db.js para acceder)
// - 'filesystem': Guardar en sistema de archivos
storage: 'filesystem',
// Las siguientes opciones solo se toman en cuenta si storage = 'filesystem'
// para mostrar los logs tambien en la consola
console: process.env.NODE_ENV === 'production' ? false : true,
// directorio con los logs
outputDirectory: './logs',
// nombre de archivo de logs
outputFilename: 'logs.log',
// formato de logs
format: 'combined',
// nivel de verbosidad, posibles: error, info, warning, debug
level: 'info'
};
#### Para desarrollo
```sh
$ npm run seeders
```
#### Para producción
```sh
$ env NODE_ENV=production npm run seeders
```
## Inicializar la base de datos
> Los seeders ponen `123456` como contraseña de los usuarios.
1. Ejecutar lo siguiente para crear las tablas, esto eliminará las tablas y los datos de estas para reescribirlos.
## Iniciar el proyecto
#### Para desarrollo
```sh
$ env NODE_ENV=production npm run setup
$ npm run dev
```
2. Ejecutar lo siguiente para poblar las tablas con datos iniciales.
#### Para producción con PM2
Ejecutar esto dentro el directorio del proyecto:
```sh
$ env NODE_ENV=production npm run seeders
$ pm2 start npm -- start --name "proyecto-api"
```
> Los seeders ponen 123456 como contraseña de los usuarios.
## Iniciar el servicio con pm2
ó
```sh
$ env NODE_ENV=production pm2 start src/application/server.js --name "proyecto-api"
```
......
......@@ -32,7 +32,7 @@ Creamos una llave ssh para poder conectarnos directamente con el servidor de pro
``` bash
# Generamos la llave ssh dentro la carpeta deploy/ssh con el nombre deploy
ssh-keygen -t rsa -C "ogutierrez@agetic.gob.bo - deploy"
ssh-keygen -t rsa -C "ogutierrez@email.gob.bo - deploy"
```
## 5. Configurando servidor
......@@ -95,7 +95,7 @@ ansible-playbook -i inventory.ini backend.yml --private-key ssh/deploy
``` bash
# Probando conexión a la bd en el servidor de producción
psql -U agetic apostilla
psql -U usuario base-de-datos
# Comprobamos el estado del servicio backend-admin
systemctl status backend-admin
......@@ -110,7 +110,7 @@ systemctl status backend-proxy
Si se desea probar el deploy de manera local seguir los siguientes pasos:
1. Requisitos.-
1. Requisitos.-
- Instalar Virtualbox https://www.virtualbox.org/wiki/Downloads
- Instalar Vagrant https://www.vagrantup.com/downloads.html
......
postgresql_databases:
- name: database
owner: agetic
owner: usuario
hstore: yes
postgresql_users:
- name: agetic
pass: agetic
- name: usuario
pass: usuario
encrypted: no
postgresql_user_privileges:
- name: agetic
- name: usuario
db: database
priv: "ALL"
{
"restartable": "rs",
"ignore": [
".git",
"node_modules/**/node_modules",
"dist"
],
"verbose": true,
"execMap": {
"js": "node"
},
"runOnChangeOnly": false,
"watch": [
"src/**/*.js",
"src/**/*.graphql",
"src/**/*.gql"
],
"env": {
"NODE_ENV": "development"
},
"ext": "js,json,graphql"
}
\ No newline at end of file
......@@ -4,8 +4,7 @@
"description": "Base backend",
"main": "src/application/index.js",
"scripts": {
"instalar": "npm install && npm run app-devs",
"app-devs": "git clone https://gitlab.agetic.gob.bo/base/app-iop.git node_modules/app-iop && git clone https://gitlab.agetic.gob.bo/agetic/app-notificaciones.git node_modules/app-notificaciones",
"start": "NODE_ENV=production node src/application/server",
"start-dev": "DEBUG=app:* nodemon src/application/server",
"startdev": "DEBUG=app:* nodemon src/application/server",
"dev": "DEBUG=app:* nodemon src/application/server",
......@@ -41,17 +40,19 @@
"amqplib": "^0.5.5",
"apollo-server-express": "^2.9.12",
"app-iop": "git+https://gitlab.softwarelibre.gob.bo/agetic/app-iop.git",
"app-logs": "git+ssh://git@github.com/strymsg/app-logs.git",
"app-logs": "^2.0.0",
"app-notificaciones": "git+https://gitlab.softwarelibre.gob.bo/agetic/app-notificaciones.git",
"app-params": "^0.2.5",
"asyncawait": "^3.0.0",
"axios": "^0.19.1",
"bcrypt": "^4.0.1",
"body-parser": "^1.19.0",
"casual": "^1.6.2",
"chalk": "^3.0.0",
"cors": "^2.8.5",
"csvtojson": "^2.0.10",
"defaults": "^1.0.3",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-fileupload": "^1.1.6",
"express-jwt": "^5.3.1",
......
......@@ -35,14 +35,22 @@ module.exports = function setupUsuarioController (services) {
};
persona = { persona };
} else {
if (tipoDoc === 'CI') {
persona = await Iop.segip.buscarPersona(ci, fechaNacimiento, complemento);
if (process.env.SEGIP === 'true') {
if (tipoDoc === 'CI') {
persona = await Iop.segip.buscarPersona(ci, fechaNacimiento, complemento);
} else {
return res.send({ observacion: 'La persona no está registrada en el sistema, complete los datos para registrarla.' });
}
} else {
return res.send({ observacion: 'La persona no está registrada en el sistema, complete los datos para registrarla.' });
}
}
} else {
persona = await Iop.segip.buscarPersona(ci, fechaNacimiento, complemento);
if (process.env.SEGIP === 'true') {
persona = await Iop.segip.buscarPersona(ci, fechaNacimiento, complemento);
} else {
return res.send({ warning: 'La validación con el SEGIP está deshabilitada.' });
}
}
} catch (e) {
return next(e);
......
......@@ -3,14 +3,12 @@
const domain = require('../domain');
const Params = require('app-params');
const Logs = require('app-logs');
const { config } = require('../common');
const { config, mail } = require('../common');
const Graphql = require('./graphql');
const { mergeGraphql } = require('./lib/util');
module.exports = async function setupModule (settings = { iop: true }) {
try {
global.IOP = !!settings.iop;
// Cargando Capa del dominio
let services = await domain(settings);
......@@ -33,7 +31,7 @@ module.exports = async function setupModule (settings = { iop: true }) {
// Uniendo Graphql de usuarios con Graphql de Logs
mergeGraphql(graphql, services.Log.graphql, ['DateL']);
if (global.IOP) {
if (process.env.IOP === 'true') {
// Agregando Iop a los servicios
const Iop = require('app-iop');
services.Iop = await Iop(config.db);
......@@ -42,6 +40,9 @@ module.exports = async function setupModule (settings = { iop: true }) {
mergeGraphql(graphql, services.Iop.graphql, ['DateI']);
}
// Configurando el envio de email
mail.init(services);
return {
services,
graphql,
......
'use strict';
const debug = require('debug')('app:app');
require('dotenv').config();
const express = require('express');
const fileUpload = require('express-fileupload');
const bodyParser = require('body-parser');
......@@ -15,7 +16,7 @@ const ip = require('./lib/ip');
const { errors } = require('../common');
const api = require('./api');
const graphql = require('./graphql/server');
const port = process.env.PORT || 3000;
const port = process.env.PORT;
const app = express();
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
......
......@@ -2,6 +2,4 @@ node_modules/
.vscode/
yarn-error.log
npm-debug.log
alias.sh
src/config/db.js
src/config/mail.js
\ No newline at end of file
alias.sh
\ No newline at end of file
'use strict';
const debug = require('debug')('app:db');
require('dotenv').config();
const db = {
database: process.env.DB_NAME || 'postgres',
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASS || 'postgres',
host: process.env.DB_HOST || 'localhost',
dialect: 'postgres',
timezone: 'America/La_Paz',
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASS,
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT,
timezone: process.env.DB_TIMEZONE,
logging: s => debug(s)
};
......
'use strict';
const debug = require('debug')('base-backend:logs');
require('dotenv').config();
const logsConfig = {
// Donde guardar los logs?
// - 'database': Guardar en la base de datos (se usa db.js para acceder)
// - 'filesystem': Guardar en sistema de archivos
storage: 'database',
storage: process.env.LOG_STORAGE,
// Las siguientes opciones solo se toman en cuenta si storage = 'filesystem'
// para mostrar los logs tambien en la consola
console: process.env.NODE_ENV === 'production' ? false : true,
console: process.env.NODE_ENV !== 'production',
// directorio con los logs
outputDirectory: './logs',
outputDirectory: process.env.LOG_OUTPUT_DIRECTORY,
// nombre de archivo de logs
outputFilename: 'logs.log',
outputFilename: process.env.LOG_OUTPUT_FILENAME,
// formato de logs
format: 'combined',
format: process.env.LOG_FORMAT,
// nivel de verbosidad, posibles: error, info, warning, debug
level: 'info'
level: process.env.LOG_LEVEL
};
module.exports = logsConfig;
\ No newline at end of file
module.exports = logsConfig;
......@@ -3,17 +3,17 @@
const debug = require('debug')('base-backend:correo');
const correoConfig = {
origen: process.env.EMAIL_SENDER || 'info@midominio.gob.bo',
host: process.env.EMAIL_HOST || 'smtp.midominio.gob.bo',
port: process.env.EMAIL_PORT || 587,
secure: false,
ignoreTLS: false,
origen: process.env.MAIL_SENDER,
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
secure: process.env.MAIL_SECURE === 'true',
ignoreTLS: process.env.MAIL_IGNORE_TLS === 'true',
auth: {
user: '<unusuario@midominio.gob.bo>', // Obligatorio para desarrollo
pass: '<password>' // Obligatorio para desarrollo
user: process.env.MAIL_AUTH_USER,
pass: process.env.MAIL_AUTH_PASS
},
tls: {
rejectUnauthorized: false
rejectUnauthorized: process.env.MAIL_TLS_REJECT_UNAUTHORIZED === 'true'
},
logging: s => debug(s)
};
......
'use strict';
function enviar (data) {
const nodemailer = require('nodemailer');
const smtpTransport = require('nodemailer-smtp-transport');
const { mail } = require('../config');
const transporter = nodemailer.createTransport(smtpTransport(mail));
const settings = {
from: mail.origen,
to: data.para,
subject: data.titulo,
text: data.mensaje,
html: data.html,
attachments: data.attachments
};
return new Promise((resolve, reject) => {
transporter.sendMail(settings, (error, info) => {
if (error === null) {
resolve(info);
const mime = require('mime');
const { mail } = require('../config');
const text = require('./text');
let Iop = null;
let Parametro = null;
let Mail = null;
async function init (services) {
if (process.env.MAIL_SMTP === 'true') {
Parametro = services.Parametro;
const nodemailer = require('nodemailer');
const smtpTransport = require('nodemailer-smtp-transport');
Mail = nodemailer.createTransport(smtpTransport(mail));
} else {
Iop = services.Iop;
Mail = require('app-notificaciones');
}
}
async function enviar (data, template = 'TEMPLATE_CORREO_BASE') {
return new Promise(async (resolve, reject) => {
if (process.env.MAIL_SMTP === 'true') {
let tmpl = await Parametro.getParam(template);
let contenido = text.nano(tmpl.valor, { mensaje: data.contenido, year: new Date().getFullYear() });
const settings = {
from: mail.origen,
to: data.para,
subject: data.asunto,
// text: data.mensaje,
html: contenido
};
if (data.adjuntoBase64) {
let mimeType = data.adjuntoBase64.split(';')[0];
mimeType = mimeType.split(':')[1];
settings.attachments = [{
filename: `archivo_adjunto.${mime.extension(mimeType)}`,
path: data.adjuntoBase64
}];
} else {
if (error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
resolve(error);
} else {
reject(error);
settings.attachments = [{ path: data.adjunto }];
}
Mail.sendMail(settings, (error, info) => {
console.log('✅📧 Respuesta envio correo SMPT', error, info);
if (error) {
if (error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
return resolve(error);
} else {
return reject(error);
}
}
return resolve({ message: 'Correo enviado correctamente' });
});
} else {
let pne = await Iop.findByCode('PNE-01');
let cli = new Mail(pne.token, pne.url);
const settings = {
para: Array.isArray(data.para) ? data.para : [data.para],
asunto: data.asunto,
contenido: data.contenido,
adjuntoBase64: data.adjuntoBase64,
adjunto: data.adjunto
};
let correo = await cli.correo(settings);
console.log('✅📧 Respuesta envio correo APP-NOTIFICACIONES', correo);
if (correo && !correo.finalizado) {
return reject(new Error(`No se pudo enviar el correo: ${correo.mensaje}`));
}
});
return resolve({ message: 'Correo enviado correctamente' });
}
});
}
module.exports = {
init,
enviar
};
'use strict';
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const saltRounds = 5;
function encrypt (password) {
let shasum = crypto.createHash('sha256');
shasum.update(password);
return shasum.digest('hex');
function hashPassword (text) {
return bcrypt.hash(text, saltRounds);
}
function compare (text, hash) {
return bcrypt.compare(text, hash);
}
function nano (template, data) {
......@@ -20,5 +23,6 @@ function nano (template, data) {
module.exports = {
nano,
encrypt
hashPassword,
compare
};
......@@ -8,15 +8,14 @@ const path = require('path');
const Logs = require('app-logs');
const Params = require('app-params');
module.exports = async function initDomain (settings = { iop: true }) {
global.IOP = !!settings.iop;
module.exports = async function initDomain () {
// Obteniendo repositorios de la capa de infrastructura
let repositories = await db(config.db).catch(errors.handleFatalError);
// Cargando Parámetros
repositories.Parametro = await Params(config.db);
if (global.IOP) {
if (process.env.IOP === 'true') {
const Iop = require('app-iop');
// Agregando servicio Iop a los repositorios
repositories.Iop = await Iop(config.db);
......@@ -26,7 +25,7 @@ module.exports = async function initDomain (settings = { iop: true }) {
let logs;
if (config.logs.storage === 'database') {
logs = await Logs(config.db).catch(errors.handleFatalError);
} else if(config.logs.storage === 'filesystem') {
} else if (config.logs.storage === 'filesystem') {
logs = await Logs({ logsConfig: config.logs }).catch(errors.handleFatalError);
}
......@@ -34,25 +33,25 @@ module.exports = async function initDomain (settings = { iop: true }) {
// Cargando Value Objects que se encuentran en la carpeta value-object
let valueObjects = util.loadClasses(path.join(__dirname, 'value-objects'),
{
exclude: ['index.js', 'ValueObject.js'],
// para excluir archivos por expresión regular
excludeRegex: [/[~|#]$/, /^(.#)/]
});
{
exclude: ['index.js', 'ValueObject.js'],
// para excluir archivos por expresión regular
excludeRegex: [/[~|#]$/, /^(.#)/]
});
// Cargando Validaciones en base a los ValueObjects
let validations = util.loadValidations(path.join(__dirname, 'value-objects'),
{
exclude: ['index.js', 'ValueObject.js'],
excludeRegex: [/[~|#]$/, /^(.#)/]
});
{
exclude: ['index.js', 'ValueObject.js'],
excludeRegex: [/[~|#]$/, /^(.#)/]
});
// Cargando todos los servicios que se encuentran en la carpeta services y en sus subcarpetas, adjuntando logs
let services = util.loadServices(path.join(__dirname, 'services'), repositories, valueObjects,