0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-07 23:01:25 -05:00

feat(core,console): add trust unverified email settings to Azure SSO (#6800)

add trust unverified email settings to Azure OIDC SSO connector
This commit is contained in:
simeng-li 2024-11-13 10:28:58 +08:00 committed by GitHub
parent 64e4b08b25
commit 4e826deabe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 72 additions and 8 deletions

View file

@ -2,9 +2,11 @@ import { SsoProviderName } from '@logto/schemas';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { isDevFeaturesEnabled } from '@/consts/env';
import CopyToClipboard from '@/ds-components/CopyToClipboard';
import FormField from '@/ds-components/FormField';
import InlineNotification from '@/ds-components/InlineNotification';
import Switch from '@/ds-components/Switch';
import TextInput from '@/ds-components/TextInput';
import { uriValidator } from '@/utils/validator';
@ -83,6 +85,17 @@ function OidcMetadataForm({ providerConfig, config, providerName }: Props) {
<FormField title="enterprise_sso.metadata.oidc.scope_field_name">
<TextInput {...register('scope')} error={Boolean(errors.scope)} />
</FormField>
{isDevFeaturesEnabled && providerName === SsoProviderName.AZURE_AD_OIDC && (
<FormField
title="enterprise_sso_details.trust_unverified_email"
tip={t('enterprise_sso_details.trust_unverified_email_tip')}
>
<Switch
label={t('enterprise_sso_details.trust_unverified_email_label')}
{...register('trustUnverifiedEmail')}
/>
</FormField>
)}
</>
);
}

View file

@ -20,6 +20,8 @@ export const oidcConnectorConfigGuard = z
clientSecret: z.string(),
issuer: z.string(),
scope: z.string().optional(),
// The following fields are only available for EntraID (OIDC) connector
trustUnverifiedEmail: z.boolean().optional(),
})
.partial();

View file

@ -2,18 +2,56 @@ import { SsoProviderName, SsoProviderType } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import camelcaseKeys from 'camelcase-keys';
import { decodeJwt } from 'jose';
import { z } from 'zod';
import assertThat from '#src/utils/assert-that.js';
import OidcConnector from '../OidcConnector/index.js';
import { fetchToken, getIdTokenClaims, getUserInfo } from '../OidcConnector/utils.js';
import { OidcSsoConnector } from '../OidcSsoConnector/index.js';
import { type SingleSignOnFactory } from '../index.js';
import { SsoConnectorError, SsoConnectorErrorCodes } from '../types/error.js';
import { type SingleSignOnConnectorData, type SingleSignOn } from '../types/connector.js';
import {
SsoConnectorConfigErrorCodes,
SsoConnectorError,
SsoConnectorErrorCodes,
} from '../types/error.js';
import { basicOidcConnectorConfigGuard } from '../types/oidc.js';
import { type ExtendedSocialUserInfo } from '../types/saml.js';
import { type SingleSignOnConnectorSession } from '../types/session.js';
export class AzureOidcSsoConnector extends OidcSsoConnector {
export const azureOidcConnectorConfigGuard = basicOidcConnectorConfigGuard.extend({
trustUnverifiedEmail: z.boolean().optional(),
});
export class AzureOidcSsoConnector extends OidcConnector implements SingleSignOn {
private readonly trustUnverifiedEmail: boolean;
constructor(readonly data: SingleSignOnConnectorData) {
const parseConfigResult = azureOidcConnectorConfigGuard.safeParse(data.config);
if (!parseConfigResult.success) {
throw new SsoConnectorError(SsoConnectorErrorCodes.InvalidConfig, {
config: data.config,
message: SsoConnectorConfigErrorCodes.InvalidConnectorConfig,
error: parseConfigResult.error.flatten(),
});
}
const { trustUnverifiedEmail, ...oidcConfig } = parseConfigResult.data;
super(oidcConfig);
this.trustUnverifiedEmail = trustUnverifiedEmail ?? false;
}
async getConfig() {
return this.getOidcConfig();
}
async getIssuer() {
return this.issuer;
}
/**
* Handle the sign-in callback from the OIDC provider and return the user info
*
@ -67,10 +105,13 @@ export class AzureOidcSsoConnector extends OidcSsoConnector {
id,
...conditional(name && { name }),
...conditional(picture && { avatar: picture }),
...conditional(email && email_verified && { email }),
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
...conditional(email && (email_verified || this.trustUnverifiedEmail) && { email }),
...conditional(phone && phone_verified && { phone }),
...camelcaseKeys(rest),
...conditional(email && !email_verified && { unverifiedEmail: email }),
...conditional(
email && !email_verified && !this.trustUnverifiedEmail && { unverifiedEmail: email }
),
...conditional(phone && !phone_verified && { unverifiedPhone: phone }),
};
}
@ -106,6 +147,6 @@ export const azureOidcSsoConnectorFactory: SingleSignOnFactory<SsoProviderName.A
name: {
en: 'Microsoft Entra ID (OIDC)',
},
configGuard: basicOidcConnectorConfigGuard,
configGuard: azureOidcConnectorConfigGuard,
constructor: AzureOidcSsoConnector,
};

View file

@ -2,7 +2,10 @@ import { type I18nPhrases } from '@logto/connector-kit';
import { type SsoProviderType, type SsoProviderName } from '@logto/schemas';
import { type AzureAdSsoConnector } from '../AzureAdSsoConnector/index.js';
import { type AzureOidcSsoConnector } from '../AzureOidcSsoConnector/index.js';
import {
type azureOidcConnectorConfigGuard,
type AzureOidcSsoConnector,
} from '../AzureOidcSsoConnector/index.js';
import {
type googleWorkspaceSsoConnectorConfigGuard,
type GoogleWorkspaceSsoConnector,
@ -29,7 +32,7 @@ export type SingleSignOnConnectorConfig = {
[SsoProviderName.AZURE_AD]: typeof samlConnectorConfigGuard;
[SsoProviderName.GOOGLE_WORKSPACE]: typeof googleWorkspaceSsoConnectorConfigGuard;
[SsoProviderName.OKTA]: typeof basicOidcConnectorConfigGuard;
[SsoProviderName.AZURE_AD_OIDC]: typeof basicOidcConnectorConfigGuard;
[SsoProviderName.AZURE_AD_OIDC]: typeof azureOidcConnectorConfigGuard;
};
export type SingleSignOnFactory<T extends SsoProviderName> = {

View file

@ -109,6 +109,11 @@ const enterprise_sso_details = {
auth_params_tooltip:
'Additional parameters to be passed in the authorization request. By default only (openid profile) scopes will be requested, you can specify additional scopes or a exclusive state value here. (e.g., { "scope": "organizations email", "state": "secret_state" }).',
},
trust_unverified_email: 'Trust unverified email',
trust_unverified_email_label:
'Always trust the unverified email addresses returned from the identity provider',
trust_unverified_email_tip:
'The Entra ID (OIDC) connector does not return the `email_verified` claim, meaning that email addresses from Azure are not guaranteed to be verified. By default, Logto will not sync unverified email addresses to the user profile. Enable this option only if you trust all the email addresses from the Entra ID directory.',
};
export default Object.freeze(enterprise_sso_details);