I use VueJS (cli 3) & axios, and NodeJS - ExpressJS in the back-end. I am trying to secure my post user edit using CSRF token.
Vue View (edit user - focus to mySubmitEd):
<template>
<div class="one-user">
<h1>this user</h1>
<h2>{{name}} - {{surname}} - {{ perm }}</h2>
<h2>Edit</h2>
<input type="text" v-model="name">
<input type="text" v-model="surname">
<input type="text" v-model="perm">
<button #click="mySubmitEd">Edit</button>
<button #click="mySubmit">Delete</button>
</div>
</template>
<script>
import axios from 'axios'
import io from 'socket.io-client'
export default {
name: 'one-user',
data () {
return {
name: '',
surname: '',
perm: '',
csrf: '',
id: this.$route.params.id,
socket: io('localhost:7000')
}
},
mounted () {
axios.get('http://localhost:7000/api/get-user/' + this.id)
.then(res => {
const data = res.data.user
this.name = data.name
this.surname = data.last_name
this.perm = data.permalink
this.csrf = res.data.csrfToken
axios.defaults.headers.common['X-CSRF-TOKEN'] = this.csrf
})
.catch(error => console.log(error))
},
methods: {
mySubmit () {
const formData = {
_id: this.id
}
axios.post('http://localhost:7000/api/delete-user', formData)
.then(this.$router.push({ name: 'get-user' }))
.catch(error => console.log(error))
},
mySubmitEd () {
const formData = {
_id: this.id,
name: this.name,
last_name: this.surname,
permalink: this.perm,
_csrf: this.csrf
}
console.log(formData._csrf)
axios.post('http://localhost:7000/api/update-user', formData)
.catch(error => console.log(error))
}
}
}
</script>
server.js file:
...
const cookieParser = require('cookie-parser');
const csurf = require('csurf');
...
app.use(cookieParser());
const csrfProtection = csurf({ cookie: true });
app.use(csrfProtection);
...
back-end controller which get the user:
controller.getOneUser = function(req, res) {
User.findOne({ _id: req.params.userId }).exec(function(err, user) {
res.json({user, csrfToken: req.csrfToken()});
});
};
back-end update post:
controller.updateUser = function(req, res) {
User.findById(req.body._id, function(err, user) {
user.set({
name: req.body.name,
last_name: req.body.last_name,
permalink: req.body.permalink,
price: req.body.price
});
user.save();
res.send(user);
});
};
My errors in NodeJS-exress console:
ForbiddenError: invalid csrf token
My errors in browser:
POST http://localhost:7000/api/update-user 403 (Forbidden)
I don't know what is happened because I see in network tab(chrome) the csrf token is the same in the headers and what I send (ex.):
X-CSRF-TOKEN: PddyOZrf-AdHppP3lMuWA2n7AuD8QWFG3ta0
_csrf: "PddyOZrf-AdHppP3lMuWA2n7AuD8QWFG3ta0"
I don't know what I have miss here. I can't find where is the problem.
If you want more information please asked me to help you.
I had to pass in the headers the cookie correctly, so I did 2 corrections:
First in Vue view:
I passed credentials:
axios.create({withCredentials: true}).get(...)
and
axios.create({withCredentials: true}).post(...)
and secondly in server.js file before routes I put this:
...
const corsOptions = {
origin: 'http://localhost:8080',
credentials: true,
}
app.use(cors(corsOptions));
app.use(bodyParser.json());
app.use(cookieParser());
const csrfProtection = csurf({ cookie: true });
app.use(csrfProtection);
app.use(function (req, res, next) {
res.cookie('XSRF-TOKEN', req.csrfToken());
res.locals._csrf = req.csrfToken();
next();
});
...
Related
My database is connected, I am able to authenticate and log in, render the current stored list, when I click on edit one of them I get this error:
const castError = new CastError();
[1] ^
[1]
[1] CastError: Cast to ObjectId failed for value ":id" (type string) at path "_id" for model "Coffee"
Then I get signed out. Nothing gets saved and the connection to the database drops.
this is my coffeeRoutes.ts:
const router = Router();
router.get('/', getAllCoffee);
router.get('/:id', getOneCoffee);
router.post('/', addCoffee);
router.put('/:id', editCoffee);
router.delete('/:id', deleteCoffee);
export default router;
This is coffee.ts:
import { Document, Schema, Model, model } from 'mongoose';
interface ICoffee extends Document {
name: string;
description: string;
price: number;
}
const coffeeSchema: Schema = new Schema({
name: {
type: String,
required: true,
unique: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
}
});
const Coffee: Model<ICoffee> = model<ICoffee>('Coffee', coffeeSchema);
export { ICoffee, Coffee };
coffeeController.ts:
export const getAllCoffee: RequestHandler = async (req, res, next) => {
let coffeeList = await Coffee.find();
res.status(200).json(coffeeList);
}
export const getOneCoffee: RequestHandler = async (req, res, next) => {
let itemId = req.params.id;
let coffee = await Coffee.findById(itemId);
res.status(200).json(coffee);
}
export const addCoffee: RequestHandler = async (req, res, next) => {
let user: IUser | null = await verifyUser(req);
if (!user) {
return res.status(403).send();
}
const newCoffee: ICoffee = new Coffee({
name: req.body.name,
description: req.body.description,
price: req.body.price
});
try {
await newCoffee.save();
res.status(201).json(newCoffee);
}
catch (err) {
res.status(500).send(err);
}
}
export const editCoffee: RequestHandler = async (req, res, next) => {
let user: IUser | null = await verifyUser(req);
if (!user) {
return res.status(403).send();
}
let itemId = req.params.id;
const updatedCoffee: ICoffee = new Coffee({
_id: itemId,
name: req.body.name,
description: req.body.description,
price: req.body.price
});
await Coffee.findByIdAndUpdate(itemId, { $set: updatedCoffee })
res.status(200).json(updatedCoffee);
}
export const deleteCoffee: RequestHandler = async (req, res, next) => {
let user: IUser | null = await verifyUser(req);
if (!user) {
return res.status(403).send();
}
let itemId = req.params.id;
let result = await Coffee.findByIdAndDelete(itemId);
res.status(200).json(result);
}
App.ts:
const connectionString: string = 'mongodb://localhost:27017/testDB'
mongoose.connect(connectionString).then(
() => console.log('database connection successful!'),
err => console.log('Error connecting to the database', err));
const app = express();
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({extended: true}));
const cors = require('cors');
const corsOptions = {
origin: [ 'http://localhost:3001' ]
};
app.use(cors(corsOptions));
// routes
app.use('/api/coffee', coffeeRoutes);
app.use('/api/users', userRoutes);
app.use((req: Request, res: Response, next: NextFunction) => {
res.status(404).end();
});
app.listen(3000);
I appreciate any help! I've been playing with this for a few hours now, I'm newbie with fullstack so I can't understand what is happening.
This is what I get in my terminal:
[1] OPTIONS /api/coffee/:id 204 0.696 ms - 0
[1] {
[1] host: 'localhost:3000',
[1] connection: 'keep-alive',
[1] 'content-length': '424',
[1] accept: 'application/json, text/plain, */*',
[1] authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2MzE2YzBiNjQyMjYyNDM1YzdiYWZhOTgiLCJpYXQiOjE2NjI1MjE5NjksImV4cCI6MTY2MjUyNTU2OX0.fAusx0ov8IjLA10YXZqL-OljrtShkUjMIA7SveC357k',
[1] 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36',
[1] 'content-type': 'application/x-www-form-urlencoded',
[1] 'sec-gpc': '1',
[1] origin: 'http://localhost:3001',
[1] 'sec-fetch-site': 'same-site',
[1] 'sec-fetch-mode': 'cors',
[1] 'sec-fetch-dest': 'empty',
[1] referer: 'http://localhost:3001/',
[1] 'accept-encoding': 'gzip, deflate, br',
[1] 'accept-language': 'en-US,en;q=0.9'
[1] }
[1] /home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/query.js:4884
[1] const castError = new CastError();
[1] ^
[1]
[1] CastError: Cast to ObjectId failed for value ":id" (type string) at path "_id" for model "Coffee"
[1] at model.Query.exec (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/query.js:4884:21)
[1] at Query.then (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/query.js:4983:15)
[1] at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[1] messageFormat: undefined,
[1] stringValue: '":id"',
[1] kind: 'ObjectId',
[1] value: ':id',
[1] path: '_id',
[1] reason: BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer
[1] at new BSONTypeError (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/bson/lib/error.js:41:28)
[1] at new ObjectId (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/bson/lib/objectid.js:67:23)
[1] at castObjectId (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/cast/objectid.js:25:12)
[1] at ObjectId.cast (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schema/objectid.js:246:12)
[1] at SchemaType.applySetters (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schematype.js:1201:12)
[1] at SchemaType._castForQuery (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schematype.js:1648:15)
[1] at SchemaType.castForQuery (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schematype.js:1636:15)
[1] at SchemaType.castForQueryWrapper (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schematype.js:1612:20)
[1] at cast (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/cast.js:347:32)
[1] at Query.cast (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/query.js:5312:12),
[1] valueType: 'string'
[1] }
This is my editCoffee.js:
import React, { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import axios from 'axios';
export const EditCoffee = () => {
const [coffee, setCoffee] = useState({
name: "",
description: "",
price: ""
});
let params = useParams();
// let { editCoffee } = useContext(CoffeeProvider);
function editCoffee(coffee, id) {
let token = localStorage.getItem('myCoffeeToken')
let headers = {
Authorization: 'Bearer ' + token
};
return axios.put(baseUrl + id, coffee, { headers })
.then(response => {
getAllCoffee();
return new Promise(resolve => resolve(response.data));
}
);
}
function getAllCoffee() {
return axios.get(baseUrl).then(response => setCoffee(response.data))
};
const baseUrl = "http://localhost:3000/api/coffee/";
let navigate = useNavigate();
function handleChange(event) {
setCoffee((prevValue) => {
return { ...prevValue, [event.target.name]: event.target.value }
});
}
function handleSubmit(event) {
event.preventDefault();
editCoffee(editCoffee, params.id).then(() => {
navigate(`/coffee`);
}).catch(error => {
console.log(error);
navigate(`/signin`);
});
}
return (
<form onSubmit={handleSubmit}>
<h1>EDIT COFFEE</h1>
<span>Coffee Name </span>
<input placeholder="Enter coffee name" type="text" name="name" value={coffee.name} onChange={handleChange} />
<br></br><br></br>
<span>Description </span>
<input placeholder="Enter description" type="text" name="description" value={coffee.description} onChange={handleChange} />
<br></br><br></br>
<span>Price </span>
<input placeholder="Enter price" type="number" name="price" value={coffee.price} onChange={handleChange} />
<br></br><br></br>
<button type='submit'>Edit Coffee</button>
</form>
)
};
This is the page that takes me to editCoffee:
import React from 'react';
import CoffeeContext from '../contexts/CoffeeContext';
import { Link } from "react-router-dom";
function CoffeeList(props) {
return (
<CoffeeContext.Consumer>
{
({ coffee }) => {
return <div>
<h1>Coffee List</h1>
<Link to="/coffee/new">Add New Coffee</Link>
<div>
{coffee.map((c) => {
return (
<div key={c.id}>
<h2>{c.name} | ${c.price}</h2>
<p>{c.description}</p>
<Link to={`/edit/${c.id}`}>
<button>Edit</button>
</Link>
{/* <button>Edit</button> */}
<button>Delete</button>
</div>
)
})}
</div>
</div>
}
}
</CoffeeContext.Consumer>
);
}
export default CoffeeList;
Your problem is here...
const updatedCoffee: ICoffee = new Coffee({
_id: itemId,
You're trying to manually set the _id property (a MondoDB ObjectId) from a string. This won't ever work. It's also unnecessary to create a new model object when you're updating an existing one.
Try this instead
// just a plain object
const updatedCoffee = {
name: req.body.name,
description: req.body.description,
price: req.body.price,
};
res.json(
await Coffee.findByIdAndUpdate(itemId, updatedCoffee, {
returnDocument: "after",
})
);
On the front-end, you need to link to the edit route with the model _id property. So instead of this
<button>Edit</button>
you should be using something like
<div>
{coffee.map((c) => (
<div key={c._id}>
<h2>
{c.name} | ${c.price}
</h2>
<p>{c.description}</p>
<Link to={`/edit/${c._id}`}>
<button>Edit</button>
</Link>
<button>Delete</button>
</div>
))}
</div>;
i've an app whcih sends email to users to reset their passwords
this is the link i send to the user email to be able to do password reset
http://localhost:8081/reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MmU2YWJmMmMzMzI0Mjk1NGQyNmVjZjIiLCJpYXQiOjE2NTk0ODkzODEsImV4cCI6MTY1OTQ5MDI4MX0.6omB-TkXXcwrjv0MaJQxltyERIoJZmkm8sY74AAqgxo
but each time i try to access the link i get
Cannot GET /reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
this is my route
{ name: "reset", path: "/reset/:token", component: Reset },
this is my script tag
<script>
import axios from "axios";
export default {
data: () => ({
valid: true,
password: "",
}),
mounted() {
console.log("the id is :" + this.$route.params.token);
},
methods: {
async handleSubmit() {
const response = await axios
.post("http://localhost:5000/api/auth/reset-password", {
newPassword: this.password,
token: this.$route.params.token
})
.then(res => {
console.log(res);
})
.catch(error => {
console.log(error);
});
},
}
};
</script>
please how can i go about these
when I submit the form to the Submit form button, everything works and the uploaded file is logged into the server console, but why does the Multipart: Boundary not found error occur when I click on Submit using fetch
const express = require("express");
var bodyParser = require("body-parser");
const app = express();
const multer = require("multer");
const fileStorageEngine = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./images");
},
filename: (req, file, cb) => {
cb(null, Date.now() + "--" + file.originalname);
},
});
const upload = multer({ storage: fileStorageEngine });
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(bodyParser.text());
app.get("/", (req, res) => {
res.send(`
<form action="/push" method="POST" enctype="multipart/form-data">
<input type="file" name="image" />
<button type="submit">Submit form</button>
<button onclick="send(event)" type="submit">Submit using fetch</button>
</form>
<script>
function send(event) {
event.preventDefault();
let formData = new FormData(event.currentTarget.parentElement);
fetch("/push", {
body: formData,
headers: {
"Content-Type": "multipart/form-data",
},
method: "POST",
}).then((data) => console.log(data));
}
</script>
`);
});
app.post("/push", upload.single("image"), (req, res) => {
console.log(req.file);
});
app.listen(3000);
I suppose you want to upload an image? to do so you will need to pass three parameter in your fetch request uri , type and name in your formdata. as an exemple :
fetch("/push", {
body: {uri :formData.uri ,
type: formData.type,
name: forData.name}
headers: {
"Content-Type": "multipart/form-data",
},
method: "POST",
}).then((data) => console.log(data));
}
When I sign up a user, the header gets set in the /signup route.
Header token set successfully
But when I try to access the header on / route. The authorization fails because in auth.js, getting the header with const token = req.header('curToken') returns undefined. In the network log I can see that the header that has been set (curToken) is empty or non-existent, but is allowed. Can not get Header token
// index.js
const express = require('express')
require('./db/mongoose')
const userRouter = require('./routers/user')
const app = express()
const port = process.env.PORT || 3000
const cors = require('cors')
app.options('*', cors())
app.use(cors())
app.use(express.json())
app.use(userRouter)
app.listen(port, () => {
console.log(`Server is running on port ${port}`)
})
// user.js (express route)
const express = require('express')
const User = require('../models/user')
const auth = require('../middleware/auth')
const router = new express.Router()
// signup
router.post('/signup', async (req, res) => {
const user = new User(req.body)
try {
await user.save()
const token = await user.generateAuthToken()
res.set('curToken', token) // set header
console.log('registered')
res.send({ user, token })
} catch(err) {
res.status(400).send(err)
}
})
// test if token header is accesible
router.get('', auth, async (req, res) => {
try {
// const users = await User.find({})
res.send("logged in.")
} catch(err) {
res.send(err)
}
})
module.exports = router
// auth.js middleware
const jwt = require('jsonwebtoken')
const User = require('../models/user')
const auth = async (req, res, next) => {
res.header('Access-Control-Expose-Headers', 'curToken')
try {
const token = req.header('curToken') // get header
console.log('auth token:', token) // header returns undefined
const decoded = jwt.verify(token, 'thisismysecretcode')
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) {
throw new Error()
}
req.token = token
req.user = user
next()
} catch(err) {
res.status(401).send({error: 'Please authenticate.'})
}
}
module.exports = auth
// Sign Up
<template>
<div>
SignUp
<form #submit="sendRegData" #submit.prevent>
<input type="text" v-model="firstName" name="firstName" placeholder="First name"><br>
<input type="text" v-model="lastName" name="lastName" placeholder="Last name"><br>
<input type="text" v-model="email" name="email" placeholder="Email"><br>
<input type="text" v-model="password" name="password" placeholder="Password"><br>
<input type="submit" value="Send">
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: "SignUp",
data() {
return {
firstName: null,
lastName: null,
email: null,
password: null
}
},
methods: {
async sendRegData() {
try {
await axios.post('http://localhost:3000/signup', {
firstName: this.firstName,
lastName: this.lastName,
email: this.email,
password: this.password
}).then((res) => {
let token = res.data.token
// console.log('token res received in front:', token)
// localStorage.setItem("currentToken", token)
// console.log('curToken:', localStorage.getItem("currentToken"))
})
} catch(err) {
console.log(err)
}
}
}
};
</script>
// login
<template>
<div class="home">
<!-- <h1>{{ User.firstName }}</h1> -->
</div>
</template>
<script>
import axios from 'axios';
export default {
name: "Page",
data() {
return {
Users: {},
dataFetched: false
}
},
async mounted() {
try {
this.Users = await axios.get('http://localhost:3000/')
console.log(this.Users)
// this.User = this.User.data[0] // transform first object from array
// this.dataFetched = true
} catch(err) {
console.error(err)
}
}
};
</script>
I've set up a node express server with post method on localhost/send and vue app on localhost.
Vue app is working perfect, even on remote machine.
Post request requires json object and it sends an mail via nodemailer.
It works when I make post request via postman app.
Problem appears when I want to send email making post request via Vue app (axios). I store whole email data in Vuex and use "computed" to use it in my component. I can render data, but in my email whole data is undefined.
What am I doing wrong?
Code below:
node server
const express = require('express');
const bodyParser = require('body-parser');
const nodemailer = require('nodemailer');
const path = require('path');
const app = express();
app.use('/', express.static(path.join(__dirname, 'render')));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname+'/render/index.html'));
});
app.post('/send', (req, res) => {
const email = {
name: req.body.name,
email: req.body.email,
phone: req.body.phone,
startPoint: req.body.startPoint,
endPoint: req.body.endPoint,
dateTime: req.body.dateTime
};
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: 'mail.advertidea.pl',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: 'emailIsCorrect', // generated ethereal user
pass: 'passIsCorrect' // generated ethereal password
},
tls:{
rejectUnauthorized:false
}
});
// mail for admin
// setup email data with unicode symbols
let adminMailOptions = {
from: '"GoodTransfer" <test#advertidea.pl>', // sender address
to: 'kamil.grzaba#gmail.com', // list of receivers
subject: 'New transfer request', // Subject line
html: `<p>${email.name}, asks for transfer.<p><br>
<p>Transfer details:</p><br><br>
<p>starting point: ${email.startPoint}</p>
<p>ending point: ${email.endPoint}</p>
<p>date and time: ${email.dateTime}</p><br><br>
<p>clients email: ${email.email}</p>
<p>phone number: ${email.phone}</p>` // html body
};
// send mail with defined transport object
transporter.sendMail(adminMailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Message sent: %s', info.messageId);
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
});
Vuex store
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
email: {
name: '',
email: 'test#test.pl',
phone: '',
startPoint: '',
endPoint: '',
date: new Date().toISOString().substr(0, 10),
},
},
getters: {
email: state => state.email,
},
mutations: {
updateEmail(state, email) {
this.state.email = email;
},
},
actions: {
},
});
Vue component
import axios from 'axios';
export default {
name: 'Book',
data() {
return {
newEmail: '',
valid: false,
emailRules: [
v => !!v || 'E-mail is required',
v => /.+#.+/.test(v) || 'E-mail must be valid',
],
};
},
computed: {
email: {
get() {
return this.$store.state.email;
},
set(value) {
this.$store.commit('updateMessage', value);
},
/* return this.$store.getters.email; */
},
},
methods: {
submitForm() {
console.log(JSON.stringify(this.email));
axios.post('http://goodtransfer.pixelart.pl/send', JSON.stringify(this.email), 'json')
.then((res) => {
console.log(res);
console.log(res.data);
});
},
},
};
Ok, I just found out what was the problem. When You make request via axios You should use object as a payload, not already strigified data.