Question

Using Meteor accounts (and accounts-ui) is there an easy way to make new user sign-ups invitation only? For example by providing an invitation link or an invitation code.

The only thing related I could find in the Meteor documentation is Meteor.sendEnrollmentEmail but it doesn't solve my problem.

Était-ce utile?

La solution

You can do this with the built in package, but I found it alot easier and powerful to roll a simple implementation.

You'll need to:

  • Create a collection, eg UserInvitations to contain the invites to become a user.
  • Create UI for making UserInvitations / insert some using meteor mongo
  • Using iron-router or similar create a route, eg:

    Router.map ->
      @route 'register',
        path: '/register/:invitationId'
        template: 'userRegistration'
        data: ->
          return {
            invitationId: @params.invitationId
          }
        onBeforeAction: ->
          if Meteor.userId()?
            Router.go('home')
          return
    
  • When the form in userRegistration is submitted - call

    Accounts.createUser({invitationId: Template.instance().data.invitationId /*,.. other fields */})
    
  • On the server, make an Accounts.onCreateUser hook to pass through the invitationId from options to the user

    Accounts.onCreateUser(function(options, user){
      user.invitationId = options.invitationId
      return user;
    });
    
  • Also, on the server make an Accounts.validateNewUser hook to check the invitationId and mark the invitation as used

    Accounts.validateNewUser(function(user){
      check(user.invitationId, String);
      // validate invitation
      invitation = UserInvitations.findOne({_id: user.invitationId, used: false});
      if (!invitation){
        throw new Meteor.Error(403, "Please provide a valid invitation");
      }
      // prevent the token being re-used.
      UserInvitations.update({_id: user.invitationId, used: false}, {$set: {used: true}});
    
      return true
    });
    

Now, only users that have a valid unused invitationId can register.

EDIT: Oct 2014 - Updated to use meteor 0.9.x API's

Autres conseils

To do it with the built in stuff, you can plumb together the existing Accounts.sendEnrollmentEmail - however it's a little more complicated than the other solution given.

Using the example code below, call the enroll method as such:

Meteor.call('enroll', 'john.smith', 'js@harvard.edu', {name: 'John Smith'});

Meteor will then email the user a link (You can configure the template with Accounts.emailTemplates)

When they click the link, meteor calls the function passed to Accounts.onEnrollmentLink - in this case you can take them to a password setup page; but you have to mess around with their done callback.

Modify the following code, where it says INSERT XXX HERE ; then in your code call SomeGlobalEnrollmentObjectThing.cancel() if the user cancels, or SomeGlobalEnrollmentObjectThing.complete(theUsersNewPassword) if they submit the new password.

if (Meteor.isServer){
  Meteor.methods({
    "enroll": function(username, email, profile){
      var userId;
      check(username, String);
      check(email, String); // Or email validator
      check(profile, {
        name: String
      }); // your own schema

      // check that the current user is privileged (using roles package)
      if (!Roles.isInRole(this.userId, 'admin')){
        throw new Meteor.Error(403);
      }

      userId = Accounts.createUser({
        username: username,
        email: email,
        profile: profile
      });

      Accounts.sendEnrollmentEmail(userId);

    }
  });
} else {
  // uses `underscore`, `reactive-var` and `tracker` packages

  function Enrollment(){
    this.computation = null;
    this.token = new ReactiveVar(null);
    this.password = new ReactiveVar(null);
    this.cancelled = new ReactiveVar(false);
    this.done = null;
    this._bind();
  }

  _.extend(Enrollment.prototype, {

    _bind: function(){
      Accounts.onEnrollmentLink(_.bind(this.action, this));
    },

    reset: function(){
      this.token.set(null);
      this.password.set(null);
      this.cancelled.set(false);
      this.done = null;
      if (this.computation !== null){
        this.computation.stop();
        this.computation = null;
      }
    },

    cancel: function(){
      this.cancelled.set(true);
    },

    complete: function(password){
      this.password.set(password);
    },

    action: function(token, done){
      this.reset();
      this.token.set(token);
      this.done = done;
      this.computation = Tracker.autorun(_.bind(this._computation, this));
      // --- INSERT REDIRECT LOGIC HERE [TAKE TO PASSWORD SETUP PAGE]--- //
    },

    _computation: function(){
      var password;
      if (this.cancelled.get()){
        this.reset();
        this.done();
        // --- INSERT REDIRECT LOGIC HERE [USER CANCELLED]--- //
      } else {
        password = this.password.get();
        if (password !== null){
          Accounts.resetPassword(this.token.get(), password, _.bind(this._complete, this));
        }
      }
    },

    _complete: function(err){
      // TODO - check if we were reset before callback completed
      this.reset();
      this.done();
      if (err){
        // --- INSERT REDIRECT LOGIC HERE [RESET FAILED] --- //
      } else {
        // --- INSERT REDIRECT LOGIC HERE [SUCCESS] --- //
      }
    }
  });

  SomeGlobalEnrollmentObjectThing = new Enrollment();
}

I have created a specific solution to this, since all the other solutions only allow you to explicitly create password-based accounts. The t3db0t:accounts-invite package allows account creation with any service only when you allow them, such as with an 'accept invitation' route. Live demo here.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top