@codice-progressio/express-authentication
Version:
Libreria de autenticacion y utilidades.
775 lines (694 loc) • 24.6 kB
JavaScript
const ExpressBrute = require("@codice-progressio/express-brute")
const MongooseStore = require("@codice-progressio/express-brute-mongoose")
const BruteForceSchema = require("@codice-progressio/express-brute-mongoose/dist/schema")
const mongoose = require("./configuraciones").mongoose
const model = mongoose.model(
"bruteforce",
new mongoose.Schema(BruteForceSchema)
)
const store = new MongooseStore(model)
const bruteforce = new ExpressBrute(store, {
freeRetries: 5,
failCallback: (req, res, next, nextValidDate) => {
const { DateTime } = require("luxon")
let proximaHoraValida = DateTime.fromISO(
new Date(nextValidDate).toISOString()
).diffNow(["hours", "minutes", "seconds"])
return res.send({
mensaje: `Demasiados intentos en muy poco tiempo. Vuelve a intentarlo en ${proximaHoraValida.hours
.toString()
.padStart(2, "0")}:${proximaHoraValida.minutes
.toString()
.padStart(2, "0")}:${proximaHoraValida.seconds
.toString()
.padStart(2, "0")}`,
})
},
})
const msjError_codigo_no_valido = "El código no es valido"
function comprobarAdministradorMismoUsuario(req) {
return require("./utilidades").comprobarAdministradorMismoUsuario(req)
}
function enviarCorreoConfirmacionUsuario(us) {
const configuraciones = require("./configuraciones")
if (!us._id) throw "No se recibio el id del usuario"
let html = require("./plantillas.email").correo_confirmacion({
correo: us.email,
link_confirmacion: `${configuraciones.correo.dominio}/${configuraciones.ruta_usuario}/confirmar`,
codigo: us.email_validado.codigo + us._id,
nombre: us.nombre,
nombre_aplicacion: configuraciones.correo.nombre_aplicacion,
})
var mailOptions = {
from: configuraciones.correo.mailOptions.from,
to: us.email,
subject:
"Se ha creado un usuario con tu correo | " +
configuraciones.correo.nombre_aplicacion,
html,
}
const utilidades = require("./utilidades")
return utilidades.correo(mailOptions)
}
function enviarCorreoRecuperacionContrasena(usuario) {
const configuraciones = require("./configuraciones")
if (!usuario._id) throw "No se recibio el id del usuario"
let html = require("./plantillas.email").correo_recuperacion_password({
link_confirmacion: configuraciones.correo.dominio_recuperacion,
codigo: usuario.email_validado.codigo + usuario._id,
nombre: usuario.nombre,
nombre_aplicacion: configuraciones.correo.nombre_aplicacion,
})
var mailOptions = {
from: configuraciones.correo.mailOptions.from,
to: usuario.email,
subject:
"Recuperar contraseña | " + configuraciones.correo.nombre_aplicacion,
html,
}
const utilidades = require("./utilidades")
return utilidades.correo(mailOptions)
}
const generarCodigoDeActivacion_Recuperacion = () => {
return Math.floor(100000 + Math.random() * 900000)
}
async function comprobarIntentos(
opciones = {
codigo: undefined,
esLogin: true,
esValidacion: false,
usuario: undefined,
}
) {
//Ningun usuario bloqueado se comprueba
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const Usuario = require("./configuraciones").usuario.modelo
async function reiniciarContadores() {
try {
// Reiniciamos los contadores.
await Usuario.updateOne(
{ _id: opciones.usuario._id },
{
"email_validado.intentos": 0,
"email_validado.bloqueado": false,
"email_validado.intento_hora": null,
}
).exec()
} catch (error) {
reject(error)
}
}
try {
//Si el usuario esta bloqueado, debe de tener una hora a la que se bloqueo.
if (opciones.usuario.email_validado.bloqueado) {
// Para desbloquearlo deben pasar los mínutos definidos.
const { DateTime } = require("luxon")
let minutosFaltantes = DateTime.fromISO(
new Date(opciones.usuario.email_validado.intento_hora).toISOString()
)
.plus({ minutes: 5 })
.diffNow(["minutes", "seconds"])
// Si los minutos son 0 ó negativos desbloqueamos para el siguiente intento
if (minutosFaltantes <= 0) {
await reiniciarContadores()
// Modificamos minutos faltantes para que muestre solo 0 en caso de que
// sea mayor, esto con fines de estilo.
minutosFaltantes = 0
}
throw `Demasiados intentos. Vuelve a intentar en ${minutosFaltantes.minutes
.toString()
.padStart(2, "0")}:${minutosFaltantes.seconds
.toFixed(0)
.toString()
.padStart(2, "0")} minutos`
}
if (opciones.esValidacion) {
// El codigo de verificacion debe ser igual
//Si el contador va arriba de 2 BLOQUEAMOS
if (opciones.usuario.email_validado.intentos > 1) {
await Usuario.updateOne(
{ _id: opciones.usuario._id },
{
"email_validado.bloqueado": true,
"email_validado.intento_hora": new Date(),
}
).exec()
}
if (opciones.usuario.email_validado.codigo != opciones.codigo) {
//Si el contador no va arriba sumamos +1 intentos
await Usuario.updateOne(
{ _id: opciones.usuario._id },
{ $inc: { "email_validado.intentos": 1 } }
).exec()
throw msjError_codigo_no_valido
}
}
//Si todo fue bien, pues devolvemos todas las opciones
// y de paso reiniciamos todos los valores de intentos.
await reiniciarContadores()
resolve(opciones)
} catch (error) {
reject(error)
}
})
}
module.exports = {
create_administrador: {
metodo: "post",
path: "/crear-administrador",
pre_middlewares: [bruteforce.prevent],
// No requiere permiso
permiso: null,
cb: async (req, res, next) => {
let Usuario = require("./configuraciones").usuario.modelo
let configuraciones = require("./configuraciones")
let permisos = configuraciones.permisos
let administrador = await Usuario.find({
permissions: permisos.administrador.permiso,
}).countDocuments()
if (administrador > 0) return next("Ya existe el administrador")
let codice_security = require("./index")
if (!req.body?.password) return next("No definiste el password")
let us = null
codice_security.hash
.crypt(req.body.password)
.then(async password => {
let usuario = new Usuario(req.body)
usuario.email = usuario.email.toLowerCase()
usuario.password = password
// Agregamos todos los permisos al usuario administrador
usuario.permissions.push(...obtenerTodosLosPermisosEnArrayString())
usuario["email_validado"] = {
codigo: generarCodigoDeActivacion_Recuperacion(),
}
return usuario.save()
})
.then(usuario => {
const utilidades = require("./utilidades")
us = usuario
us.password = utilidades.emoticones.random()
return enviarCorreoConfirmacionUsuario(us)
})
.then(() => {
res.send({ usuario: us })
})
.catch(_ => next(_))
},
},
update_permisos_administrador: {
metodo: "put",
path: "/restaurar-permisos-administrador",
pre_middlewares: [bruteforce.prevent],
permiso: require("./configuraciones").permisos.administrador,
cb: (req, res, next) => {
let Usuario = require("./configuraciones").usuario.modelo
let configuraciones = require("./configuraciones")
let permisos = configuraciones.permisos
let _id = req.body._id
Usuario.findOne({
_id,
permissions: permisos.administrador.permiso,
})
.select(" permissions ")
.then(usuario => {
if (!usuario) throw "No existe el id"
let permisosFicheroSeguridad = obtenerTodosLosPermisosEnArrayString()
while (usuario.permissions?.length > 0) usuario.permissions.pop()
// Agregamos todos los permisos
usuario.permissions.push(...permisosFicheroSeguridad)
return usuario.save()
})
.then(usuario =>
res.send({
mensaje: "Se restauraron los permisos de administrador del usuario",
usuario,
})
)
.catch(_ => next(_))
},
},
login: {
metodo: "post",
path: "/login",
permiso: null,
// pre_middlewares: [bruteforce.prevent],
cb: (req, res, next) => {
const password = req.body?.password
const email = req.body?.email
const credencialesError = "Credenciales incorrectas"
const codice_security = require("./index")
let usuarioBD = null
// El usuario debe existir.
const Usuario = require("./configuraciones").usuario.modelo
Usuario.findOne({ email })
.select("+password +permissions +email_validado")
.lean()
.exec()
.then(usuario => {
if (!usuario) throw credencialesError
usuarioBD = usuario
//Debe estaer validado para hacer login
if (!usuario.email_validado.validado)
throw "El usuario no ha sido validado"
if (usuario.inhabilitado) throw "El usuario está inhabilitado"
// Comprobamos el password
return codice_security.hash.compare(password, usuario.password)
})
.then(passwordCorrecto => {
if (!passwordCorrecto) throw credencialesError
delete usuarioBD.password
delete usuarioBD.email_validado
//Firmamos un token
return codice_security.token.generar(usuarioBD)
})
.then(token => {
res.send({ token })
})
.catch(err =>
{
console.log(err);
return next(err);
}
)
},
},
read: {
metodo: "get",
path: "/",
permiso: require("./configuraciones").permisos.leer_todo,
cb: async (req, res, next) => {
// Obtener y filtrar todos los usuarios
const limit = (req.params.limit ?? 10) * 1
const skip = (req.params.skip ?? 0) * 1
const busqueda = {}
const termino = req.query?.termino
if (termino) {
// Si hay un termino se lo aplicamos a los campos necesarios
busqueda["$or"] = ["nombre", "email"].map(x => {
return { [x]: { $regex: termino, $options: "i" } }
})
}
let Usuario = require("./configuraciones").usuario.modelo
try {
let usuarios = await Usuario.find(busqueda)
.limit(limit)
.skip(skip)
.exec()
let total = await Usuario.find(busqueda).countDocuments()
return res.send({ usuarios, total })
} catch (error) {
next(error)
}
},
},
read_id: {
metodo: "get",
path: "/id/:id",
permiso: require("./configuraciones").permisos.login,
cb: (req, res, next) => {
// Un usuario diferente no puede leer los datos de otro usuario
let paso = comprobarAdministradorMismoUsuario(req, res, next)
if (!paso)
throw next({ error: "No puedes leer los datos de otro usuario" })
let Usuario = require("./configuraciones").usuario.modelo
Usuario.findById(req.params.id)
.select("+permissions")
.exec()
.then(usuario => res.send({ usuario }))
.catch(_ => next(_))
},
},
read_confirmar: {
metodo: "get",
path: "/confirmar",
pre_middlewares: [bruteforce.prevent],
permiso: null,
cb: (req, res, next) => {
// Debemos resibir un query
const codigoCompleto = req.query?.codigo
if (!codigoCompleto) throw next("Codigo no valido")
// El código es de 6 digitos.
const codigo = codigoCompleto.slice(0, 6)
// Y despues debe incluir el id del usuario.
const _id = codigoCompleto.slice(6)
const Usuario = require("./configuraciones").usuario.modelo
// Comprobamos que el usuario este esperando un codigo de
// de confirmacion.
Usuario.findById(_id)
// Cargamos la propiedad en el modelo.
.select("+email_validado")
.exec()
.then(async usuario => {
if (!usuario) throw msjError_codigo_no_valido
if (usuario.email_validado?.validado) throw "El link caduco"
return comprobarIntentos({
usuario,
codigo,
esValidacion: true,
})
})
.then(opciones => {
// La comprobacion salio correcta, por lo tanto hacemos lo que tenemos
// que hacer.
return Usuario.updateOne(
{ _id: opciones.usuario._id },
{
"email_validado.validado": true,
}
).exec()
})
.then(usuario => {
if (usuario.nModified !== 1)
throw "Hubo un problema validando al usuario"
res.send({ mensaje: "Usuario habilitado" })
})
.catch(_ => next(_))
},
},
create: {
metodo: "post",
path: "/",
permiso: require("./configuraciones").permisos.crear_usuario,
cb: async (req, res, next) => {
let Usuario = require("./configuraciones").usuario.modelo
let codice_security = require("./index")
let us = null
if (!req.body?.password) throw next("No definiste el password")
codice_security.hash
.crypt(req.body.password)
.then(async password => {
let usuario = new Usuario(req.body)
usuario.email = usuario.email.toLowerCase()
usuario.password = password
//Permiso por defecto
usuario.permissions.push(
require("./configuraciones").permisos.login.permiso
)
usuario["email_validado"] = {
codigo: generarCodigoDeActivacion_Recuperacion(),
}
return usuario.save()
})
.then(usuario => {
usuario.password = require("./utilidades").emoticones.random()
us = usuario
return enviarCorreoConfirmacionUsuario(us)
})
.then(() => {
res.send({ usuario: us })
})
.catch(_ => next(_))
},
},
/**
* Actualizamos solo nombre y correo
*/
update: {
metodo: "put",
path: "/id/:id",
permiso: require("./configuraciones").permisos.login,
cb: async (req, res, next) => {
let puedeModificar = comprobarAdministradorMismoUsuario(req, res, next)
if (!puedeModificar)
throw next({ error: "No puedes modificar a otro usuario" })
let Usuario = require("./configuraciones").usuario.modelo
const _id = req.params.id
const not_allowed = ["password", "permissions", "email_validado", "inhabilitado"]
for (let key in req.body) {
if (not_allowed.includes(key)) {
delete req.body[key]
}
}
await Usuario.updateOne({ _id }, req.body).exec()
return res.send({ mensaje: "Usuario actualizado" })
},
},
update_password: {
metodo: "put",
path: "/password/:id",
permiso: require("./configuraciones").permisos.login,
cb: async (req, res, next) => {
let password = req.body?.password
if (!password) throw next("No definiste el password")
let puedeModificar = comprobarAdministradorMismoUsuario(req, res, next)
if (!puedeModificar)
throw next({ error: "No puedes modificar el password de otro usuario" })
let Usuario = require("./configuraciones").usuario.modelo
let id = req.params.id
let usuario = await Usuario.findById(id).select("password").exec()
if (!usuario) throw next("No existe el id")
let codice_security = require("./index")
codice_security.hash
.crypt(password)
.then(pass => {
usuario.password = pass
return usuario.save()
})
.then(() => res.send())
.catch(_ => next(_))
},
},
// Generamos un link de recuperacion de password para el usaurio
update_generar_link_recuperar_password: {
metodo: "get",
path: "/generar-link-recuperar-password",
pre_middlewares: [bruteforce.prevent],
permisos: null,
cb: (req, res, next) => {
let Usuario = require("./configuraciones").usuario.modelo
if (!req?.query?.email) return next("Es necesario el correo")
Usuario.findOne({ email: req.query.email })
.select("email_validado email nombre")
.exec()
.then(async usuario => {
//Debe estar validado
if (!usuario) return
if (!usuario?.email_validado?.validado)
return next("Usuario no validado")
// no retornamos naa
else {
// Modificamos al usuario
usuario.email_validado.recuperar_contrasena = true
usuario.email_validado.codigo =
generarCodigoDeActivacion_Recuperacion()
usuario.markModified("email_validado.recuperar_contrasena")
let us = await usuario.save()
return enviarCorreoRecuperacionContrasena(us)
}
})
.then(() =>
// Si no exite el email, no se va a enviar nada,pero shhhh!
res.send({ mensaje: `Se envío un correo a ${req.query.email}` })
)
.catch(_ => next(_))
},
},
update_recuperar_password_email: {
metodo: "post",
path: "/recuperar-password-email",
pre_middlewares: [bruteforce.prevent],
permiso: null,
cb: async (req, res, next) => {
//Debemos obtener el password nuevo
let password = req.body?.password
if (!password) throw next("No definiste el password")
let Usuario = require("./configuraciones").usuario.modelo
let codigoCompleto = req.body.codigo
// El código es de 6 digitos.
let codigo = codigoCompleto.slice(0, 6)
// Y despues debe incluir el id del usuario.
let id = codigoCompleto.slice(6)
//Buscamos al usuario.
let usuario = await Usuario.findById(id)
.select("password email_validado")
.exec()
// El usuario debe de existir y debe estar esperando para recuperar contraseña
let msjError = "Usuario no valido"
if (!usuario || !usuario.email_validado?.recuperar_contrasena || !codigo)
return next(msjError)
//Comprobamos el codigo
comprobarIntentos({
usuario,
codigo,
esValidacion: false,
})
.then(() => {
let codice_security = require("./index")
return codice_security.hash.crypt(password)
})
.then(pass => {
usuario.password = pass
usuario.email_validado.recuperar_contrasena = false
usuario.email_validado.codigo = null
usuario.markModified("email_validado.recuperar_contrasena")
return usuario.save()
})
.then(() =>
res.send({ mensaje: "Se modifico la contraseña correctamente" })
)
.catch(_ => next(_))
},
},
update_agregar_permiso: {
metodo: "put",
path: "/agregar-permiso",
pre_middlewares: null,
permiso: require("./configuraciones").permisos.agregar_permiso,
cb: (req, res, next) => {
let configuraciones = require("./configuraciones")
let Usuario =require("./configuraciones").usuario.modelo
let permiso = req.body.permiso
if (!permiso) return next("No se recibio ningún permiso")
// El permiso debe existir en la lista.
let permisos = Object.keys(
require(configuraciones.easy_permissions_path.fichero_permiso_permiso())
)
if (!permisos.includes(permiso))
return next("El permiso enviado no es válido")
//Buscamos al usuario.
Usuario.findById(req.body._id)
.select("permissions")
.exec()
.then(usuario => {
if (!usuario) throw "No existee el id"
if (usuario.permissions.includes(permiso))
throw "El usuario ya tiene el permiso"
usuario.permissions.push(permiso)
return usuario.save()
})
.then(usuario => res.send({ usuario }))
.catch(_ => next(_))
},
},
update_eliminar_permiso: {
metodo: "put",
path: "/eliminar-permiso",
pre_middlewares: null,
permiso: require("./configuraciones").permisos.eliminar_permiso,
cb: (req, res, next) => {
let Usuario =require("./configuraciones").usuario.modelo
let permiso = req.body.permiso
if (!permiso) return next("No se recibio ningún permiso")
//Buscamos al usuario.
Usuario.findById(req.body._id)
.select("permissions")
.exec()
.then(usuario => {
if (!usuario) throw "No existe el id"
usuario.permissions.pull(permiso)
return usuario.save()
})
.then(usuario => res.send({ usuario }))
.catch(_ => next(_))
},
},
// Elimina completamente un usuario
delete_eliminar_usuario: {
metodo: "delete",
path: "/:id",
pre_middlewares: null,
permiso: require("./configuraciones").permisos.administrador,
cb: (req, res, next) => {
let Usuario = require("./configuraciones").usuario.modelo
let id = req.params.id
Usuario.findById(id)
.then(usuario => {
if (!usuario) throw "No existe el id"
return usuario.remove()
})
.then(usuario => {
res.send({ usuario })
})
.catch(_ => next(_))
},
},
// Inhabilita al usuario
delete_inhabilitar_usuario: {
metodo: "delete",
path: "/inhabilitar/:id",
pre_middlewares: null,
permiso: require("./configuraciones").permisos.inhabilitar_usuario,
cb: (req, res, next) => {
let Usuario = require("./configuraciones").usuario.modelo
let id = req.params.id
Usuario.findById(id)
.select("inhabilitado")
.then(usuario => {
if (!usuario) throw "No existe el id"
usuario.inhabilitado = true
return usuario.save()
})
.then(usuario => {
res.send({ usuario })
})
.catch(_ => next(_))
},
},
// Inhabilita al usuario
update_inhabilitar_usuario: {
metodo: "put",
path: "/habilitar/:id",
pre_middlewares: null,
permiso: require("./configuraciones").permisos.inhabilitar_usuario,
cb: (req, res, next) => {
let Usuario =require("./configuraciones").usuario.modelo
let id = req.params.id
Usuario.findById(id)
.select("inhabilitado")
.then(usuario => {
if (!usuario) throw "No existe el id"
usuario.inhabilitado = false
return usuario.save()
})
.then(usuario => {
res.send({ usuario })
})
.catch(_ => next(_))
},
},
read_todos_los_permisos: {
metodo: "get",
path: "/info/permisos-existentes",
pre_middlewares: null,
permiso: require("./configuraciones").permisos.agregar_permisos,
cb: (req, res) => {
let cEasy = require("@codice-progressio/easy-permissions").configuraciones
let path =
cEasy.path +
cEasy.nombreCarpetaPermisos +
"/" +
cEasy.nombreArchivoPermisos
let permisos = require(path)
res.send({ permisos })
},
},
}
/**
*Obtenemos todos los permisos existentes creados actualmente.
*
* @returns
*/
function obtenerTodosLosPermisosEnArrayString() {
let permisos = []
//Obtenemos las configuraciones de easy_permissions desde
// las configuraciones generales.
let easy_permissions_configuraciones =
require("./configuraciones").easy_permissions.configuraciones
// Convertimos cada dato para la construcción de los archivos en
// variables más cortas.
let path = easy_permissions_configuraciones.path
let nombreCarpetaPermisos =
easy_permissions_configuraciones.nombreCarpetaPermisos
let nombreArchivoPermisos =
easy_permissions_configuraciones.nombreArchivoPermisos
// Recreamos la ruta completa
let rutaDePermisos = `${path}${nombreCarpetaPermisos}/${nombreArchivoPermisos}`
// Llamamos al archivo de permisos
let permisosDeApp = require(rutaDePermisos)
// Obtenemos las claves
permisos.push(...Object.keys(permisosDeApp))
// Si hay repetidos los eliminamos.
permisos = Array.from(new Set(permisos))
return permisos
}