In React JS App there is forgot password functionality, on entering email end users get password reset link if the user's email exists in DB. When user clicks the link inside email it gets redirect and load blank screen. I can't figure out the problem, I had checked all routes and looks like perfect.
This is ResetPassword Component where user can enter set his new password.
const ResetPassword = (props) => {
const userId = props.match.params.id
const [password,setPassword] = useState({
password: '', confirmPassword: ''
})
const baseUrl = process.env.REACT_APP_baseUrl
const changePassUrl = `${baseUrl}/user/reset/${userId}`
const history = useHistory()
const resetPassword = async e => {
e.preventDefault()
const options = {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(password)
}
await fetch(changePassUrl, options)
.then(response => response.json())
.then(user => {
console.log('Success:', user);
if(user.status===true){
toast.success('Password changed Successfully. Login with New Password',{position: 'top-center', autoClose: 8000})
history.push('/login')
}
else if(user.status === false){
toast.error('Please try again!',{position: 'top-center', autoClose: 8000})
}
})
.catch((error) => {
console.error('Error:', error);
});
}
App.js route of above component.
<Route exact path='/reset/:id' component={ResetPassword}/>
Node API that sends the reset link via NodeMailer.
module.exports.resetUserPassword = (req, res) => {
User.findOne({email: req.body.email},
(err, users) => {
if (err) {
res.status(500).json({
status: false,
message: 'some error occured in Finding User.',
error: err,
});
}
if (users) {
const url = process.env.Url
var transporter = nodemailer.createTransport({
host: process.env.mailHost,
port: 587,
auth: {
user: process.env.mailUser,
pass: process.env.mailPass
}
});
var mailOptions = {
from: process.env.mailUser,
to: users.email,
subject: 'Password Reset Link',
html: `
<div style='color: #000'>
<h2>Hello ${users.fullName},</h2>
<h3>A request has been received to change the password for your Jobs#MyCityMyStore Account.</h3>
<h3>Click here to reset your password</h3>
<p>If you did not initiate this request, Please contact us immediately at </p><a href='#'>support#support.com</a><br/>
<p>Thank you.</p>
<p>support#support Team</p>
</div>`
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
res.status(200).json({ status: true, email: users.email });
}else{
res.status(200).json({status: false, data: 'User not found '+users})
}
}
);
};
Related
I am trying to implement a mechanism to allow new users to verify their account with a link sent to their email address.
Here's is my subscribe route in routes/user/index.js, which is working to send the email correctly:
require('dotenv').config();
const { v4 } = require('uuid');
const nodemailer = require('nodemailer');
const sibApiV3Sdk = require('sib-api-v3-sdk');
const express = require('express');
const stripe = require('../middlewares/stripe')
const db = require("../models");
const cors = require('cors');
const User = db.user;
const Feedback = db.feedback;
const defaultClient = sibApiV3Sdk.ApiClient.instance;
// Configure API key authorization: api-key
const apiKey = defaultClient.authentications['api-key'];
apiKey.apiKey = process.env.SENDINBLUE_API_KEY;
const transactionalEmailsApi = new sibApiV3Sdk.TransactionalEmailsApi();
// Configure nodemailer
const transporter = nodemailer.createTransport({
host: "smtp-relay.sendinblue.com",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: "noreply#redacted.ai",
pass: "redacted"
}
});
// Define generateVerificationToken function
function generateVerificationToken() {
return v4();
}
// Prepare Core Router
let app = express.Router()
app.post('/stripe/subscribe', async (req, res) => {
const domainURL = process.env.DOMAIN;
const { priceId, trial } = req.body
try {
let user = await User.findOne({ _id: req.user._id });
let customer = user.customerId ? { customer: user.customerId } : {customer_email: user.email};
const subscription_data = trial ? { trial_period_days: 7 } : {};
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
...customer,
line_items: [
{
price: priceId,
quantity: 1,
},
],
subscription_data,
success_url: `${domainURL}/index.html`,
cancel_url: `${domainURL}/signup/failed`,
});
let credits = 0;
if (priceId === process.env.STRIPE_PRODUCT_FREE) {
credits = 0;
} else if (priceId === process.env.STRIPE_PRODUCT_ENTRY) {
credits = 0;
} else if (priceId === process.env.STRIPE_PRODUCT_PRO) {
credits = 0;
}
// Check if user already has a verification_token
if (!user.verification_token) {
const token = generateVerificationToken();
console.log("generated token: ", token);
const updatedUser = await User.findOneAndUpdate({ _id: req.user._id },
{
credits: credits,
verification_token: token,
isVerified: false
},
{
new: true
});
console.log("updatedUser: ", updatedUser);
const verificationLink = `${domainURL}/verify?token=${updatedUser.verification_token}`;
const mailOptions = {
from: 'noreply#redacted.ai',
to: user.email,
subject: 'Verify your email address',
html: `Please click on the following link to verify your email address: ${verificationLink}`
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
} else {
console.log('User already has a verification token.');
}
const updateResult = await User.updateOne({_id: req.user._id}, {verification_token: user.verification_token}, {upsert: true});
console.log("updateResult: ", updateResult);
// Send verification email after stripe step completes
if (!user.isVerified) {
const verificationLink = `${domainURL}/verify?token=${user.verification_token}`;
const mailOptions = {
from: 'noreply#redacted.ai',
to: user.email,
subject: 'Verify your email address',
html: `Please click on the following link to verify your email address: ${verificationLink}`
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
}
// Redirect user to the verify route
res.redirect(303, session.url);
} catch (e) {
res.status(400);
return res.send({
error: {
message: e.message,
}
});
}
});
Immediately after this, I then have a verify route:
app.get('/verify', cors(), async (req, res) => {
const { token } = req.query;
try {
// Find user with matching token
let user = await User.findOne({ verification_token: token });
if (!user) {
throw new Error('Invalid token');
}
// Update user verification status
user.isVerified = true;
const savedUser = await user.save();
// Send verification email
if (savedUser.isVerified) {
const mailOptions = {
from: 'noreply#redacted.ai',
to: savedUser.email,
subject: 'Email Verified!',
html: `Your email has been verified! Welcome to redacted.ai`
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
}
// Redirect user to success page
res.redirect('/index.html#/verify/success');
} catch (e) {
res.status(400);
return res.send({
error: {
message: e.message,
}
});
}
});
I also have a file called src/verify.js to deal with things on the front-end:
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
function Verify(props) {
const [isVerified, setIsVerified] = useState(false);
const { token } = props.match.params;
const history = useHistory();
useEffect(() => {
// logic to process token and set isVerified to true/false
fetch(`/verify/${token}`)
.then(res => res.json())
.then(data => {
if (data.isVerified) {
setIsVerified(true);
}
});
}, [token]);
useEffect(() => {
// redirect to success page if isVerified is true
if (isVerified) {
history.push('/index.html#/verify/success');
}
}, [isVerified, history]);
return (
<div>
{isVerified ? (
<div>Your email has been verified! Welcome to redacted.ai</div>
) : (
<div>Invalid token.</div>
)}
</div>
);
}
export default Verify;
However, when the user clicks on the link in their email, the verify route does not initiate. Rather, nothing happens at all in the terminal except for profile refresh, and I'm not sure why.
As an aside, I can see in the database that the new user has a verification_token and isVerfied is set to false. That verification_token matches the one sent in the email.
I'm very new to Node JS so suspect I'm missing something obvious! Thanks so much in advance.
PS.
As a work around, I attempted to try a different approach, and created a form that the user could go to and copy and paste in their verification_token: the form looked like this:
import React, { useState } from 'react';
import TOOLS from './tools'
import config from './config'
import Header from './Header'
let baseURL = config.baseURL
const VerifyPage = () => {
const [verificationToken, setVerificationToken] = useState('');
const [error, setError] = useState('');
const [responseData, setResponseData] = useState({});
const handleSubmit = async (event) => {
event.preventDefault();
const token = window.localStorage.getItem('token');
console.log('Token: ', token);
console.log('Authorization header: ', `Bearer ${token}`);
// Make a request to your API to verify the token
try {
const response = await fetch(`${baseURL}user/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ verificationToken })
});
console.log('Request URL: ', `${baseURL}user/verify`);
console.log('Request body: ', JSON.stringify({ verificationToken }));
const responseJson = await response.json();
setResponseData(responseJson);
console.log('Response status: ', response.status);
console.log('Response text: ', await response.text());
if (!response.ok) {
throw new Error(responseJson.message);
}
// Show a success message or redirect to another page
} catch (error) {
setError(error.message);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="verificationToken">Verification Token:</label>
<input
type="text"
id="verificationToken"
value={verificationToken}
onChange={(event) => setVerificationToken(event.target.value)}
/>
</div>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="submit">Verify</button>
{responseData && JSON.stringify(responseData)}
</form>
);
};
export default VerifyPage;
And with this in place, I altered my verify route to looks like this:
app.post("/verify", (req, res) =\> {
console.log('Received request to /verify');
const { token } = req.body;
console.log('Received verification token:', token);
User.findOne({ token }, (err, user) => {
if (err) {
console.error('Error finding user:', err);
return res.status(500).send({ error: "Error verifying account" });
}
if (!user) {
console.error('No user found with provided verification token');
return res.status(400).send({ error: "Invalid Token" });
}
user.isVerified = true;
user.verificationToken = undefined;
user.save((err) => {
if (err) {
console.error('Error saving user:', err);
return res.status(500).send({ error: "Error verifying account" });
}
console.log('User verified successfully');
return res.send({ message: "Account verified successfully" });
});
});
});`
But whenever the user attempted to put in their valid token, they would get something like this:
"Unexpected token 'F', "Forbidden" is not valid JSON"
Moreover, this is all that would show up in the terminal:
`"OPTIONS /api/user/verify 204 0.285 ms - 0 POST /api/user/verify 403 1.558 ms - 9" `
And, under the JWT code, this would show up in the console: "Failed to load resource: the server responded with a status of 403 (Forbidden)"
Truth be told, I'd be happy to get this working either via the form, or by having users click the link in the email. The latter is preferable - but either would work.
I would like to be able to redirect from registration-page to login-page on successfull registration and again from login-page to home-page afteer successfull login.
I dont know what methods to use or where to call them.
This is the register call.
app.post("/api/register", async (req, res) => {
const { username, password: plainTextPassword } = req.body;
const password = await bcrypt.hash(plainTextPassword, 10);
try {
const response = await User.create({
username,
password
})
console.log("User created", response)
} catch (error) {
if (error.code === 11000) {
return res.json({ status: "error", error: "Username already in use" })
}
throw error
}
res.json({ status: "ok" });
});
This is the script
<script>
const form = document.getElementById("reg-form");
form.addEventListener("submit", registerUser);
async function registerUser(event) {
event.preventDefault();
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
const result = await fetch("/api/register", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username,
password
})
}).then((res) => res.json())
if (result.status === "ok") {
alert("Success");
} else {
alert(result.error)
}
}
</script>
</body>
</html>
You should return the line that redirects
return res.redirect('/UserHomePage');
i build full functions that worked and send emil to user when he "forgot password",its worked perfect on postMan, now the problem is when i clicked on the email url is direct me to "Cannot GET /resetpassword/eyJhbGciOiJIUzI1NiIsInR5cCeyJfaWQiOiI1ZmQ5ZmFmZjFjODA2YTgyMjZjMTQ5YmUiLCJp"
if i was do that for web app its work, but how can i redirect to open my app and show the screen for reset password?
exports.sendEmail = emailData => {
const transporter = nodeMailer.createTransport({
host: "smtp.gmail.com",
port: 587,
secure: false,
requireTLS: true,
auth: {
user: "roeigr7#gmail.com",
pass: "iaoqymixqarajkbv",
},
});
return transporter.sendMail(emailData);
};
and the data send to:
const emailData = {
from: "noreply#Lior.com",
to: email,
subject: "Password Reset Instructions",
text: `Please use the following link to reset your password: ${process.env.CLIENT_URL}/resetpassword/${token}`,
html: `<p>Please use the following link to reset your password:</p> <p>${process.env.CLIENT_URL}/resetpassword/${token}</p>`,
---------reset password FUNCTION:
exports.resetPassword = (req, res) => {
const { resetPassword, newPassword } = req.body;
User.findOne({ resetPassword }, (err, user) => {
// if err or no user
if (err || !user)
return res.status("401").json({
error: "Invalid Link!",
});
const updatedFields = {
password: newPassword,
resetPassword: "",
};
user = _.extend(user, updatedFields);
user.updated = Date.now();
user.save((err, result) => {
if (err) {
return res.status(400).json({
error: err,
});
}
res.json({
message: `סיסמא שונתה בהצלחה`,
});
});
});
};`
--------- resetPassword component:
const resetPassword = async resetInfo => {
try {
const response = await indexApi.put("resetPassword", {
resetInfo,
});
console.log("forgot password response: ", response);
} catch (err) {
console.log(err.response.data.error);
}
};
const resetPassModal = () => {
return (
<View style={{ marginVertical: 30 }}>
<Text onPress={resetPassword} style={styles.titletext}>
reset
</Text>
I would like to send an email to myself(outlook adress) using sendgrid.
I have added a sender authentification for my email adress ( the same i use in send grid message: to and from parameters)
Inside an express node server, i process the mail transport. see the code below.
app.post('/api/email', (req, res, next) => {
console.log(req.body);
sendGrid.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: 'The email i have register as sender authentification',
from: 'The email i have register as sender authentification',
subject: 'information request with same from and to',
text: 'Hello i am ' + req.body.name + req.body.informationDetails
};
sendGrid.send(msg)
.then(result => {
res.status(200)
res.json({
success: true
});
})
.catch(err => {
console.log('error: ', err);
res.status(401).json({
success: false
});
})
});
The sendgrid activity page shows me that the mails have been delivered to the adress i have register but i am unable to receive the email inside my mailbox.
i am little lost on how to solve that
Moreover, in the frontend, i am using axios to connect the backend to send the request parameters, but the res value cant be displayed.
Axios.post('/api/email', this.state)
.then(res => {
console.log("check before if success");
console.log(res);
if(res.data.success) {
this.setState({
name: "",
phoneNumber: "",
informationDetails: "",
disabled: false
});
} else {
console.log("no success");
console.log(res);
this.setState({
disabled: false
});
}
})
.catch(err => {
this.setState({
disabled: false
});
})
Does someone have an idea?
i don't know whether you have solved it or not. but i have a working example following your problem. i won't be explaining it to you because of not being sure of executing it futher.
nodejs:
app.post("/contact", (req,res,next) => {
const name = req.body.name;
const email = req.body.email;
const subject=req.body.subject;
const message = req.body.message;
const msg = {
to: "it's ur desired email", //receiver's email
from: email, // Change to your verified sender
subject: subject,
text: "testing from local",
html: `<p>Name: ${name}</p> sends a message from <p>Email: ${email}</p>: <br/><p>Message: ${message}</p>`,
};
sgMail
.send(msg)
.then((result) => {
res.json({success: true});
})
.catch((error) => {
console.error(error);
res.status(500).json("Error")
});
App.js from frontend:
axios.post('http://localhost:5000/contact', this.state)
.then(res => {
if(res.data.success) {
this.setState({
disabled: false,
emailSent: true
});
} else {
this.setState({
disabled: false,
emailSent: false
});
}
})
.catch(err => {
console.log(err);
this.setState({
disabled: false,
emailSent: false
});
})
}
i have added the subject area because i needed it for my project and it looks cool for me.
let me know if it works
I am trying to set up a password-reset route inside my node application, which is suppose to send an email to the user with a password reset link.
The issue that I am having is that I keep receiving the error TypeError: cannot read property json of undefined. To my understanding this means that I am not passing the email correctly to my server side code.
I am having troubles locating the exact place of my bug. I've followed this example, however I've changed it to a more of a MVC architecture. Meaning that I've split the Node code into a service and a controller.
Here is my ReactJS code:
class PasswordResetComponent extends Component {
constructor(props){
super(props);
this.state = {
email:'',
showError:false,
messageFromServer:''
}
}
handleChange = name => event => {
this.setState({
[name]: event.target.value
});
}
sendEmail = e => {
e.preventDefault();
if(this.state.email == ' '){
this.setState({
showError: false,
messageFromServer:' ',
});
}else{
(async () => {
const rawResponse = await fetch(process.env.REACT_APP_SERVER_URI + '/user/password-reset', {
method: 'POST',
body: this.state.email
});
const response = await rawResponse.json();
console.log(response)
})();
}
}
render(){
console.log(this.state.email)
const {email, messageFromServer, showError, showNullError} = this.state;
return(
<div className="container-fluid no-gutters page-login">
<div className="row">
<div className="login-wrapper">
<form onSubmit={this.sendEmail} method="POST">
<div className="form-row">
<div className="form-group col-md-12">
<input type="email" name="email" className="form-control" value={email} onChange={this.handleChange('email')} placeholder="Email" required/>
</div>
</div>
<button type="submit" className="btn btn-primary btn-lg btn-block">Send Confirmation</button>
</form>
<Link to="/login"><button className="mt-4 btn btn-info">Back to Login</button></Link>
</div>
</div>
</div>
);
}
}
Inside my React code, I manage to successfully print out the value of this.state.email meaning that it should be passing fine. I also receive a 200 status on the request.
Here is my Service for password reset:
exports.passwordResetService = async function(email,res, req){
try{
User.findOne({
email: email
}).then(user => {
if( user == null){
res.json('email not in db');
}else{
const token = crypto.randomBytes(20).toString('hex');
console.log(token);
user.update({
resetPasswordToken: token,
resetPasswordExpires: Date.now() + 360000,
});
const transporter = nodemailer.createTransport({
service: 'gmx',
host: 'mail.gmx.com',
port: 587,
secure: true,
auth: {
user: `${process.env.EMAIL_ADDRESS}`,
pass: `${process.env.EMAIL_PASSWORD}`,
},
});
const mailOptions = {
from:'testaccount9909#gmx.com',
to: `${user.email}`,
subject: `Password Reset Email Broh'`,
text:
`Please click the following link to reset your account's password:`+
`http://localhost:3003/reset/${token}\n\n`+
`If you simply ignore the link, your password will remain unchanged!`
}
console.log('sending email...');
transporter.sendMail(mailOptions, function(err, response){
if(err){
console.error('there was an error: ', err);
} else {
console.log('here is the resposne:', response);
res.status(200).json('recovery email sent');
}
})
}
}).catch(error =>
console.log(error)
);
}catch(e){
throw Error("And Error occured while resetting the password");
}
}
Here is my Controller for password reset:
exports.passwordReset = async function(req, res, next){
try{
let passwordResetValue = await UserService.passwordResetService(req.body.email);
return res.status(200).json({status:200, data: passwordResetValue, error:"false", message:"Password reset email has been sent!"})
}catch(e){
console.log(e, "Wopsie wopsie email reset error!");
res.status(400);
}
};
The Place where I am getting the error is inside the Service, more specifically:
if( user == null){
res.json('email not in db');
}
Why am I not passing the user email to the NodeJS service?
The reason you're getting cannot read property json of undefined is because res is not available to passwordResetService since you've not passed it from the route callback and since it's not a middleware
Since all that you're doing with passwordResetService is do some validation and send email you can make few changes to your code like:
// Since you want to send the some success message, you can make a promise wrapper
// to return a promise which you can later await to get status or failure
function mailSender(mailOptions) {
return new Promise((resolve, reject) => {
transporter.sendMail(mailOptions, function(err, response){
if(err){
// could just reject(err) instead
reject('there was an error: ', err);
} else {
console.log('here is the resposne:', response);
resolve('recovery email sent');
}
});
});
}
exports.passwordResetService = async function(email){
let mailStatus;
try{
const user = User.findOne({ email });
if (user === null) {
return ({ message: "email not in db" });
}
const token = crypto.randomBytes(20).toString('hex');
console.log(token);
user.update({
resetPasswordToken: token,
resetPasswordExpires: Date.now() + 360000,
});
const transporter = nodemailer.createTransport({
service: 'gmx',
host: 'mail.gmx.com',
port: 587,
secure: true,
auth: {
user: `${process.env.EMAIL_ADDRESS}`,
pass: `${process.env.EMAIL_PASSWORD}`,
},
});
const mailOptions = {
from:'testaccount9909#gmx.com',
to: `${user.email}`,
subject: `Password Reset Email Broh'`,
text:
`Please click the following link to reset your account's password:`+
`http://localhost:3003/reset/${token}\n\n`+
`If you simply ignore the link, your password will remain unchanged!`
}
console.log('sending email...');
// wait for promise to resolve, if promise is rejected, catch block will catch the error
mailStatus = await mailSender(mailOptions);
} catch(e) {
throw Error("And Error occured while resetting the password");
}
return mailStatus;
}