mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
feat(core,experience): implement google workspace connector (#4925)
* feat(core,experience): implement google workspace connector implement google workspace connector * fix(core): clean code clean code * fix(core): fix ut fix ut * fix(core): fix ut fix ut * fix(test): fix integration tests fix integration tests * fix(core): make the sso data as public property and remove getter make the sso data as public perperty and remove getter
This commit is contained in:
parent
4b90782ae0
commit
f880329d16
14 changed files with 153 additions and 59 deletions
|
@ -26,6 +26,7 @@ const insertUserSsoIdentityMock = jest.fn();
|
|||
const updateUserMock = jest.fn();
|
||||
const findUserByEmailMock = jest.fn();
|
||||
const insertUserMock = jest.fn();
|
||||
const generateUserIdMock = jest.fn().mockResolvedValue('foo');
|
||||
const storeInteractionResultMock = jest.fn();
|
||||
const getAvailableSsoConnectorsMock = jest.fn();
|
||||
const getSingleSignOnSessionResultMock = jest.fn();
|
||||
|
@ -79,6 +80,7 @@ describe('Single sign on util methods tests', () => {
|
|||
{
|
||||
users: {
|
||||
insertUser: insertUserMock,
|
||||
generateUserId: generateUserIdMock,
|
||||
},
|
||||
ssoConnectors: {
|
||||
getAvailableSsoConnectors: getAvailableSsoConnectorsMock,
|
||||
|
|
|
@ -304,7 +304,7 @@ const registerWithSsoAuthentication = async (
|
|||
// Insert new user
|
||||
const { id: userId } = await usersLibrary.insertUser(
|
||||
{
|
||||
id: generateStandardId(),
|
||||
id: await usersLibrary.generateUserId(),
|
||||
...syncingProfile,
|
||||
lastSignInAt: Date.now(),
|
||||
},
|
||||
|
|
|
@ -94,7 +94,7 @@ describe('fetchConnectorProviderDetails', () => {
|
|||
providerLogo: ssoConnectorFactories[connector.providerName].logo,
|
||||
providerConfig: {
|
||||
...connector.config,
|
||||
scope: 'openid', // Default scope
|
||||
scope: 'openid profile email', // Default scope
|
||||
tokenEndpoint: 'http://example.com/token',
|
||||
},
|
||||
})
|
||||
|
|
17
packages/core/src/sso/AzureAdSsoConnector/index.ts
Normal file
17
packages/core/src/sso/AzureAdSsoConnector/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { SsoProviderName } from '@logto/schemas';
|
||||
|
||||
import { SamlSsoConnector } from '../SamlSsoConnector/index.js';
|
||||
import { type SingleSignOnFactory } from '../index.js';
|
||||
import { samlConnectorConfigGuard } from '../types/saml.js';
|
||||
|
||||
export class AzureAdSsoConnector extends SamlSsoConnector {}
|
||||
|
||||
export const azureAdSsoConnectorFactory: SingleSignOnFactory<SsoProviderName.AZURE_AD> = {
|
||||
providerName: SsoProviderName.AZURE_AD,
|
||||
logo: 'https://logtoeu.blob.core.windows.net/public-blobs/risa4g/aAYaRZOiGoxS/2023/09/03/zqdr28er/azure.png',
|
||||
description: {
|
||||
en: 'This connector is used to connect with Azure AD Single Sign-On.',
|
||||
},
|
||||
configGuard: samlConnectorConfigGuard,
|
||||
constructor: AzureAdSsoConnector,
|
||||
};
|
54
packages/core/src/sso/GoogleWorkspaceSsoConnector/index.ts
Normal file
54
packages/core/src/sso/GoogleWorkspaceSsoConnector/index.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
|
||||
import { type SsoConnector, SsoProviderName } from '@logto/schemas';
|
||||
|
||||
import OidcConnector from '../OidcConnector/index.js';
|
||||
import { type SingleSignOnFactory } from '../index.js';
|
||||
import { type SingleSignOn } from '../types/index.js';
|
||||
import { basicOidcConnectorConfigGuard } from '../types/oidc.js';
|
||||
|
||||
// Google use static issue endpoint.
|
||||
const googleIssuer = 'https://accounts.google.com';
|
||||
|
||||
export class GoogleWorkspaceSsoConnector extends OidcConnector implements SingleSignOn {
|
||||
static googleIssuer = googleIssuer;
|
||||
|
||||
constructor(readonly data: SsoConnector) {
|
||||
const parseConfigResult = googleWorkspaceSsoConnectorConfigGuard.safeParse(data.config);
|
||||
|
||||
if (!parseConfigResult.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, parseConfigResult.error);
|
||||
}
|
||||
|
||||
super({
|
||||
...parseConfigResult.data,
|
||||
issuer: googleIssuer,
|
||||
});
|
||||
}
|
||||
|
||||
// OIDC connector doesn't have additional properties.
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
getProperties = () => undefined;
|
||||
|
||||
async getConfig() {
|
||||
return this.getOidcConfig();
|
||||
}
|
||||
|
||||
async getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
}
|
||||
|
||||
export const googleWorkspaceSsoConnectorConfigGuard = basicOidcConnectorConfigGuard.omit({
|
||||
issuer: true,
|
||||
});
|
||||
|
||||
export const googleWorkSpaceSsoConnectorFactory: SingleSignOnFactory<SsoProviderName.GOOGLE_WORKSPACE> =
|
||||
{
|
||||
providerName: SsoProviderName.GOOGLE_WORKSPACE,
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMy41MiAxMi4yNzI5QzIzLjUyIDExLjQyMiAyMy40NDM2IDEwLjYwMzggMjMuMzAxOCA5LjgxODM2SDEyVjE0LjQ2MDJIMTguNDU4MkMxOC4xOCAxNS45NjAyIDE3LjMzNDUgMTcuMjMxMSAxNi4wNjM2IDE4LjA4MlYyMS4wOTI5SDE5Ljk0MThDMjIuMjEwOSAxOS4wMDM4IDIzLjUyIDE1LjkyNzUgMjMuNTIgMTIuMjcyOVoiIGZpbGw9IiM0Mjg1RjQiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMiAyMy45OTk5QzE1LjI0IDIzLjk5OTkgMTcuOTU2NCAyMi45MjU0IDE5Ljk0MTggMjEuMDkyNkwxNi4wNjM2IDE4LjA4MTdDMTQuOTg5MSAxOC44MDE3IDEzLjYxNDUgMTkuMjI3MiAxMiAxOS4yMjcyQzguODc0NTUgMTkuMjI3MiA2LjIyOTA5IDE3LjExNjMgNS4yODU0NiAxNC4yNzk5SDEuMjc2MzdWMTcuMzg5QzMuMjUwOTEgMjEuMzEwOCA3LjMwOTA5IDIzLjk5OTkgMTIgMjMuOTk5OVoiIGZpbGw9IiMzNEE4NTMiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik01LjI4NTQ1IDE0LjI3OThDNS4wNDU0NSAxMy41NTk4IDQuOTA5MDkgMTIuNzkwNyA0LjkwOTA5IDExLjk5OThDNC45MDkwOSAxMS4yMDg5IDUuMDQ1NDUgMTAuNDM5OCA1LjI4NTQ1IDkuNzE5ODFWNi42MTA3MkgxLjI3NjM2QzAuNDYzNjM2IDguMjMwNzIgMCAxMC4wNjM0IDAgMTEuOTk5OEMwIDEzLjkzNjIgMC40NjM2MzYgMTUuNzY4OSAxLjI3NjM2IDE3LjM4ODlMNS4yODU0NSAxNC4yNzk4WiIgZmlsbD0iI0ZCQkMwNSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTEyIDQuNzcyNzNDMTMuNzYxOCA0Ljc3MjczIDE1LjM0MzYgNS4zNzgxOCAxNi41ODczIDYuNTY3MjdMMjAuMDI5MSAzLjEyNTQ1QzE3Ljk1MDkgMS4xODkwOSAxNS4yMzQ1IDAgMTIgMEM3LjMwOTA5IDAgMy4yNTA5MSAyLjY4OTA5IDEuMjc2MzcgNi42MTA5MUw1LjI4NTQ2IDkuNzJDNi4yMjkwOSA2Ljg4MzY0IDguODc0NTUgNC43NzI3MyAxMiA0Ljc3MjczWiIgZmlsbD0iI0VBNDMzNSIvPgo8L3N2Zz4K',
|
||||
description: {
|
||||
en: 'This connector is used to connect with Google Workspace Single Sign-On.',
|
||||
},
|
||||
configGuard: googleWorkspaceSsoConnectorConfigGuard,
|
||||
constructor: GoogleWorkspaceSsoConnector,
|
||||
};
|
|
@ -4,6 +4,7 @@ import { assert, conditional } from '@silverhand/essentials';
|
|||
import snakecaseKeys from 'snakecase-keys';
|
||||
|
||||
import { type BaseOidcConfig, type BasicOidcConnectorConfig } from '../types/oidc.js';
|
||||
import { type ExtendedSocialUserInfo } from '../types/saml.js';
|
||||
import {
|
||||
type SingleSignOnConnectorSession,
|
||||
type CreateSingleSignOnSession,
|
||||
|
@ -28,7 +29,7 @@ class OidcConnector {
|
|||
constructor(private readonly config: BasicOidcConnectorConfig) {}
|
||||
|
||||
/* Fetch the full-list of OIDC config from the issuer. Throws error if config is invalid */
|
||||
getOidcConfig = async (): Promise<BaseOidcConfig> => {
|
||||
async getOidcConfig(): Promise<BaseOidcConfig> {
|
||||
const { issuer } = this.config;
|
||||
|
||||
const oidcConfig = await fetchOidcConfig(issuer);
|
||||
|
@ -37,11 +38,6 @@ class OidcConnector {
|
|||
...this.config,
|
||||
...oidcConfig,
|
||||
};
|
||||
};
|
||||
|
||||
/** `Issuer` will be used by SSO identity to indicate the source of the identity */
|
||||
async getIssuer() {
|
||||
return this.config.issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,14 +48,14 @@ class OidcConnector {
|
|||
* @param oidcQueryParams.redirectUri The redirect uri for the OIDC provider
|
||||
* @param setSession Set the connector session data to the oidc provider session storage.
|
||||
*/
|
||||
getAuthorizationUrl = async (
|
||||
async getAuthorizationUrl(
|
||||
{
|
||||
state,
|
||||
redirectUri,
|
||||
connectorId,
|
||||
}: { state: string; redirectUri: string; connectorId: string },
|
||||
setSession: CreateSingleSignOnSession
|
||||
) => {
|
||||
) {
|
||||
assert(
|
||||
setSession,
|
||||
new ConnectorError(ConnectorErrorCodes.NotImplemented, {
|
||||
|
@ -84,7 +80,7 @@ class OidcConnector {
|
|||
});
|
||||
|
||||
return `${oidcConfig.authorizationEndpoint}?${queryParameters.toString()}`;
|
||||
};
|
||||
}
|
||||
|
||||
get issuer() {
|
||||
return this.config.issuer;
|
||||
|
@ -99,7 +95,10 @@ class OidcConnector {
|
|||
* @remark Forked from @logto/oidc-connector
|
||||
*
|
||||
*/
|
||||
getUserInfo = async (connectorSession: SingleSignOnConnectorSession, data: unknown) => {
|
||||
async getUserInfo(
|
||||
connectorSession: SingleSignOnConnectorSession,
|
||||
data: unknown
|
||||
): Promise<ExtendedSocialUserInfo> {
|
||||
const oidcConfig = await this.getOidcConfig();
|
||||
const { nonce, redirectUri } = connectorSession;
|
||||
|
||||
|
@ -112,12 +111,12 @@ class OidcConnector {
|
|||
|
||||
return {
|
||||
id: sub,
|
||||
name: conditional(name),
|
||||
avatar: conditional(picture),
|
||||
email: conditional(email_verified && email),
|
||||
phone: conditional(phone_verified && phone),
|
||||
...conditional(name && { name }),
|
||||
...conditional(picture && { avatar: picture }),
|
||||
...conditional(email && email_verified && { email }),
|
||||
...conditional(phone && phone_verified && { phone }),
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default OidcConnector;
|
||||
|
|
|
@ -7,8 +7,8 @@ import { type SingleSignOn } from '../types/index.js';
|
|||
import { basicOidcConnectorConfigGuard } from '../types/oidc.js';
|
||||
|
||||
export class OidcSsoConnector extends OidcConnector implements SingleSignOn {
|
||||
constructor(private readonly _data: SsoConnector) {
|
||||
const parseConfigResult = basicOidcConnectorConfigGuard.safeParse(_data.config);
|
||||
constructor(readonly data: SsoConnector) {
|
||||
const parseConfigResult = basicOidcConnectorConfigGuard.safeParse(data.config);
|
||||
|
||||
if (!parseConfigResult.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, parseConfigResult.error);
|
||||
|
@ -17,23 +17,24 @@ export class OidcSsoConnector extends OidcConnector implements SingleSignOn {
|
|||
super(parseConfigResult.data);
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
// OIDC connector doesn't have additional properties.
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
getProperties = () => undefined;
|
||||
|
||||
getConfig = async () => this.getOidcConfig();
|
||||
getIssuer = async () => this.issuer;
|
||||
async getConfig() {
|
||||
return this.getOidcConfig();
|
||||
}
|
||||
|
||||
async getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
}
|
||||
|
||||
export const oidcSsoConnectorFactory: SingleSignOnFactory<SsoProviderName.OIDC> = {
|
||||
providerName: SsoProviderName.OIDC,
|
||||
logo: 'https://freesvg.org/img/techtonik_OpenID.png',
|
||||
description: {
|
||||
en: ' This connector is used to connect with OIDC single sign-on identity provider.',
|
||||
en: 'This connector is used to connect with OIDC single sign-on identity provider.',
|
||||
},
|
||||
configGuard: basicOidcConnectorConfigGuard,
|
||||
constructor: OidcSsoConnector,
|
||||
|
|
|
@ -104,9 +104,9 @@ export class SamlSsoConnector extends SamlConnector implements SingleSignOn {
|
|||
|
||||
export const samlSsoConnectorFactory: SingleSignOnFactory<SsoProviderName.SAML> = {
|
||||
providerName: SsoProviderName.SAML,
|
||||
logo: 'https://www.svgrepo.com/show/448246/saml.svg',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAgMjAuMDk5QzAgMTcuNDYzMyAyLjUxNDM0IDcuNjMwMjggNy43MTI5MSAxLjA0MTE0QzQuMDQzMzMgNy45NjgxOCAxLjkwODcxIDEyLjY1ODQgMy40NjU3MSAxNi4zMTQ0QzQuNjg4OTEgMTkuMTg2NiAxMC45NzQ4IDE4LjI3NDMgMTQuMzcyNSAxNy4xMjU0QzguMzI0NTEgMTkuODYyNCAyLjExNzk0IDIwLjIzNDEgMCAyMC4wOTlaIiBmaWxsPSIjQzEyNzJEIi8+CjxwYXRoIGQ9Ik0xMS4yNjA1IDAuNzQ5ODc4QzEzLjU1NTYgMi4wNjc3MSAyMC44NjEzIDkuMTQ5NzEgMjQgMTYuOTIxNkMxOS44MDI1IDEwLjI5NzYgMTYuNzg1NSA2LjExNDA0IDEyLjgyMzMgNS42MjcwMUM5LjcxMDQ5IDUuMjQ0NCA3LjM2MjA1IDExLjExNDMgNi42NjM2NCAxNC42MTUxQzcuMzA0MTggOC4wMzc2OCAxMC4wODM4IDIuNTA2MzggMTEuMjYwNSAwLjc0OTg3OFoiIGZpbGw9IiNDMTI3MkQiLz4KPHBhdGggZD0iTTIyLjUzMTkgMjAuMDc1NUMyMC4yMzY4IDIxLjM5MzMgMTAuNDE2OCAyNC4xNDQzIDIuMDc5NTMgMjIuOTYxNkM5Ljk0NjU0IDIyLjY1ODUgMTUuMDk4MiAyMi4xNTE5IDE3LjUwMzQgMTguOTgyOUMxOS4zOTMgMTYuNDkzMyAxNS40NTU2IDExLjUzNTcgMTIuNzU2MiA5LjE4Mzg0QzE4LjE2MzcgMTMuMDI0MiAyMS41OTA3IDE4LjE4MzggMjIuNTMxOSAyMC4wNzU1WiIgZmlsbD0iI0MxMjcyRCIvPgo8L3N2Zz4K',
|
||||
description: {
|
||||
en: ' This connector is used to connect to SAML single sign-on identity provider.',
|
||||
en: 'This connector is used to connect to SAML single sign-on identity provider.',
|
||||
},
|
||||
configGuard: samlConnectorConfigGuard,
|
||||
constructor: SamlSsoConnector,
|
||||
|
|
|
@ -1,29 +1,40 @@
|
|||
import { type I18nPhrases } from '@logto/connector-kit';
|
||||
import { SsoProviderName } from '@logto/schemas';
|
||||
|
||||
import {
|
||||
type AzureAdSsoConnector,
|
||||
azureAdSsoConnectorFactory,
|
||||
} from './AzureAdSsoConnector/index.js';
|
||||
import {
|
||||
type GoogleWorkspaceSsoConnector,
|
||||
googleWorkSpaceSsoConnectorFactory,
|
||||
type googleWorkspaceSsoConnectorConfigGuard,
|
||||
} from './GoogleWorkspaceSsoConnector/index.js';
|
||||
import { oidcSsoConnectorFactory, type OidcSsoConnector } from './OidcSsoConnector/index.js';
|
||||
import { type SamlSsoConnector, samlSsoConnectorFactory } from './SamlSsoConnector/index.js';
|
||||
import { type basicOidcConnectorConfigGuard } from './types/oidc.js';
|
||||
import { type samlConnectorConfigGuard } from './types/saml.js';
|
||||
|
||||
type SingleSignOnConstructor<T extends SsoProviderName> = T extends SsoProviderName.OIDC
|
||||
? typeof OidcSsoConnector
|
||||
: T extends SsoProviderName.SAML
|
||||
? typeof SamlSsoConnector
|
||||
: never;
|
||||
type SingleSignOnConstructor = {
|
||||
[SsoProviderName.OIDC]: typeof OidcSsoConnector;
|
||||
[SsoProviderName.SAML]: typeof SamlSsoConnector;
|
||||
[SsoProviderName.AZURE_AD]: typeof AzureAdSsoConnector;
|
||||
[SsoProviderName.GOOGLE_WORKSPACE]: typeof GoogleWorkspaceSsoConnector;
|
||||
};
|
||||
|
||||
type SingleSignOnConnectorConfig<T extends SsoProviderName> = T extends SsoProviderName.OIDC
|
||||
? typeof basicOidcConnectorConfigGuard
|
||||
: T extends SsoProviderName.SAML
|
||||
? typeof samlConnectorConfigGuard
|
||||
: never;
|
||||
type SingleSignOnConnectorConfig = {
|
||||
[SsoProviderName.OIDC]: typeof basicOidcConnectorConfigGuard;
|
||||
[SsoProviderName.SAML]: typeof samlConnectorConfigGuard;
|
||||
[SsoProviderName.AZURE_AD]: typeof samlConnectorConfigGuard;
|
||||
[SsoProviderName.GOOGLE_WORKSPACE]: typeof googleWorkspaceSsoConnectorConfigGuard;
|
||||
};
|
||||
|
||||
export type SingleSignOnFactory<T extends SsoProviderName> = {
|
||||
providerName: T;
|
||||
logo: string;
|
||||
description: I18nPhrases;
|
||||
configGuard: SingleSignOnConnectorConfig<T>;
|
||||
constructor: SingleSignOnConstructor<T>;
|
||||
configGuard: SingleSignOnConnectorConfig[T];
|
||||
constructor: SingleSignOnConstructor[T];
|
||||
};
|
||||
|
||||
export const ssoConnectorFactories: {
|
||||
|
@ -31,6 +42,8 @@ export const ssoConnectorFactories: {
|
|||
} = {
|
||||
[SsoProviderName.OIDC]: oidcSsoConnectorFactory,
|
||||
[SsoProviderName.SAML]: samlSsoConnectorFactory,
|
||||
[SsoProviderName.AZURE_AD]: azureAdSsoConnectorFactory,
|
||||
[SsoProviderName.GOOGLE_WORKSPACE]: googleWorkSpaceSsoConnectorFactory,
|
||||
};
|
||||
|
||||
export const standardSsoConnectorProviders = Object.freeze([
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import { scopePostProcessor } from './oidc.js';
|
||||
import { scopePostProcessor, RequiredOidcScope } from './oidc.js';
|
||||
|
||||
describe('scopePostProcessor', () => {
|
||||
it('`openid` will be added if not exists (with empty string)', () => {
|
||||
expect(scopePostProcessor('')).toEqual('openid');
|
||||
const defaultScopes = Object.values(RequiredOidcScope);
|
||||
|
||||
it('`RequiredOidcScopes` will be added if not exists (with empty string)', () => {
|
||||
for (const scope of defaultScopes) {
|
||||
expect(scopePostProcessor('')).toContain(scope);
|
||||
}
|
||||
});
|
||||
|
||||
it('`openid` will be added if not exists (with non-empty string)', () => {
|
||||
expect(scopePostProcessor('profile')).toEqual('profile openid');
|
||||
});
|
||||
|
||||
it('return original input if openid exists', () => {
|
||||
expect(scopePostProcessor('profile openid')).toEqual('profile openid');
|
||||
it('`RequiredOidcScopes` will be added if not exists (with non-empty string)', () => {
|
||||
const scopes = scopePostProcessor('read');
|
||||
for (const scope of defaultScopes) {
|
||||
expect(scopePostProcessor('read')).toContain(scope);
|
||||
}
|
||||
expect(scopes).toContain('read');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { type CamelCaseKeys } from 'camelcase-keys';
|
||||
import { z } from 'zod';
|
||||
|
||||
const openidScope = 'openid' as const;
|
||||
export enum RequiredOidcScope {
|
||||
OPEN_ID = 'openid',
|
||||
PROFILE = 'profile',
|
||||
EMAIL = 'email',
|
||||
}
|
||||
|
||||
const scopeDelimiter = /[ +]/;
|
||||
/**
|
||||
* Scope config processor for OIDC connector. openid scope is required to retrieve id_token
|
||||
|
@ -13,12 +18,10 @@ const scopeDelimiter = /[ +]/;
|
|||
*/
|
||||
export const scopePostProcessor = (scope = '') => {
|
||||
const splitScopes = scope.split(scopeDelimiter).filter(Boolean);
|
||||
const defaultScopes = Object.values(RequiredOidcScope);
|
||||
const parsedScopes = new Set([...defaultScopes, ...splitScopes]);
|
||||
|
||||
if (!splitScopes.includes(openidScope)) {
|
||||
return [...splitScopes, openidScope].join(' ');
|
||||
}
|
||||
|
||||
return scope;
|
||||
return Array.from(parsedScopes).join(' ');
|
||||
};
|
||||
|
||||
export const basicOidcConnectorConfigGuard = z.object({
|
||||
|
@ -48,7 +51,6 @@ export type BaseOidcConfig = CamelCaseKeys<OidcConfigResponse> & {
|
|||
|
||||
export const oidcAuthorizationResponseGuard = z.object({
|
||||
code: z.string(),
|
||||
state: z.string(),
|
||||
});
|
||||
|
||||
export const oidcTokenResponseGuard = z.object({
|
||||
|
|
|
@ -18,13 +18,13 @@ export const getSingleSignOnConnectors = async (email: string) =>
|
|||
export const getSingleSignOnUrl = async (
|
||||
connectorId: string,
|
||||
state: string,
|
||||
callbackUri: string
|
||||
redirectUri: string
|
||||
) => {
|
||||
const { redirectTo } = await api
|
||||
.post(`${ssoPrefix}/${connectorId}/authorization-url`, {
|
||||
json: {
|
||||
state,
|
||||
callbackUri,
|
||||
redirectUri,
|
||||
},
|
||||
})
|
||||
.json<Response>();
|
||||
|
|
|
@ -61,7 +61,7 @@ export const partialConfigAndProviderNames: Array<{
|
|||
clientId: 'foo',
|
||||
clientSecret: 'foo',
|
||||
issuer: logtoIssuer,
|
||||
scope: 'openid',
|
||||
scope: 'openid profile email',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -17,6 +17,8 @@ export type SsoConnectorMetadata = z.infer<typeof ssoConnectorMetadataGuard>;
|
|||
export enum SsoProviderName {
|
||||
OIDC = 'OIDC',
|
||||
SAML = 'SAML',
|
||||
AZURE_AD = 'AzureAD',
|
||||
GOOGLE_WORKSPACE = 'GoogleWorkspace',
|
||||
}
|
||||
|
||||
export type SupportedSsoConnector = Omit<SsoConnector, 'providerName'> & {
|
||||
|
|
Loading…
Add table
Reference in a new issue