JWT Authentication error on Updating an item in MERN stack - javascript

Can't update/edit. It's a basic MERN CRUD app with JWT, RTK, and MUI. When I try, I get
PUT http://localhost:3000/api/purchases/[object%20Object] 401 (Unauthorized) xhr.js:210
in Chrome Dev Tools. Also I'm getting 2 error messages saying Not Authorized tableForm.jsx:29 and Not Authorized Table.jsx:17
Those messages are originating from a console.log in my auth middleware, but only happen when updating. Get, Create, and Delete all work fine.
const protect = asyncHandler(async (req, res, next) => {
let token
if(req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
try{
//Get token from header
token = req.headers.authorization.split(' ')[1]
//Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET)
//Get user from the token
req.user = await User.findById(decoded.id).select('-password')
next()
} catch (error) {
console.log(error)
res.status(401)
throw new Error('Not authorizecd')
}
}
if (!token) {
res.status(401)
throw new Error('Not authorized, no token')
}
} )
In Table and tableForm, I also have
useEffect(() => {
if (isError) {
console.log(message)
}
Maybe my Slice or Controller is wrong, but I've checked similar questions and projects on here and GitHub, so idk. Here, they are:
//Update Purchase
export const updatePurchase = createAsyncThunk(
'purchases/update',
async (purchaseData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token
return await purchaseService.updatePurchase(purchaseData, token)
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString()
return thunkAPI.rejectWithValue(message)
}
})
//extra reducers part
.addCase(updatePurchase.pending, (state) => {
state.isLoading = true
})
.addCase(updatePurchase.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.purchases = state.purchases.map((purchase) => purchase._id === action.payload._id ? action.payload : purchase)
})
.addCase(updatePurchase.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
//"Service" part
const updatePurchase = async (purchaseId, purchaseData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`
}
}
const response = await axios.put(API_URL + purchaseId, purchaseData, config)
return response.data
}
//and the Controller
const updatePurchase = asyncHandler(async (req, res) => {
const purchase = await Purchase.findById(req.params.id)
if (!purchase){
res.status(400)
throw new Error('Purchase not found')
}
//const user = await User.findById(req.user.id)
//Check for user
if(!req.user) {
res.status(401)
throw new Error('User not found')
}
//Make sure the user matches the purchases
if (purchase.user.toString() !==req.user.id) {
res.status(401)
throw new Error('User not authorized')
}
const updatedPurchase = await Purchase.findByIdAndUpdate(req.params.id, req.body, {
new: true,
})
res.status(200).json(updatedPurchase)
})
Any and all help would be appreciated. I've tried everything and have been working on this for days, but I'm kind of new so I'm sure I'm missing something obvious.
Here is the git repo if needed.
https://github.com/LazAustin/final_license_manager.git
Here is the front end where updatePurchase is used. I'm not sure what to pass anymore. The ID and/or the whole Purchase? Either way I've tried everything.
function EditForm({purchase}) {
const [title, setTitle] = useState(purchase.title);
const [producer, setProducer] = useState(purchase.producer);
... //shortened for reading purposes
const [notes, setNotes] = useState(purchase.notes);
const dispatch = useDispatch()
const onSubmit = (e) => {
e.preventDefault()
const purchaseData = {
title,
producer
... //just shortening for reading purposes
notes,
id: purchase._id
}
dispatch(updatePurchase(purchaseData)) // <-tried different combinations of id and purchase on front and back end.
//I think its supposed to be updatePurchase(purchase._id, purchaseData) and same on the slice but that didnt work either
}

Nvm! Figured it out, just in case anyone else rolls on this question. Forgot my id was set to "purchaseId" in the Service. Changed my service back to what I originally had it as (below). Then below that is my new front end with purchaseId: purchase._id
const updatePurchase = async (purchaseData, token) => {
const { purchaseId, ...body } = purchaseData;
const config = {
headers: {
Authorization: `Bearer ${token}`
}
}
const response = await axios.put(API_URL + purchaseId, body, config)
return response.data
}
const onSubmit = (e) => {
e.preventDefault();
const purchaseData = ({
title,
...,
notes,
purchaseId: purchase._id
})
dispatch(updatePurchase(purchaseData))
}

Related

sending push notification firebase react-js

I was working on a project using Firebase cloud messaging react. I was sending this to my server, but it doesn't work. Surely I have tried, but I don't know what's wrong again.
Below is the code.
Here it sends a POST request to Firebase, and it should send a notification to the user.
async function sendNotification(id, userMessage) {
const headers = {
'Authorization': `key=${code}`,
'Content-Type': 'application/json'
}
const message = {
'to': `${id}`,
'content_available': true,
'apns_priority': 5,
'notification': {
body: `${userMessage}`
},
const url = 'https://fcm.googleapis.com/fcm/send'
//console.log(code)
await axios.post(url, message, {
headers: headers
})
}
const sendMessageToServer = async (e) => {
//e.preventDefault();
toggle()
const docRe = doc(database, "help", mailer);
const data = {
email: user.email,
user: newMessage,
}
//console.log(data, 'not clear')
setNewMessage('')
//console.log(data, newMessage, 'cleared')
setShow(false)
if(newMessage === '') {
}
else {
const docRef = doc(database, "users", mailer);
await updateDoc(docRe, {
msg: arrayUnion(data)
})
.then(() => {
async function p() {
const id = await getDoc(docRef)
//console.log(id.data())
sendNotification(id.data().notice, `Admin : ${data.user}`)
}
p()
})
}
Sometimes it sends to my localhost because I tested there, but it doesn't work on my Netlify app. Secondly, I noticed that it keeps generating the same token for each user, but that's not the issue, but if you can help in both I would be grateful.
export default function Dashboard() {
async function callToken() {
await getToken(messaging, {vapidKey: process.env.NOTIFICATION})
.then((code) => {
//console.log(code)
async function docRef() {
const dc = doc(database, "users", auth.currentUser.email);
await updateDoc(dc, {
notice: code
});
}
docRef()
})
}
async function requestPermission() {
await Notification.requestPermission()
.then((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.')
callToken()
}
})
}
const goTo = useNavigate();
useEffect(() => {
onAuthStateChanged(auth, (data) => {
if(!data) {
goTo('/login')
}
else {
currentBalance();
requestPermission()
}
})
})
}
Please know I imported all required modules.

Why am I getting different response from my data when local and when on heroku?

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

Unsubscribe email using Fetch api Javascript

I have a form where i enter an email and it gets ''subscribed'' in a user.json file using a fetch api on node server.My task is to :
upon clicking on the "Unsubscribe" button, implement the functionality for unsubscribing from the community list. For that, make POST Ajax request using http://localhost:3000/unsubscribe endpoint.
I tried to make the function but it wasnt succeseful so i deleted it. Also,i need to do the following :
While the requests to http://localhost:3000/subscribe and
http://localhost:3000/unsubscribe endpoints are in progress, prevent
additional requests upon clicking on "Subscribe" and "Unsubscribe".
Also, disable them (use the disabled attribute) and style them using
opacity: 0.5.
For me ajax requests,fetch and javascript is something new,so i dont know really well how to do this task,if you could help me i'll be happy,thanks in advance.
fetch code for subscribing:
import { validateEmail } from './email-validator.js'
export const sendSubscribe = (emailInput) => {
const isValidEmail = validateEmail(emailInput)
if (isValidEmail === true) {
sendData(emailInput);
}
}
export const sendHttpRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data ? {
'Content-Type': 'application/json'
} : {}
}).then(response => {
if (response.status >= 400) {
return response.json().then(errResData => {
const error = new Error('Something went wrong!');
error.data = errResData;
throw error;
});
}
return response.json();
});
};
const sendData = (emailInput) => {
sendHttpRequest('POST', 'http://localhost:8080/subscribe', {
email: emailInput
}).then(responseData => {
return responseData
}).catch(err => {
console.log(err, err.data);
window.alert(err.data.error)
});
}
index.js from route node server:
const express = require('express');
const router = express.Router();
const FileStorage = require('../services/FileStorage');
/* POST /subscribe */
router.post('/subscribe', async function (req, res) {
try {
if (!req.body || !req.body.email) {
return res.status(400).json({ error: "Wrong payload" });
}
if (req.body.email === 'forbidden#gmail.com') {
return res.status(422).json({ error: "Email is already in use" });
}
const data = {email: req.body.email};
await FileStorage.writeFile('user.json', data);
await res.json({success: true})
} catch (e) {
console.log(e);
res.status(500).send('Internal error');
}
});
/* GET /unsubscribe */
router.post('/unsubscribe ', async function (req, res) {
try {
await FileStorage.deleteFile('user.json');
await FileStorage.writeFile('user-analytics.json', []);
await FileStorage.writeFile('performance-analytics.json', []);
await res.json({success: true})
} catch (e) {
console.log(e);
res.status(500).send('Internal error');
}
});
module.exports = router;
And user.json file looks like this :
{"email":"Email#gmail.com"}
This is my attempt for unsubscribing :
export const unsubscribeUser = () => {
try {
const response = fetch('http://localhost:8080/unsubscribe', {
method: "POST"
});
if (!response.ok) {
const message = 'Error with Status Code: ' + response.status;
throw new Error(message);
}
const data = response.json();
console.log(data);
} catch (error) {
console.log('Error: ' + error);
}
}
It gives the following errors:
Error: Error: Error with Status Code: undefined
main.js:2
main.js:2 POST http://localhost:8080/unsubscribe 404 (Not Found)
FileStorage.js:
const fs = require('fs');
const fsp = fs.promises;
class FileStorage {
static getRealPath(path) {
return `${global.appRoot}/storage/${path}`
}
static async checkFileExist(path, mode = fs.constants.F_OK) {
try {
await fsp.access(FileStorage.getRealPath(path), mode);
return true
} catch (e) {
return false
}
}
static async readFile(path) {
if (await FileStorage.checkFileExist(path)) {
return await fsp.readFile(FileStorage.getRealPath(path), 'utf-8');
} else {
throw new Error('File read error');
}
}
static async readJsonFile(path) {
const rawJson = await FileStorage.readFile(path);
try {
return JSON.parse(rawJson);
} catch (e) {
return {error: 'Non valid JSON in file content'};
}
}
static async writeFile(path, content) {
const preparedContent = typeof content !== 'string' && typeof content === 'object' ? JSON.stringify(content) : content;
return await fsp.writeFile(FileStorage.getRealPath(path), preparedContent);
}
static async deleteFile(path) {
if (!await FileStorage.checkFileExist(path, fs.constants.F_OK | fs.constants.W_OK)) {
return await fsp.unlink(FileStorage.getRealPath(path));
}
return true;
}
}
module.exports = FileStorage;
You should consider using a database for handling CRUD operations on your persisted data. If you must use filestorage, theres a flat file DB library called lowdb that can make working the files easier.
As for preventing duplicate requests, you can track if user has already made a request.
let fetchBtn = document.getElementById('fetch')
let isFetching = false
fetchBtn.addEventListener('click', handleClick)
async function handleClick(){
if (isFetching) return // do nothing if request already made
isFetching = true
disableBtn()
const response = await fetchMock()
isFetching = false
enableBtn()
}
function fetchMock(){
// const response = await fetch("https://example.com");
return new Promise(resolve => setTimeout (() => resolve('hello'), 2000))
}
function disableBtn(){
fetchBtn.setAttribute('disabled', 'disabled');
fetchBtn.style.opacity = "0.5"
}
function enableBtn(){
fetchBtn.removeAttribute('disabled');
fetchBtn.style.opacity = "1"
}
<button type="button" id="fetch">Fetch</button>

Why am I getting an error of Unexpected token < in JSON at position 0

I am creating a MERN app, I am new to this. I tried following some tutorials on the net but I have encountered some error.
I am submitting the post request like this. As you can see I am not specifying the content type because I know that if you are using the 'multipart/form-data it will automatically append it to the headers. I am using a react hook here which is why I am not directly using the fetch method.
const formData = new FormData();
formData.append("email", formState.inputs.email.value);
formData.append("name", formState.inputs.name.value);
formData.append("password", formState.inputs.password.value);
formData.append("image", formState.inputs.image.value);
const responseData = await sendRequest(
`${process.env.REACT_APP_BACKEND_BASE_URL}/api/users/signup`,
"POST",
formData
);
meanwhile my signup route is like this, UNDER user-routes.js
router.post(
"/signup",
fileUpload.single("image"),
[
check("name").not().isEmpty(),
check("email")
.normalizeEmail() // Test#test.com => test#test.com
.isEmail(),
check("password").isLength({ min: 6 }),
],
usersController.signup
);
as you can also see, I am catching it in the route by using the fileUpload.single("image")
if you need to see my hook that I am using here it is, but I am pretty sure that the hook works fine. and it has no issues whatsoever, so here it is: this is a react hook
export const useHttpClient = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
const activeHttpRequests = useRef([]);
const sendRequest = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const httpAbortCtrl = new AbortController();
activeHttpRequests.current.push(httpAbortCtrl);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: httpAbortCtrl.signal,
});
const responseData = await response.json();
// console.log("Response: ", response);
// console.log("Data: ", responseData);
activeHttpRequests.current = activeHttpRequests.current.filter(
(reqCtrl) => reqCtrl !== httpAbortCtrl
);
if (!response.ok) {
throw new Error(responseData.message);
}
setIsLoading(false);
return responseData;
} catch (err) {
setError(err.message);
setIsLoading(false);
throw err;
}
},
[]
);
const clearError = () => {
setError(null);
};
useEffect(() => {
return () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
activeHttpRequests.current.forEach((abortCtrl) => abortCtrl.abort());
};
}, []);
return { isLoading, error, sendRequest, clearError };
};
I will include the signup here from my users.controller:
const signup = async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(
new HttpError("Invalid inputs passed, please check your data.", 422)
);
}
const { name, email, password } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
const error = new HttpError(
"Signing up failed, please try again later.",
500
);
return next(error);
}
if (existingUser) {
const error = new HttpError(
"User exists already, please login instead.",
422
);
return next(error);
}
let hashedPassword;
try {
hashedPassword = await bcrypt.hash(password, 12);
} catch (err) {
const error = new HttpError(
"Could not create user, please try again.",
500
);
return next(error);
}
//res.json({ message: "AFTER HASHING" });
const createdUser = new User({
name,
email,
image: req.file.path,
password: hashedPassword,
places: [],
});
try {
await createdUser.save();
} catch (err) {
const error = new HttpError(
"Signing up failed, please try again later.",
500
);
return next(error);
}
let token;
try {
token = jwt.sign(
{ userId: createdUser.id, email: createdUser.email },
process.env.JWT_KEY,
{ expiresIn: "1h" }
);
} catch (err) {
const error = new HttpError(
"Signing up failed, please try again later.",
500
);
return next(error);
}
res
.status(201)
.json({ userId: createdUser.id, email: createdUser.email, token: token });
};
You may want to use JSON.Parse() around your responseData
When ever I run into this bug it is because I either,
parsed an already parsed object
Stingified a string
Used a string as an object
This link goes into more depth about the bug and common ways of dissecting the issue.
Try console.log() in your hook before you send the data and also log what the responseData looks like after it retrieves the data

firebase reauthentication flow with Token in react js app not working properly

after an hour the user get disconnected from the firebase functions and gets an error.
the connection to firebase in the app work like this:
After the first connection via google, the token is sent to firebase functions.
after that a getIdToken(ture) to force a refresh in useEffect.
the token is saved in the state via mobX and every time a commend requires to send or get data from the data base it's passes the token to the firebase functions
I have noticed that I don't get a new token in .then(function (idToken) {...}
this is the error :
FirebaseAuthError: Firebase ID token has expired.
Get a fresh ID token from your client app and try again (auth/id-token-expired).
See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
...
...
...
> errorInfo: {
> code: 'auth/id-token-expired',
> message: 'Firebase ID token has expired.
Get a fresh ID token from your client app and try again (auth/id-token-expired).
See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
> },
> codePrefix: 'auth'
> }
Things that I have tried already:
is to separate the firebase.auth().currentUser.getIdToken(true).then() to a different useEffect().
call getIdToken() after I get an error from the firebase functions.
const UserSubscriber = () => {
//using mobX here
const { user } = useStores();
const token = user.store.token;
React.useEffect(() => {
if (!token.length || !firebase.auth().currentUser) return;
firebase.auth().currentUser.getIdToken(true).then(function (idToken) {
const decodedToken = jwt.decode(idToken, '', true);
if (!decodedToken.user_id) return;
const unsub = firebase.firestore().collection('users').doc(decodedToken.user_id).onSnapshot(docSnapshot => {
const data = docSnapshot.data();
//user.mergeData() is just to store data
if (!data) return user.mergeData({ noUser: true, token: idToken })
user.mergeData({ ...data, noUser: false, token: idToken })
});
return () => unsub();
}).catch(function (error) {
user.logOut();
});
}, [token, user]);
return useObserver(() => (
<div />
));
}
and in the backend
app.use(async (req, res, next) => {
try {
const decodedToken = await admin.auth().verifyIdToken(req.body.token);
let uid = decodedToken.uid;
req.uid = uid;
return next();
} catch (error) {
console.log(error);
return res.status(401).send();
}
});
I have tried firebase.auth().onAuthStateChanged(userAuth => {...}) (in side of the useEffect())
firebase.auth().onAuthStateChanged(userAuth => {
userAuth.getIdToken().then(function (idToken) {
const decodedToken = jwt.decode(idToken, '', true);
if (!decodedToken.user_id) return;
const unsub = firebase.firestore().collection('users').doc(decodedToken.user_id).onSnapshot(docSnapshot => {
const data = docSnapshot.data();
if (!data) return user.mergeData({ noUser: true, token: idToken })
user.mergeData({ ...data, noUser: false, token: idToken })
});
return () => unsub();
}).catch(function (error) {
user.logOut();
});
})
;
}

Categories

Resources