I'm trying to implement a feature that detects whether or not an account has already signed up. Now when doing parallel requests I'm getting a deadlock. I think I understand why it is happening but I'm unsure on how to solve it.
Here's a simplified version of what I'm doing;
START TRANSACTION;
-- check if user has already signed up (returned rows > 0, throw error if so)
SELECT * FROM users WHERE email = 'example#site.com' FOR UPDATE;
-- user has not signed up yet.. create the account.
INSERT INTO users SET ...;
COMMIT;
Now this in itself works fine. However when two parallel request happen, a deadlock is made because the transaction will both create a FOR UPDATE lock, which is allowed because initially when there is no account signed up yet there are no rows to lock. Atleast, that's what I think is happening.. correct me if I'm wrong.
I'm curious on how I were to fix this, I still want to check whether not an account has registered already so I can show the user a message. Of course the email has a unique constraint but I do not want to rely on that because the auto increment index will increment, even when it violates the constraint.
Also I'm using typeorm, a sample of my code;
public async registerUser(email: string, password: string, displayName?: string) {
const connection = await getConnection();
connection.transaction(async (manager) => {
// First we need to make sure that this email isn't already registered. If
// it has been registered we can throw a simple UserError which will be
// caught by our error handler.
const hasAlreadyRegistered = await this.findUser(email, manager);
if (hasAlreadyRegistered) throw new UserError('Email has already been registered.');
// At last we can create the user, linking him to the previously created
// authentication strategy.
const user = new User();
user.email = email;
user.displayName = displayName || randomBytes(8).toString('hex');
user.strategies = [authentication];
await manager.save(user);
logger.silly('> Created user row.');
return user;
});
}
I have solved this by just checking for the constraint error in the end (per suggestion of #Shadow). It saves me a lot of hassle.
Code
try {
await manager.save(user);
} catch (err: any) {
// Check whether or not this entry violates an unique constraint.
if (err.code === 'ER_DUP_ENTRY') {
throw new UserError('Email has already been registered.');
} else throw err;
}
Related
I am having the following problem with node and mysql:
I have a function registerUser that takes the req.body with the user credentials and store them into a mysql db.
First of all I check that the email provided does not already exist. I have done this validation working with postgres in the following manner: if(user.rows.lenght!==0) return res.send("user already exist")
Then I pass to the next line of code that insrts the credentials into the db.
My problem is that using mysql, user.rows is undefined. I am having trouble extracting the data from the response which would allowme to perform some sort of validation.
My code is like this:
registerUser:async(req,res)=>{
const resolver=Resolver(res)
try {
//get data from req.body
const {userName, userEmail, userPassword}=req.body
//Check if user alreday exist on db by email
const user=db.query("SELECT * FROM users WHERE user_email=?",
[userEmail],(err,result)=>{
if(err) console.log(err)
else if(result.length!==0) return res.status(401).send('user already exist')
})
The callback function of the query does not stop the execution of the registeruser function. Also, the result comes with the user credentaials which is what I need, but I dont know how to extract it from the callback in order to use it in the scope of registerUser
I have been trying to setup my Nodejs MySQL database configuration. I found this passport.js config for MySQL on Github. The config works properly but there is a part that I do not understand.
var insertQuery = "INSERT INTO users ( email, password ) values ('" + email +"','"+ password +"')";
console.log(insertQuery);
connection.query(insertQuery,function(err,rows){
newUserMysql.id = rows.insertId;
return done(null, newUserMysql);
});
I am confused about the insertID field. The table I am using does not have a field called insertID. It does however have a field named ID. I tried changing that line to
newUserMysql.id = rows.Id;
bu doing so gives me:
Error: Failed to serialize user into session
Leaving it as it is gives me no error
Looks like insertID has nothing to do with the ID field of my table but I do not understand what it means
That probably represents LAST_INSERT_ID() which is the ID of the last row inserted.
The response of an INSERT is not "rows" but a result object, so maybe better named it'd be:
connection.query("...", function(err, result) {
newUserMysql.id = result.insertId;
return done(null, newUserMysql);
});
It's important to note that using Promises dramatically simplifies your code, and async/await can take that even further. This could be as simple as:
let result = await connection.query("...");
newUserMysql.id = result.insertId;
return newUserMysql;
Where that's inside an async function with a Promise-driven database library like Sequelize. You're not handling the potential errors in your first case. In the second you'll get exceptions which will wake you up when there's problems.
I am using Auth0 for a login service but I have a need to add a user to a database in MySQL every time an account is registered through Auth0.
They give this following script template but I am a newbie and need help debugging and understanding it. My specific questions are detailed as comments:
function create(user, callback) {
var connection = mysql({
host: 'localhost', //what should this be?
user: 'KNOWN/Understood',
password: 'KNOWN/Understood',
database: 'KNOWN/Understood'
});
connection.connect();
var query = "INSERT INTO users SET ?"; //what does this do?
bcrypt.hash(user.password, 10, function (err, hash) { //what does this do?
if (err) { return callback(err); }
var insert = {
password: hash,
email: user.email
};
connection.query(query, insert, function (err, results) {
if (err) return callback(err);
if (results.length === 0) return callback();
callback(null);
});
});
}
Is there anything else I need to change for this script or understand or call in for it to work?
I often get the error missing username for Database connection with requires_username enabled and I'm unsure what this means.
I'm assuming you already went through this tutorial on custom databases so let's address your specific questions.
host: 'localhost' // What should this be?
This and the other properties of this object define the way to connect to your custom MySQL database. The database needs to be reached from within Auth0 servers so this needs to be a host name accessible from the Internet.
"INSERT INTO users SET ?"; // What does this do?
This defines an SQL insert command that uses ? as a placeholder for later substitution.
If you see where this query is later used, you will noticed it's invoked with an additional insert object parameter that will cause the above query to be expanded into something like:
INSERT INTO users SET email = 'user#example.com', password = 'asdf34ASws'
bcrypt.hash(user.password, 10, function (err, hash) // What does this do?
This hashes the user provided password so that it's not stored in plain text in the database.
If you chose to require a username in addition to email you need to address this in your custom scripts as I believe the default templates assume that only email will be used.
This means that when creating the user in your database you also need to store the username and in the script to verify a user you also need to return the username.
Problem:
I am working on an Android app which interacts with nodejs REST server using node orm for mysql backend. On my server, I have a functionality of authenticating users based on email verification. Once verification is successful, node orm fetches the user object, changes the verified column value and saves it back.
But, the change is not reflecting in the db after execution. Only if we run the same code another time, it is reflecting in the database
Code
exports.activateEmail = function(email, callback) {
log.info('In verifyEmailDao.js, activateEmail module');
var db = connectionObj.getConnection();
var Candidate = db.models.jobseeker_id;
Candidate.find({email : email}, function(err,candidate){
if(err){
log.info('cannot find account with email to activate', email);
callback(false, null);
}
else {
candidate[0].verified = true;
log.info('candidate email now activated.! status is', candidate[0].verified);
candidate[0].save(function(error){
log.info('Email verified any errors?', error);
callback(true, candidate[0].id);
});
}
});
}
Edit 1:
jobseeker_id.js (node-orm model)
var orm = require('orm');
module.exports = function(db){
console.log('coming inside candidateId.js');
var JobSeekerId = db.define('jobseeker_id', {
id : {type:'serial' , key:true},
first_name : String,
last_name : String,
email : String,
password : String,
verified : Boolean
},{
validations : {
email : orm.enforce.unique("Already registered")
}
});
}
Server log:
{"name":"test-app" "msg":"In verifyEmailDao.js, activateEmail module"}
{"name":"test-app","msg":"candidate email now activated.! status is true"}
{"name":"test-app","msg":"Email verified any errors? null"}
{"name":"test-app","msg":"Email sucessfully activated. Now deleting the entry from verify email link table for candidate id 30}
{"name":"test-app","msg":"In verifyEmailDao.js, deleteRandomLink module"}
{"name":"test-app","msg":"error is---> null"}
{"name":"test-app","msg":"Entry deleted from verify email table as email is activated"}
There will no be no changes in the log when I execute the code for second time, but the change in the db will be reflected!
After 2 days of hell I finally fixed the issue by adding a statement db.settings.set('instance.cache', false) to the db config file. Though I did'nt clearly understand how db update issue was resolved by setting the cache to false, this did the trick!
I am trying to update some user data via the admin SDK. I thought this would work
function directoryUpdate(userId, userDept, userLocation, userPhone, userTitle) {
var update = {
organizations:
{
name: "Next Step Living",
title: userTitle,
primary: true,
type: "work",
department: userDept,
location: userLocation
},
phones:
{
value: userPhone,
type: "work",
primary: true,
}
};
update = AdminDirectory.Users.update(update, userId);
Logger.log('User %s updated with result %s.', userId, update)
return true;
}
but it is not updating the organization or phone data on the record. It also does not throw any kind of error.
three questions, what is the proper syntax to do this update, I assume this works like the API update and behaves like an upsert, is that true, and what is the best practice for capturing any errors during the update. I would like to return a false when the update fails and capture that info. Thanks in advance for your help.
Thanks for your question!
This "inspired" me to work out how the update API worked, as I had got as far as retrieving a User object, updating the properties but had not worked out how to persist the data back to Google.
So, here's my prototype code, which appears to work (the objective being to reset the user's password based on entries in a spreadsheet).
It doesn't seem the most elegant code to me, being that there are two round-trips to the Admin API, and we have to post the email address twice, but I guess that is a side-effect of the JSON API.
var emailAddress = userListSheet.getRange(row, 1).getValue();
var password = userListSheet.getRange(row, 2).getValue();
Logger.log('Email: %s, Password: %s', emailAddress, password);
// Reset user's password
var user = AdminDirectory.Users.get(emailAddress);
user.password = password;
if (changePasswordAtNextLogin == 'Yes') {
user.changePasswordAtNextLogin = true;
}
AdminDirectory.Users.update(user, emailAddress);
Figured out the syntax issue. You do need a set of [] around the name value pairs under organization and phones. organizations:[{....}], phones:[{...}]}; and no, at the end of primary: true under phones. Also changed it from an update to a patch but not sure if that was really required;
update = AdminDirectory.Users.patch(update, userId);
And Yes, it did behave like an upsert and modified existing data and added new data just like the API.
Still need to figure out the best way to capture any errors though so if you have any suggestions please post them.
Looks like supplying an invalid email address is a fatal error that can not be caught and dealt with in code. What I did was get all the primary emails out of Google, store them in an array, and validate that the email I was using was in that list prior to running the update. Since everything else is just a string or logical replacement it should not throw any errors so I am confident that the script will not fail. Now all I have to worry about is the time limit.