How do I display user validation error on client?

So I decided to implement my own account management system as I accounts-ui family of packages wouldn’t quite fit in my react template without a lot of hacks.

I have defined the following files.

#server/accounts-validation.js

Accounts.validateNewUser((user) => {
    console.log("validate ", user)
    if (user.emails[0].address.length >= 10) {
      return true;
    } else {
      throw new Meteor.Error(403, 'Email must have at least 10 characters');
    }
});

And

imports/api/signupuser.js

import { Meteor } from 'meteor/meteor';
import { check, Match } from 'meteor/check';
import { Accounts } from 'meteor/accounts-base'

const checkEmptyString = Match.Where((x) => {
    check(x, String)
    return x.length > 0;
});

const checkPhone = Match.Where((x) => {
    return Boolean(x.match("[0-9]*")) && x.length > 0
});

const generateUsername = (full_name) => {
    let username = full_name.split(' ').join('-')
    // check if username is already present. If so append an arbitrary digit
    // To Be Finished
    return username
}

export const SignUpUser = (full_name, email, phone, membership_type, password, c_password, accept_tc) => {

    check(full_name, checkEmptyString)
    check(email, checkEmptyString);
    check(phone, checkPhone);
    if (!Match.test(membership_type, Match.OneOf("Artist", "Sponsor"))) {
        console.log("Args here")
        throw new Match.Error('Member must be one of Artist or Sponsor')
    }
    check(password, checkEmptyString);
    check(c_password, checkEmptyString);
    check(accept_tc, true);

    // Add more validations 
    if (password !== c_password) {
        throw new Meteor.Error('Passwords do not match');
    }

    if (Meteor.isClient) {
        Accounts.createUser({username: generateUsername(full_name), email: email, full_name: full_name, 
            phone: phone, membership_type: membership_type, password: password}, (err) => {
            if (err) console.log("createUser error: ", err)
            }
        );
    }
};

Sign up is currently working as expected. But my problem remains. If an error is thrown (eg throw new Meteor.Error(403, 'Email must have at least 10 characters')) somewhere along the chain of validating the new user (I plan to add more validations), how do I display that error on the client? I don’t mean having it in the browser console. I’m thinking I could use React states to store them and display them next to the form if they exist but I don’t know how to get the error in the client in the first place.

Because Accounts.createUser returns errors in a callback, you should also allow a callback argument to SignUpUser. Then you can pass the error back to whoever called the function.

export const SignUpUser = (full_name, email, phone, membership_type, password, c_password, accept_tc, callback) => {
    // skip lines
    Accounts.createUser({
        username: generateUsername(full_name), 
        email: email, 
        password: password,
        // Note that Accounts.createUser only accepts arbitary user data in a profile object.
        profile: {
            full_name: full_name, 
            phone: phone, 
            membership_type: membership_type,
        },
    }, (err) => {
            if (err) {
                console.log("createUser error: ", err)
                return callback(err);
            }
    });
};

A few more questions that might change the advice:
Is this function being called on the server or client?
Is it called as part of a meteor method or directly?


Unrelated PS: Why do you have a requirement that emails are at least 10 chars?!?

The function is being called on the client, in a react template. It is called directly at the click of a button. It’s not part of a meteor method.

This is how I’m calling it.

SignUpUser(full_name, email, phone_number, membership_type, password, cPassword, accept_tc, callback)

I did implement what you wrote and it worked as you said. But I still got a problem. I actually wanted the returned error message so I can navigate based on what error message I get.

Haha. This is just for me to test that the error is really thrown. Easy to write a@b.c and see what error I got.

Great, no need to modify advice then

To show the error, you need to take look for it in the callback, store it, and render it.
Here’s a quick example that shows what I mean:

class Example extends React.Component {
    constructor(props) {
        super(props);
        this.state = { error: null };
        this.onClick = this.onClick.bind(this);
    }

    onClick() {
        // ...
        SignUpUser(
            full_name,
            email,
            phone_number,
            membership_type,
            password,
            cPassword,
            accept_tc,
            error => {
                if (error) {
                    this.setState({ error: error.message });
                }
            }
        );
    }

    render() {
        return (
            <div>
                {this.state.error && <div className="error">{this.state.error}</div>}
                <button onClick={this.onClick}>Sign Up</button>
            </div>
        );
    }
}

Thanks for this. Works as I wanted.

One question though. I have some checks in my client side code. I’m beginning to think I have to do away with them because check doesn’t return a meaningful error. It doesn’t even say which item has the error. It just throws Match failed without telling me where it failed. E.g.

import { check } from 'meteor/check';

check(email, String);
check(password, String);

In the code above, if either of the two fails the check, an error is thrown but there is no information as to where that error originates from. The console just logs

errorClass {
message: "Match error: Failed Match.Where validation",
path: "", 
sanitizedError: errorClass, 
errorType: "Match.Error", 
stack: "Error: Match error: Failed Match.Where validation↵…7ec5ce1afbb6b8813e8f72771a46bd0f8822850f:36812:5)"
}
errorType: "Match.Error",
message: "Match error: Failed Match.Where validation"path: ""sanitizedError: errorClass {isClientSafe: true, error: 400, reason: "Match failed", details: undefined, message: "Match failed [400]", …}stack: "Error: Match error: Failed Match.Where validation↵    at check (http://localhost:3000/packages/check.js?hash=ed4766fc1fb7df3478a5d9376f8ba77251fca8f8:77:17)↵    at SignUpUser (http://localhost:3000/app/app.js?hash=7b31b53e04ebddb640f98bb4c261557361cb0bf3:168:3)↵    at UserSignUp (http://localhost:3000/app/app.js?hash=7b31b53e04ebddb640f98bb4c261557361cb0bf3:1620:5)↵    at onSubmit (http://localhost:3000/app/app.js?hash=7b31b53e04ebddb640f98bb4c261557361cb0bf3:1650:20)↵    at HTMLUnknownElement.callCallback (http://localhost:3000/packages/modules.js?hash=7ec5ce1afbb6b8813e8f72771a46bd0f8822850f:36378:14)↵    at Object.invokeGuardedCallbackDev (http://localhost:3000/packages/modules.js?hash=7ec5ce1afbb6b8813e8f72771a46bd0f8822850f:36428:16)↵    at invokeGuardedCallback (http://localhost:3000/packages/modules.js?hash=7ec5ce1afbb6b8813e8f72771a46bd0f8822850f:36485:31)↵    at invokeGuardedCallbackAndCatchFirstError (http://localhost:3000/packages/modules.js?hash=7ec5ce1afbb6b8813e8f72771a46bd0f8822850f:36499:25)↵    at executeDispatch (http://localhost:3000/packages/modules.js?hash=7ec5ce1afbb6b8813e8f72771a46bd0f8822850f:36790:3)↵    at executeDispatchesInOrder (http://localhost:3000/packages/modules.js?hash=7ec5ce1afbb6b8813e8f72771a46bd0f8822850f:36812:5)"
__proto__: Error

How do you handle such? Or perhaps I should just perform the checks myself in my template e.g.

if (!full_name) {
    setSignUpError("Please enter your name")
}

check is merely a tool to examine whether the data you receive is of the type you expect it to be (i.e. you expect a string but someone tries to give you an object). This is basically a data security tool, not one for validation.

Validation (like making sure that a password and the repeated password in a signup form match) has to be done in addition to that.

Thanks for pointing that out.

So how do you typically use check in your workflow?

check is most commonly used to protect methods and database calls:

ie. this noSQL injection attack:

Meteor.methods({
  getTodosForUser(userId) {
    return Todos.find({ user: userId });
  }
});

You’re expecting a string, but someone could send a mongo selector instead:

Meteor.call('getTodosForUser', { $ne: null })

By using check, you prevent noSQL injection:

  getTodosForUser(userId) {
    check(userId, String)
    return Todos.find({ user: userId });
  }

Like @coagmano posted. This means that any other kind of validation (like, validating that the new password is complex enough) sits on top of the checks

Following your discussions I’m thinking right now that check is the last thing I should do just before interacting with the database using mongo’s insert, update, etc. commands. Am I right?

it’s actually the first thing you should do.

Okay. Thanks. Two quick questions

  1. I use simpl-schema for validation before my insert calls. Do I still have to use check() as well.

  2. For the method below

'file.rename'(file_id, new_name) {
    check(file_id, String);
    check(new_name, String);
    if (! Meteor.userId()) {
        throw new Meteor.Error('not-authorized');
    }
},

If any of the check()s throws an error, do I need to report that error to the user? If yes, how?

If you are using simpleSchema to validate the method inputs then you don’t need to use check.

If you just have simpleSchema attached to your collection with collection2 then you will still need to check the method inputs

Ideally you should perform validation on the client and give the user feedback before they contact the server. SimpleSchema errors are good in this regard as they also give all the information about each property that failed validation