Creating an express middleware to send emails - javascript

I've been trying to make an express middleware that sends an email using Nodemailer after the previous middleware finishes. I've come up with a few different designs, but ultimately each different version has it's drawback.
Ultimately, I would like the middleware to have a response from the previous middleware. If it is a success, then send a success email, otherwise, send an error email.
I came up with a dual design where one variation pushes to an error middleware, and a success leads to the next middleware. This contains some slight issues of sending multiple headers, specifically on an the second middleware erroring. I could say, if the mail errors out, do nothing. But that doesn't seem right. If anyone has any suggestions on a good design, that would be great.

From what you described, I would suggest not to create different middleware for that, but to just create one generic email function that would handle different type of messages. Then, just use that function in the first middleware and pass different parameters based on use case (success/error).
email-controller.js
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
},
});
exports.send_email_message = (send_to, subject, message) => {
return new Promise((resolve, reject) => {
const email_message = {
from: { name: process.env.EMAIL_FRIENDLY_NAME },
to: send_to,
subject: subject,
text: message
};
transporter.sendMail(email_message).then(() => {
resolve(true);
}).catch((error) => {
reject(false);
});
})
}
custom-router.js
const { send_email_message } = require('./email-controller');
const express = require('express');
const router = express.Router();
router.get('/custom-middleware', async (req, res, next) => {
try {
// You can calculate "success" variable based on your custom logic
if(success){
await send_email_message('example#gmail.com', 'Success', 'This is body of success message.');
return res.status(200).json({ success: true });
} else {
await send_email_message('example#gmail.com', 'Error', 'This is body of error message.');
return res.status(400).json({ success: false });
}
} catch(error) {
return res.status(400).json({ success: false });
}
});
module.exports = router;

Related

How to call a route at a particular time

I have a MERN stack Library Management System website.
In my app currently for admin i have given a Notify button to send emails to all user that have any books due in the library. For this an array of defaulty user gets passed as a req body to send emails. Admin gets this list of users from database on initial render of that particular component.
But i want to automate sending of emails and want my server to trigger automatic emails at 10:00 am to all the users who have due books.
On Notify button click my notifyBookDefaulties controller gets triggered.
I tried to use a setTimeout and a timer as well to call my route at 10:00 am and trigger emails but i am not able to get desired output.
Below i my notifyBookDefaulties controller:
const notifyBookDefaulties = asyncHandler(async (req, res) => {
const admin = await Auth.findById(req.user.id);
// to check if user exists by that id in the databse
// and that user is a admin (got by token)
if (!admin && admin.admin !== true) {
res.status(401);
throw new Error("Not Authorized");
}
const { users, bookID, title } = req.body; // here users is the list of user id's
let emails = "";
// to get email of each user from their user id
for (let user of users) {
try {
const defaulty = await Auth.findById(user);
emails += defaulty.email + ",";
} catch (error) {
res.status(400);
throw new Error(error);
}
}
// to get comma separated list of emails
const emailList = emails.slice(0, -1).toString();
// try block tries to send email and catch block catches any error if occured
try {
var transporter = nodemailer.createTransport({
service: process.env.SERVICE,
auth: {
user: process.env.USER,
pass: process.env.PASS,
},
});
var mailOptions = {
from: process.env.USER,
to: emailList,
subject: "Return Book",
html: `<!DOCTYPE html><html lang="en"><body>This is to remind you that the book titled ${title} and ID ${bookID} issued by you is due.</body></html>`,
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
res.status(400).json({ msg: error });
} else {
res.status(200).json({ msg: "E-Mail Successfully sent" });
}
});
} catch (error) {
console.log(error);
res.status(500).json({ msg: error });
}
});
Below is my server.js:
require("dotenv").config();
const express = require("express");
const { errorHandler } = require("./middleware/errorMiddleware");
const connectDB = require("./config/db");
const cors = require("cors");
const port = process.env.PORT || 5000;
connectDB();
const app = express();
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 204
};
app.use(cors(corsOptions))
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use("/api/admin", require("./routes/adminRoutes"));
app.use("/api/user", require("./routes/userRoutes"));
app.use("/api/actions", require("./routes/authRoute"));
app.use(errorHandler);
app.listen(port, () => {
console.log(`Running on ${port}`);
});
My controller gets called for below route:
router.post("/notify", protect, notifyBookDefaulties);
and the url is:
http://localhost:5000/api/admin/notify
Note: here i have not included my function which fetches the list of user id's, of users that have due books. To fetch defaulting users i have a separate controller and i will merge that into this controller once i get the logic to send mails at 10:00 am.
If there is any other way to implement this i would like to know. If any more clarity needed do tell. Thanks in advance.
Sounds like a cron job, check this package https://www.npmjs.com/package/node-cron

Why does fetch in the browser send an empty body to my API unlike a API Client like Insomnia

I am currently trying to create a login system with a Mongo Database, but it won't work when I try to fetch POST the login credentials to my express.js API via the Chrome Browser. Unlike in any browser itt works when I use the Insomnia Client. I personally think the problem is either in the header or middleware part of the code. I am grateful for every indication where my problem might be.
Code:
Login function:
async function login() {
const data = getUserDataEncrypted(); //gets username and password
await fetch(url + "/checkUser/login", {
method: "POST",
mode: 'cors',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then(res => {
console.log(res.json());
});
}
Used Middleware:
require('dotenv').config();
const express = require("express");
const app = express();
const mongoose = require('mongoose');
app.use(require("cors")());
app.use(express.json());
app.use(require("morgan")("combined"));
Server Side:
router.post('/login', async (req, res) => {
try {
const user = await User.find({ username: req.body.username });
if (user[0].password === req.body.password) {
res.send({
message: "Successfull Login",
login: true
});
return;
} else {
res.send({
message: "Password incorrect",
login: false
});
return;
}
} catch (error) {
res.send({
message: error.message,
req: req.body
});
}
});
User Data:
async function getUserDataEncrypted() {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
password = await SHA256Encyption(password);
const data = {
username: username,
password: password
}
return data;
}
Images:
In the login function, try to console.log the data from getUserDataEncrypted().
If its null or undefined try to use await.
await getUserDataEncrypted();
const data = getUserDataEncrypted(); //gets username and password
You need to do some basic debugging. If you had logged the value of data then the problem would be obvious.
getUserDataEncrypted is marked as async so it returns a promise.
console.log(JSON.stringify(Promise.resolve()));
Promises stringify to empty objects.
You need to await the promise or get (and use) its result in a then() callback.

Verifying username match from outside API during the registration process

I am making a web application that allows Fortnite players to find other players to play with. Users should be able to register, login, post and comment. I have designed the frontend portion of the user login and registration features as well as the backend of the user registration but one of my requirements is that:
Before registration, the server should check whether the username provided is a real Fortnite username using the FortniteTracker API which provides user profiles using their very simple API.
Example Call: GET https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}
How do I verify the username exists before allowing the user to create the account?
I have tried creating a separate endpoint for the API call from the server side but I didn't know how to implement it into my /register endpoint
script.js
function registerRequest(username,password) {
$.ajax({
url: "http://localhost:8080/register",
type: 'POST',
data: JSON.stringify({username,password}),
contentType: "application/json",
error : function(err) {
console.log('Error here!', err)
},
success: function(data) {
console.log('Success!')
// What do I put here?
}
});
}
function handleRegisterSubmit(event) {
event.preventDefault();
const username = $(event.currentTarget).find('.username-register').val()
const password = $(event.currentTarget).find('.password-register').val()
const passwordConfirm = $(event.currentTarget).find('.password-confirm').val()
if (password === passwordConfirm) {
registerRequest(username,password)
}
else {
console.error("Passwords did not match")
}
}
$(function onLoad() {
displayRegisterPage()
$(`.js-content-section`).on('submit', '.js-register-form', handleRegisterSubmit)
}
})
server.js
app.post('/register', jsonParser, (req, res) => {
const requiredFields = ['username', 'password']
for (let i = 0; i < requiredFields.length; i++) {
const field = requiredFields[i]
if (!(field in req.body)) {
const message = `Missing \`${field}\` in request body`
console.error(message)
return res.status(400).send(message)
}
}
let username = req.body.username;
let password = req.body.password;
User.findOne({username})
.then(user => {
if (user) {
const message = `username is already taken`
console.error(message)
return res.status(400).send(message)
}
else {
User.create({username, password})
.then(user => {
const userRes = {
id: user._id,
username: user.username
}
res.status(201).json(userRes)
}
)
}
})
.catch(err => {
console.error(err)
res.status(500).json({ error: 'something went horribly wrong'})
})
})
app.get('/login', (req, res) => {
const usernameReq = User.findById(req.body.username);
if (usernameReq) {
console.log(usernameReq)
res.status(201).json(usernameReq)
}
})
schema.js
const UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true,
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
I expect that if I register with "ninja" as a username I should be able to register since that is a valid Fortnite username. The actual output currently allows users to register with any username that isnt already taken in the database.
You would need packages like axios, request, request-promise (Promise supported version of request) etc to make the external api call. You can try implementing within the register like.
const rp = require('request-promise');
app.post('/register', jsonParser, async (req, res) => {
...
let username = req.body.username;
let password = req.body.password;
const options = {
method : 'GET',
uri: 'https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}',
resolveWithFullResponse: true
}
const data = await rp(options)
// check if response code is 200 and check for the expected body
...
// continue rest of the code
}
Or have another middleware to call the external endpoint and do the checks like:
async function checkUser (req, res, next) {
const options = {
method : 'GET',
uri: 'https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}',
resolveWithFullResponse: true
}
const data = await rp(options)
// check if response code is 200 and check for the expected body
if (checks ok)
// if all check ok go to next middleware
next()
else
// if checks did not succeed
// you could pass error to error handler like next(new Error("Hey you do not exist"))
// or render something here
}
Then mount it like:
app.post('/register', jsonParser, checkUser, (req, res) {
...
You can do it simply by sending the username to the API https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}
It will give you a response mentioning about the user exists or not. Based on the response you can make another AJAX request to register the user only if the user does not exist.
I use a Promise request to resolve, reject when someone enters their username. It is only called onClick. in your request you will be able to determine if the call was successfull or not with the username.

JWT Login - No authorization token was found in middleware

I followed a tutorial to add login and registration to my Node.js app using JWT token and I'm having a hard time logging in and redirecting to my 'logged in' admin page. User registration works great, but the login portion I can't figure out.
This is the tutorial I was following:
https://medium.freecodecamp.org/learn-how-to-handle-authentication-with-node-using-passport-js-4a56ed18e81e
My code for login looks like this:
router.post('/login', auth.optional, (req, res, next) => {
console.log(req.body);
var user = {
email: req.body.email,
password: req.body.password
}
if (!user.email) {
return res.status(422).json({
errors: {
email: 'is required',
},
});
}
if (!user.password) {
return res.status(422).json({
errors: {
password: 'is required',
},
});
}
return passport.authenticate('local', { session: false }, (err, passportUser, info) => {
if (err) {
return next(err);
}
if (passportUser) {
const user = passportUser;
user.token = passportUser.generateJWT();
console.log("TOKEN: " + user.token);
res.setHeader('Authorization', 'Token ' + user.token);
return res.json({ user: user.toAuthJSON() });
}
return res.status(400).json({
errors: {
message: info,
},
});
})(req, res, next);
});
My '/admin' "logged in" route looks like this:
router.get("/admin", auth.required, function(req, res) {
res.render('admin', {
user : req.user // get the user out of session and pass to template
});
});
I'm not sure how I can redirect to my '/admin' route while also passing the token because currently I am seeing the following error after logging in. Makes sense since I am not passing the token to the '/admin' route...but how do I do that? :)
UnauthorizedError: No authorization token was found at middleware
Thanks in advance for the help!
EDIT:
Still can't figure this out and don't really understand how this flow is supposed to work...where do the headers need to be set to the token and how do I redirect to my admin page once the login is successful.
Here is my middleware code if this helps:
const getTokenFromHeaders = (req) => {
console.log("REQ: " + JSON.stringify(req.headers));
const { headers: { authorization } } = req;
if(authorization && authorization.split(' ')[0] === 'Token') {
return authorization.split(' ')[1];
}
return null;
};
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
}),
optional: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
credentialsRequired: false,
}),
};
Your code does not have a problem. You seem to be confused with the login flow from server to client (Frontend/Web).
Let's first have a look the RESTFUL way of doing it. The article also refers to the same flow.
The RESTFUL API flow looks like this:
User requests for login:
POST: /api/v1/auth/login with username and password in request body.
If successful, user is returned with basic inforamtion and token.
If not, user is returned a 401 (Unauthorized) status code.
The login flow ends here.
The token provided earlier to the user is used to make subsequent calls to the backend, which a user can use to perform different operations on the sustem. In essence, it is the client which requests server for subsequent actions with the token provided in the login request.
So for your case, user after receiving the token should make a request for retrieving admin information from the backend.
But, I am assuming you are rendering views from your server-side and you want to render the admin view once the user is successfully logged in, and that's pretty straight forward.
Instead of your res.json() after successful login. You need to use res.render().
res.render('admin', {
user: user.toAuthJSON() // assuming your user contains the token already
})
Edit:
Since res.render() does not change the url in the browser. For that, you need to use res.redirect(). But the problem is, you can not send context in res.redirect().
To achieve that, you will need to pass in the user token as query paramter. See here.
TL;DR
// assuming you are using Node v7+
const querystring = require('querystring');
const query = querystring.stringify({
token: user.token,
});
const adminRoute = '/admin?' + query;
res.redirect(adminRoute)
And in your admin route, you need to slightly modify the code.
Verify the token belongs to a real user and get user information out of the token.
Render the admin template with user information retrieved from step 1.
router.get("/admin", function(req, res) {
// verify the token
const token = req.query.token;
const user = null;
jwt.verify(token, 'secret', function (err, decoded) {
if (err) {
res.status(401).send('Unauthorized user')
}
// decoded contains user
user = decoded.user
});
res.render('admin', {
user : user
});
});
I'm somewhat new to this as well, but I've got it working as follows.
In your server.js file:
const passport = require("passport");
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
app.use(passport.initialize());
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = Keys.secretOrKey;
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
// somefunction looks up the id in jwt payload and
// supplies passport the authenticated user via the "Done" function
somefunction.user(jwt_payload.id)
.then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
});
})
);
In your API definitions
const jwt = require("jsonwebtoken");
router.post("/login", (req, res) => {
const { userInfo } = req.body;
// userInfo has username and password in it
// anotherFuction validates the user id and password combo
anotherFunction(userInfo.id, userInfo.password)
.then(isAuthenticated => {
if (isAuthenticated) {
const payload = {
id: user.sAMAccountName,
firstname: user.givenName,
lastname: user.sn
};
// Sign Token with the payload
jwt.sign(
payload,
Keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: "Bearer " + token
});
}
);
} else {
// don't mind the statuses ^_^'
return res.status(401).json({ error: "Login failed." });
}
})
.catch(err => {
return res.status(400).json(err);
});
});
After calling the API you want to set the auth token. The following lets you delete the token if nothing is passed in, effectively "Logging out".
const setAuthToken = token => {
if (token) {
// Apply to every request
axios.defaults.headers.common["Authorization"] = token;
} else {
// Delete Auth Header
delete axios.defaults.headers.common["Authorization"];
}
};
If you're trying to use it in the front end, you need to use jwt_decode to pull the values from the token and set it however you deem necessary. If using redux to store login data it should look something like this. As I feel that the discussion of using localstorage for jwtToken is outside of the scope of this, just know would need to check for the token.
if (localStorage.jwtToken) {
setAuthToken(localStorage.jwtToken);
const decoded = jwt_decode(localStorage.jwtToken);
store.dispatch({
type: USER_LOGIN,
payload: decoded
});
}
Hope this helped.
From one beginner in JWT to another. Good luck.

How do I insert a transporter into services in an Node.js app?

I'm a new developer and I'm setting up an app for an organization that takes donations.
I am trying to set it up where the director gets a notification whenever she gets a donation(ideally I would like this to happen both when she gets a new donation AND when she has a new donor sign up).
I got the nodemailer part to work on a mini test app to set it up correctly. Now I just need to insert it into the app in the correct place.
Right now it's firing off every time I make a change(I have all the code in the index.js file). I spoke with a senior developer about nodemailer and he mentioned one of the perks is that you can take the transporter and insert it wherever you need it.
So I'm thinking I can keep the bulk of the nodemailer code in the index.js file and put the transporter section where it needs to go. I've been playing around with placing it in different places in the services file but I clearly just don't understand the flow.
Here is what I have so far:
donationcontroller.js
//create donation
router.post('/createdonation', validate, function (req, res) {
transporter.sendMail(HelperOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log("The donation was sent!");
console.log(info);
});
});
donationServices.js
//create donation
exports.createDonation = function(req, res){
let newDonation = {
used_clothing : 0,
new_clothing : 0,
used_shoes : 0,
new_shoes : 0,
baby_food: 0,
diaper_bags: 0,
bottles: 0,
pacifiers: 0,
diapers_boxes: 0,
beds: 0,
misc_items: 0
}
newDonation[req.body.donationOption] = req.body.donationAmount
return donation.create(newDonation)
.then(
function createSuccess(donation) {
res.json ({
donation: donation
});
},
function createError(err){
res.send(500, err.message);
},
);
}
Index.js
// nodemailer
// houses the data to send in the email
let transporter = nodemailer.createTransport({
service: 'gmail',
// make true if using ssl certificate
secure: false,
// stmp port
port: 25,
auth: {
user: 'test.test#gmail.com',
pass: 'password'
},
// protocol
tls: {
rejectUnauthorized: false
}
});
// use to construct body of email
let HelperOptions = {
from: '"Tester" <Test.test#gmail.com',
to: 'test.test#gmail.com',
subject: 'dd',
text: 'dd'
};
// contains callback data
transporter.sendMail(HelperOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log("The donation was sent!");
console.log(info);
});
Edit
I fundamentally misunderstood your initial question when I dove into this the first time. What I should have clarified is that you absolutely do not want to use nodemailer in your Angular code. This should only be happening in your backend (Node.js), as that is the only place where your API keys or usernames/passwords will be secure from prying eyes. As soon as you put that into a frontend app, anyone can hijack your gmail account. So, keep the trigger for your emails in your backend, and you'll be in great shape. Whenever a donation is saved or whatever, just hit your backend API in some way and then you can call your sendEmail function. :)
Original Post
It looks like you probably want to put that transporter in its own module (I'm assuming Index.js is your top-level entry point?), so it can be more easily imported to any file. Try this:
sendEmail.js
let transporter = nodemailer.createTransport({
service: 'gmail',
// make true if using ssl certificate
secure: false,
// stmp port
port: 25,
auth: {
user: 'test.test#gmail.com',
pass: 'password'
},
// protocol
tls: {
rejectUnauthorized: false
}
});
module.exports = function ({from, to, subject, text}) {
// Promisify it, so you can easily chain it in the wild
return new Promise(function (resolve, reject) {
// use to construct body of email
let HelperOptions = {
from,
to,
subject,
text
};
// contains callback data
transporter.sendMail(HelperOptions, (error, info) => {
if (error) {
console.log(error);
return reject(error);
}
console.log("The donation was sent!");
console.log(info);
resolve(info);
});
});
};
Then you can use this function anywhere in your code that you want to send an email like so:
donationServices.js
// import the email function
const sendEmail = require('./path/to/sendEmail.js')
// create donation
exports.createDonation = function(req, res){
let newDonation = {
used_clothing : 0,
new_clothing : 0,
used_shoes : 0,
new_shoes : 0,
baby_food: 0,
diaper_bags: 0,
bottles: 0,
pacifiers: 0,
diapers_boxes: 0,
beds: 0,
misc_items: 0
}
newDonation[req.body.donationOption] = req.body.donationAmount
return donation.create(newDonation)
.then(
function createSuccess(donation) {
sendEmail({
from: ‘test#gmail.com’,
to: ‘boss#gmail.com’,
subject: req.body.donationOption + ' donation alert'
text: 'yay donations!\n' + JSON.stringify(donation) // you get the point...
}).then(function (info) {
res.json({
donation: donation
});
}).catch(function (err) {
res.status(500).send(err);
// or maybe you don't care that the email failed:
// res.status(200).send({message: 'donation sent, but email didn\'t'});
});
},
function createError(err){
res.send(500, err.message);
}
);
}
Hopefully that's clear as mud. :)
P.S. I see you might be using username/password for nodemailer authentication. From personal experience, I would strongly recommend you invest the hours to brush up on OAuth2, etc, and get that working with refresh tokens and the whole shebang. It will really be worth it and you will learn a ton in the process. Happy coding!

Categories

Resources