AdonisJS catch database insert errors with api-only while using ORM - orm

Is there a way to catch database insert errors i.e. duplicate entry when using the Adonis ORM in api-only mode.
In the example below, if the username is a duplicate, it just sends a 500 internal server error and dumps a full html page of the error to the client.
Even if I don't know what the error is, I would at least like to be able to catch the error and send a more usable json object like
{error: "Unable to insert user"}
Here is some example code
const User = use('App/Models/User')
class UserController {
async register({ request }) {
const { username, email, password, first_name, last_name } = request.all()
const user = new User()
user.fill({
username,
email,
password,
first_name,
last_name
})
await user.save()
return user
}
}
Partly Solved:
I have partly solved this issue with the following code, but I feel as though there must be a better / built in way to achieve this.
const User = use('App/Models/User')
class UserController {
async saveData(object, errorMessage){
try{
await object.save()
return object
}
catch(err) {
return {error:`${errorMessage}`}
}
}
async register({ request, response }) {
const { username, email, password, first_name, last_name } = request.all()
const user = new User()
user.fill({
username,
email,
password,
first_name,
last_name
})
return this.saveData(user, "Unable to save user")
}
}

Try & catch is what you're looking for. You can also use global exceptions.
Regarding your duplicate error, you should also use the AdonisJs Validator to make sure you won't get this errors.
So before you save your model, check that the given user is unique for example.
So you would have something like this
async register({ request, response }) {
const { username, email, password, first_name, last_name } = request.all()
const rules = {
username: 'required|unqiue:username'
};
const validation = await validate(request.all(), rules);
if (validation.fails()) {
response.status(500).send(validation.messages());
}
const user = new User()
user.fill({
username,
email,
password,
first_name,
last_name
})
await user.save();
return user;
}

Related

Firebase updatePassword removes sign_in_second_factor phone property from token

I do a reautenticate with a user whom is already logged in as a Multifactor user
async reauthenticate(oldPassword: string): Promise<SignInEmailPassword> {
const user = firebase.auth().currentUser;
try {
if (user?.email) {
const cred = firebase.auth.EmailAuthProvider.credential(user.email, oldPassword);
await user.reauthenticateWithCredential(cred);
}
return { exception: '', token: '' };
} catch (reason) {
let phoneNumber = '****';
if ((reason as any).code === 'auth/multi-factor-auth-required') {
// The user is enrolled in MFA, must be verified
this.mfaResolver = (reason as any).resolver;
phoneNumber = (reason as any).resolver.hints[0].phoneNumber;
}
return { exception: 'auth/multi-factor-auth-required', phoneNumber };
}
}
I do the phone verification like
const phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
const phoneOpts = {
multiFactorHint: this.mfaResolver.hints[0],
session: this.mfaResolver.session,
};
try {
this.verificationId = await phoneAuthProvider.verifyPhoneNumber(phoneOpts, recaptcha);
All good so far ( the recaptcha works with some other code, not mentioned here )
Then the actual SMS is verified, like:
const cred = firebase.auth.PhoneAuthProvider.credential(this.verificationId, phoneCode);
const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
const user = firebase.auth().currentUser;
try {
if (this.mfaResolver) {
await this.mfaResolver.resolveSignIn(multiFactorAssertion);
}
all good, and then finally
I can update the password with
const user = firebase.app().auth().currentUser;
if (user) {
await user.updatePassword(password);
}
If I console.log the token JUST before the updatePassword, I get my old token, with the
"sign_in_second_factor": "phone" property, but the token AFTER the updatePassword suddenly is without the sign_in_second_factor property, so basically it broke the token.
My solution is now to log out, and force the user to log back in ( again with MFA ), but an unnecessary step.
Is this avoidable,
to me it looks like a firebase bug, as it generates a valid token, WITHOUT a sign_in_second_factor being present, while it is a MFA firebase user.

AXIOS POST in vue not working: Suggestions?

I'm trying to get a sign up feature. I have used expressjs to make the backend POST method that looks like this:
export const insertUser = (data, result) => {
db.query("INSERT INTO users VALUES ?", [data], (err, results) => {
if(err) {
console.log(err);
result(err, null);
} else {
result(null, results);
}
});
}
I then have a frontend where I use axios to post the values into the database, as this:
async insertUser() {
try {
await axios.post("http://localhost:5000/users", {
first_name: this.first_name,
second_name: this.second_name,
email: this.email,
road: this.road,
town: this.town,
post_code: this.post_code,
user_name: this.username,
password: this.password,
});
this.first_name = "";
this.second_name = "";
this.email = "";
this.road = "";
this.town = "";
this.post_code = "";
this.username = "";
this.password = "";
this.$router.push("/");
} catch (err) {
console.log(err);
}
However, I have used postman to try and post the information but I get an error saying that INSERT INTO users VALUES is incorrect. I have triple checked everything but for the life of me can't see the issue.
seem's the problem is in the nodejs function
"INSERT INTO users VALUES (?,?,?)"
for each data you need to put an '?' in the values
also if you have an id in our database that is auto increment you need to specify columns to update
"INSERT INTO users(name,email) VALUES (?,?)"
i think your data is an array no need to put it in brackets just : ,data,

How do we hash a put request password?

Hi everyone am trying to hash my put request using bcrypt in my express-mongoose server
the put request
// updating a user
router.put('/:id', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send(error.details[0].message)
const user = await User.findByIdAndUpdate(req.params.id, {
$set : {
name: req.body.name,
email: req.body.email,
password: req.body.password
}
})
// hashing user passwords
const salt = await bcrypt.genSalt(10)
user.password = await bcrypt.hash(user.password, salt)
if (!user) return res.status(404).send('User with that id does not exist')
res.send(user)
})
All other functions inside the update request is working perfectly apart from hashing the updated password. As a newbie I need your help/ best approach recommendation in this.
Thanks in advance...
Solution 1: Easy Way
For your personal solution, without really modifying the code, it works like the following.
// updating a user
router.put('/:id', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send(error.details[0].message)
// Why not make the hash function here?
const salt = await bcrypt.genSalt(10)
const newPassword = await bcrypt.hash(req.body.password, salt)
const user = await User.findByIdAndUpdate(req.params.id, {
$set : {
name: req.body.name,
email: req.body.email,
password: newPassword
}
})
if (!user) return res.status(404).send('User with that id does not exist')
res.send(user)
})
You have a mistake in your user.password call. The findByIdAndUpdate method does not return an object that you can modify instantly. In above workaround, we simply move the function so that it hashes the new password first before updating your document.
Solution 2: My Own Style
For my personal solution, I'd go like this. Let's say that you have a userModel that stores the schema of your User entity. I will add a new middleware that will run every time the password changes.
/** your user schema code. **/
userSchema.pre('save', async function (next) {
// Only run the encryption if password is modified.
if (!this.isModified('password')) {
return next();
}
// Hash the password with BCRYPT Algorithm, with 12 characters of randomly generated salt.
this.password = await bcrypt.hash(this.password, 12);
next();
});
Next, we'll create a new dedicated route in order to handle password changes. I think it's better if we define a new route for it as passwords are sensitive data. Below is pseudocode, don't instantly copy and paste it, it wouldn't work.
const user = await User.findById(...);
user.password = req.body.password;
await user.save({ validateBeforeSave: true });
Remember that save middleware runs every time after the save command is run.
Further reading about Mongoose's middlewares here.

Nuxtjs - Authentication

I wrote a sign up functionality in nuxtjs. It saves a new user in my database. However, there seems to be a problem with generating a token afterwards, to log in the user.
The register action gets called by a method in the register component. It returns the error response in the catch block. It seems to fail after the token is generated on the server.
Action in the store
async register ({ commit }, { name, slug, email, password }) {
try {
const { data } = await this.$axios.post('/users', { name, slug, email, password })
commit('SET_USER', data)
} catch (err) {
commit('base/SET_ERROR', err.response, { root: true })
throw err
}
}
Post function on the nodejs server
router.post('/users', async (req, res) => {
try {
const body = _.pick(req.body, ['name', 'slug', 'email', 'password']);
const user = new User(body);
await user.save();
const token = await user.generateAuthToken(); // execution seems to fail on this line
console.log(token); // never gets called
req.session['token'] = 'Bearer ' + token;
req.session['user'] = user;
res.header('Authorization', 'Bearer ' + token).send(user);
} catch (err) {
res.status(400).json({ message: "Der Account konnte leider nicht erstellt werden" });
}
});
GenerateAuthToken function in mongo model User
UserSchema.methods.generateAuthToken = function () {
var user = this;
var access = 'auth';
var token = jwt.sign({_id: user._id.toHexString(), access}, process.env.JWT_SECRET).toString();
user.tokens.push({access, token});
return user.save().then(() => {
return token;
});
};
Error message
I would be tremendously thankful for any kind of help!
Maybe it doesn't help too much, but I would try to create a dummy token and try to make everything works with it. One of my debugging techniques is to isolate every single part of my code and be sure that everything works piece for piece, maybe that technique is slow but most of the time it works.
If everything works, I would continue debugging the generateAuthToken function.
If your console log never gets called, then the error could be in the function.
I hope it helps a little and sorry I don't know too much about MongoDB but everything seems to be ok.

tcomb-form-native set error dynamically

Assume I have login form with the following fields:
const Email = t.refinement(t.String, (str) => {
const reg = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
return reg.test(str);
});
const Password = t.refinement(t.String, (str) => {
return str.length >= 6; // minimum password length should be 6 symbols
});
const Person = t.struct({
email: Email,
password: Password,
});
The data user enters into the fields validates and then I send a request to the authentication server and the server validates received data additionally and it turns out that there is no user with such credentials. So it return according response:
{
success: false,
data: { password: ['User with such credentials is not found.'] }
}
The question is, how can I set dynamically error to tcomb property? Something like:
this.refs.form.getComponent('password').refs.input.addError(someError);
Is it possible?
In case someone is still looking for an answer, binding the "error message" field option to the internal state of the surrounding component should work. For example:
render() {
// ... rest of code omitted
let formOptions = {
fields: {
email: {
hasError: this.state.emailHasError,
error: this.state.emailErrorMessage
}
}
};
return (<Tcomb.form.Form
ref="myForm"
options={formOptions}
/>);
}
And then on receiving server response, you can update the main component state, like:
this.setState({
emailHasError: true,
emailErrorMessage: 'Invalid email address'
});