How do i redirect in node.js - node.js

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');

Related

Struggling to verify new users: Node JS

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.

How to catch error of rejected promise on client side?

I am trying to have my server reject the signup request if the user tries to sign up with an existing account. However, I cant seem to reject it properly and pass the error message to my client side.
//server.js
app.post('/signup', (req, res) => {
const email = req.body.email
const plainTextPassword = req.body.password;
//check if user already exists
User.find({ email: email }, (err, existingUser) => {
//account doesnt exist
if (existingUser.length === 0) {
bcrypt.hash(plainTextPassword, saltRounds, async (err, hash) => {
try {
const user = new User({
email: email,
password: hash
});
let result = await user.save();
if (result) {
res.send(result)
}
} catch (e) {
res.send(e);
}
})
} else {
//notify user that account exists
return Promise.reject(new Error('Account already exists'))
}
})
})
//reduxSlice.js
export const signup = createAsyncThunk(
'userAuth/signup',
async (payload, thunkAPI) => {
const { email, password } = payload
try {
const result = await fetch(
signupPath, {
mode: 'cors',
credentials: 'include',
method: "post",
body: JSON.stringify({ email, password }),
headers: {
'Content-Type': 'application/json'
}
}
)
return result.json()
} catch (error) {
console.log(error) //this line executes
}
}
)
From my reduxdev tools, my signup is still fulfilled EVEN though I rejected it from my server. Also, my server crashes after one attempt, which leads me to suspect there is an uncaught error.
The client only receives what you do res.send() with or next(err) which will then call res.send(). Promises are local only to the server and are not something that gets sent back to the client.
In your original code, I'd suggest that you use ONLY promise-based asynchronous operations and then you can throw in your code, have one place to catch all the errors and then send an error back to the client from there.
class ServerError extends Error {
constructor(msg, status) {
super(msg)
this.status = status;
}
}
app.post('/signup', (req, res) => {
try {
const email = req.body.email
const plainTextPassword = req.body.password;
//check if user already exists
const existingUser = await User.find({ email: email });
//account doesnt exist
if (existingUser.length !== 0) {
throw new ServerError('Account already exist', 403);
}
const hash = await bcrypt.hash(plainTextPassword, saltRounds);
const user = new User({
email: email,
password: hash
});
const result = await user.save();
res.send(result);
} catch (e) {
if (!e.status) e.status = 500;
console.log(e);
res.status(e.status).send({err: e.message});
}
});
Then, in your client code that is using fetch(), you need to check result.ok to see if you got a 2xx status back or not. fetch() only rejects if the network connection to the target host failed. If the connection succeeded, even if it returns an error status, the fetch() promise will resolve. You have to examine result.ok to see if you got a 2xx status or not.
//reduxSlice.js
export const signup = createAsyncThunk(
'userAuth/signup',
async (payload, thunkAPI) => {
const { email, password } = payload
try {
const result = await fetch(
signupPath, {
mode: 'cors',
credentials: 'include',
method: "post",
body: JSON.stringify({ email, password }),
headers: {
'Content-Type': 'application/json'
}
});
// check to see if we got a 2xx status success
if (!result.ok) {
throw new Error(`signup failed: ${response.status}`);
}
return result.json()
} catch (error) {
console.log(error) //this line executes
}
}
)

When page reloaded, the JWT token set the original

I tried to check JWT token worked actually or not. set in local storage. but after reloading, it set the original automatically. so here is any wrong or good suggestion?
front end:
in front end code is.
let userToken = localStorage.getItem("accessToken")
useEffect(() => {
axios.get(`http://localhost:5000/mycars/${user.uid}`,
{
headers: {
email: user.email,
token: userToken
}
})
.then(response => {
console.log(response);
if (response.data.success) {
setError("No car found")
}
else if (response.data.error) {
setError("Unauthorized access")
}
else {
setMycars(response.data)
setError("")
}
})
.catch(err => {
console.log("error is ", err);
})
}, [user.uid, user.email, userToken])
backend:
app.get('/mycars/:id', async (req, res) => {
const uid = req.params.id;
const getEmail = req.headers.email;
const accessToken = req.headers.token;
try {
const decoded = await jwt.verify(accessToken, process.env.DB_JWTTOKEN,
function (err, decoded) {
let email;
if (err) {
email = "invalid email"
}
if (decoded) {
email = decoded.email
}
return email;
});
// console.log(getEmail, decoded);
if (getEmail === decoded) {
const query = {}
const cursor = carCollection.find(query);
const cars = await cursor.toArray();
const mycars = await cars.filter(car => car.uid === uid)
if (mycars.length === 0) {
res.send({ success: "No car found" })
}
else {
res.send(mycars)
}
}
else {
res.send({ error: "Unauthorized access" })
}
}
catch (err) {
}
with the code , I wanted to check the JWT token manually changing in local storage. when page reloaded, the token execute new. and the edited not remaining

Problem structuring two factor authentication

const login = (req, res) => {
// console.log(req.body);
// let email = req.body.email.toLowerCase();
sequelize.models.User.findOne({
where: {
email: req.body.email,
},
})
.then(async (user) => {
if (!user) {
// console.log(" email not found is true");
return res.status(401).json({
success: false,
message: " Authentication failed, Wrong Credentials",
});
}
if (user.isActive == false) {
// console.log("user is not activated", user.isActive);
return res.status(400).json({
success: false,
message: "account is not activated",
});
}
console.log("test entry");
await user.comparePassword(req.body.password, async (err, isMatch) => {
console.log(req.body.password);
if (isMatch && !err) {
console.log("user crap");
// role_id: user.role_id,
const payload = {
user_id: user.user_id,
};
const options = {
expiresIn: "10day",
};
const token = await jwt.sign(payload, process.env.SECRET, options);
console.log("sssssss", payload);
if (user.twoFactorAuth == false) {
return res.json({
success: true,
token,
});
} else {
// let mobile = user.phone;
await twoFactorAuth(user); // we call the 2fa that will send a otp to the users cellphone
// console.log("after cb");
}
} else {
return res.json({
success: false,
msg: "Authentication failed.",
});
}
});
// console.log("user crap", user.user_id);
})
.catch((error) => {
return res.status(400).send(error);
});
};
const twoFactorAuth = async (user) => {
var data = qs.stringify({
sender: "hehe",
mobile: user.phone,
channel: "sms",
});
var config = {
method: "POST",
url: "https://blablabla",
headers: {
Authorization: "Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
},
data: data,
};
axios(config)
.then( async function (response) {
console.log(JSON.stringify(response.data));
// await verifyTwoFactorAuth (realToken)
})
.catch(function (error) {
console.log(error);
});
};
const verifyTwoFactorAuth = async(req, res) => {
//console.log("tet",req);
let otpcode = req.body.otpcode;
let mobile = req.body.mobile;
var data = qs.stringify({ mobile: mobile, code: otpcode });
var config = {
method: "POST",
url: "https://blablabla",
headers: {
Authorization: "Bearer xxxxxxxxxxxxxxxxxxxxxxxx",
},
data: data,
};
axios(config)
.then(async function (response) {
console.log(JSON.stringify(response.data));
if (response.data.code == 63 || response.data.status == 200) {
return res.json({
success: true,
token,
});
} else if (response.data.code == 21 || response.data.status == 422) {
return res.status(400).json({
success: false,
message: "wrong code, check your sms again",
});
}
})
.catch(function (error) {
console.log(error);
});
};
Hello, I am looking for a structure solution to how I should implement what I want.
Scenario: user try to login, system checks for username and passoword and generates the TOKEN, system finds that 2fa is active in users settings, system sends OTP to users cellphone.
Now my struggle begins, I am not sure what to do next, I thought about storing the token in users fields as tempToken then i look for the user via users mobile and extract the token that way, but I dont believe that this is best practice.
Any ideas of how to tackle this would be appreciated ! thank you

How to display the back-end error message instead of status code

I created a Backend server and posted it to Heroku and now I am using the server URL to post and get data from it. However when I display an error message it's getting me the status code instead of the actual error.
My Backend login code.
export const loginUser = asyncHandler(async(req, res) => {
const { email, password } = req.body;
const user = await userModel.findOne({ email });
const token = generateToken(user._id)
if(user && (await user.matchPassword(password))){
res.json({
_id:user._id,
username: user.username,
email: user.email,
profilePic:user.profilePic,
admin:user.admin,
token:token,
});
} else {
res.status(400)
throw new Error("invalid email or password please check and try again!");
}
});
My user Actions code since I am suing redux
export const login = (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const url = `${process.env.REACT_APP_SERVER_URL}api/loginuser`;
const config = {
headers: {
"Content-type": "application/json",
},
};
const { data } = await axios.post(
url,
{
email,
password,
},
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
Error in frontend display
First you need to send an error message from your Back End like so:
export const loginUser = asyncHandler(async(req, res) => {
const { email, password } = req.body;
const user = await userModel.findOne({ email });
const token = generateToken(user._id)
if(user && (await user.matchPassword(password))){
res.json({
_id:user._id,
username: user.username,
email: user.email,
profilePic:user.profilePic,
admin:user.admin,
token:token,
});
} else {
res.status(400).json({errorMessage :"invalid email or password please check and try again!" })
}
});
Then get it in the React part (make sure to read the comment I added after that console.log):
export const login = (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const url = `${process.env.REACT_APP_SERVER_URL}api/loginuser`;
const config = {
headers: {
"Content-type": "application/json",
},
};
const { data } = await axios.post(
url,
{
email,
password,
},
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
console.log(error); // look at the console, as I may miss the correct retuned error object
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response.data.errorMessage
});
}
};
you need to return response instead of throwing the error:
res.status(400)
res.json({
message: "invalid email or password please check and try again!"
});

Resources