I'm student in web development. Currently, I'm trying to build a basic project, where I'm stack in implementing reset password feature, I really need help in how fetching reset password API in front-end using Axios. In short, the reset password API that I implemented works fine on Postman, but whenever I tried to pass in front-end and fetch the API in order to enable users to enter their new password and passwordValidation I kinda lost, below I share my code snippets:
backend code reset password
resetPassword = async(req, res) => {
try {
// Step 1: Get user based on the token
const validateHashedToken = crypto
.createHash('sha256')
.update(req.params.token)
.digest('hex');
const user = await User.findOne(
{
passwordResetToken: validateHashedToken,
passwordResetExpires: { $gt: Date.now() }
});
user.password = req.body.password;
user.passwordValidation = req.body.passwordValidation;
user.passwordResetToken = undefined;
user.passwordResetExpires = undefined;
await user.save();
// Step 3: Update the "passwordChangedAt" date
// Step 4: Log the user in and send a JWT
genResJWT(user, 200, res);
} catch (error) {
console.log('error', error)
}
};
Routes:
router
.route('/api/v1/users/resetpassword/:token')
.get(viewsController.getResetPasswordUrl)
.patch(viewsController.resetpassword);
controllers
exports.getResetPasswordUrl = async(req, res) => {
try {
const { token } = req.params.token;
const validToken = await User.findOne(
{
passwordResetToken: token
}
);
res.status(200).render('resetPassword',
{
title: 'resetpassword',
token: validToken
});
} catch (error) {
console.log(error);
}
};
exports.resetpassword = (req, res) => {
// I'm stack here and I really need help
res.status(200).render('profile', {
title: 'reset password successfuly'
});
};
front-end fetching api code:
import axios from 'axios';
export const resetPassword = async (password, passwordValidation) => {
try {
const res = await axios({
method: 'PATCH',
url:
`http://127.0.0.1:3000/api/v1/users/resetpassword/:token`,
data: {
password,
passwordValidation
}
});
if (res.data.status === 'success') {
window.setTimeout(() => {
location.replace('/me');
}, 500);
}
} catch (error) {
console.log('error', error.response.data.message);
}
};
On the front end, you are making a request to http://127.0.0.1:3000/api/v1/users/resetpassword/:token. Since token is a route parameter, you are directly passing in the string ":token" and not the actual value of the token.
Try this instead:
const res = await axios({
method: 'PATCH',
url:
`http://127.0.0.1:3000/api/v1/users/resetpassword/${token}`,
data: {
password,
passwordValidation
}
});
where token is a variable you need to define.
Assuming that you are using express, here is some documentation about parameter routing: https://expressjs.com/en/guide/routing.html#route-parameters
I fixed my issue with the following steps:
1- Use only GET request in my '/resetpassword/:token' route and submit the PATCH request with Axios.
2- Pass the 'token' along with the 'password' and the 'passwordValidation' as input data in the PATCH request.
3- create a hidden input within the 'resetPassword' form in order to submit the 'token' with the password and the 'passwordValidation' whenever users confirm their updated password.
Below is my code snippet in order to explain how goes the solution:
Routes:
router.get(
'/resetpassword/:token',
viewsController.resetPassword
)
controllers
exports.resetPassword = (req, res) => {
const token = req.params.token;
res.status(200).render('/login', {
title: 'reset password successfuly', { token }
});
};
front-end fetching API code:
import axios from 'axios';
export const resetPassword = async (password, passwordValidation, token) => {
try {
const res = await axios({
method: 'PATCH',
url:
`/api/v1/users/resetpassword/${token}`,
data: {
password,
passwordValidation
}
});
if (res.data.status === 'success') {
window.setTimeout(() => {
location.assign('/login');
}, 1000);
}
} catch (error) {
console.log('error', error.response.data.message);
}
};
the resetPassword form:
extends goaheadtravel
block content
main.main
.resetpassword-form
h2.heading-secondary.ma-bt-lg Please enter a new password and validate it
form.form.resetpassword--form
.form__group.ma-bt-md
label.form__label(for='password') Password
input#password.form__input(type='password' placeholder='••••••••' required='' minlength='8')
.form__group.ma-bt-md
label.form__label(for='passwordValidation') Confirm password
input#passwordValidation.form__input(type='password' placeholder='••••••••' required='' minlength='8')
input#resetToken(type='hidden' value=`${token}`)
.form__group.right
button.btn.btn--green Confirm new password
Hope that my solution will help other developers!
Related
I am trying to verify user emails with JWT. My current set up is that a JWT is sent to a user when they try to log in if they do not have a confirmed email.
When the email is sent it composes a URL with the token and then sends the request to the server to verify the email. It worked great in postman as I could easily add the email that I want to verify in the body. But I can't think of a way how to do it in the browser.
This is the code that should verify the email.
confirmEmail = async (req, res, next) => {
const { email } = req.body
const param = req.params.token
const user = await userModel.findOne({email})
if(!user)
{
throw new HttpException(401, 'User not found')
}
if(user.confirmed)
{
throw new HttpException(401, 'User already confirmed')
}
if(!user.confirmed)
{
const confirmJWT = jwt.verify(param, process.env.SECRET_JWT)
if(!confirmJWT)
{
throw new HttpException(200, 'Token invalid')
}
const result = await userModel.emailConfirmed(email)
}
res.send('Database updated.')
}
This is the code that generates the JWT and sends it in an email.
if(!user.confirmed)
{
const emailToken = jwt.sign(
{
email: user.email
},
process.env.SECRET_JWT,
{
expiresIn: '15m'
}
)
console.log(emailToken)
emailModel.verifyEmail(email, emailToken)
throw new HttpException(401, 'Email not confirmed')
}
I was wondering if there is any way I can use the just the token to find the email of the user or is that not possible with JWT?
export const verifyEmail = () => {
try
{
return API()
.post(`/api/confirm/:token`, {}, {
params: {
token: store.user.authToken
},
email: store.user.email
})
.then(({data: userData}) => {
console.log('worked')
})
}
catch(error)
{
console.log(error)
}
}
import { verifyEmail } from '../../services/authAPI'
import { useUserStore } from '../../stores/user'
const store = useUserStore()
export default {
data()
{
return {
email: store.user.email
}
},
methods: {
async handleSubmit()
{
try
{
const response = await verifyEmail(this.email)
}
catch(err)
{
console.log(err)
}
}
}
}
</script>
Basically you do not need to send the email in the body as already encoded the email into the JWT. Once you do const verifiedToken = jwt.sign(token, secret key) You can do verifiedToken.email to grab the email.
I am working on login functionality in my project, now, flow looks like this (from front-end to back-end):
async login() {
await login({
password: this.userPassword,
login: this.userLogin,
twoFactor: this.twoFactor
}).then((res) => {
if (res.error) {
//
} else {
console.log(res)
}
})
}
And here is starts problems, as you can see if something goes wrong, I return status code 401 and some error message. When I login with correct data, there is no problem with getting token, but when I provide wrong data I have external pending login endpoint in development tools in browser and then, after some time, Error: Request failed with status code 401 in front end terminal. Without this status(401) with just JSON it works fine, but when I try to add 401 code, application crashes.
const userService = require('./../services/userService')
const crypto = require('./../services/cryptoService')
const jwt = require('./../services/jwtService')
const twoFactorService = require('node-2fa')
module.exports = {
login: async (req, res) => {
let { login, password, twoFactor } = req.body
password = crypto.encrypt(password, process.env.APP_KEY)
const result = await userService.getUserToLogin(login, password)
if (!result) {
res.status(401).json({
error: 'Unauthorized'
})
} else {
const faCode = result.twofatoken
const result2F = twoFactorService.verifyToken(faCode, twoFactor);
if ( !result2F || result2F.delta !== 0 ) {
res.status(401).json({
error: 'Unauthorized'
})
} else {
const userId = crypto.encrypt(result.id, process.env.CRYPTO_KEY)
const token = await jwt.sign({
uxd: userId,
});
res.json(token);
}
}
}
}
Actually, I have no idea on what to do with that and how to handle this error.
Ok, here is the answer. Actually, you just need to handle this error in your router:
router.post('/login', async (req, res) => {
try {
const data = await api.post('/login', req.body)
res.json(data.data)
} catch (e) {
// Probably you have here just console.log(e), but this way, you can handle it
res.status(e.response.status).json(e.response.data)
}
})
I am working on an Application which i have also deployed in heroku. The issue is that when I login in using heroku, user is nested inside a data object. but when I work locally or use postman, user isnt nested.
Help Please.
I get this response on the deployed version.
data: {
user: {
email: "my_email"
name: "my_name"
role: "user"
_id: "6205807deeadcfa734f954f3".
}
status: "success"
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyMDU4MDdkZWVhZGNmYTczNGY5NTRmMyIsImlhdCI6MTY0NDg0NTYyMCwiZXhwIjoxNjQ1NDUwNDIwfQ.YeWFNrN8rsLPJvvU8JQDwBVG4aBqqEuo7ssgLrR3O8M"
But when I log in locally, I get the response as
user: {
email: "my_email"
name: "my_name"
role: "user"
_id: "6205807deeadcfa734f954f3".
}
status: "success"
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyMDU4MDdkZWVhZGNmYTczNGY5NTRmMyIsImlhdCI
For Heroku, the USER is nested inside data but for local host and postman, the user isnt nested.
My codes are:
exports.login = catchAsync(async (req, res, next) => {
const { email, password } = req.body
if (!email || !password) {
return next(new AppError('Please provide email and password!', 400))
}
const user = await User.findOne({ email }).select('+password')
if (!user || !(await user.comparePassword(password, user.password))) {
return next(new AppError('Incorrect email or password', 401))
}
createSendToken(user, 200, req, res)
})
These are my api codes
const createSendToken = (user, statusCode, req, res) => {
const token = signToken(user._id)
res.cookie('jwt', token, {
expires: new Date(
Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000
),
httpOnly: true,
})
user.password = undefined
res.status(statusCode).json({
status: 'success',
token,
user,
})
}
For my react, The function code is:
function request(path, { data = null, token = null, method = 'GET' }) {
return (
fetch(`${process.env.REACT_APP_API}${path}`, {
method,
headers: {
Authorization: token ? `Bearer ${token}` : '',
'Content-Type': 'application/json',
},
body:
method !== 'GET' && method !== 'DELETE' ? JSON.stringify(data) : null,
})
.then((response) => {
// If Successful
if (response.ok) {
if (method === 'DELETE') {
// If delete, nothing returned
return true
}
return response.json()
}
// If errors
return response
.json()
.then((json) => {
// Handle Json Error response from server
if (response.status === 400) {
const errors = Object.keys(json).map(
(k) => `${json[k].join(' ')}`
)
throw new Error(errors.join(' '))
}
throw new Error(JSON.stringify(json))
})
.catch((e) => {
if (e.name === 'SyntaxError') {
throw new Error(response.statusText)
}
throw new Error(e)
})
})
.catch((e) => {
// Handle all errors
toast(e.message, { type: 'error' })
})
)
}
The main sign in function
export function signIn(email, password) {
return request('/api/v1/auth/login', {
data: { email, password },
method: 'POST',
})
}
Then I import this into my auth context and execute it there
import {signIn as signInApi} from '../apis'
const AuthContext = createContext()
export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem('token'))
const [user, setUser] = useState(
JSON.parse(localStorage.getItem('user'))
)
const [loading, setLoading] = useState(false)
const signIn = async (email, password, callback) => {
setLoading(true)
const res = await signInApi(email, password)
if (res.token) {
localStorage.setItem('token', res.token)
localStorage.setItem('user', JSON.stringify(res.user)) // This stores the user in localhost but returns undefined for user in the one deployed to heroku. I have to use
localStorage.setItem('user', JSON.stringify(res.data.user)) which now works on the deployed one but not on the local one
setToken(res.token)
setUser(res.user)
callback()
}
setLoading(false)
}
}
it seems the deployed version is using built in implementaion of createSendToken and not the one you provided. need to check your project structure.
in order to validate this change the function name and the call createSendToken to something else and you will find the issue
I have a site where you can register as user and log in. Now i am trying to make a profile page where you can change your username, email and password.
I can only get the data from the first user from the table of users in database. How to get user that is currently logged in?
Here i create a user and save to db. This works fine:
router.post("/users", async (req, res) => {
const user = new User(req.body)
try {
await user.save()
const token = await user.generateAuthToken()
console.log(user, token)
res.status(201).redirect("/")
} catch (e) {
res.status(400).send(e)
}
})
Then i log that user in. That also work as intended.
router.post("/users/login", async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
const token = await user.generateAuthToken()
console.log(user, token)
res.cookie('auth_token', token, { maxAge: 21600000 })
res.redirect("/frontpage")
} catch (e) {
res.status(400).send(`<div style="background-color: red; text-align: center"><h1> Wrong credentials - Did you register...?</h1> Try again please </h2> Register </h2><p> ${e} </p></div>`)
}
})
Then i try to get the data using this route and AJAX. Obviously this only get gets the first entry in the database. I tried different things with findById, but coudnt retrieve any data. What am i doing wrong?
router.get("/users/me", async (req, res) => {
try {
const user = await User.findOne()
if (!user) {
return res.status(404).send()
}
console.log(user.id, user.name, user.email)
res.send(user)
} catch (e) {
res.status(500).send()
}
AJAX call:
(async function getProfile() {
try {
$.ajax({
method: "GET",
url: "/users/me",
dataType: "json"
}).done(function (user) {
$("#addData")
.append($("<tr>"))
.append($("<input>").val(user.name))
.append($("<tr>"))
.append($("<input>").val(user.email))
.append($("<tr>"))
.append($("<input>").val(user.password))
;
})
} catch (error) {
console.log(error);
}
})();
It looks like this
And here is thehtml:
<div class="centered">
<h2>Profile page</h2>
<form action="/users/me" method="GET">
<table id="profile">
<thead></thead>
<tbody id="addData">
</tbody>
</table>
<button type="submit" class="btn btn-primary">Update account</button>
<a href="/login"><button class="btn btn-danger" id="btn-profile" onclick="deleteUser()">Delete
account</button></a>
</form>
Then i want to be able to edit name and email and save it again. Here is the post route:
router.post("/users/me", async (req, res) => {
const updates = Object.keys(req.body)
console.log("Updates1" + updates)
const allowedUpdates = ["name", "email", "password", "age"]
const isValidOperation = updates.every((update) => allowedUpdates.includes(update))
if (!isValidOperation) {
return res.status(400).send({ error: "Invalid updates!" })
}
try {
updates.forEach((update) => req.user[update] = req.body[update])
await req.user.save()
console.log(updates)
res.send(req.user)
res.redirect("/profile");
} catch (e) {
res.status(400).send(e)
}
})
And the AJAX call to the route:
(async function editProfile() {
try {
$.ajax({
method: "POST",
url: "/users/me",
dataType: "json"
}).done(function (user) {
$("#addData")
.append($("<tr>"))
.append($("<input>").val(user.name))
.append($("<tr>"))
.append($("<input>").val(user.email))
.append($("<tr>"))
.append($("<input>").val(user.password))
;
})
} catch (error) {
console.log(error);
}
})();
When i click the update btn i get a page with the unedited json, like this:
Any help would be much appreciated
I believe you are looking for express-session which would allow you to give the client a cookie as a key to an object with variables on your server. This way, as they are logged in, you can have access to their _id as it occurs in your database, and allow them to edit their specific document.
I'm working on a MEAN Stack application. I have made authentication and authorization using jWt and it works like a charm.
The problem that I'm facing is how to get the user data in the Profile page component. I'm thinking about several options:
First, sending the email from the Login component to the Dashboard and then pass it to Profile. It will be easy from then to send a get request to get the user with the email.
Second, I don't know if it possible but I'm thinking of using the jwt I'm returning to the user to get his data since I created it with the provided email in Login
This is how I created the jwt token:
login: async (data, model) => {
try {
/**
* Fetch the admin from the Database
*/
const adminData = await baseRepository.findOne({ email: data.email }, model);
/**
* Check if an admin with that email exists
*/
if (!adminData) {
return (400, { message: "ADMIN NOT FOUND" })
} else {
/**
* Compare the input password with the hashed password in the database
*/
const admin = { email: adminData.email }
if (await bcrypt.compare(data.password, adminData.password)) {
/**
* Create a jwt Token and send it back to the client
*/
const accessToken = jwt.sign(admin, process.env.ACCESS_TOKEN_SECRET)
return ({ status: 200, accessToken: accessToken })
}
return ({ status: 401, accessToken: null })
}
}
catch (err) {
throw err
}
}
That's the method from the repository that I'm using to handle the request in the controller this way:
login: async (req, res) => {
try {
console.log("yo")
const { status, accessToken } = await authRepository.login(req.body, Admin)
if (status == 400) {
res.status(400).json({ message: "ADMIN NOT FOUND" })
} else if (status == 401) {
res.status(401).json({ message: "WRONG PASSWORD" })
}
res.status(200).json({ accessToken: accessToken })
}
catch (e) {
res.status(400).send({ error: e })
}
}
And these are the libraries I'm using:
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
The response of the login service need to be the profile info