Say I have a user domain class with fields username and password. For simplicity say I would like to store the passwords as a SHA-512 hash. I also want to validate the password prior to hashing it, but also transparently hash the password before saving it. Is there a way to do this in the domain object?
static constraints =
{
username(blank: false, unique: true);
password(minSize: 10);
}
Instead of saying:
def user = new User(username: "joe", password: createHash("joepass"));
where I can't validate the hash
def user = new User(username: "joe", password: "joepass");
if(user.validate())
{
user.save(); // Would then turn password into a hash on save
}
else
{
// Handle validation errors
}
Following GORM Events I've come up with the following:
def beforeInsert = { doHash(); }
def beforeUpdate = { doHash(); }
void doHash()
{
if(this.password.size() != 32)
{
this.password = this.password.encodeAsHash(); // I wrote a codec for this
}
}
Now this works fine when creating new users. However, if I create a user, give them a password, and save them, then change the password and re-save neither of these methods gets called and the plain test password gets stored.
Use the GORM Events
On the save or update events you can do the create hash
def beforeInsert = {
// do hash magic
}
def beforeUpdate = {
// do hash magic
}
Related
I have users and companies and want to store a company for each user and all of the users of each company in Firebase.
user={
"id":"tjkdEnc3skdm2Jjknd"
"name":"Adam",
"street":"Sideway 4",
"company":"dHend4sdkn25"
}
companies={
"id":"dHend4sdkn25",
"name":"Comp Ltd.",
"members":[
{
"id":"tjkdEnc3skdm2Jjknd"
"name":"Adam"
},{
"id":"dfjnUkJKB3sdn8n2kj"
"name":"Berta"
}
]
}
All explanations say that duplicate data is the best way to deal with and so I want to write some cloud functions to keep thigs in sync when editing on one of the sides.
Basically I started with
exports.userChangedCompany = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
const data = change.after.data();
const previousData = change.before.data();
if (data.company == previousData.company) {
return null;
}
else{
admin.firestore().doc('companies/'+data.company).set({ ... });
}
});
to update the companies when a user changed the company. Unfortunately I haven't found any hint how to set the new company-data properly.
Can someone please help me?
It sounds like you just need to remove user from members array of old company and add in that array of new company. You just need IDs of both companies.
async function updateCompanies(userId, username, oldCompanyId, newCompanyId) {
const companiesRef = await admin.firestore().collection("companies")
const userObj = {id: userId, name: username}
// Removing from old company and adding in new company
await Promise.all([
companiesRef.doc(oldCompanyId).update({members: admin.firestore.FieldValue.arrayRemove(userObj)}),
companiesRef.doc(newCompanyId).update({members: admin.firestore.FieldValue.arrayUnion(userObj)})
])
return true
}
You can just call this function in your cloud function. Just make sure you pass correct params. The reason why you need to pass the username as well is you have array of objects (members) and hence you need the complete object to add/remove using arrayUnion/arrayRemove.
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.
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 ``.
I'm building a step counter app.
I got an iOS app that pushes the sum of each day to /users/{mobile}/steps/{date}/
When a new steps child is updated or added, I want to sum the value of all the steps for that particular user and update his stepsTotal.
To achieve that I need to
Find the original user and sum all the steps.
Save the new value to stepsTotal.
I would be most grateful if someone could give some help here. :-)
database
{
"users": {
"92291000": {
"firstName": "Tore",
"stepsTotal": "1500",
"steps": {
"02-09-2017": "500",
"03-09-2017": "1000"
},
import.js
var db = admin.database();
var dbRoot = db.ref("/");
var usersRef = dbRoot.child("users");
// This works
function saveUser(attributes) {
let mobile = attributes.mobile;
delete attributes['mobile']
let user = usersRef.child(mobile);
user.update(attributes);
}
function increaseSteps( { mobile=null, steps=null } = {}) {
// Find the User
console.log("looking for mobile", mobile); // OK
let userRef = usersRef.child(mobile);
// Here I'm not able to read the old data from the user.
userRef.transaction(function(user) {
console.log("user: ", user); // null
// ^ User is null.
});
/*
If I mangage to find user above, I expect to do something like this.
Or it is possible to only update *stepsTotal*?
*/
let attributes = {
firstName: user.firstName,
lastName: user.lastName,
stepsTotal: user.stepsTotal + steps,
}
user.update( attributes );
}
If I understand correctly, you have a problem in this snippet of the code:
let userRef = usersRef.child(mobile);
// Here I'm not able to read the old data from the user.
userRef.transaction(function(user) {
console.log("user: ", user); // null
// ^ User is null.
});
In Firebase Database transactions the initial value is often null. From the Firebase documentation on transactions:
Transaction Function is Called Multiple Times
Your transaction handler is called multiple times and must be able to handle null data. Even if there is existing data in your database it may not be locally cached when the transaction function is run.
This is due to how Firebase transactions work behind the scenes. To learn more about that, see my answers here Transcation updateFunction parameter is null and Firebase runTransaction not working.
The solution is to handle both cases: if the user node doesn't exist yet count the initial number of steps, otherwise update the number of steps:
let userRef = usersRef.child(mobile);
userRef.transaction(function(user) {
return (user || 0) + new_steps_for_user;
});
I am using the following code querying a dynamodb instance from within a node.js/express framework. The code is querying a table called User which has a hash key primary key of username with no range. It has no local secondary indexes, and 3 global secondary indexes (last_name-first_name, email_last-name, and company_[no range]).
I want to prevent duplicate usernames and duplicate email addresses. I can successfully prevent duplicate usernames but not duplicate email addresses. The docs state the
"ComparisonOperator": "NULL" or Exists: false lines should do it (they are mutually exclusive and I do NOT try them at same time). But, only the username validation is 'honored' by the AWS sdk, ie. the code below prevents duplicate usernames from being entered in to the system but duplicate email still occurs.
If I leave both "Expected:" keys out (username and email) the putitem simply adds a new record or updates the existing record with the same username (as documentation states and as I expect) but leaving both in, or just the email key in will NOT prevent duplicate emails in the database. Please help.
thanks
var d = new Date();
var dt = d.getTime();
params = {
TableName: 'User',
Expected: {
"username": {
Exists: false
// tried this also -> "ComparisonOperator": "NULL"
},
"email": {
Exists: false
// tried this also -> "ComparisonOperator": "NULL"
}
},
Item: {
username: {S: req.body.username},
created: {N: "" + dt + ""},
company: {S: req.body.company},
fname: {S: req.body.fname},
lname: {S: req.body.lname},
companies: {SS: [req.body.company]},
email: {S: req.body.email},
is_admin: {S: req.body.is_admin},
is_vendor: {S: req.body.is_vendor},
password: {S: req.body.pass}
}
};
dynamodb.putItem(params, function(err, data) {
var obj = new Object();
obj.data = {};
obj.data.username = req.body.username;
obj.data.fname = req.body.fname;
obj.data.lname = req.body.lname;
obj.data.company = req.body.company;
obj.data.email = req.body.email;
obj.data.is_admin = req.body.is_admin;
obj.data.is_vendor = req.body.is_vendor;
if (err) {
obj.status = "false";
obj.error = err;
}
else{
obj.status = "true";
}
res.send(JSON.stringify(obj));
});
I recommend first doing a query against the email index to check for existence of the email address before creating the new user.
It sounds like you are expecting the update condition to act as a global unique key constraint, similar to what a relational database might offer. DynamoDB only really enforces uniqueness on the primary key attributes. The update condition is only evaluated against the item attributes that are matched by the key. Your conditional update strategy works for username, since username is the primary hash key and a duplicate username would match the same row. The condition on email only guarantees that the email field is null on the one row that matches the username key.