From 9da980c62c8266d8920a58e7452f4ccb3e047b6c Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 19 Mar 2024 15:08:43 +0800 Subject: [PATCH] refactor(core): refactor oidc sso connector refactor oidc sso connector --- packages/core/src/sso/OidcConnector/index.ts | 30 ++++++++--- .../core/src/sso/OktaSsoConnector/index.ts | 50 +------------------ 2 files changed, 23 insertions(+), 57 deletions(-) diff --git a/packages/core/src/sso/OidcConnector/index.ts b/packages/core/src/sso/OidcConnector/index.ts index f0c3d3d7b2c..32e7205dc93 100644 --- a/packages/core/src/sso/OidcConnector/index.ts +++ b/packages/core/src/sso/OidcConnector/index.ts @@ -1,7 +1,11 @@ import { generateStandardId } from '@logto/shared/universal'; import { conditional } from '@silverhand/essentials'; +import camelcaseKeys from 'camelcase-keys'; import snakecaseKeys from 'snakecase-keys'; +import assertThat from '#src/utils/assert-that.js'; + +import { SsoConnectorError, SsoConnectorErrorCodes } from '../types/error.js'; import { type BaseOidcConfig, type BasicOidcConnectorConfig, @@ -13,7 +17,7 @@ import { type CreateSingleSignOnSession, } from '../types/session.js'; -import { fetchOidcConfig, fetchToken, getIdTokenClaims } from './utils.js'; +import { fetchOidcConfig, fetchToken, getIdTokenClaims, getUserInfo } from './utils.js'; /** * OIDC connector @@ -91,8 +95,7 @@ class OidcConnector { * @param data unknown oidc authorization response * @param connectorSession The connector session data from the oidc provider session storage * @returns The user info from the OIDC provider - * @remark Forked from @logto/oidc-connector - * + */ async getUserInfo( connectorSession: SingleSignOnConnectorSession, @@ -102,18 +105,29 @@ class OidcConnector { const { nonce, redirectUri } = connectorSession; // Fetch token from the OIDC provider using authorization code - const { idToken } = await fetchToken(oidcConfig, data, redirectUri); + const { idToken, accessToken } = await fetchToken(oidcConfig, data, redirectUri); + + assertThat( + accessToken, + new SsoConnectorError(SsoConnectorErrorCodes.AuthorizationFailed, { + message: 'The access token is missing from the response.', + }) + ); + + // Verify the id token and get the user id + const { sub: id } = await getIdTokenClaims(idToken, oidcConfig, nonce); - // Decode and verify the id token - const { sub, name, picture, email, email_verified, phone, phone_verified } = - await getIdTokenClaims(idToken, oidcConfig, nonce); + // Fetch user info from the userinfo endpoint + const { sub, name, picture, email, email_verified, phone, phone_verified, ...rest } = + await getUserInfo(accessToken, oidcConfig.userinfoEndpoint); return { - id: sub, + id, ...conditional(name && { name }), ...conditional(picture && { avatar: picture }), ...conditional(email && email_verified && { email }), ...conditional(phone && phone_verified && { phone }), + ...camelcaseKeys(rest), }; } } diff --git a/packages/core/src/sso/OktaSsoConnector/index.ts b/packages/core/src/sso/OktaSsoConnector/index.ts index 1d96558bee2..27c04b8c6e0 100644 --- a/packages/core/src/sso/OktaSsoConnector/index.ts +++ b/packages/core/src/sso/OktaSsoConnector/index.ts @@ -1,60 +1,12 @@ import { SsoProviderName } from '@logto/schemas'; -import { conditional } from '@silverhand/essentials'; -import camelcaseKeys from 'camelcase-keys'; -import assertThat from '#src/utils/assert-that.js'; - -import { fetchToken, getUserInfo, getIdTokenClaims } from '../OidcConnector/utils.js'; import { OidcSsoConnector } from '../OidcSsoConnector/index.js'; import { type SingleSignOnFactory } from '../index.js'; -import { 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'; import { logoBase64, logoDarkBase64 } from './consts.js'; -export class OktaSsoConnector extends OidcSsoConnector { - /** - * Override the getUserInfo method from the OidcSsoConnector class - * - * @remark Okta's IdToken does not include the sufficient user claims like email_verified, phone_verified, etc. {@link https://devforum.okta.com/t/email-verified-claim/3516/2} - * This method will fetch the user info from the userinfo endpoint instead. - */ - override async getUserInfo( - connectorSession: SingleSignOnConnectorSession, - data: unknown - ): Promise { - const oidcConfig = await this.getOidcConfig(); - const { nonce, redirectUri } = connectorSession; - - // Fetch token from the OIDC provider using authorization code - const { idToken, accessToken } = await fetchToken(oidcConfig, data, redirectUri); - - assertThat( - accessToken, - new SsoConnectorError(SsoConnectorErrorCodes.AuthorizationFailed, { - message: 'The access token is missing from the response.', - }) - ); - - // Verify the id token and get the user id - const { sub: id } = await getIdTokenClaims(idToken, oidcConfig, nonce); - - // Fetch user info from the userinfo endpoint - const { sub, name, picture, email, email_verified, phone, phone_verified, ...rest } = - await getUserInfo(accessToken, oidcConfig.userinfoEndpoint); - - return { - id, - ...conditional(name && { name }), - ...conditional(picture && { avatar: picture }), - ...conditional(email && email_verified && { email }), - ...conditional(phone && phone_verified && { phone }), - ...camelcaseKeys(rest), - }; - } -} +export class OktaSsoConnector extends OidcSsoConnector {} export const oktaSsoConnectorFactory: SingleSignOnFactory = { providerName: SsoProviderName.OKTA,