I am trying to write a test for sending a 404 error whenever a blog is posted without a token and I keep getting "TypeError: Cannot read properties of undefined (reading '_id')". I know this is because my user variable is null which is due to there being no token sent with the request. Shouldn't the code stop reading after the response is sent? Or is there another way to counteract this? User will only be defined when a token is sent with the request. I can sort of solve this by using if/else statements to only use code if the user var is defined but is there a more effective way? Any help would be greatly appreciated.
const blogRouter = require('express').Router();
const req = require('express/lib/request');
const Blog = require('../models/blog')
const User = require('../models/user')
const jwt = require('jsonwebtoken');
const { default: mongoose } = require('mongoose');
const { decode } = require('jsonwebtoken');
require('dotenv').config()
blogRouter.post('/', async (request, response) => {
console.log("Request body of blog is",request.body)
var user = request.user
console.log("User is", user)
try{
const decodedToken = jwt.verify(request.token, process.env.SECRET)
console.log("decodedToken is", decodedToken)
}
catch(err){
if(!request.token || !decodedToken.id){
console.log("got in the catch block")
response.status(401).json({error: "token missing or invalid"})
}
else {
console.log(err)
}
}
const body = request.body
const newBlog = new Blog({
title: body.title,
author:body.author,
url: body.url,
likes: body.likes,
user: user._id // Error here because user is null
})
console.log("newBlog is",newBlog)
if (newBlog.title == null && newBlog.url == null){
response.status(400)
response.end()
}
else{
newBlog.save()
user.blog = user.blog.concat(newBlog._id)
user.save()
.then(result => {
console.log("Saved to database!!!!!")
response.status(201).json(result)
})
.catch(error => {
console.log("Could not save to database")
})
}
})
module.exports = blogRouter
Related
In my backend is something happening, which I can't understand. If I'm registering a new User, it's working fine, and I can see the new User in my JSON File, but if I'm doing a put request after that to change my own user's data he deletes the new User which I made before?
My put request from my frontend:
//Changing user Data
export async function changeData(id, body) {
try {
await axios.put(`http://localhost:8000/users/${id}`, body, {
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem('auth._token.local')
}
});
return true;
}
catch (e) {
return false;
}
}
My endpoint in my node backend for registering a user and changing data of a user
// Register New User
server.post('/register', (req, res) => {
console.log("register with request body", req.body)
const {username, password, firstname, lastname, roles} = req.body
if(!username || !password || !firstname || !lastname || !roles) {
const status = 400
const message = "Bad Request, make sure all properties are set in request body"
res.status(status).json({status, message})
return
}
if (req.headers.authorization === undefined || req.headers.authorization.split(' ')[0] !== 'Bearer') {
const status = 401
const message = 'Error in authorization format'
res.status(status).json({status, message})
return
}
// Send only token part to admin check
if(!isAdmin(req.headers.authorization.split(' ')[1])) {
const status = 401
const message = 'Only permitted by admin'
res.status(status).json({status, message})
return
}
if(isAuthenticated({username, password}) === true) {
const status = 401
const message = 'Email and Password already exist'
res.status(status).json({status, message})
return
}
fs.readFile("./users.json", (err, file) => {
if (err) {
const status = 401
const message = err
res.status(status).json({status, message})
return
}
// Get current users data
const data = JSON.parse(file.toString())
// Get the id of last user
const last_item_id = data.users[data.users.length - 1].id
//Add new user
data.users.push({id: last_item_id + 1, username: username, password: password, firstname: firstname, lastname: lastname, roles: roles}) //add some data
const writeData = fs.writeFile("./users.json", JSON.stringify(data), (err, result) => { // WRITE
if (err) {
const status = 401
const message = err
res.status(status).json({status, message})
}
})
})
res.status(201).json({status: 201, message: "Successfully created"})
})
// handle changing user data
server.use((req, res, next) => {
console.log('Entering Users')
if(req.method === 'PUT' && req.url.includes("/users")) {
if(req.body) {
const decodedToken = jwt.decode(req.headers.authorization.split(' ')[1])
const userList = JSON.parse(fs.readFileSync('./users.json', 'UTF-8'))
const userinfo = userList.users.find((user) => user.id === decodedToken.id)
if(!req.body.password) {
req.body.password = userinfo.password
}
// if admin made the request, he should be able to change roles
if(req.body.roles && decodedToken.roles && decodedToken.roles.includes("admin")) {
console.log("Able to change");
next()
return
}
req.body.roles = decodedToken.roles
} else {
res.status(400).json(
{
status: 400,
message: "Bad request, make sure all properties are set in request body"
}
)
return
}
}
next()
})
The only thing i noticed is that after the register comes, the JSON file gets to a one-liner, but I don't think that this is the problem. It seems like the put works with an old user List? I'm not sure. Thanks in forward.
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'm new to React and building a MERN stack. I'm having issues passing a variable from my front end to my back end. I've tried using console logs to debug and I can see that my request body is coming up blank. I've spent hours trying to figure out what I'm doing wrong but I haven't had any breakthrough yet.
Please see my code below.
User Frontend Hook
const fetchUser = (dispatch) => {
return async () => {
const email = await AsyncStorage.getItem("email");
console.log("async email:", email);
try {
console.log("sending email:", email);
const userInfo = await trackerApi.get("/users", {email});
dispatch({ type: "fetch_users", payload: userInfo.data });
} catch (err) {
console.log(err);
}
};
};
Express/Axios Backend
router.get("/users", async (req, res) => {
console.log("Request Body:", req.body);
try {
const { email } = req.body;
// console.log("Email for req: ", email);
const user = await User.find({ email: email });
console.log("Users for req: ", user);
res.send(user);
} catch (err) {
console.log(err);
}
});
The issue is related to the HTTP method, your route/API is GET call and get method does not have the body, either update to post or use req.query.
Client
const userInfo = await trackerApi.post("/users", {email});
// OR
const userInfo = await trackerApi.post("/users", { data: {email});
Server
router.post("/users", async (req, res) => {
I'm currently working on a Google Sign-in Auth app with a React frontend and an Express backend and I'm currently stuck in the part of the process where I'm validating tokens on the backend. The docs for this process show this code to validate the token:
const {OAuth2Client} = require('google-auth-library');
...
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
//const domain = payload['hd'];
}
verify().catch(console.error);
I've implemented this code in my own project here:
//verify token
async function verify(token, client) {
const ticket = await client.verifyIdToken({
idToken: token,
audience: keys.google.clientID,
});
const payload = ticket.getPayload();
const userid = payload['sub'];
const domain = payload['hd'];
const email = payload['email']
console.log('User ID: ' + userid);
console.log('Domian: ' + domain);
console.log('Email: ' + email);
var message = '';
var cookie = {};
await User.find({email: email}, (error, user) => {
if(error) {
message = error;
} else if (user.length === 0) {
message = 'this user is not in the database';
} else {
message = 'this user is in the database';
const session = new Session({
email: email,
session_token: token
});
cookie = {
email: email,
session_token: token
};
session.save((error, session) => {
if (error) {
console.log(error);
} else {
console.log('session saved');
}
});
console.log(message);
}
});
return Promise.resolve(cookie);
}
//recieve token id from frontend, verify it, and send session back in response
router.post('/google', (req, res) => {
const body = req.body.tokenID;
const client = new OAuth2Client(keys.google.clientID);
let cookie = verify(body, client).catch(console.error);
console.log('Cookie:' + cookie);
return res.send(cookie);
});
Currently when this runs everything inside the async function executes, but the return statement only returns the empty promise object. I think I'm making a mistake using async and await incorrectly, but I don't know how to correctly get the function to wait for all the work verifying the token and then update the DB before returning.
Not sure if this will help, but when I call the route my console gives me this output:
(I took out my personal info from the output fields, but assume these lines actually have gmail account info)
...
Cookie:[object Promise]
User ID: <GOOGLE ID>
Domian: <DOMAIN>
Email: <USER EMAIL>
this user is in the database
session saved
Thanks for reading!
Since "verify" function is an async function, you should add "await" before calling it. For catching errors you can simply place it in a try/catch:
router.post('/google', async (req, res) => {
const body = req.body.tokenID;
const client = new OAuth2Client(keys.google.clientID);
try {
let cookie = await verify(body, client);
console.log('Cookie:' + cookie);
return res.send(cookie);
} catch(error) {
// handling error
console.log(error);
return res.send("error")
}
});
`
You're mixing async/await with callback based calls. I don't know the internals of the library you're using, but the pattern should look more like this:
var cookie = {};
try{
const user = await User.find({email: email});
if (user.length === 0) {
console.log('this user is not in the database');
}
else {
console.log('this user is in the database');
const session = new Session({
email: email,
session_token: token
});
try{
await session.save();
console.log('session saved');
} catch(err){
console.log(err);
}
return {
email: email,
session_token: token
};
} catch(error){
console.log(error);
}
I'm trying to get a single email from an Office 365 Mailbox.
I'm sending the email id to my app via a POST (req.body.id) and then calling this code in order to get some email properties:
router.post('/id', async function(req, res, next) {
console.log("email with ID -> ", req.body.id)
let parms = { title: 'Inbox', active: { inbox: true } };
const accessToken = await authHelper.getAccessToken(req.cookies, res);
const userName = req.cookies.graph_user_name;
if (accessToken && userName) {
parms.user = userName;
// Initialize Graph client
const client = graph.Client.init({
authProvider: (done) => {
done(null, accessToken);
}
});
try {
const result = await client
.api('/me/messages/', req.body.id)
.select('id,subject,from,toRecipients,ccRecipients,body,sentDateTime,receivedDateTime')
.get();
parms.messages = result.value;
console.log("email -> ", result.value);
res.render('message', parms);
} catch (err) {
parms.message = 'Error retrieving messages';
parms.error = { status: `${err.code}: ${err.message}` };
parms.debug = JSON.stringify(err.body, null, 2);
res.render('error', parms);
}
} else {
// Redirect to home
res.redirect('/');
}
});
At the moment, result.value contains all of the messages in the mailbox instead of just the message with provided id.
Could someone tell me where my error is, please?
The api method has a single path parameter. Calling it like .api('/me/messages/', req.body.id) is effectivly sending it a path ("/me/messages/") along with an additional parameter it ignores.
You need to send it a single string so you'll need to append the req.body.id to the path ({path} + {id}):
const result = await client
.api('/me/messages/' + req.body.id)
.select('id,subject,from,toRecipients,ccRecipients,body,sentDateTime,receivedDateTime')
.get();