diff --git a/.gitignore b/.gitignore index e8d1c81..4a90b82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Archivos de configuración +config.private.ts + # Logs logs *.log diff --git a/package-lock.json b/package-lock.json index b4af5a6..978f25c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -933,6 +933,18 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1369,8 +1381,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "moment": { "version": "2.25.3", @@ -1518,11 +1529,21 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "nocache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" }, + "nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-89ps+SBGpo0D4Bi5ZrxcrCiRFaMmkCt+gItMXQGzEtZVR3uAD3QAQIDoxTWnx3ky0Dwwy/dhFrQ+6NNGXpw/qQ==" + }, "nodemon": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", @@ -2016,8 +2037,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-support": { "version": "0.5.16", @@ -2191,6 +2211,12 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, + "uglify-js": { + "version": "3.12.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.7.tgz", + "integrity": "sha512-SIZhkoh+U/wjW+BHGhVwE9nt8tWJspncloBcFapkpGRwNPqcH8pzX36BXe3TPBjzHWPMUZotpCigak/udWNr1Q==", + "optional": true + }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", @@ -2294,6 +2320,11 @@ "string-width": "^2.1.1" } }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", diff --git a/package.json b/package.json index 040a5ee..f9eb25e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", + "handlebars": "^4.7.6", "helmet": "^3.21.2", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.15", @@ -31,6 +32,7 @@ "mongoose": "^5.6.9", "morgan": "^1.9.1", "needle": "^2.4.1", + "nodemailer": "^6.4.17", "passport": "^0.4.1", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", diff --git a/src/controllers/andesPrescription.controller.ts b/src/controllers/andesPrescription.controller.ts new file mode 100644 index 0000000..ec4168b --- /dev/null +++ b/src/controllers/andesPrescription.controller.ts @@ -0,0 +1,20 @@ +import { Request, Response } from 'express'; + +class AndesPrescriptionController { + + public create = async (req: Request, res: Response): Promise => { + try{ + const body = req.body; + const params = req.params; + console.log( body, params); + + return res.status(200).json( { msg: "Success", body: req.body} ); + }catch(err){ + console.log(err); + return res.status(500).json('Server Error'); + } + } + +} + +export default new AndesPrescriptionController(); diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 2145b06..46fef2d 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -8,11 +8,12 @@ import IUser from '../interfaces/user.interface'; import User from '../models/user.model'; import IRole from '../interfaces/role.interface'; import Role from '../models/role.model'; +import { renderHTML, MailOptions, sendMail } from '../utils/roboSender/sendEmail'; -class AuthController{ +class AuthController { public register = async (req: Request, res: Response): Promise => { - try{ + try { const { username, email, enrollment, cuil, businessName, password, roleType } = req.body; const newUser = new User({ username, email, password, enrollment, cuil, businessName }); const role: IRole = await Role.schema.methods.findByRoleOrCreate(roleType); @@ -21,13 +22,13 @@ class AuthController{ await newUser.save(); await role.save(); return res.status(200).json({ - newUser + newUser }); - }catch(e){ + } catch (e) { let errors: { [key: string]: string } = {}; Object.keys(e.errors).forEach(prop => { - errors[ prop ] = e.errors[prop].message; + errors[prop] = e.errors[prop].message; }); return res.status(422).json(errors); } @@ -36,15 +37,29 @@ class AuthController{ public resetPassword = async (req: Request, res: Response): Promise => { const { _id } = req.user as IUser; const { oldPassword, newPassword } = req.body; - try{ + try { const user: IUser | null = await User.findOne({ _id }); - if(!user) return res.status(404).json('No se ha encontrado el usuario'); + if (!user) return res.status(404).json('No se ha encontrado el usuario'); const isMatch: boolean = await User.schema.methods.isValidPassword(user, oldPassword); - if(!isMatch) return res.status(401).json({ message: 'Su contraseña actual no coincide con nuestros registros'}); + if (!isMatch) return res.status(401).json({ message: 'Su contraseña actual no coincide con nuestros registros' }); - await user.update({password: newPassword}); + await user.update({ password: newPassword }); return res.status(200).json('Se ha modificado la contraseña correctamente!'); - }catch(err){ + } catch (err) { + console.log(err); + return res.status(500).json('Server Error'); + } + } + + public recoverPassword = async (req: Request, res: Response): Promise => { + const { authenticationToken, newPassword } = req.body; + try { + const user: IUser | null = await User.findOne({ authenticationToken: authenticationToken }); + if (!user) return res.status(404).json('No se ha encontrado el usuario'); + + await user.update({ password: newPassword }); + return res.status(200).json('Se ha modificado la contraseña correctamente!'); + } catch (err) { console.log(err); return res.status(500).json('Server Error'); } @@ -52,19 +67,19 @@ class AuthController{ public login = async (req: Request, res: Response): Promise => { const { _id } = req.user as IUser; - try{ + try { - const user: IUser | null = await User.findOne({_id}).populate({path: 'roles', select: 'role'}); + const user: IUser | null = await User.findOne({ _id }).populate({ path: 'roles', select: 'role' }); - if(user){ + if (user) { const roles: string | string[] = []; - await Promise.all(user.roles.map( async (role) => { + await Promise.all(user.roles.map(async (role) => { roles.push(role.role); })); const token = this.signInToken(user._id, user.username, user.businessName, roles); const refreshToken = uuidv4(); - await User.updateOne({_id: user._id}, {refreshToken: refreshToken}); + await User.updateOne({ _id: user._id }, { refreshToken: refreshToken }); return res.status(200).json({ jwt: token, refreshToken: refreshToken @@ -72,7 +87,7 @@ class AuthController{ } return res.status(httpCodes.EXPECTATION_FAILED).json('Debe iniciar sesión');//in the case that not found user - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json('Server Error'); } @@ -80,10 +95,10 @@ class AuthController{ public logout = async (req: Request, res: Response): Promise => { const { refreshToken } = req.body; - try{ + try { await User.findOneAndUpdate({ refreshToken: refreshToken }, { refreshToken: '' }); return res.status(204).json('Logged out successfully!'); - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json("Server error"); } @@ -91,21 +106,21 @@ class AuthController{ public refresh = async (req: Request, res: Response): Promise => { const refreshToken = req.body.refreshToken; - try{ - const user: IUser | null = await User.findOne({refreshToken: refreshToken }).populate({path: 'roles', select: 'role'}); + try { + const user: IUser | null = await User.findOne({ refreshToken: refreshToken }).populate({ path: 'roles', select: 'role' }); - if(user){ + if (user) { // in next version, should embed roles information const roles: string | string[] = []; - await Promise.all(user.roles.map( async (role) => { - roles.push(role.role); + await Promise.all(user.roles.map(async (role) => { + roles.push(role.role); })); const token = this.signInToken(user._id, user.username, user.businessName, roles); // generate a new refresh_token const refreshToken = uuidv4(); - await User.updateOne({_id: user._id}, {refreshToken: refreshToken}); + await User.updateOne({ _id: user._id }, { refreshToken: refreshToken }); return res.status(200).json({ jwt: token, refreshToken: refreshToken @@ -114,7 +129,7 @@ class AuthController{ return res.status(httpCodes.EXPECTATION_FAILED).json('Debe iniciar sesión');//in the case that not found user - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json('Server error'); } @@ -126,23 +141,23 @@ class AuthController{ // son los campos que permitiremos actualizar. const { id } = req.params; const values: any = {}; - try{ + try { _(req.body).forEach((value: string, key: string) => { - if (!_.isEmpty(value) && _.includes(["email", "password", "username", "enrollment", "cuil", "businessName"], key)){ + if (!_.isEmpty(value) && _.includes(["email", "password", "username", "enrollment", "cuil", "businessName"], key)) { values[key] = value; } }); const opts: any = { runValidators: true, new: true, context: 'query' }; - const user: IUser | null = await User.findOneAndUpdate({_id: id}, values, opts).select("username email cuil enrollment businessName"); + const user: IUser | null = await User.findOneAndUpdate({ _id: id }, values, opts).select("username email cuil enrollment businessName"); return res.status(200).json(user); - }catch(e){ + } catch (e) { // formateamos los errores de validacion - if(e.name !== 'undefined' && e.name === 'ValidationError'){ + if (e.name !== 'undefined' && e.name === 'ValidationError') { let errors: { [key: string]: string } = {}; Object.keys(e.errors).forEach(prop => { - errors[ prop ] = e.errors[prop].message; + errors[prop] = e.errors[prop].message; }); return res.status(422).json(errors); } @@ -154,15 +169,15 @@ class AuthController{ public getUser = async (req: Request, res: Response): Promise => { // obtenemos los datos del usuario, buscando por: "email" / "username" / "cuil" const { email, username, cuil } = req.body; - try{ + try { const users: IUser[] | null = await User.find({ - $or: [{"email": email}, {"username": username}, {"cuil": cuil}] + $or: [{ "email": email }, { "username": username }, { "cuil": cuil }] }).select("username email cuil enrollment, businessName"); - if(!users) return res.status(400).json('Usuario no encontrado'); + if (!users) return res.status(400).json('Usuario no encontrado'); return res.status(200).json(users); - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json("Server Error"); } @@ -171,16 +186,48 @@ class AuthController{ public assignRole = async (req: Request, res: Response): Promise => { const { id } = req.params; const { roleId } = req.body; - try{ - const role: IRole | null = await Role.findOne({ _id : roleId }); - if(role){ - await User.findByIdAndUpdate({ _id: id },{ + try { + const role: IRole | null = await Role.findOne({ _id: roleId }); + if (role) { + await User.findByIdAndUpdate({ _id: id }, { roles: role }); } - const user: IUser | null = await User.findOne({ _id : id }); + const user: IUser | null = await User.findOne({ _id: id }); return res.status(200).json(user); - }catch(err){ + } catch (err) { + console.log(err); + return res.status(500).json('Server Error'); + } + } + + /* modificar toke */ + public getToken = async (req: Request, res: Response): Promise => { + const { username } = req.body; + + try { + const user: IUser | null = await User.findOne({ username: username }).populate({ path: 'roles', select: 'role' }); + if (!user) { + return res.status(422).json({message: "Usuario no encontrado."}); + } + // in next version, should embed roles information + const roles: string | string[] = []; + await Promise.all(user.roles.map(async (role) => { + roles.push(role.role); + })); + + const token = JWT.sign({ + iss: "recetar.andes", + sub: user._id, + usrn: user.username, + bsname: user.businessName, + rl: roles, + iat: new Date().getTime() + }, (process.env.JWT_SECRET || env.JWT_SECRET), { + algorithm: 'HS256' + }); + return res.status(200).json({jwt: token}); + } catch (err) { console.log(err); return res.status(500).json('Server Error'); } @@ -201,6 +248,42 @@ class AuthController{ return token; } + /** + * Envía un link para recuperar la contraseña en caso qeu sea un usuario temporal con email (fuera de onelogin). + * AuthUser + */ + public setValidationTokenAndNotify = async (username: string) => { + try { + let usuario: any = await User.findOne({ username }); + if (usuario) { + usuario.authenticationToken = uuidv4(); + await usuario.save(); + + const extras: any = { + titulo: 'Recuperación de contraseña', + usuario, + url: `${process.env.APP_DOMAIN}/auth/recovery-password/${usuario.authenticationToken}`, + }; + const htmlToSend = await renderHTML('emails/recover-password.html', extras); + const options: MailOptions = { + from: `${process.env.EMAIL_USERNAME}`, + to: usuario.email.toString(), + subject: 'Recuperación de contraseña', + text: '', + html: htmlToSend, + attachments: null + }; + + await sendMail(options); + return usuario; + } else { + return null; + } + } catch (error) { + throw error; + } + } + } export default new AuthController(); diff --git a/src/controllers/prescription.controller.ts b/src/controllers/prescription.controller.ts index a52b4c1..06c5b1b 100644 --- a/src/controllers/prescription.controller.ts +++ b/src/controllers/prescription.controller.ts @@ -7,8 +7,10 @@ import Supply from '../models/supply.model'; import IPatient from '../interfaces/patient.interface'; import Patient from '../models/patient.model'; import User from '../models/user.model'; +import Role from '../models/role.model'; import IUser from '../interfaces/user.interface'; import moment = require('moment'); +import IRole from '../interfaces/role.interface'; class PrescriptionController implements BaseController{ @@ -100,6 +102,16 @@ class PrescriptionController implements BaseController{ } } + public get = async (req: Request, res: Response): Promise => { + try{ + const prescriptions: IPrescription[] | null = await Prescription.find({}).sort({ field: 'desc', date: -1}); + return res.status(200).json(prescriptions); + }catch(err){ + console.log(err); + return res.status(500).json('Server Error'); + } + } + public getByUserId = async (req: Request, res: Response): Promise => { try{ const { userId } = req.params; @@ -150,15 +162,19 @@ class PrescriptionController implements BaseController{ const { pharmacistId } = req.body; const dispensedBy: IUser | null = await User.findOne({_id: pharmacistId}); + if(!dispensedBy) return res.status(4000).json("Farmacia no encontrada"); + const userRole: IRole | null = await Role.findOne({role: "admin", _id: { $in: dispensedBy.roles } }) // checkeamos el rol del usuario no sea admin + const controlPrescription: IPrescription | null = await Prescription.findOne({_id: id, status: 'Dispensada'}); if(!controlPrescription) return res.status(404).json('La receta no se encontró.'); const limitTime = moment(controlPrescription.dispensedAt).add(2, 'hours'); // plus 2 hours to dispensedBy const timeNow = moment(); - - if(timeNow.isAfter(limitTime)) return res.status(422).json('Ya no se puede anular la dispensa de la receta.'); + + /* Si ya pasó el tiempo valido para cancelar y no tiene rol admin, entonces cancelamos la accion */ + if(timeNow.isAfter(limitTime) && userRole?.role !== 'admin') return res.status(422).json('Ya no se puede anular la dispensa de la receta.'); const opts: any = {new: true}; const prescription: IPrescription | null = await Prescription.findOneAndUpdate({_id: id, status: 'Dispensada'}, { diff --git a/src/controllers/supply.controller.ts b/src/controllers/supply.controller.ts index 6a3f336..18b4425 100644 --- a/src/controllers/supply.controller.ts +++ b/src/controllers/supply.controller.ts @@ -12,13 +12,27 @@ class SupplyController implements BaseController{ } public create = async (req: Request, res: Response): Promise => { - const { id, name, unity, sex, image } = req.body; + const { + name, + activePrinciple, + power, + unity, + firstPresentation, + secondPresentation, + description, + observation, + pharmaceutical_form + } = req.body; const newSupply: ISupply = new Supply({ - id, name, + activePrinciple, + power, unity, - sex, - image + firstPresentation, + secondPresentation, + description, + observation, + pharmaceutical_form }); try{ await newSupply.save(); @@ -109,6 +123,33 @@ class SupplyController implements BaseController{ } } + public get = async (req: Request, res: Response): Promise => { + try{ + const { supplyName } = req.query; + let target: string = decodeURIComponent(supplyName); + target = target.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + let search: string = ""; + let supplies: ISupply[]; + // first word should apper in all coincidence + const words = target.split(" "); + if(words.length > 1){ + // find by multiple words + for(let i = 0; i < words.length; i++){ + search += '\"' + words[i].trim() + '\"' + " "; + } + supplies = await Supply.find({$text: {$search: search}}).limit(20); + }else{ + // find by regex with the first word + supplies = await Supply.find({name: { $regex: new RegExp( target, "ig")} }).limit(20); + } + + return res.status(200).json(supplies); + }catch(err){ + console.log(err); + return res.status(500).json('Server Error'); + } + } + } export default new SupplyController(); diff --git a/src/interfaces/supply.interface.ts b/src/interfaces/supply.interface.ts index c829af1..dcf3ba1 100644 --- a/src/interfaces/supply.interface.ts +++ b/src/interfaces/supply.interface.ts @@ -3,10 +3,12 @@ import { Document } from 'mongoose'; export default interface ISupply extends Document { id: string; name: string; + activePrinciple: string; + power: string; + unity: string; + firstPresentation: string; + secondPresentation: string; description: string; observation: string; - unity: string; - supply_area: string; - createdAt?: Date; - updatedAt?: Date; + pharmaceutical_form: string; } \ No newline at end of file diff --git a/src/interfaces/user.interface.ts b/src/interfaces/user.interface.ts index 3ad8a89..88e2e43 100644 --- a/src/interfaces/user.interface.ts +++ b/src/interfaces/user.interface.ts @@ -9,6 +9,7 @@ export default interface IUser extends Document{ password: string; roles: IRole[]; refreshToken?: string; + authenticationToken?: string; createdAt?: Date; updatedAt?: Date; isValidPassword(thisUser: IUser, password: string): Promise; diff --git a/src/middlewares/passport-config-andes.middleware.ts b/src/middlewares/passport-config-andes.middleware.ts new file mode 100644 index 0000000..465331a --- /dev/null +++ b/src/middlewares/passport-config-andes.middleware.ts @@ -0,0 +1,60 @@ +import {Request, Response, NextFunction} from 'express'; +import passport from 'passport'; +import passportJwt from 'passport-jwt'; +import { env, httpCodes } from '../config/config'; +import User from '../models/user.model'; +import IUser from '../interfaces/user.interface'; + +const JwtStrategy = passportJwt.Strategy; +const ExtractJwt = passportJwt.ExtractJwt; + +// Config passport JWT strategy +// We will use Bearer token to authenticate +// This configuration checks: +// -token expiration +// -user exists +passport.use(new JwtStrategy({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: (process.env.JWT_SECRET || env.JWT_SECRET) +}, async (payload, done: (err?: any, user?: IUser | boolean, info?: {code: number, message: string}) => any | Response) => { + try{ + // find the user specified in token + const user = await User.findOne({ _id: payload.sub }).select('_id'); + + // if user doesn't exists, handle it + if(!user){ + return done(null, false, {code: httpCodes.EXPECTATION_FAILED, message: 'Debe iniciar sesión'}); + } + + // otherwise, return the user + done(null, user); + }catch(err){ + console.log('in error'); + done(err, false); + } +})); + + + +const authenticationMiddleware = (req: Request, res: Response, next: NextFunction, authenticationType: string) => { + passport.authenticate(authenticationType, {session: false}, (err, user: IUser | boolean, info?: {code: number, message: string}): any | Response => { + try{ + + if (err) return next(err) + + if(typeof(info) !== 'undefined') return res.status(info.code).json({message: info.message}); + + req.user = user; + next(); + }catch(error){ + if(error.code == 'ERR_HTTP_INVALID_STATUS_CODE') return res.status(httpCodes.EXPECTATION_FAILED).json({message: 'Debe iniciar sesión'}); + + return res.status(500).json('Server Error') + } + })(req, res, next); + +}; + +export const checkAuthAndes = (req: Request, res: Response, next: NextFunction) => { + authenticationMiddleware(req, res, next, 'jwt'); +} diff --git a/src/models/supply.model.ts b/src/models/supply.model.ts index 09fb439..077a4ac 100644 --- a/src/models/supply.model.ts +++ b/src/models/supply.model.ts @@ -31,12 +31,9 @@ export const supplySchema = new Schema({ }, observation: { type: String, - }, - createdAt: { - type: Date, - default: Date.now - }, - updatedAt: Date, + } +},{ + timestamps: true }); // Model diff --git a/src/models/user.model.ts b/src/models/user.model.ts index 05b0b8f..0cb8492 100644 --- a/src/models/user.model.ts +++ b/src/models/user.model.ts @@ -60,6 +60,9 @@ export const userSchema = new Schema({ refreshToken: { type: String, }, + authenticationToken: { + type: String, + }, createdAt: { type: Date, default: Date.now diff --git a/src/routes/andes.ts b/src/routes/andes.ts new file mode 100644 index 0000000..5798498 --- /dev/null +++ b/src/routes/andes.ts @@ -0,0 +1,20 @@ +import { Router } from 'express'; +import andesController from '../controllers/andesPrescription.controller'; +import { checkAuthAndes } from '../middlewares/passport-config-andes.middleware'; + + +class AndesRoutes{ + + constructor(private router: Router = Router()){} + + // deefine your public routes inside of routes function + public routes(): Router{ + + this.router.post('/prescriptions', checkAuthAndes, andesController.create); + + return this.router; + } +} + +const andesRoutes: AndesRoutes = new AndesRoutes(); +export default andesRoutes.routes(); diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 649c907..0c86c1a 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,18 +1,35 @@ -import { Router} from 'express'; +import { Router } from 'express'; import { passportMiddlewareLocal, checkAuth } from '../middlewares/passport-config.middleware'; import authController from '../controllers/auth.controller'; class AuthRoutes { - constructor(private router: Router = Router()){} + constructor(private router: Router = Router()) { } - routes(): Router{ + routes(): Router { this.router.post('/login', passportMiddlewareLocal, authController.login); this.router.get('/jwt-login', checkAuth, authController.login); // this.router.post('/register', authController.register); this.router.post('/logout', authController.logout); this.router.post('/refresh', authController.refresh); + this.router.post('/recovery-password', authController.recoverPassword); + this.router.post('/setValidationTokenAndNotify', async (req, res, next) => { + try { + const username = req.body.usuario; + if (username) { + const user = await authController.setValidationTokenAndNotify(username); + if(user) { + res.json( { status: 'ok', msg: 'Se ha enviado un correo a su casilla!'}); + } else { + res.json( { status: 'notfound', msg: 'Usuario no encontrado! Por favor revise sus datos'}); + } + return next(403); + } + } catch (error) { + return next(error); + } + }); return this.router; } diff --git a/src/routes/private.ts b/src/routes/private.ts index a7e927a..2d4c7ba 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -13,6 +13,7 @@ import patientController from '../controllers/patient.controller'; // import pharmacyController from '../controllers/pharmacy.controller'; import supplyController from '../controllers/supply.controller'; import authController from '../controllers/auth.controller'; + class PrivateRoutes{ constructor(private router: Router = Router()){} @@ -31,7 +32,9 @@ class PrivateRoutes{ // this.router.post('/roles/:id/assign-user', roleController.assignUser); + this.router.get('/user/get-token', hasPermissionIn('readAny','user'), authController.getToken); this.router.get('/auth/user/find', hasPermissionIn('readAny','user'), authController.getUser); + this.router.post('/auth/register', hasPermissionIn('updateAny','user'), authController.register); this.router.post('/auth/reset-password', authController.resetPassword); this.router.patch('/auth/user/:id', hasPermissionIn('updateAny','user'), authController.updateUser); @@ -39,6 +42,8 @@ class PrivateRoutes{ this.router.get('/patients/get-by-dni/:dni', patientController.getByDni); this.router.get('/prescriptions/find/:patient_id&:date?', prescriptionController.getPrescriptionsByDateOrPatientId); this.router.get('/prescriptions/get-by-user-id/:userId', prescriptionController.getByUserId); + this.router.get('/prescriptions', prescriptionController.get); + this.router.get('/supplies', supplyController.get); this.router.get('/supplies/get-by-name', supplyController.getByName); // roles @@ -88,9 +93,9 @@ class PrivateRoutes{ // this.router.delete(`/professionals/:id`, hasPermissionIn('deleteAny','patient'), professionalController.delete); // supply - this.router.get(`/supplies/`, hasPermissionIn('readAny','patient'), supplyController.index); + this.router.get(`/supplies/`, hasPermissionIn('readAny','supplies'), supplyController.index); + this.router.post(`/supplies/`, hasPermissionIn('createAny','supplies'), supplyController.create); this.router.patch('/supplies/:id', hasPermissionIn('updateAny','supplies'), supplyController.update); - // this.router.post(`/supplies/`, hasPermissionIn('createAny','patient'), supplyController.create); // this.router.get(`/supplies/:id`, hasPermissionIn('readAny','patient'), supplyController.show); // this.router.put(`/supplies/:id`, hasPermissionIn('updateAny','patient'), supplyController.update); // this.router.delete(`/supplies/:id`, hasPermissionIn('deleteAny','patient'), supplyController.delete); diff --git a/src/routes/public.ts b/src/routes/public.ts index 7f9c34c..4a0165d 100644 --- a/src/routes/public.ts +++ b/src/routes/public.ts @@ -1,4 +1,5 @@ import { Router } from 'express'; + class PublicRoutes{ constructor(private router: Router = Router()){} diff --git a/src/routes/routes.ts b/src/routes/routes.ts index b84bfc3..65aa4e2 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -2,10 +2,12 @@ import { Router } from 'express'; import { checkAuth } from '../middlewares/passport-config.middleware'; + // routes import authRoutes from './auth'; import pbulicRoutes from './public'; import privateRoutes from './private'; +import andesRoutes from './andes'; class Routes { @@ -16,7 +18,8 @@ class Routes { // auth this.router.use('/auth', authRoutes); this.router.use('', pbulicRoutes); - + // andes specific routes + this.router.use('/andes', andesRoutes); // private: requires authentication this.router.all('*', checkAuth, privateRoutes); diff --git a/src/utils/rbac_abac.ts b/src/utils/rbac_abac.ts index 6ccafa8..b96c235 100644 --- a/src/utils/rbac_abac.ts +++ b/src/utils/rbac_abac.ts @@ -53,7 +53,9 @@ class AccessControlLoader { // supplies { role: 'professional', resource: 'supplies', action: 'read:any', attributes: '*' }, { role: 'pharmacist', resource: 'supplies', action: 'read:any', attributes: '*' }, - { role: 'admin', resource: 'supplies', action: 'update:any', attributes: '*' } + { role: 'admin', resource: 'supplies', action: 'create:any', attributes: '*' }, + { role: 'admin', resource: 'supplies', action: 'update:any', attributes: '*' }, + { role: 'andes', resource: 'andesPrescription', action: 'create:any', attributes: '*' } ]; this.accessControl.setGrants(grantList); console.log('grants initialized'); diff --git a/src/utils/roboSender/sendEmail.ts b/src/utils/roboSender/sendEmail.ts new file mode 100644 index 0000000..04f761d --- /dev/null +++ b/src/utils/roboSender/sendEmail.ts @@ -0,0 +1,76 @@ +import * as fs from 'fs'; +import moment = require('moment'); +import Handlebars from 'handlebars'; +// const handlebars = require('handlebars'); +const path = require('path'); +const nodemailer = require('nodemailer'); + +Handlebars.registerHelper('datetime', (dateTime: any) => { + return moment(dateTime).format('D MMM YYYY [a las] H:mm [hs]'); +}); + +export interface MailOptions { + from: string; + to: string; + subject: string; + text: string; + html: string; + attachments: any; +} + +export function sendMail(options: MailOptions) { + return new Promise((resolve, reject) => { + const transporter = nodemailer.createTransport({ + host: `${process.env.EMAIL_HOST}`, + port: parseInt(`${process.env.EMAIL_PORT}`, 10), + secure: (`${process.env.EMAIL_SECURE}` === 'true'), + auth: { + user: `${process.env.EMAIL_USERNAME}`, + pass: `${process.env.EMAIL_PASSWORD}` + }, + }); + + const mailOptions = { + from: options.from, + to: options.to, + subject: options.subject, + text: options.text, + html: options.html, + attachments: options.attachments + }; + + transporter.sendMail(mailOptions, (error: any, info: any) => { + if (error) { + return reject(error); + } + return resolve(info); + }); + }); +} + +export function renderHTML(templateName: string, extras: any): Promise { + return new Promise((resolve, reject) => { + // [TODO] Analizar el path relativo o absoluto + const TEMPLATE_PATH = './templates/'; + const url = path.join(TEMPLATE_PATH, templateName); + fs.readFile(url, { encoding: 'utf-8' }, (err, html) => { + if (err) { + return reject(err); + } + try { + const template = Handlebars.compile(html); + const htmlToSend = template({nombre: extras.usuario.businessName, url: extras.url}); + return resolve(htmlToSend); + } catch (exp) { + return reject(exp); + } + + }); + }); +} + +export function registerPartialTemplate(key: string, fileName: string) { + const filePath = path.join(process.cwd(), `templates/${fileName}`); + const file = fs.readFileSync(filePath); + Handlebars.registerPartial(key, file.toString()); +} diff --git a/templates/emails/recover-password.html b/templates/emails/recover-password.html new file mode 100644 index 0000000..dc21e66 --- /dev/null +++ b/templates/emails/recover-password.html @@ -0,0 +1,11 @@ +{{#> layout }} +
+ Hola {{ nombre }}!
+
+

+ Recibiste este mensaje porque solicitaste cambiar tu contraseña. +

+
+ +Para obtener una nueva contraseña, dirigite al formulario de Regeneración de contraseña +{{/layout}} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index e687315..7f8d846 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -64,7 +64,7 @@ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "include": [ - "./src/**/*" + "./src/**/*", "config.private.ts" ], "exclude": [ "node_modules"