Integration into custom auth system where emails are not unique?

That’s exactly what I had in mind as well.
I’l include the link to the topic Category Group Review/Moderation as I initially struggled to locate it through searching.
I always struggle to find documentation in the announcements category

2 Likes

Honestly, this is probably a good idea regardless, Admins have ALL the rights (download backups, see/change configuration secrets, see people’s PII, access to Personal Messages, …) and it’s best to keep this circle tight.

There’s a lot of room for “high privileges” without being full Admins.

2 Likes

Thank you both for this, I was not aware this was a separate thing!

Thank you for that clarification, I was not aware that this was the case. You’re right, we would definitely limit this anyway now that I know.

I’ve looked over the limits/requirements of this and passed it along to my team. It does unfortunately seem like we’d still have to self host. The FOSS hosting you are offering is very generous, we just simply don’t fit in the requirements.

The largest offender being the page view limits. 50k/m page views is likely more than enough for most FOSS projects, but we generate much more traffic than that. Our main website has gotten 1.87m total requests from 56.47k unique visitors over the past 7 days alone. I fear that we will easily reach the page view limit at this rate.

Thank you for pointing this out to me however!

4 Likes

It would probably be worth figuring out a way of dealing with this. If you know which users on your server are Discourse staff (admins or moderators), maybe set the SSO email field for those users to their actual email. Any duplicate email accounts that they have would get the fake email address.

There are a few cases where staff being able to receive emails from Discourse is useful. The first one you’re likely to run into is that Discourse provides a /u/admin-login route for staff users. That route displays a form that accepts a staff email address. Discourse will send a one use login link to the staff member. It’s handy for setting up SSO - if you accidentally lock yourself out of the site when setting it up, it will let you get back in. It’s also useful if there’s ever an issue with your authentication server.

1 Like

I agree, and it is something I’ve been thinking about (for reasons outside of Discourse). The big issue comes from the fact that regular members can, and have, become staff. So even if we required unique emails for staff users, we couldn’t guarantee that users have them which would cause issues when a user whose account has a duplicate email becomes a staff member.

That being said, now that it’s clear that DiscourseConnect first uses the unique ID for user lookups and the “Discourse uses emails to map external users to Discourse users” part of the DiscourseConnect post only refers to linking new SSO users to existing Discourse accounts, and my misunderstanding of how the login prompt works has been cleared up, is there anything actually preventing us from just sending duplicate email addresses as is? Or is this uniqueness strictly enforced?

Yes. I left out a step in my description of the login flow. Discourse will first try to find the user based on external_id, then try to find the user based on their email. If neither is found, Discourse will attempt to create the user. That’s how your users will initially get registered on Discourse. Discourse doesn’t allow for duplicate email addresses, so if an attempt is made to create a user with an email address that is already being used, Discourse will throw an error.

You’ll need to ensure that the emails sent in the payload are unique per user.

Understood, thank you for that clarification :+1: is it possible to manually update a user’s email at a later time? Now that the login prompt issue has been solved I may consider implementing the idea my team had before, using fake emails and an SMTP server we run that maps those fake emails to a user’s real email. For example we’d update everyone’s Discourse email to something like userid@example.com, which connects to our SMTP server first and takes the userid to look up the user’s real email. This isn’t something we’d have ready for some time however, so we’d need to update users’ Discourse emails later if possible.

Yes. You’ll need to enable the auth overrides email site setting for this. When enabled, a user’s Discourse email is synced with the email that included in the auth payload (the DiscourseConnect payload for your case) each time the user logs in. If it’s not enabled, the user’s email will be set to the auth payload’s email when the account is initially created, but not updated on subsequent logins.

Assuming auth overrides email is enabled, you can also update it without requiring users to login by making an API request to the sync_sso route: Sync DiscourseConnect user data with the sync_sso route.

You could also update user’s email addresses in bulk from the site’s Rails console, but (I think) doing it that way will trigger a confirmation email to be sent from Discourse to the user. That won’t work with fake email addresses.

Maybe you could just set the emails to something meaningful to begin with. Once you’ve got a Discourse site setup, you should do some tests to see what email domains Discourse will accept for fake emails. Going from memory, I think @invalid.com is accepted. I’m not sure about other domains. On your end, you could map something like <userId>@invalid.com to the user’s actual email address.

You have been nothing but incredibly helpful, thank you so much! The API solution is what I had in mind, but knowing it can sync itself works perfectly as well.

We could, yes. I was going to attempt to use plus-addressing first, that way at least some users would still get emails from the start. Then translate over to our own SMTP mapping server later to support everyone, including those who plus-addressing didn’t work for.

1 Like

@simon @supermathie You two have been incredibly helpful so far, I’m hoping I can step slightly out of the scope of the thread and ask for some followup help?

I’ve installed Discourse on a local machine for testing, using Install Discourse for development using Docker as my guide. I couldn’t seem to find any other guides on how to set it up for local testing? The wiki seems to only go over production setups, which requires having your domain/DNS/SMTP already set up. We did not want to expose the forum to the public until everything was implemented on our side, so we needed local testing where none of this was required.

I’ve got it up and running using that guide, and implemented the SSO on a local instance of our site, but I’ve run into 2 issues so far:

  1. The redirection to return_sso_url only seems to half work? In my case the URL is http://localhost:3000/session/sso_login. It does redirect successfully, however after the initial redirect it sends me to http://localhost:3000, which just displays the error RuntimeError: Discourse does not support compiling scss/sass files via Sprockets. The only thread I could find about this error is Error when building: discourse does not support compiling scss/sass files via sprockets, but that didn’t really seem to go anywhere. The OP did not accept any solution, and the only thing that happened was asking about RAM and swap sizes (the machine this is running on has 32gb of RAM and 2gb swap. So I doubt this is the issue?)
  2. avatar_force_update seems to not be respected? Or at least, not for admin users? I have enabled discourse connect overrides avatar in the site settings, and in the SSO response payload I am setting both avatar_url and avatar_force_update. But when logging in to the admin account (which is linked to my external account) it does not show my external profile picture? I can see that external_avatar_url is being set correctly when checking the admin user’s data through the API, it just doesn’t seem to be getting used in the UI?

There’s a list of installation methods here: Set up a local Discourse Development Environment?. I have a non-Docker development site (using the Ubuntu guide). If it’s possible for you to do, I think you’ll get the best results with the non-Docker approach. One of the reasons I use it is to not have to deal with networking issues for API requests between Discourse and other applications that I’m developing locally. It’s faster than Docker too.

It should be. Make sure that the application that you’re generating the SSO payload on isn’t converting the boolean value true to 1. That’s a common problem. To get around it, you can set any boolean values in the SSO payload to the strings "true" or "false". Discourse will interpret them correctly. Check that first to see if it’s the issue. It could be something else. The code that handles avatar_force_update is kind of complex, but readable: discourse/app/models/discourse_connect.rb at 187204705323b650d61ed25862eb1a0c733aa63c · discourse/discourse · GitHub.

Edit: For the issue of boolean values in the SSO payload, I guess it’s more accurate to say that in the process of generating the SSO payload, the environment is going to convert the boolean values true/false to strings. Discourse is expecting the strings to be "true" or "false", other programming environments may deal with them differently. For example:

PHP:

wp> strval(true)
=> string(1) "1"

as opposed to Ruby:

irb(main):001> true.to_s
=> "true"

Python (I’m not sure how Discourse handles this one):

>>> str(True)
'True'
2 Likes

I’ll give one of these a shot. Thank you for pointing me in that direction. That’s fairly counterintuitive though. Typically people look for the Docker solution as the “quick and easy” method to set things up. This is the first instance I’ve had where it was recommended to avoid the Docker version. Though it still begs the question of why this happens with the Docker version? Using another installation method to get around the issue works for now, but it doesn’t solve the underlying issue.

It definitely isn’t being set to 1. I’m working in JavaScript, and the boolean value true will always stringify into the string "true":

String(true); // 'true'

(true).toString(); // 'true'

// This is what my code is actually using
querystring.stringify({
	avatar_force_update: true
}); // 'avatar_force_update=true'

I’ve never worked with Ruby before, so not sure how far I’ll get actually digging into this. But just at a glance I’m not seeing any issues? However I have verified that both the relevant setting in the Discourse dashboard and in the SSO payload are set:

Screenshot from 2024-05-05 20-52-06
Screenshot from 2024-05-05 20-53-34

I have not tested trying to change the image of a standard user account from something other than what the account was created with, but when I logged in to a standard user account for the first time Discourse did download the avatar, and did apply it as expected. The only account not being updated is the admin account?

To be fair, in PHP you’d almost always want to use var_export to get the “real” string representation of a variable, since it returns the valid PHP needed to recreate said variable:

var_export(true); // true
1 Like

Have a look at the DiscourseConnect section that’s at the bottom of the user’s admin page. There’s a button you can click on that will expand to show the values of the last SSO payload that was received by Discourse.

You can also find the same information from the Rails console. For example:

>SingleSignOnRecord.last

# or
>SingleSignOnRecord.where(external_id: 2)

You should enable the verbose discourse connect logging site setting. That adds some additional details to the logs that are generated at Admin / Logs / Error Logs. For the avatar issue, it’s possible the relevant log entries will show up as normal error logs, not the DiscourseConnect logs.

Possibly the issue is specific to WordPress. It always returns 1 for true and "1" for the string representation of true.

1 Like

It’s the expected payload, and the profile picture URL is correctly displayed:

Screenshot from 2024-05-06 09-57-57

The profile picture SHOULD be this:

normal_face

However it still shows as:

Screenshot from 2024-05-06 09-58-21
Screenshot from 2024-05-06 10-02-21

Which is my Gravatar:

Screenshot from 2024-05-06 10-02-50

Unless I’m misunderstanding this setting, or unless there’s something else I also need to set, I have enabled the setting to override these:

Screenshot from 2024-05-06 10-05-42

I have also tried this in incognito mode, different browsers, and clearing my browser cache just to rule out a caching issue.

This is on your local dev site?

I’m wondering if uploading the avatar failed for some reason. Try enabling the verbose discourse connect logging site setting, then logging out and logging in again. There should be at least one message in the logs related to the avatar:

Possibly there will be another error related to the upload failing.

In case you haven’t already figured it out, this means: “you must access via the ember-cli proxy instead of a browser directly”.

Use bin/ember-cli -u to launch it for development.

I duplicated your avatar situation:

before:

login payload:

after:

but:

I think this is a bug where Discourse correctly sets the custom avatar URL but doesn’t switch from the Gravatar to the custom picture.

If I create a new user:

it works right away:

image

so I suspect this is triggered by having an account with a gravatar set prior to using DiscourseConnect.

2 Likes

I managed to get the Ubuntu steps to work (after quite a bit of tweaking, as the installation script was conflicting with packages and software I already have installed). That fixed the original error I had but SSO still only half works. Now instead of the scss/sass error, SSO gives Account login timed out, please try logging in again. every time. Clicking the Login button a second time while on this error page completes the login, however. No code changes happened on my end, only the way Discourse is running.

I’m going to just chalk this all up to quirks of running this locally. I hope that a production deployment does not have these same issues.

This didn’t seem to do anything with the Docker version. The script would exit with no output, and nothing seemed to really happen on the Discourse side. That also goes against the steps listed in the Docker guide, which feels weird. Is there a reason this isn’t mentioned there?

It feels odd to me that the Docker method seems to have these issues, and that the official advice is to install Discourse natively (despite the installation script not always working out of the box, since it assumes things like the use of bashrc and will attempt to install potentially conflicting versions of packages already installed)? Should there maybe be a warning about the potential issues with all the available hosting versions? Just at a glance it’s not clear which is the one to go with, and typically people assume that if a Docker build is available then that should be the go-to.

Glad to hear it’s not just me! :smiley: Bugs are never fun but I’m glad to have helped find this one.

That may be the case, but you should be able to get DiscourseConnect working locally without any issues. Are you accessing Discourse at http://localhost:4200 ? There’s a weird (to my mind) issue where Discourse can be accessed at either localhost:4200 or 127.0.0.1:4200 in a local dev environment. I’d stick with the localhost domain. It’s treated as a different session than 127.0.0.1.

Anyway, that’s just a guess about what’s going on. Make sure to enable the verbose logging setting and look at the logs for details.

The installation instructions should make it clear that you don’t need to run the script. You can just go through the script step by step to make sure all dependencies are installed.

I am yes.

The ones linked earlier do not make this clear. I went to Set up a local Discourse Development Environment?, and selected Install Discourse on Ubuntu or Debian for Development as I am running Ubuntu 22.04.3. This page does say that you don’t have to run the entire script, but only in small text after the part that instructs users to run it.

To me this was not clear, since when reading the instructions top-to-bottom one would naturally assume they have to finish the current step before continuing. So I fought the installation script, only installing what I needed, only to then continue reading after finishing to see that that was intended the whole time and I didn’t have to actually fight with anything.

Putting that disclaimer after the instructions definitely doesn’t make them clear, imo.

1 Like

It seems like it believes the nonce is incorrect? I’m seeing this in the logs now when logging in:

Can't verify CSRF token authenticity. 3:44 pm
Verbose SSO log: Started SSO process add_groups: admin: avatar_force_update: avatar_url: bio: card_background_url: confirmed_2fa: email: external_id: failed: groups: locale: locale_force_ 3:44 pm
Verbose SSO log: Nonce is incorrect, was generated in a different browser session, or has expired add_groups: admin: avatar_force_update: true avatar_url: https://pretendo-cdn.b-cdn.net/mii/1424784 3:44 pm
Verbose SSO log: Started SSO process add_groups: admin: avatar_force_update: avatar_url: bio: card_background_url: confirmed_2fa: email: external_id: failed: groups: locale: locale_force_ 3:44 pm
Verbose SSO log: User was logged on PN_Jon add_groups: admin: avatar_force_update: true avatar_url: https://pretendo-cdn.b-cdn.net/mii/1424784406/normal_face.png bio: card_background_url: confirm 3:44 pm
MaxMindDB (/home/jon/discourse/vendor/data/GeoLite2-City.mmdb) could not be found: No such file or directory @ rb_sysopen - /home/jon/discourse/vendor/data/GeoLite2-City.mmdb 3:44 pm
MaxMindDB (/home/jon/discourse/vendor/data/GeoLite2-ASN.mmdb) could not be found: No such file or directory @ rb_sysopen - /home/jon/discourse/vendor/data/GeoLite2-ASN.mmdb 3:44 pm

Upon further inspection it seems it’s because the SSO redirect is not using localhost. It’s sending me back to 127.0.0.1. If I swap from using localhost to using 127.0.0.1 initially then the issue is fixed.