Creating Active Users via the API gem

I have the API gem up and running and I am trying to create a active user on our discourse site via the api. I am creating accounts using information that I have already verified, so a email confirmation is no required. I would just like to create a active account and to be able to have the users login right away.

I am able to successfully create the account, with the optional :active arg. and I get a response from my discourse server as.

{"success"=>true, "active"=>true, "message"=>"Your account is activated and ready to use.", "user_id"=>7}

However, when I go and test the login on the discourse server i get the please confirm your email prompt.

This was also noted by another user in this post
https://meta.discourse.org/t/api-to-create-a-user-without-sending-out-activation-email/23432/4?u=jmatsuba

Please advise if this is a documented bug, or if there is a fix planned. If time permits, will try and build a fix and do a PR.

3 Likes

Here is what I’ve been able to discover.

Part of the problem is that a email token is generated regardless if a user is active or not.

line 82 of user.rb
 after_create :create_email_token

Upon login, the sessions controller calls email_confirmed?

  def email_confirmed?
    email_tokens.where(email: email, confirmed: true).present? || email_tokens.empty?
  end

Any suggestions on the best way to resolve this bug? Maybe check to see if the token being created is for an active user that has never logged in, in this case make the token active? Or maybe not even create a token in this case?

3 Likes

I just spent the better part of today working on this and found a workaround for it. Here’s the process you have to go through to make this work.

  1. Create the account and set active=true. Note the user_id that is provided from the API call. In your above example, the user_id=7 so we’ll use that in this example.
  2. Make an API call to deactivate the user account. It will be something like this:
    http://discourseURL/admin/users/7/deactivate.json?api_key=xxxx&api_username=yyyy
  3. Make an API call to activate the user account. It will be something like this:
    http://discourseURL/admin/users/7/activate.json?api_key=xxxx&api_username=yyyy

It appears that what happens is that when you deactivate the account, it ends up calling after_save, which makes a call to expire_old_email_tokens. This would normally expire all of the tokens but it doesn’t because of the following if statement inside of that function:

if password_hash_changed? && !id_changed?

Since the password hash hasn’t changed, it doesn’t actually expire the tokens. I assume that there’s some special case that this if statement is handling which is most likely being used to bypass situations where the password was updated.

The third step is to activate the account and the code that activates the user account is as follows:

  def activate
    email_token = self.email_tokens.active.first
    if email_token
      EmailToken.confirm(email_token.token)
    else
      self.active = true
      save
    end
  end

So essentially what this is doing is checking the first active email_token and if it exists, it confirms the token, EVEN if you don’t currently have it available (which via the API, you apparently can’t get at).

Two things to note about this process.

  1. I wouldn’t do other things outside of this code. It’s entirely possible for the expire_old_email_tokens to make things a bit screwy if you deactivate and activate the account more than once or don’t supply the right parameters the first time. It will basically break and you’ll need to validate the email address by sending it to them.
  2. There’s going to be a time-window here based on your installation that could potentially expire the tokens. Don’t wait too long between when you create the account and when you deactivate/activate the account. The default setting is 24 hours so it shouldn’t be a big deal if you’re doing this programmatically, but it’s something to think about.

On a side note, I really feel like if you’re programmatically creating a user, manually setting the password to a known value and are setting the account to ‘active’, the system should NOT be creating that token and forcing it to be confirmed. At the VERY least, there should be an additional parameter you can supply that would allow you to bypass the email validation on account creation.

6 Likes

@MikeTaber I’m trying to implement this now. I’ve run into the issue that when a new user is created via the api it creates a email Token that is already expired. So it cannot be confirmed. I’m trying to dig into why the email token is being created as confirmed: false, expired: true.

Did you encounter this? Any suggestions on a workaround?

What does my previous comment and solution to the problem not cover with regards to this new problem?

The issue I’m having is that when a user is created using the api gem, for some reason the email token generated is marked as expired = true so deactivating the user account and reactivating the user account does not fix the issue that the token is expired.

After tracing the issue. It appears expire_old_email_tokens on line 838 of the user model is running on and password_hash_changed? && !id_changed? is returning true, so the token is marked as expired before it can be activated.

D, [2015-10-08T13:49:03.416778 #6577] DEBUG -- :   SQL (1.0ms)  UPDATE "email_tokens" SET "expired" = 't' WHERE "email_tokens"."user_id" = $1 AND (not expired)  [["user_id", 18]]
D, [2015-10-08T13:49:03.422387 #6577] DEBUG -- :    (3.2ms)  COMMIT

Did you encounter this issue? Why would the password_hash_changed? be true if the password was not changed? Maybe because we are setting the password when we create the account?

I didn’t run into that particular issue but I can’t say I obsessed over it to be honest. I realized very quickly that even if this worked for me, I was going to have a ton of other problems because the API didn’t give me access to 95% of the other things I needed to be able to set on a user account. I ended up going the route of hiring someone to do a direct database import.

Sorry I can’t be more help here, but I didn’t really validate whether that function was actually returning true or not. I assumed it was, but didn’t check it in the debugger. I did notice that if I didn’t follow those three steps exactly, things wouldn’t work right. If I tried to log in with the user account in the middle, then other things happened which completely invalidated that process. It’s entirely possible that if someone hits a page while you’re in the middle of making those three calls, that other side effects happen.

Honestly, creating users via the API leave s a bit to be desired.

As I discussed, the issue is it appears expire_old_email_tokens on line 838 of the user model is running on and password_hash_changed? && !id_changed? is returning true, so the token is marked as expired before it can be activated.

I’m assuming this is because the I’m setting the password with the api call, and it’s going from non existent to being set as something.

Theoretically, you could create an account with a fake email address, deactivate, update the email address to the real email, then activate the account.

Unfortunately, this is too much of a workaround for the project we are working on. We have decided to settle with sending our users 2 emails. Not an ideal workflow.

Would love to see this addressed in the future. @codinghorror

Maybe @techapj can look into it if you can document the exact need and circumstances with an example?

1 Like

Is there a reason that username is required when creating a user through the gem/api? That puts the burden of ensuring unique usernames on me when creating users en masse.

This is not the right way to mass-create users.

Try the bulk invites system?

Hm, let me look into that. This is for a discussion forum that’s only accessible to a set group of users (who are already signed up and confirmed on the parent site). They currently just click a link, and they’re taken to the discussion forum and an account is automatically created.

I want to create users in bulk so that people will start receiving the emails about the forum activity, for those who have never clicked through to the forum. (This is because we’re currently using google groups for a mailing list for these people, and want to do an instant transition over to forums.)

Just getting back to this. We use SSO to login to Discourse from the parent site, so bulk invites isn’t going to work (according to the first sentence here Sending Bulk User Invites).

What is the best approach for me? We’ve got thousands of confirmed users on the parent site, but want the ones who haven’t yet clicked though to the Discourse forum to start receiving emails. (They’re already getting discussion email though Google Groups, which we are going to switch off soon).

1 Like

I had a similar problem, and just wrote a loop in a small ruby script to create all the users.

Hello Everyone,

I am able to create users like below but still have the manual step to approve them. Is this doable via the API GEM or curl?

 client.create_user(
   name: "Bruce Wayne",
   email: "bruce@wayne.com",
   username: "batman",
   password: "WhySoSerious",
   active: "true"
 )

A parameter like approved: "true" would have been extremely useful here.

1 Like

I looked into this and the best way to create a “pre-activated” (ready to login) user account is to first create a user and activate it just after the user is created.

Here is how to do it via official discourse_api gem:

# create user
user = client.create_user(
  name: "Bruce Wayne",
  email: "bruce@wayne.com",
  username: "batman",
  password: "WhySoSerious"
)

# activate user
client.activate(user["user_id"])

Here is how to do it via cURL request:

# create user
curl -X POST --data "name=dave&username=dave&email=dave@example.com&password=daveIsAwesome&api_key=x1y2z3&api_username=xyz" http://discourse.example.com/users

# activate user
curl -X PUT "http://discourse.example.com/admin/users/{USER_ID}/activate.json?api_key=x1y2z3&api_username=xyz"
9 Likes

@techAPJ: This does seem to work, but how can I then programmatically get a link that I can send to the newly created user so they can log right in and, ideally, jump to a specific post? This auto-created user won’t know his/her password and so won’t be able to log in. I assume it isn’t recommended to send them the password in plaintext.

I would make an api call that sends them a password reset email. This way you (1) verify their email, and (2) they get to choose their own password.

http://docs.discourse.org/#tag/Password-Reset%2Fpaths%2F~1session~1forgot_password%2Fpost

3 Likes

The thing I’m trying to accomplish is that they don’t get an email from a system that they don’t know anything about.

My forum is a tool for members of an organization. They know they joined the organization, and they get a manual email from someone else welcoming them.

But they don’t know anything about the forum. I want to create the user programmatically (from their membership data) and make a link that the welcoming committee can send as part of their welcome email. Currently, I create and approve the user, and I’m finding that almost no one understands the email that they get from the system, and they delete it. When we want them to join a conversation at the forum, I have to tell them about it and then manually regenerate the email. This is annoying and preventing smooth onboarding.

If they eventually try to log in from another device, or really do need to reset their password, by then they’re familiar with the software and will jump through the validation steps. But putting up that wall before they’ve gotten one second of benefit from the site is causing most people not to bother.