-
Notifications
You must be signed in to change notification settings - Fork 334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SSO support #2578
SSO support #2578
Conversation
Please note that this PR depends on the latest lemmy-js-client (which doens't currently have a published version to use) |
ff5f058
to
26af93f
Compare
@dessalines The issues can be seen in the linting results. Some of the changes are related to the following changes amongst others: We will wait until these are addressed in main (with the latest lemmy-js-client) before rebasing this PR. |
This just needs a new lemmy-js-client deploy then to account for those changes? I've just pushed up EDIT: Oh I gotcha now, I've fixed those broken ones in #2695 . You can either merge from that to pass tests, or just wait until that gets merged soon. |
I'm gonna give this a local lookover once that's done. |
We'll wait until the other PR is merged to complete this PR in one shot. |
26af93f
to
7e23d5c
Compare
Co-authored-by: Anthony Lawn <[email protected]>
7e23d5c
to
452f7f2
Compare
@SleeplessOne1917 we integrated the latest fixes from main and passed linting checks. This PR is ready for review. |
@@ -250,7 +258,7 @@ export class AdminSettings extends Component< | |||
> | |||
<div className="row"> | |||
<TaglineForm | |||
taglines={this.state.siteRes.taglines} | |||
taglines={this.state.siteRes.taglines || []} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it wasn't necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We added this today because we were getting taglines
as undefined
and map
function was being called on it.
const newOAuthProvider = res.data; | ||
this.setState(s => { | ||
s.siteRes.admin_oauth_providers = ( | ||
s.siteRes.admin_oauth_providers || [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of these || []
are probably unecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both oauth_providers and admin_oauth_providers are optional.
export interface GetSiteResponse {
// ...
oauth_providers?: Array<PublicOAuthProvider>;
admin_oauth_providers?: Array<OAuthProvider>;
}
); | ||
if (index >= 0) { | ||
Object.assign( | ||
s.siteRes.admin_oauth_providers[index] || {}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I greatly doubt this Object.assign is necessary. Check out the editListImmutable
function we have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
editListImmutable
seems to require a key in every object. Example:
let list = [{ provider: { id: 1 } }, { provider: { id: 2 } }];
let item = { provider: { id: 2, new: value } };
In our case, the data structure is:
let siteRes = { admin_oauth_providers: [{id: 1}, {id: 2}] };
let item = { id: 2, new: value };
page: 1 + Math.floor(index / this.itemsPerPage), | ||
loading: false, | ||
})), | ||
customEmojis: (this.isoData.site_res.custom_emojis || []).map( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change was probably inadvertant also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one also we were getting undefined
as custom_emojis
.
@@ -52,6 +57,9 @@ async function handleLoginSuccess(i: Login, loginRes: LoginResponse) { | |||
|
|||
if (site.state === "success") { | |||
UserService.Instance.myUserInfo = site.data.my_user; | |||
const isoData = setIsoData(i.context); | |||
isoData.site_res.oauth_providers = site.data.oauth_providers; | |||
isoData.site_res.admin_oauth_providers = site.data.admin_oauth_providers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really strange. IsoData is only for the initial fetch, and you should just use siteRes if you need these providers afterward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's been a while since we wrote this... We needed to update the the site_res data because the oauth_providers
from site_res changes depending on whether the user is logged in as an admin or not.
When you first load the page (not authenticated), you only get the public oauth_provider data needed to display the SSO login buttons. If you log in as an admin, we override this data. If you log out we override the data too.
That said this is not critical data (it does not include any secrets).
async componentDidMount() { | ||
// store state in local storage | ||
const local_oauth_state = JSON.parse( | ||
localStorage.getItem("oauth_state") || "{}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go through and get rid of all these || {}
, they're not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is different from the rest. In this case, we're checking LocalStorage
which could return null
. JSON.parse
can handle null
in javascript but in TS it requires a string.
@@ -98,7 +100,7 @@ class PasswordInput extends Component<PasswordInputProps, PasswordInputState> { | |||
autoComplete={isNew ? "new-password" : "current-password"} | |||
onInput={onInput} | |||
value={value} | |||
required | |||
required={required !== false} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you using required !== false
because you want it to default to true even if it's undefined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. We wanted to keep it as required by default and add the ability to disable it when needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the suggested code changes, I think the UI for adding OAuth providers in the admin settings needs some work. The text inputs are very wide when I don't think they need to be. I think it would be best to display them in 2 columns except on mobile, where they can all be one column.
For the OAuth ID claim, when adding the Privacy Portal preset, the value is "sub". I have no idea what this means. More generally, I think an explanation of what all of those fields do would be helpful, whether that takes the form of help buttons on the input labels that give a description of them, an info box on the page that explains how to set things up, or even a link to a third party resource that can teach the user how to do it.
Finally, for the Oauth Scopes field, I see for the Privacy Portal preset the input has 2 words separated by a space: "openid" and "email". Are these supposed to be selections from a limited set of values? If so, I think a fieldset with checkboxes for those options would be best. If not, I'll have to think of something else.
Since the change to the UI could enc up being a lot, I'll gladly take a stab at it once you implement the code changes me and Dessalines suggested.
if ( | ||
!this.props.state || | ||
!this.props.code || | ||
!local_oauth_state?.state || | ||
!local_oauth_state?.oauth_provider_id || | ||
!local_oauth_state?.expires_at || | ||
this.props.state !== local_oauth_state.state || | ||
local_oauth_state.expires_at < Date.now() | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given all of the negations, I think it would be good to DeMorgan this:
if ( | |
!this.props.state || | |
!this.props.code || | |
!local_oauth_state?.state || | |
!local_oauth_state?.oauth_provider_id || | |
!local_oauth_state?.expires_at || | |
this.props.state !== local_oauth_state.state || | |
local_oauth_state.expires_at < Date.now() | |
) { | |
if ( | |
!(this.props.state && | |
this.props.code && | |
local_oauth_state?.state && | |
local_oauth_state?.oauth_provider_id && | |
local_oauth_state?.expires_at && | |
this.props.state === local_oauth_state.state) || | |
local_oauth_state.expires_at < Date.now() | |
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels a bit cleaner not to have a parenthesis. But we can update it if you have a strong opinion on it.
[ | ||
this.titleName(siteView), | ||
...(oauth_provider !== undefined | ||
? [`(${oauth_provider.display_name})`] | ||
: []), | ||
].join(" ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be written as:
[ | |
this.titleName(siteView), | |
...(oauth_provider !== undefined | |
? [`(${oauth_provider.display_name})`] | |
: []), | |
].join(" ") | |
[ | |
this.titleName(siteView), | |
...(oauth_provider | |
? [`(${oauth_provider.display_name})`] | |
: []), | |
].join(" ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
return handleUseOAuthProvider({ | ||
i: undefined, | ||
index: undefined, | ||
oauth_provider: oauthProvider, | ||
username, | ||
answer, | ||
show_nsfw, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be:
return handleUseOAuthProvider({ | |
i: undefined, | |
index: undefined, | |
oauth_provider: oauthProvider, | |
username, | |
answer, | |
show_nsfw, | |
}); | |
return handleUseOAuthProvider({ | |
oauth_provider: oauthProvider, | |
username, | |
answer, | |
show_nsfw, | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
@privacyguard Thanks for doing the code changes. I'm going to be tweaking the UI for this PR a bit and should have my own PR out either Sunday or Monday. I do still need some questions answered. In case you missed them (I made a lot of review comments so they might have gotten buried):
|
@SleeplessOne1917 perfect. Concerning the documentation for all the fields: comments from Lemmy. Concerning the Concerning the |
While working on this, I came across something about the DTOs that can be tweaked to make it easier for clients: LemmyNet/lemmy#5046 When that PR is merged, I'll generate the types for the JS client and make a PR for that. Then I'll get back to the changes that I made branching off of this PR's branch. The changes I'm making to the frontend diverge from this PR enough that I'll most likely make my PR merge directlly into main instead of the branch this PR uses. |
I ended up getting a bit sidetracked on this due to the backend PR for the oauth struct serde serialization and juggling some PRs using the new js client version. With that stuff out of the way, my PR should be up some time this week. |
Closing in favor of SSO Support - alternate UI |
Implements LemmyNet/lemmy#2930.
This PR is based on LemmyNet/lemmy#4238 and #2278 by @thepaperpilot.
We noticed that the original PR is outdated and has a lot of conflicts with the recent changes. We tried to keep the previous commits whenever possible (in lemmy-js-client and lemmy-ui).
How is works?
Admins can configure external OIDC providers from within the admin settings.
Once an OIDC provider is configured, users will be able to Sign In / Sign Up using external OIDC providers.
Available Configuration
The usual OIDC endpoints
auto_verify_email: When enabled, users signing up using OIDC won't need to go through email verification.
auto_approve_application: When enabled, users signing up using OIDC won't need manual approval even if applications are required.
account_linking_enabled: When enabled, users attempting with sign up with OIDC using an existing user email would link the OIDC account to the existing user.
Disclaimer
This is our first ever rust contribution.
Who we are? Why are we contributing to Lemmy?
Privacy Portal is an OIDC provider and an email aliasing service focused on privacy. We have decided to contribute to select open source projects that empower Free Speech online.
Our OIDC provider services are currently offered free of charge. In the future, we will have a generous free plan that will cover most deployments.
Using Privacy Portal as your OIDC provider offers your users great privacy benefits. User emails will automatically get replaced by single-purpose Privacy Aliases during sign up. Users will be able to enter any name (to be used as username). Users can benefit from email encryption and much more.