Does it consider security problem when I pass req.body directly to the database (I use mongoose and nodejs)?
Let's say I have a person schema (name: string, age: number) and in req.body, someone tries to add an extra field to it. Do I need to extract only field that I need before passing it to db
const {name, age} = req.body
const person = new Person({name, age})
person.save()...
OR this is ok because mongoose already take care of that
const person = new Person(req.body)
person.save()...
Note: I am asking about extra fields, not about whether or not we should santinize the field that declared in schema
No, it's not a security problem in itself.
And that's not related with Mongoose either. That's pure JavaScript.
You are using destructuring assignment on the req.body params, so you are extracting exactly specified arguments.
const body = { name: 'bob', age: 12, malicious_entry: "rm -rf" };
let {name, age} = body;
console.log(name, age, malicious_entry) // ReferenceError: malicious_entry is not defined
And if you pass it to a constructor:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let {name, age, malicious_entry} = body;
let person = new Person(name, age, malicious_entry);
console.log(person) // Person { name: 'bob', age: 12 }
I let you choose if you want to record an internet request directly in your database without checking it, but clearly extra parameters are not the problem.
Related
I am trying to to update my user and applying Unique validator on email to prevent duplicates.
I need to ignore email uniqueness for provided user_id, and those records which are marked is_deleted to 1.
Only first statement works, if I place is_deleted,1 before id,${data.user_id} it works for deleted. but not for user_id.
get rules() {
const data = this.ctx.request.post()
console.log('current context: ', data.user_id)
return {
// email: `required|email|unique:users,email,id,${data.user_id},is_deleted,1`,
email: `required|email|unique:users,email,id,${data.user_id}|unique:users,email,is_deleted,1`,
phone_number: 'required|max:10',
status_id: 'required'
}
}
However, only first statement for ignore works, second one is not working
I would recommend extending the validation framework and add a custom rule (a good name would be unique_email). You will find it more productive and testable. The code would be similar to this:
const uniqueEmailFn = async (data, field, message, args, get) => {
const email = get(data, 'email')
const userId = get(data, 'user_id')
if (!email) {
/**
* skip validation if value is not defined. `required` rule
* should take care of it.
*/
return
}
const [table, column] = args
const row = await Database.table('users')
.where('id', userId)
.where('email', email)
.where('is_deleted', false)
.first()
if (row) {
throw 'The inputted e-mail is already taken'
}
}
Generally speaking, it's preferable to use the default rules for simple and generic validations, and business-specific ones can be added by extending the framework.
What is the best approach to do a batch update or transaction, that reads a value of the first update, then uses this value to make further updates?
Here is an example:
//create person
const id = await db
.collection("person")
.add({ ...person })
.then(ref => ref.id)
//then do a series of updates
let batch = db.batch()
const private_doc = db
.collection("person")
.doc(id)
.collection("private")
.doc("data")
batch.set(private_doc, {
last_modified,
version: 1,
versions: []
})
const some_index = db.collection("data").doc("some_index")
batch.update(some_index, {
[id]: { first_name: person.first_name, last_name: person.last_name, last_modified }
})
const another_helpful_doc = db.collection("some_other_collection").doc("another_helpful_doc")
batch.update(another_helpful_doc, {
[id]: { first_name: person.first_name, last_name: person.last_name, image: person.image }
})
return batch.commit().then(() => {
person.id = id
return person
})
You can see here if there is an error any of the batch updates, the person doc will still be created - which is bad. I could add in a catch to delete the person doc if anything fails, however interested to see if this is possible with transactions or batches.
You can call the doc() method, without specifying any path, in order to create a DocumentReference with an auto-generated ID and, then, use the reference later. Note that the document corresponding to the DocumentReference is NOT created.
So, the following would do the trick, since all the writes/updates are included in the batched write:
const new_person_ref = db.collection("person").doc();
const id = new_person_ref.id;
let batch = db.batch()
batch.set(new_person_ref, { ...person })
const private_doc_ref = db // <- note the addition of ref to the variable name, it could help avoiding errors, as this is not a DocumentSnapshot but a DocumentReference.
.collection("person")
.doc(id)
.collection("private")
.doc("data")
batch.set(private_doc_ref, {
last_modified,
version: 1,
versions: []
})
//....
How do I insert a value into a select statement using JavaScript, specifically when using express and postgres?
The createUser, and listAllUsers, is working (included below for reference). The try/catch is working and satisfying the request or throwing the error for those two as well.
Any help would be greatly appreciated!
When using Postman, the output that I receive when I send the get (localhost:4000/user/id with a x-www-formurlencoded key value user_id = 3) is…
{
"name": "error",
"length": 90,
"severity": "ERROR",
"code": "42601",
"position": "37",
"file": "scan.l",
"line": "1134",
"routine": "scanner_yyerror"
}
And in the terminal, it shows the following (trapped from my console.log).
3
QUERY: SELECT * FROM users WHERE user_id = ${user_id}
When I user curl it says the same in the terminal. Here is the curl command and putput…
curl -X GET localhost:4000/user/3
{"name":"error","length":90,"severity":"ERROR","code":"42601","position":"37","file":"scan.l","line":"1134","routine":"scanner_yyerror"}ww10sc2353621:~ james.mcgreggor$ curl -X GET localhost:4000/user/3
Ultimately the 3 that I am passing as the user_id is not being substituted in the select statement. That is my problem. I cannot figure out how to correctly do this. Should I even be taking this approach, or should I try passing it as a parameter in the URL?
This is from my User class file (User.js)
const db = require('../connectors/db.js');
class User {
constructor(id, user_id, first_name, middle_initial, last_name, email, type) {
this.id = id;
this.first_name = first_name;
this.middle_initial = middle_initial;
this.last_name = last_name;
this.email = email;
this.type = type;
this.user_id = user_id;
}
static newUser(user_id, first_name, middle_initial, last_name, email, type) {
return db.one(`
INSERT INTO users ("user_id", "first_name", "middle_initial", "last_name", "email", "type")
VALUES ('${user_id}', '${first_name}', '${middle_initial}', '${last_name}', '${email}', '${type}')
returning id
`)
}
static async allUsers() {
const findAllQuery = 'SELECT * FROM users;';
return db.query(findAllQuery)
}
static async selectUser(user_id) {
console.log(user_id);
const findOneQuery = 'SELECT * FROM users WHERE user_id = ${user_id}';
return db.query(findOneQuery)
}
}
module.exports = User;
This is from my Routes file (Routes.js)
const express = require('express');
const dataFunctions = require('./catalog.js');
const AppRouter = express.Router();
AppRouter.post('/user', dataFunctions.createUser);
AppRouter.get('/users', dataFunctions.listAllUsers);
AppRouter.get('/user/:id', dataFunctions.listUserByUserID);
AppRouter.delete('/user/:id', dataFunctions.deleteUserByUserID);
module.exports = AppRouter;
This is from my Catalog file (Routes.js)
const Users = require('../models/users.js')
// Create
async function createUser(req, res) {
try {
console.log(req.body);
const userId = await Users.newUser(req.body.user_id, req.body.first_name, req.body.middle_initial, req.body.last_name, req.body.email, req.body.type)
res.status(201).send(`User ID: ${userId.id}`);
} catch(error) {
res.status(400).send(error);
}
}
// List all
async function listAllUsers(req, res) {
try {
const userList = await Users.allUsers();
console.log(userList);
res.status(200).send(userList);
} catch(error) {
res.status(400).send(error);
}
}
// List by ID
async function listUserByUserID(req, res) {
try {
const userList = await Users.selectUser(req.body.user_id);
console.log(userList);
res.status(200).send(userList);
} catch(error) {
res.status(400).send(error);
}
}
module.exports = {
createUser,
listAllUsers,
listUserByUserID
}
Never use string concatenation for querying, you already have mechanism called prepared statement, signature like
.query('SELECT * FROM `books` WHERE `author` = ?', ['David'])
It will sanitize input for you and partially prevent sql-injection attacks, also always do validation of input values. And if you are not want to use ORM like typeorm, Sequelize, you can use knex.js which can only create query strings and fully manage db interaction
You should never insert the values directly into your query like that. Consider the example:
db.query(`
INSERT INTO messages (message, username)
VALUES ('${message}', '${username}')
`);
If the username was my authenticated username, but the message was whatever value I typed into the UI, I could pretend to be someone else by sending a message like: I am stupid', 'someone_else') --
The SQL would then look like:
INSERT INTO messages (message, username)
VALUES ('I am stupid', 'someone_else') --', 'my_username')
The --', 'my_username') bit is treated as a comment, so it looks like someone_else said I am stupid. This is one of the most common and easily exploitable vulnerabilities in web applications.
Solution 1
You could parameterise your query:
db.query(`
INSERT INTO messages (message, username)
VALUES (?, ?)
`, [message, username]);
This is secure, but harder to read (in my opinion) and you have to be very careful to always do this consistently.
Solution 2
https://www.atdatabases.org provides database APIs that are relatively easy to use, and totally safe from this kind of attack. You would just do:
import connect, {sql} from '#databases/pg';
const db = connect();
db.query(sql`
INSERT INTO messages (message, username)
VALUES (${message}, ${username})
`);
to safely execute the same query. The sql tag ensures the data is properly handled/escaped and #databases/pg automatically enforces that you always add the sql tag. N.B. there are then no quotes around the parameters.
Instead of using single quotes in select query in static async selectUser use ``.
In my user edit route, I am trying to use the Lodash merge function to update the returned user document (from Mongoose) with the updates sent in req.body. Here is my code:
const { dobYear, dobMonth, dobDay } = req.body;
const dob = formatDob(dobYear, dobMonth, dobDay);
const user = await db.User.findById(req.params.id);
const updates = pick(req.body, [ 'fullName', 'email', 'password', 'gender', 'address', 'avatar_url' ]);
merge(user, [updates, dob]);
let updatedUser = await user.save();
The problem is even when I send an updated email in the request, the merge does not seem to actually overwrite the old email value with the new one (from updates).
You are trying to merge an object with an array in the way you are passing the arguments to merge.
See documentation. And I assume your confusion came from the fact that in the docs they have _.merge(object, [sources]). However if you see in the Arguments section they have:
[sources] (...Object): The source objects. Meaning a list of objects rather than an actual array.
Try this:
var user = { 'a': 1, 'b': 2 };
var updates = { 'a': 3, 'c': 4, 'email': 'aaa#bbb.com' };
let dob = '11-11-2019'
let result = _.merge(user, updates, {dob}); // <-- ...Object
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
merge(user, [updates, dob]);.
Incorrect usage. Accepts the following :
Arguments
object (Object): The destination object.
[sources] (...Object): The source objects.
Observe the..., that means it accepts a variable number of arguments.
merge(user, updates, { dob })
I'm trying to get the object id after adding it to the db (using collection.insert)
mongoose.model('Persons').collection.insert(person, function(err, newPerson) {
console.log('lets see you', newPerson);
});
and from the console I'm getting only result: { ok: 1, n: 1 } in stand of the new obj, any ideas how can I rich to the new object ?
thanks!
You can use save() here
var Persons = mongoose.model('Persons');
var personJSON = {
..... // persons schema values you want to insert
};
var person = new Persons(personJSON);
var result = yield person.save();
result variable will contain all the fields you inserted along with _id