[SOLVED] Accounts.createUser can't insert email ! :(

Hello,

with the package “accounts-password”/'accounts-base" and “alanning:roles” I try to create a user creation system in my backoffice. (Only the administrator can create accounts and for that it must be connected).

My form is composed of :

  • user name
  • password
  • email
  • roles

The problem is that the email address does not insert.

When I put the required email field, it returns an error like: “Email is required…”
While with a.log console I can see that my email field is returned correctly…
(I only want one email address per account.)

Here is my schema:

import SimpleSchema from 'simpl-schema';

SimpleSchema.extendOptions(['autoform']);
Schema = {};
/**
 * Création du schema pour CreateUser
 */
Schema.UserServices = new SimpleSchema({
    password: {
        type: String,
        label: "Mot de passe",
        autoform: {
            afFieldInput: {
                type: "password"
            }
        }
    },
    passwordConfirmation: {
        type: String,
        label: "Confirmation",
        custom: function(){
            if(this.value !== this.field('password').value){
                return "passwordMissmatch";
            }
        },
        autoform: {
            afFieldInput: {
                type: "password"
            }
        }
    }
});

Schema.AccountData = new SimpleSchema({
    username: {
        type: String,
        regEx: /^[a-z0-9A-Z_]{3,15}$/,
        label: "Nom d'utilisateur"
    },
    emails: {
        optional: false,
        type: Array,
        label:"Adresse email"
    },
    "emails.$": {
        type: Object
    },
    "emails.$.address": {
        type: String,
        regEx: SimpleSchema.RegEx.Email
    },
    "emails.$.verified": {
        type: Boolean,
        optional: true,
        autoform: {
            omit: true
        }
    },
    createdAt: {
        type: Date,
        autoValue: function () {
            if (this.isInsert) {
                return new Date;
            } else {
                this.unset();
            }
        },
        autoform: {
            omit: true
        }
    },
    services: {
        type: Schema.UserServices,
        optional: true,
        blackbox: true,
        autoform:{
            omit: true
        }
    },
    roles: {
        type: String,
        optional: true,
        label:"Role",
        blackbox: true
    }
});

Meteor.users.attachSchema(Schema.AccountData);

Method server side create user :

Meteor.methods({
    "usercreation": function (data) {

        if (_.isObject(data)) {

            if (data.username) {

                let id = Accounts.createUser({
                    username:data.username,
                    password:data.password,
                    emails   :data.emails
                });

                if (data.roles.length > 0) {
                    // Need _id of existing user record so this call must come
                    // after `Accounts.createUser` or `Accounts.onCreate`
                    //[].concat(user);
                    Roles.addUsersToRoles(id, data.roles, Roles.GLOBAL_GROUP);
                }

                _.extend(data, {id: id});

                return id;
            }
        }
    }
});

Hook client side :


AutoForm.hooks({
    'usercreation': {
        onSubmit: function(dataEvent){
            // Gestion du formulaire d'inscription
            this.event.preventDefault();
            if(dataEvent !== undefined) {
                Schema.AccountData.clean(dataEvent);
                console.log(dataEvent);
                let username = dataEvent.username;
                let email = dataEvent.email;
                let password = dataEvent.services.password;
                let roles = dataEvent.roles;

                Meteor.call("usercreation", {
                    username: username,
                    password: password,
                    email: email,
                    roles: roles
                }, (err, res) => {
                    if (err)
                        this.done(err);
                    else if(res)
                        this.done();
                });
            }
        },
        onSuccess: function(){
            swal("Nouvel utilisateur ajouté", "L'utilisateur a bien été créé. <br> Vous pouvez le modifier ou le supprimer à tout moment.", "success");
        },
        onError: function(formType, err){
            swal("Erreur !", err.reason, "error");
        }
    }
});

form html :

 {{#autoForm collection="Meteor.users" id="usercreation" class="ui form gestionUtilisateurs"}}
                    {{> afQuickField name="username"}}
                    {{> afQuickField name="services.password"}}
                    {{> afQuickField name="services.passwordConfirmation"}}
                    {{> afQuickField name="email"}}
                    {{> afQuickField name="roles"}}
                    <br>
                    <button type="submit" class="ui submit button">Créer l'utilisateur</button>
{{/autoForm}}

Do you have any idea?

The accounts system puts the email in an emails field which is an array of objects with the address being stored under emails.$.address. Your schema has an email field of type string so your schema validation fails.

1 Like

Hello copleykj,
thank you very much for ur answer.

Here is the emails schema with the modifications :

emails: {
        optional: false,
        type: Array,
        label:"Email"
    },
    "emails.$": {
        type: Object
    },
    "emails.$.address": {
        type: String,
        regEx: SimpleSchema.RegEx.Email
    },
    "emails.$.verified": {
        type: Boolean,
        optional: true,
        autoform: {
            omit: true
        }
    }

When I validate my form, i’ve get an error returned which is :

Exception while invoking method 'usercreation' Error: Email is required in users insert

Sanitized and reported to the client as: Email is required in users insert [400] :face_with_raised_eyebrow:

Method usercreation : (server side)

Meteor.methods({
    "usercreation": function (data) {

        if (_.isObject(data)) {

            if (data.username) {
                
                let id = Accounts.createUser({
                    username:data.username,
                    password:data.password,
                    emails  :data.emails
                });

                if (data.roles.length > 0) {
                    // Need _id of existing user record so this call must come
                    // after `Accounts.createUser` or `Accounts.onCreate`
                    //[].concat(user);
                    Roles.addUsersToRoles(id, data.roles, Roles.GLOBAL_GROUP);
                }

                _.extend(data, {id: id});

                return data;
            }
        }
    }
});

Okay, problem solved !

Method :

Meteor.methods({
    "usercreation": function (data) {

        if (_.isObject(data)) {

            if (data.username) {

                let id = Accounts.createUser({
                    username:data.username,
                    password:data.password,
                    email  :data.emails[0].address
                });

                if (data.roles.length > 0) {
                    // Need _id of existing user record so this call must come
                    // after `Accounts.createUser` or `Accounts.onCreate`
                    //[].concat(user);
                    Roles.addUsersToRoles(id, data.roles, Roles.GLOBAL_GROUP);
                }

                _.extend(data, {id: id});

                return data;
            }
        }
    }
});

Are you sure you intend that the client be able to specify any roles?

For example, I could just call your method with {username: "blah", password: "blah", email: [{address: "blah}], roles: ["site-wide-administrator"]}.

1 Like

Hello,

thank you for your answer. I’m doing a check concerning the roles we can give on the server side, it won’t be possible to give a role that won’t exist in the Roles collection.

Also, to be able to add a user, it will be necessary to be part of the “admin” role.

Your method will have to check that the current user is part of the ‘admin’ role, since you can’t trust anything from the client.
Methods can be called by anyone, including a not logged in user, and if you aren’t checking on the server side, you are allowing anyone to add any role to a new user.

This snippet doesn’t show that you’re doing any login or permissions check, so we are just ensuring that you do add the permissions check on the server side

2 Likes

Hello,

thank you for your answer. Indeed, on what I have shown you there is absolutely nothing that indicates access restrictions on this system.

An example :

Meteor.methods({
// method in server side
    "usercreation": function (data) {
// security
        if (Roles.userIsInRole(this.userId, ["administrateur"], Roles.GLOBAL_GROUP)) {

            if (_.isObject(data)) {

                if (data.username && data.password && data.emails[0].address) {
                    let id = Accounts.createUser({
                        username: data.username,
                        password:data.password,
                        email: data.emails[0].address
                    });

                    if (data.roles.__global_roles__.length > 0)
                        Roles.addUsersToRoles(id, data.roles.__global_roles__, Roles.GLOBAL_GROUP);

                    _.extend(data, {id: id});

                    return data;
                } else
                    throw new Meteor.Error(403, "Vous devez renseigner tous les champs.");
            } else
                throw new Meteor.Error(400, "Les données ne sont pas vallides, merci de réessayer ultérieurement.");
        } else
            throw new Meteor.Error(403, "Vous n'avez pas les droits nécessaires pour effectuer cette action.");
    }
});

I guess you can hijack this first security by changing the user ID with an admin user ID, but to do that you’ll need access to the list of all members and Meteor.users.find().fetch() returns nothing from the client side except for admin members, otherwise they won’t be able to find the admin _id.

Publish meteor.users :

Meteor.publish('utilisateurs', function(){
    if (Roles.userIsInRole(this.userId, ["administrateur"], Roles.GLOBAL_GROUP))
        return Meteor.users.find({}, {fields: {'createdAt':1, 'username':1, 'emails':1,  'roles':1}});
});
1 Like