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.
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?
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.
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.
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.
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.
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.
@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?
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.
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
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.
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).
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"
@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.
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.