-
Notifications
You must be signed in to change notification settings - Fork 30.3k
feat(core): Add OIDC support for SSO #15988
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
base: master
Are you sure you want to change the base?
feat(core): Add OIDC support for SSO #15988
Conversation
05a9b19
to
e02da5e
Compare
1e58dbc
to
bb6adbd
Compare
Codecov ReportAttention: Patch coverage is 📢 Thoughts on this report? Let us know! |
7e3b338
to
d8cce48
Compare
d8cce48
to
4ec25f1
Compare
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.
cubic found 7 issues across 41 files. Review them in cubic.dev
React with 👍 or 👎 to teach cubic. Tag @cubic-dev-ai
to give specific feedback.
|
||
const onSSOLogin = async () => { | ||
const redirectUrl = ssoStore.isDefaultAuthenticationSaml |
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.
The awaited call to ssoStore.getSSORedirectUrl() is executed before the try / catch block, so any rejection that occurs will bypass the error handler and result in an uncaught promise rejection.
@@ -101,7 +101,7 @@ const onUserAction = (user: IUser, action: string) => | |||
<N8nActionToggle | |||
v-if=" | |||
!user.isOwner && | |||
user.signInType !== 'ldap' && | |||
!['ldap'].includes(user.signInType) && |
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.
OIDC users are not filtered out even though the PR introduces OIDC SSO; consequently action toggles will still be shown for OIDC accounts, likely violating the intended business rule (SSO accounts should be read-only).
!['ldap'].includes(user.signInType) && | |
!['ldap', 'oidc'].includes(user.signInType) && |
@@ -72,7 +72,9 @@ const isExternalAuthEnabled = computed((): boolean => { | |||
settingsStore.settings.enterprise.ldap && currentUser.value?.signInType === 'ldap'; | |||
const isSamlEnabled = | |||
settingsStore.isSamlLoginEnabled && settingsStore.isDefaultAuthenticationSaml; | |||
return isLdapEnabled || isSamlEnabled; | |||
const isOidcEnabled = | |||
settingsStore.settings.enterprise.oidc && currentUser.value?.signInType === 'oidc'; |
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.
Accessing settingsStore.settings.enterprise without optional chaining can throw if either settings or enterprise is undefined. Use optional chaining to avoid a runtime crash.
settingsStore.settings.enterprise.oidc && currentUser.value?.signInType === 'oidc'; | |
settingsStore.settings.enterprise?.oidc && currentUser.value?.signInType === 'oidc'; |
// OIDC | ||
// ---------------------------------------- | ||
|
||
if (this.licenseState.isOidcLicensed()) { |
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.
Any problem while importing or initializing the OIDC module will throw and crash the server because this block is not inside a try-catch, unlike the preceding SAML and subsequent Source Control initializations which handle errors gracefully.
@@ -148,6 +148,7 @@ export class AuthService { | |||
// TODO: Use an in-memory ttl-cache to cache the User object for upto a minute | |||
const user = await this.userRepository.findOne({ | |||
where: { id: jwtPayload.id }, | |||
relations: ['authIdentities'], |
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.
resolveJwt does not use authIdentities, so loading this relation on every authenticated request performs an unnecessary join and increases DB query cost.
@@ -110,7 +110,8 @@ export class User extends WithTimestamps implements IUser, AuthPrincipal { | |||
@AfterLoad() | |||
@AfterUpdate() | |||
computeIsPending(): void { | |||
this.isPending = this.password === null && this.role !== 'global:owner'; | |||
this.isPending = | |||
this.password === null && !this.authIdentities?.[0] && this.role !== 'global:owner'; |
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.
If authIdentities
was not explicitly loaded when the entity was fetched, it will be undefined
, so the condition !this.authIdentities?.[0]
evaluates to true
, incorrectly marking users with existing SSO identities as isPending
. This can lead to false positives whenever the relation is not eager-loaded or joined in the query.
return await makeRestApiRequest(context, 'POST', '/sso/oidc/config', data); | ||
}; | ||
|
||
export const initOidcLogin = async (context: IRestApiContext) => { |
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 exported function is the only one in the file without an explicit return-type annotation, so TypeScript infers Promise<any>
. This weakens type-safety for callers and is inconsistent with the rest of the API helpers.
export const initOidcLogin = async (context: IRestApiContext) => { | |
export const initOidcLogin = async (context: IRestApiContext): Promise<string> => { |
Summary
Implement OIDC support for single sign on.
Related Linear tickets, Github issues, and Community forum posts
closes https://linear.app/n8n/issue/PAY-2836/spike-implement-support-for-oidc-authentication-method-for-sso
Review / Merge checklist
release/backport
(if the PR is an urgent fix that needs to be backported)