I'm making a call to a node.js express api from a react client.
When I make the call from the client, this is my request:
const response = await axios({
method: 'post',
url: 'http://localhost:3000/api/users/forgotPassword',
data: {email: email},
headers: {
'X-Requested-With': 'XMLHttpRequest',
}
}
);
This is the endpoint in express:
adminUserRoutes.post('/forgotPassword', (req, res) => {
console.log('it connected')
if (req.body.email === '') {
res.status(400).send('email required');
}
console.log(req.body.email)
console.log(req.body)
User.findOne({email: req.body.email}, (err, user) => {
console.log('and here')
if(user){
const token = crypto.randomBytes(20).toString('hex');
console.log('use',user)
user.resetPasswordToken = token
user.resetPasswordExpires = Date.now() + 360000
user.name = user.name
user.email = user.email
user.password = user.password
user.admin = user.admin
// console.log(user)
user.save()
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: `email`,
pass: `password`,
},
});
const mailOptions = {
from: 'devinjjordan#gmail.com',
to: `${user.email}`,
subject: 'Link To Reset Password',
text:
'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n'
+ 'Please click on the following link, or paste this into your browser to complete the process within one hour of receiving it:\n\n'
+ `http://localhost:3000/#/newpassword/${token}\n\n`
+ 'If you did not request this, please ignore this email and your password will remain unchanged.\n',
};
console.log('sending mail');
transporter.sendMail(mailOptions, (err, response) => {
if (err) {
console.error('there was an error: ', err);
// res.status(200).json('there was an error: ', err);
} else {
console.log('here is the res: ', response);
res.set({
"Access-Control-Allow-Origin": "*", // Required for CORS support to work
"Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS
})
res.status(200).json('recovery email sent');
}
});
} else {
console.error('email not in database');
res.status(403).send('email not in db');
}
})
});
What's odd about the situation is that when I make the request from postman to the same endpoint, I receive the expected response.
However, when I make the request from the client, I receive this error:
MongooseError [OverwriteModelError]: Cannot overwrite `Team` model once compiled.
at new OverwriteModelError (/Users/lukeschoenberger/Documents/Programming/news-arg/backend/node_modules/mongoose/lib/error/overwriteModel.js:20:11)
at Mongoose.model (/Users/lukeschoenberger/Documents/Programming/news-arg/backend/node_modules/mongoose/lib/index.js:517:13)
I'm using serverless labdma and am running sls offline start to open on port 3000.
What's very odd is that the 'Team' model isn't even mentioned in the api in question.
Edit:
This is the Team module:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
let Team = new Schema({
team_name: {
type: String
},
city_or_state: {
type: String
},
league: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'League'
},
primary_color: {
type: String
}
}, { timestamps: true })
module.exports = mongoose.model('Team', Team)
This turned out to be an issue with withe aws-serverless. Running aws-serverless with this flag solves the issuue: --skipCacheInvalidation -c. Longer post about it: https://github.com/dherault/serverless-offline/issues/258
You are compiling your model more than one time at runtime. Check if your model is already registered before registering it:
module.exports = mongoose.models.Team || mongoose.model('Team', Team)
I also ran in to same error few weeks ago. After trying out few things I came out with a simple fix:
just try to export person model this way -
module.exports.teamModel= mongoose.model('Team', Team);
instead of - module.exports = mongoose.model('Team', Team)
Hope this helps !
if you still get the error just check the paths in the modules you were exporting this model.
Related
I am really struggling with Nodemailer using Gmail in my NodeJS backend application. I have tried both OAuth and 2 factor authentication but am getting the following errors:
Either
Error Error: Mail command failed: 530-5.7.0 Authentication Required.
OR
Nodemailer Error: Missing credentials for "PLAIN"
Nodemailer set up is as below with OAuth when I am receiving the Authentication required error:
import nodemailer from "nodemailer";
interface mailOptions {
from: string;
to: string;
subject: string;
html: string;
}
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
service: "gmail",
port: "587",
secure: false,
auth: {
type: "OAuth2",
user: 'EMAIL',
pass: 'EMAIL PASSWORD',
clientId: 'CLIENTID',
clientSecret: 'CLIENT SECRET',
refreshToken: 'OAUTH REFRESH TOKEN',
},
});
transporter.verify((err, success) => {
err
? console.log(err)
: console.log(`=== Server is ready to take messages: ${success} ===`);
});
export const sendMail = (mailOptions: mailOptions) =>
transporter.sendMail(mailOptions, function (err, data) {
if (err) {
console.log("Error " + err);
} else {
console.log("Email sent successfully");
}
});
The Missing credentials for "PLAIN" error is set up as below using 2 factor authentication:
import nodemailer from "nodemailer";
interface mailOptions {
from: string;
to: string;
subject: string;
html: string;
}
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: 'EMAIL',
pass: '2 FACTOR GENERATED PASSWORD FOR THE APP',
},
});
transporter.verify((err, success) => {
err
? console.log(err)
: console.log(`=== Server is ready to take messages: ${success} ===`);
});
export const sendMail = (mailOptions: mailOptions) =>
transporter.sendMail(mailOptions, function (err, data) {
if (err) {
console.log("Error " + err);
} else {
console.log("Email sent successfully");
}
});
controller code is as such:
ForgotPassword: async (req: Request, res: Response) => {
try {
const user = await userSchema.findOne({ email: req.body.email });
const username = user?.username;
if (user) {
const secret = process.env.ACCESS_TOKEN + user.password;
const payload = {
email: user.email,
id: user._id,
};
const token = jwt.sign(payload, secret, { expiresIn: "15m" });
const link = `http://localhost:${process.env.PORT}/user/reset-password/${user._id}/${token}`;
// console.log(link, token);
// Add in logic for sending the link via email to user here
const mailOptions = {
from: 'EMAIL',
to: 'EMAIL',
subject: "Password Reset",
html: "HTML",
};
sendMail(mailOptions);
res.status(200).send("Password reset link has been sent to your email");
} else {
res.status(404).send("User not found!");
}
} catch (e: unknown) {}
},
Edit:
The issue appears to be with loading environment variables, I added the email and generated password for 2 step verification into my app and it works perfectly. How to I get this to work with env variables?
After posting the edit, I ended up thinking that maybe I need to enable dotenv in my service file for the nodemailer. This worked perfectly. The following is the code I now have.
import nodemailer from "nodemailer";
import * as dotenv from "dotenv";
dotenv.config();
interface mailOptions {
from: string;
to: string;
subject: string;
html: string;
}
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: 'EMAIL',
pass: '2 FACTOR GENERATED PASSWORD FOR THE APP',
},
});
transporter.verify((err, success) => {
err
? console.log(err)
: console.log(`=== Server is ready to take messages: ${success} ===`);
});
export const sendMail = (mailOptions: mailOptions) =>
transporter.sendMail(mailOptions, function (err, data) {
if (err) {
console.log("Error " + err);
} else {
console.log("Email sent successfully");
}
});
I was creating a contact form using nodemailer. I implemented it and it was working fine, but some time later I came back to do a final test in postman it started to give me the following error"message": Not Found - /contact and "stack": "Error: Not Found - /contact\n . I am not sure what's causing the error.
this is code:
const transport = {
host: 'smtp.gmail.com', // Don’t forget to replace with the SMTP host of your provider
port: 587,
secure: false, // use SSL
auth: {
user: process.env.GMAIL,
pass: process.env.PASSWORD
}
}
const transporter = nodemailer.createTransport(transport)
transporter.verify((error, success) => {
if (error) {
console.log(error);
} else {
console.log('Server is ready to take messages');
}
});
app.post('/contact', (req, res, next) => {
const name = req.body.name
const email = req.body.email
const message = req.body.message
const content = ` name: ${name} \n email: ${email} \n message: ${message} `
const mail = {
from: name,
// to: 'RECEIVING_EMAIL_ADDRESS_GOES_HERE', // Change to email address that you want to receive messages on
to: 'xxx#gmail.com', // Change to email address that you want to receive messages on
subject: 'New Message from Contact Form',
text: content
}
transporter.sendMail(mail, (err, data) => {
if (err) {
res.json({
status: 'fail'
})
} else {
res.json({
status: 'success'
})
}
})
})
How to get logged in user in express app. I want to know witch user create post. This is my Post.js model:
const postsSchema = mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
image: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
numLikes: {
type: Number,
required: true,
default: 0,
},
comments: [commentSchema],
},
{
timestamps: true,
}
);
This is my authUser function where i log in user with email and password:
const authUser = async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
});
} else {
res.status(401);
throw new Error('Invalid email or password');
}
};
generateToken function is JWT:
import jwt from 'jsonwebtoken';
const generateToken = id => {
return jwt.sign({ id }, 'abc123', {
expiresIn: '30d',
});
};
export default generateToken;
When i create post i want to know user who created it, this is my create post function:
const createPost = async (req, res) => {
const post = new Post({
user: req.user._id,
title: 'Sample Title',
description: 'Sample Desc',
image: '/images/sample.jpeg',
category: 'Sample Category',
numLikes: 0,
});
const createPost = await post.save();
res.status(201).json(createPost);
};
When i try to create post i got this error in console:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property '_id' of undefined.
I can log in with postman, i can register, i can get user by id. How to tell my app Hey i am logged in user and have access to req.user object
You need to have the client send the token back to you, which you then validate (typically via a middleware affecting some section of endpoints so you don't have to call a validation function in individual endpoints).
If instead, express is also your front end, then you need to use a library like express-session https://www.npmjs.com/package/express-session to manage cookies. A good example is available on their page:
// Use the session middleware
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
// Access the session as req.session
app.get('/', function(req, res, next) {
if (req.session.views) {
req.session.views++
res.setHeader('Content-Type', 'text/html')
res.write('<p>views: ' + req.session.views + '</p>')
res.write('<p>expires in: ' + (req.session.cookie.maxAge / 1000) + 's</p>')
res.end()
} else {
req.session.views = 1
res.end('welcome to the session demo. refresh!')
}
})
Otherwise you've sent the token to client and done nothing with it.
Do you need of a middleware like this:
module.exports = (req, res, next) => {
// Authorization token example: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWQiOiIxIiwiaWF0IjoxNTE2MjM5MDIyfQ.dYo0kOIhum5mMTRV8CAn8gQ_6aqoDQLE--vCZD4E-fg
const { authorization } = req.headers
if (!authorization) return res.send({ message: 'Token not provided', code: 400 })
const [ schema, token ] = authorization.split(' ')
if (schema !== 'Bearer') return res.send({ message: 'Token is bad formated', code: 400 })
if (!token) return res.send({ message: 'Invalid token', code: 400})
jwt.verify(token, 'abc123', (error, decoded) => {
if (error) return res.send({ message: error.message, code: 401})
req.userId = decoded.id
})
next()
}
Hope this is helpful for you.
The first thing you should do is to send token back to the client or attach cookies to your response.
After which you set up a middleware that will check cookies or token in your case using jwt.verify(token, jwtSecret). That will return the id and all other things you stored in the token, then you then store them in req.user, where you will be able to access the details later.
//if you stored token in cookies -
const {accessToken} = req.signedCookies
const payload = isTokenValid(accessToken) //verify the token
req.user = payload.user;
return next();
//if you stored in auth header
const bearerToken = req.headers.authorization
//bearerToken = "Bearer token"
const token = bearerToken.split(" ").join(",")[1]
//verify the token using jwt
const payload = isTokenValid(token)
req.user = payload
return next()
I am very new to the MERN stack and I would like some help figuring out this error. I'm trying to check if an email is already in the database upon creating a new user. Can anyone tell me why I am getting this error?
The model and scheme
//schema
const Schema = mongoose.Schema;
const VerificationSchema = new Schema({
FullName: String,
email: String,
password: String,
date: Date,
isVerified: Boolean,
});
// Model
const User = mongoose.model("Users", VerificationSchema);
module.exports = User;
The Api
const express = require("express");
const router = express.Router();
const User = require("../Models/User");
router.get("/VerifyEmail", (req, res) => {
console.log("Body:", req.body);
const data = req.body;
const newUser = new User();
newUser.find({ email: data.email }, function (err, newUser) {
if (err) console.log(err);
if (newUser) {
console.log("ErrorMessage: This email already exists");
} else {
console.log("This email is valid");
}
});
res.json({
msg: "We received your data!!!",
});
});
module.exports = router;
The api caller using axios
const isEmailValid = (value) => {
const info = {
email: value,
};
axios({
url: "http://localhost:3001/api/VerifyEmail",
method: "get",
data: info,
})
.then(() => {
console.log("Data has been sent");
console.log(info);
})
.catch(() => {
console.log("Internal server error");
});
};
if you have body in your request, change the type of request to POST...
after that for use find don't need to create a instance of model, use find with Model
router.get("/VerifyEmail", (req, res) => {
console.log("Body:", req.body);
const data = req.body;
User.find({ email: data.email }, function (err, newUser) {
if (err) console.log(err);
if (newUser) {
console.log("ErrorMessage: This email already exists");
} else {
console.log("This email is valid");
}
});
res.json({
msg: "We received your data!!!",
});
});
I prefer to use async/await and don't use Uppercase world for routing check the article: like this
router.post("/verify-email", async (req, res) => {
try {
let { email } = req.body;
let newUser = await User.findOne({ email });
if (newUser) {
console.log("ErrorMessage: This email already exists");
} else {
console.log("This email is valid");
}
} catch (error) {
res.json({
msg: "somthing went wrong",
});
}
res.json({
msg: "We received your data!!!",
});
});
The proper way to query a Model is like so:
const User = mongoose.model('Users');
User.find({<query>}, function (err, newUser) {...
So you need to get the model into a variable (in this case User) and then run the find function directly against it, as opposed to running it against an object you instantiate from it. So this is incorrect:
const newUser = new User();
newUser.find(...
So assuming all your files and modules are linked up correctly, this should work:
const User = require("../Models/User");
User.find({<query>}, function (err, newUser) {...
The problem wasn't actually the mongoose function but I needed to parse the object being sent.
let { email } = JSON.parse(req.body);
Before parsing the object looked like {"email" : "something#gmail.com"}
and after parsing the object looked like {email: 'something#gmail.com'}
I also changed the request from 'get' to 'post' and instead of creating a new instance of the model I simply used User.find() instead of newUser.find()
I'm not sure how to check if the values match with the MongoDB data. I am using PUT and trying to use findOneAndUpdate to check if the values match.
<script>
const logindetails = new Vue({
el: '#logindetails',
data: {
email: "",
password: "",
on: Boolean
},
methods: {
login: function (e) {
e.preventDefault();
const log = {
email: this.email,
password: this.password,
}
const options = {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(log)
};
fetch('http://localhost:3000/users/${this.email}/${this.password}',
options).then(response => {
[...]
</script>
This is the server code (it successfully connected to MongoDB) :
app.put('/students/:email/:password', (req, res, next) => {
console.log("login");
res.setHeader("Content-Type", "application/json");
db.collection('users').findOne({email: (req.params.email), password: (req.params.password)},
{$set: {on: true}})
.then(results => res.send(results))
.catch(err => res.send(err))
});
I personally don't think it is a good idea to put your username and password as query string, because it hurts the restful api convention. It wouldn't make sense to use a put request if there is no body being pass. Also, a post request would make more sense in a login situation .Anyway I digress, here are the usual steps to doing authentication.
1. (Client-Side) Send the email and password in the body of the fetch request
//something like this
const body = { email, password };
const response = await fetch(
"http://localhost:5000/authentication/login",
{
method: "POST",
headers: {
"Content-type": "application/json"
},
body: JSON.stringify(body)
}
);
2.(Server-Side - make sure you to use app.use(express.json()) to access req.body)
//defining middleware to access req.body
app.use(express.json());
app.post("/authentication/login", async(req,res) =>{
//1. destructure email and password
const {email, password} = req.body
//2. check if user doesn't exist
const user = await db.user.find({user_email: email})
if(!user){
return res.status(401).send("User does not exist");
}
//3. Check if password is the same as the password in the database
if(password !== user.password){
return res.status(401).send("Wrong Credential")
}
//4. This is up to you when the user is authenticated
res.json(`Welcome back ${email}`);
})