mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
feat(core,console): social connector targets (#851)
* feat(core,console): social connector targets * fix: add test
This commit is contained in:
parent
3031e3a6f1
commit
127664a62f
19 changed files with 186 additions and 108 deletions
54
packages/console/src/hooks/use-connector-groups.ts
Normal file
54
packages/console/src/hooks/use-connector-groups.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { ConnectorDTO } from '@logto/schemas';
|
||||
import { useMemo } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import { RequestError } from '@/hooks/use-api';
|
||||
import { ConnectorGroup } from '@/types/connector';
|
||||
|
||||
// Group connectors by target
|
||||
const useConnectorGroups = () => {
|
||||
const { data, ...rest } = useSWR<ConnectorDTO[], RequestError>('/api/connectors');
|
||||
|
||||
const groups = useMemo(() => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
return data.reduce<ConnectorGroup[]>((previous, item) => {
|
||||
const groupIndex = previous.findIndex(({ target }) => target === item.target);
|
||||
|
||||
if (groupIndex === -1) {
|
||||
return [
|
||||
...previous,
|
||||
{
|
||||
name: item.metadata.name,
|
||||
logo: item.metadata.logo,
|
||||
target: item.metadata.target,
|
||||
enabled: item.enabled,
|
||||
connectors: [item],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return previous.map((group, index) => {
|
||||
if (index !== groupIndex) {
|
||||
return group;
|
||||
}
|
||||
|
||||
return {
|
||||
...group,
|
||||
connectors: [...group.connectors, item],
|
||||
// Group is enabled when any of its connectors is enabled.
|
||||
enabled: group.enabled || item.enabled,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
}, [data]);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: groups,
|
||||
};
|
||||
};
|
||||
|
||||
export default useConnectorGroups;
|
|
@ -1,14 +1,12 @@
|
|||
import { ConnectorDTO } from '@logto/schemas';
|
||||
import { conditionalString } from '@silverhand/essentials';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Alert from '@/components/Alert';
|
||||
import Transfer from '@/components/Transfer';
|
||||
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||
import { RequestError } from '@/hooks/use-api';
|
||||
import useConnectorGroups from '@/hooks/use-connector-groups';
|
||||
|
||||
import * as styles from './ConnectorsTransfer.module.scss';
|
||||
|
||||
|
@ -18,7 +16,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const ConnectorsTransfer = ({ value, onChange }: Props) => {
|
||||
const { data, error } = useSWR<ConnectorDTO[], RequestError>('/api/connectors');
|
||||
const { data, error } = useConnectorGroups();
|
||||
const isLoading = !data && !error;
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
|
@ -31,8 +29,8 @@ const ConnectorsTransfer = ({ value, onChange }: Props) => {
|
|||
}
|
||||
|
||||
const datasource = data
|
||||
? data.map(({ id, metadata: { name }, enabled }) => ({
|
||||
value: id,
|
||||
? data.map(({ target, name, enabled }) => ({
|
||||
value: target,
|
||||
title: (
|
||||
<UnnamedTrans
|
||||
resource={name}
|
||||
|
|
|
@ -87,7 +87,7 @@ const SignInMethodsForm = () => {
|
|||
{primaryMethod === SignInMethodKey.Social && (
|
||||
<div className={styles.primarySocial}>
|
||||
<Controller
|
||||
name="socialSignInConnectorIds"
|
||||
name="socialSignInConnectorTargets"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ConnectorsTransfer value={value} onChange={onChange} />
|
||||
|
@ -107,7 +107,7 @@ const SignInMethodsForm = () => {
|
|||
{social && (
|
||||
<FormField title="admin_console.sign_in_exp.sign_in_methods.define_social_methods">
|
||||
<Controller
|
||||
name="socialSignInConnectorIds"
|
||||
name="socialSignInConnectorTargets"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ConnectorsTransfer value={value} onChange={onChange} />
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { ConnectorDTO, SignInExperience, SignInMethodKey, SignInMethodState } from '@logto/schemas';
|
||||
import { SignInExperience, SignInMethodKey, SignInMethodState } from '@logto/schemas';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||
import { RequestError } from '@/hooks/use-api';
|
||||
import useConnectorGroups from '@/hooks/use-connector-groups';
|
||||
|
||||
import * as styles from './SaveAlert.module.scss';
|
||||
|
||||
|
@ -13,37 +12,33 @@ type Props = {
|
|||
};
|
||||
|
||||
const SignInMethodsPreview = ({ data }: Props) => {
|
||||
const { data: connectors, error } = useSWR<ConnectorDTO[], RequestError>('/api/connectors');
|
||||
const { signInMethods, socialSignInConnectorIds } = data;
|
||||
const { data: groups, error } = useConnectorGroups();
|
||||
const { signInMethods, socialSignInConnectorTargets } = data;
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const connectorNames = useMemo(() => {
|
||||
if (!connectors) {
|
||||
if (!groups) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return socialSignInConnectorIds.map((connectorId) => {
|
||||
const connector = connectors.find(({ id }) => id === connectorId);
|
||||
return socialSignInConnectorTargets.map((connectorTarget) => {
|
||||
const group = groups.find(({ target }) => target === connectorTarget);
|
||||
|
||||
if (!connector) {
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<UnnamedTrans
|
||||
key={connectorId}
|
||||
className={styles.connector}
|
||||
resource={connector.metadata.name}
|
||||
/>
|
||||
<UnnamedTrans key={connectorTarget} className={styles.connector} resource={group.name} />
|
||||
);
|
||||
});
|
||||
}, [connectors, socialSignInConnectorIds]);
|
||||
}, [groups, socialSignInConnectorTargets]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!connectors && !error && <div>loading</div>}
|
||||
{!connectors && error && <div>{error.body?.message ?? error.message}</div>}
|
||||
{connectors &&
|
||||
{!groups && !error && <div>loading</div>}
|
||||
{!groups && error && <div>{error.body?.message ?? error.message}</div>}
|
||||
{groups &&
|
||||
Object.values(SignInMethodKey)
|
||||
.filter((key) => signInMethods[key] !== SignInMethodState.Disabled)
|
||||
.map((key) => (
|
||||
|
|
|
@ -81,11 +81,15 @@ export const compareSignInMethods = (
|
|||
before: SignInExperience,
|
||||
after: SignInExperience
|
||||
): boolean => {
|
||||
if (before.socialSignInConnectorIds.length !== after.socialSignInConnectorIds.length) {
|
||||
if (before.socialSignInConnectorTargets.length !== after.socialSignInConnectorTargets.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (before.socialSignInConnectorIds.some((id) => !after.socialSignInConnectorIds.includes(id))) {
|
||||
if (
|
||||
before.socialSignInConnectorTargets.some(
|
||||
(target) => !after.socialSignInConnectorTargets.includes(target)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
6
packages/console/src/types/connector.ts
Normal file
6
packages/console/src/types/connector.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { ConnectorDTO } from '@logto/schemas';
|
||||
|
||||
export type ConnectorGroup = Pick<ConnectorDTO['metadata'], 'name' | 'logo' | 'target'> & {
|
||||
enabled: boolean;
|
||||
connectors: ConnectorDTO[];
|
||||
};
|
|
@ -252,6 +252,36 @@ export const mockGithubConnectorInstance = {
|
|||
},
|
||||
};
|
||||
|
||||
export const mockWechatConnectorInstance = {
|
||||
connector: {
|
||||
...mockConnector,
|
||||
id: 'wechat',
|
||||
target: 'wechat',
|
||||
platform: ConnectorPlatform.Web,
|
||||
},
|
||||
metadata: {
|
||||
...mockMetadata,
|
||||
target: 'wechat',
|
||||
type: ConnectorType.Social,
|
||||
platform: ConnectorPlatform.Web,
|
||||
},
|
||||
};
|
||||
|
||||
export const mockWechatNativeConnectorInstance = {
|
||||
connector: {
|
||||
...mockConnector,
|
||||
id: 'wechat-native',
|
||||
target: 'wechat',
|
||||
platform: ConnectorPlatform.Native,
|
||||
},
|
||||
metadata: {
|
||||
...mockMetadata,
|
||||
target: 'wechat',
|
||||
type: ConnectorType.Social,
|
||||
platform: ConnectorPlatform.Native,
|
||||
},
|
||||
};
|
||||
|
||||
export const mockGoogleConnectorInstance = {
|
||||
connector: {
|
||||
...mockConnector,
|
||||
|
|
|
@ -33,7 +33,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
sms: SignInMethodState.Disabled,
|
||||
social: SignInMethodState.Secondary,
|
||||
},
|
||||
socialSignInConnectorIds: ['github', 'facebook'],
|
||||
socialSignInConnectorTargets: ['github', 'facebook', 'wechat'],
|
||||
};
|
||||
|
||||
export const mockBranding: Branding = {
|
||||
|
|
|
@ -27,7 +27,7 @@ export const isEnabled = (state: SignInMethodState) => state !== SignInMethodSta
|
|||
|
||||
export const validateSignInMethods = (
|
||||
signInMethods: SignInMethods,
|
||||
socialSignInConnectorIds: Optional<string[]>,
|
||||
socialSignInConnectorTargets: Optional<string[]>,
|
||||
enabledConnectorInstances: ConnectorInstance[]
|
||||
) => {
|
||||
const signInMethodStates = Object.values(signInMethods);
|
||||
|
@ -60,17 +60,17 @@ export const validateSignInMethods = (
|
|||
);
|
||||
|
||||
assertThat(
|
||||
socialSignInConnectorIds && socialSignInConnectorIds.length > 0,
|
||||
socialSignInConnectorTargets && socialSignInConnectorTargets.length > 0,
|
||||
'sign_in_experiences.empty_social_connectors'
|
||||
);
|
||||
|
||||
const enabledSocialConnectorIds = new Set(
|
||||
enabledConnectorInstances
|
||||
.filter((instance) => instance.metadata.type === ConnectorType.Social)
|
||||
.map((instance) => instance.connector.id)
|
||||
);
|
||||
assertThat(
|
||||
socialSignInConnectorIds.every((id) => enabledSocialConnectorIds.has(id)),
|
||||
socialSignInConnectorTargets.every((connectorTarget) =>
|
||||
enabledConnectorInstances.some(
|
||||
({ metadata: { target, type } }) =>
|
||||
target === connectorTarget && type === ConnectorType.Social
|
||||
)
|
||||
),
|
||||
'sign_in_experiences.invalid_social_connectors'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,14 +24,14 @@ describe('sign-in-experience query', () => {
|
|||
branding: JSON.stringify(mockSignInExperience.branding),
|
||||
termsOfUse: JSON.stringify(mockSignInExperience.termsOfUse),
|
||||
languageInfo: JSON.stringify(mockSignInExperience.languageInfo),
|
||||
signInMethods: JSON.stringify(mockSignInExperience.socialSignInConnectorIds),
|
||||
socialSignInConnectorIds: JSON.stringify(mockSignInExperience.socialSignInConnectorIds),
|
||||
signInMethods: JSON.stringify(mockSignInExperience.socialSignInConnectorTargets),
|
||||
socialSignInConnectorTargets: JSON.stringify(mockSignInExperience.socialSignInConnectorTargets),
|
||||
};
|
||||
|
||||
it('findDefaultSignInExperience', async () => {
|
||||
/* eslint-disable sql/no-unsafe-query */
|
||||
const expectSql = `
|
||||
select "id", "branding", "language_info", "terms_of_use", "sign_in_methods", "social_sign_in_connector_ids"
|
||||
select "id", "branding", "language_info", "terms_of_use", "sign_in_methods", "social_sign_in_connector_targets"
|
||||
from "sign_in_experiences"
|
||||
where "id" = $1
|
||||
`;
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
mockFacebookConnectorInstance,
|
||||
mockGithubConnectorInstance,
|
||||
mockGoogleConnectorInstance,
|
||||
mockWechatConnectorInstance,
|
||||
mockWechatNativeConnectorInstance,
|
||||
} from '@/__mocks__';
|
||||
import { ConnectorType } from '@/connectors/types';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
|
@ -111,6 +113,8 @@ const getConnectorInstances = jest.fn(async () => [
|
|||
mockFacebookConnectorInstance,
|
||||
mockGithubConnectorInstance,
|
||||
mockGoogleConnectorInstance,
|
||||
mockWechatConnectorInstance,
|
||||
mockWechatNativeConnectorInstance,
|
||||
]);
|
||||
jest.mock('@/connectors', () => ({
|
||||
getSocialConnectorInstanceById: async (connectorId: string) => {
|
||||
|
@ -925,6 +929,14 @@ describe('sessionRoutes', () => {
|
|||
...mockFacebookConnectorInstance.metadata,
|
||||
id: mockFacebookConnectorInstance.connector.id,
|
||||
},
|
||||
{
|
||||
...mockWechatConnectorInstance.metadata,
|
||||
id: mockWechatConnectorInstance.connector.id,
|
||||
},
|
||||
{
|
||||
...mockWechatNativeConnectorInstance.metadata,
|
||||
id: mockWechatNativeConnectorInstance.connector.id,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable max-lines */
|
||||
import path from 'path';
|
||||
|
||||
import { ConnectorMetadata } from '@logto/connector-types';
|
||||
import { LogtoErrorCode } from '@logto/phrases';
|
||||
import { PasscodeType, userInfoSelectFields } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
@ -581,12 +582,18 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
|||
router.get('/sign-in-settings', async (ctx, next) => {
|
||||
const signInExperience = await findDefaultSignInExperience();
|
||||
const connectorInstances = await getConnectorInstances();
|
||||
const instanceMap = new Map(
|
||||
connectorInstances.map((instance) => [instance.connector.id, instance])
|
||||
);
|
||||
const socialConnectors = signInExperience.socialSignInConnectorIds.map((id) => {
|
||||
return { ...instanceMap.get(id)?.metadata, id };
|
||||
});
|
||||
const socialConnectors = signInExperience.socialSignInConnectorTargets.reduce<
|
||||
Array<ConnectorMetadata & { id: string }>
|
||||
>((previous, connectorTarget) => {
|
||||
const connectors = connectorInstances.filter(
|
||||
({ metadata: { target } }) => target === connectorTarget
|
||||
);
|
||||
|
||||
return [
|
||||
...previous,
|
||||
...connectors.map(({ metadata, connector: { id } }) => ({ ...metadata, id })),
|
||||
];
|
||||
}, []);
|
||||
ctx.body = { ...signInExperience, socialConnectors };
|
||||
|
||||
return next();
|
||||
|
|
|
@ -195,7 +195,7 @@ describe('signInMethods', () => {
|
|||
sms: state,
|
||||
social: SignInMethodState.Primary,
|
||||
},
|
||||
socialSignInConnectorIds: ['github'],
|
||||
socialSignInConnectorTargets: ['github'],
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
});
|
||||
|
@ -211,7 +211,7 @@ describe('signInMethods', () => {
|
|||
sms: state,
|
||||
social: SignInMethodState.Primary,
|
||||
},
|
||||
socialSignInConnectorIds: ['github'],
|
||||
socialSignInConnectorTargets: ['github'],
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
|
@ -229,7 +229,7 @@ describe('signInMethods', () => {
|
|||
sms: SignInMethodState.Disabled,
|
||||
social: state,
|
||||
},
|
||||
socialSignInConnectorIds: ['github'],
|
||||
socialSignInConnectorTargets: ['github'],
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
});
|
||||
|
@ -245,21 +245,21 @@ describe('signInMethods', () => {
|
|||
sms: SignInMethodState.Disabled,
|
||||
social: state,
|
||||
},
|
||||
socialSignInConnectorIds: ['github'],
|
||||
socialSignInConnectorTargets: ['github'],
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('socialSignInConnectorIds', () => {
|
||||
describe('socialSignInConnectorTargets', () => {
|
||||
test.each([[['facebook']], [['facebook', 'github']]])(
|
||||
'%p should success',
|
||||
async (socialSignInConnectorIds) => {
|
||||
async (socialSignInConnectorTargets) => {
|
||||
await expectPatchResponseStatus(
|
||||
{
|
||||
signInMethods: { ...mockSignInMethods, social: SignInMethodState.Secondary },
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
},
|
||||
200
|
||||
);
|
||||
|
@ -268,11 +268,11 @@ describe('socialSignInConnectorIds', () => {
|
|||
|
||||
test.each([[[]], [[null, undefined]], [['', ' \t\n\r']], [[123, 456]]])(
|
||||
'%p should fail',
|
||||
async (socialSignInConnectorIds: any[]) => {
|
||||
async (socialSignInConnectorTargets: any[]) => {
|
||||
await expectPatchResponseStatus(
|
||||
{
|
||||
signInMethods: { ...mockSignInMethods, social: SignInMethodState.Secondary },
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
},
|
||||
400
|
||||
);
|
||||
|
|
|
@ -59,23 +59,6 @@ describe('GET /sign-in-exp', () => {
|
|||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(mockSignInExperience);
|
||||
});
|
||||
|
||||
it('should filter enabled social connectors', async () => {
|
||||
const signInExperience = {
|
||||
...mockSignInExperience,
|
||||
signInMethods: { ...mockSignInMethods, social: SignInMethodState.Secondary },
|
||||
socialSignInConnectorIds: ['facebook', 'github', 'google'],
|
||||
};
|
||||
|
||||
findDefaultSignInExperience.mockImplementationOnce(async () => signInExperience);
|
||||
|
||||
const response = await signInExperienceRequester.get('/sign-in-exp');
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
...signInExperience,
|
||||
socialSignInConnectorIds: ['facebook', 'github'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /sign-in-exp', () => {
|
||||
|
@ -83,7 +66,7 @@ describe('PATCH /sign-in-exp', () => {
|
|||
const signInMethods = { ...mockSignInMethods, social: SignInMethodState.Disabled };
|
||||
const response = await signInExperienceRequester.patch('/sign-in-exp').send({
|
||||
signInMethods,
|
||||
socialSignInConnectorIds: ['facebook'],
|
||||
socialSignInConnectorTargets: ['facebook'],
|
||||
});
|
||||
expect(response).toMatchObject({
|
||||
status: 200,
|
||||
|
@ -96,10 +79,10 @@ describe('PATCH /sign-in-exp', () => {
|
|||
|
||||
it('should update enabled social connector IDs only when social sign-in is enabled', async () => {
|
||||
const signInMethods = { ...mockSignInMethods, social: SignInMethodState.Secondary };
|
||||
const socialSignInConnectorIds = ['facebook'];
|
||||
const socialSignInConnectorTargets = ['facebook'];
|
||||
const signInExperience = {
|
||||
signInMethods,
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
};
|
||||
const response = await signInExperienceRequester.patch('/sign-in-exp').send(signInExperience);
|
||||
expect(response).toMatchObject({
|
||||
|
@ -107,17 +90,17 @@ describe('PATCH /sign-in-exp', () => {
|
|||
body: {
|
||||
...mockSignInExperience,
|
||||
signInMethods,
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should update social connector IDs in correct sorting order', async () => {
|
||||
const signInMethods = { ...mockSignInMethods, social: SignInMethodState.Secondary };
|
||||
const socialSignInConnectorIds = ['github', 'facebook'];
|
||||
const socialSignInConnectorTargets = ['github', 'facebook'];
|
||||
const signInExperience = {
|
||||
signInMethods,
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
};
|
||||
const response = await signInExperienceRequester.patch('/sign-in-exp').send(signInExperience);
|
||||
expect(response).toMatchObject({
|
||||
|
@ -125,14 +108,14 @@ describe('PATCH /sign-in-exp', () => {
|
|||
body: {
|
||||
...mockSignInExperience,
|
||||
signInMethods,
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed to update when the input is valid', async () => {
|
||||
const termsOfUse: TermsOfUse = { enabled: false };
|
||||
const socialSignInConnectorIds = ['github', 'facebook'];
|
||||
const socialSignInConnectorTargets = ['github', 'facebook', 'wechat'];
|
||||
|
||||
const validateBranding = jest.spyOn(signInExpLib, 'validateBranding');
|
||||
const validateTermsOfUse = jest.spyOn(signInExpLib, 'validateTermsOfUse');
|
||||
|
@ -142,14 +125,14 @@ describe('PATCH /sign-in-exp', () => {
|
|||
branding: mockBranding,
|
||||
termsOfUse,
|
||||
signInMethods: mockSignInMethods,
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
});
|
||||
|
||||
expect(validateBranding).toHaveBeenCalledWith(mockBranding);
|
||||
expect(validateTermsOfUse).toHaveBeenCalledWith(termsOfUse);
|
||||
expect(validateSignInMethods).toHaveBeenCalledWith(
|
||||
mockSignInMethods,
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
[mockFacebookConnectorInstance, mockGithubConnectorInstance]
|
||||
);
|
||||
|
||||
|
@ -160,7 +143,7 @@ describe('PATCH /sign-in-exp', () => {
|
|||
branding: mockBranding,
|
||||
termsOfUse,
|
||||
signInMethods: mockSignInMethods,
|
||||
socialSignInConnectorIds,
|
||||
socialSignInConnectorTargets,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { SignInExperiences } from '@logto/schemas';
|
||||
|
||||
import { getConnectorInstances, getEnabledSocialConnectorIds } from '@/connectors';
|
||||
import { getConnectorInstances } from '@/connectors';
|
||||
import {
|
||||
validateBranding,
|
||||
validateTermsOfUse,
|
||||
|
@ -21,22 +21,7 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
|
|||
* always return the default settings in DB for the /sign-in-exp get method
|
||||
*/
|
||||
router.get('/sign-in-exp', async (ctx, next) => {
|
||||
const [signInExperience, enabledSocialConnectorIds] = await Promise.all([
|
||||
findDefaultSignInExperience(),
|
||||
getEnabledSocialConnectorIds(),
|
||||
]);
|
||||
|
||||
const { socialSignInConnectorIds: selectedSocialSignInConnectorIds } = signInExperience;
|
||||
const enabledSocialConnectorIdSet = new Set(enabledSocialConnectorIds);
|
||||
|
||||
const socialSignInConnectorIds = selectedSocialSignInConnectorIds.filter((id) =>
|
||||
enabledSocialConnectorIdSet.has(id)
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
...signInExperience,
|
||||
socialSignInConnectorIds,
|
||||
};
|
||||
ctx.body = await findDefaultSignInExperience();
|
||||
|
||||
return next();
|
||||
});
|
||||
|
@ -47,7 +32,7 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
|
|||
body: SignInExperiences.createGuard.omit({ id: true }).partial(),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { socialSignInConnectorIds, ...rest } = ctx.guard.body;
|
||||
const { socialSignInConnectorTargets, ...rest } = ctx.guard.body;
|
||||
const { branding, termsOfUse, signInMethods } = rest;
|
||||
|
||||
if (branding) {
|
||||
|
@ -65,10 +50,14 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
|
|||
(instance) => instance.connector.enabled
|
||||
);
|
||||
|
||||
validateSignInMethods(signInMethods, socialSignInConnectorIds, enabledConnectorInstances);
|
||||
validateSignInMethods(
|
||||
signInMethods,
|
||||
socialSignInConnectorTargets,
|
||||
enabledConnectorInstances
|
||||
);
|
||||
}
|
||||
|
||||
// Update socialSignInConnectorIds only when social sign-in is enabled.
|
||||
// Update socialSignInConnectorTargets only when social sign-in is enabled.
|
||||
const signInExperience =
|
||||
signInMethods && isEnabled(signInMethods.social) ? ctx.guard.body : rest;
|
||||
|
||||
|
|
|
@ -126,9 +126,9 @@ export const signInMethodsGuard = z.object({
|
|||
|
||||
export type SignInMethods = z.infer<typeof signInMethodsGuard>;
|
||||
|
||||
export const connectorIdsGuard = z.string().array();
|
||||
export const connectorTargetsGuard = z.string().array();
|
||||
|
||||
export type ConnectorIds = z.infer<typeof connectorIdsGuard>;
|
||||
export type ConnectorTargets = z.infer<typeof connectorTargetsGuard>;
|
||||
|
||||
/**
|
||||
* Settings
|
||||
|
|
|
@ -26,5 +26,5 @@ export const defaultSignInExperience: Readonly<CreateSignInExperience> = {
|
|||
sms: SignInMethodState.Disabled,
|
||||
social: SignInMethodState.Disabled,
|
||||
},
|
||||
socialSignInConnectorIds: [],
|
||||
socialSignInConnectorTargets: [],
|
||||
};
|
||||
|
|
|
@ -4,6 +4,6 @@ create table sign_in_experiences (
|
|||
language_info jsonb /* @use LanguageInfo */ not null,
|
||||
terms_of_use jsonb /* @use TermsOfUse */ not null,
|
||||
sign_in_methods jsonb /* @use SignInMethods */ not null,
|
||||
social_sign_in_connector_ids jsonb /* @use ConnectorIds */ not null default '[]'::jsonb,
|
||||
social_sign_in_connector_targets jsonb /* @use ConnectorTargets */ not null default '[]'::jsonb,
|
||||
primary key (id)
|
||||
);
|
||||
|
|
|
@ -139,7 +139,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
sms: SignInMethodState.Secondary,
|
||||
social: SignInMethodState.Secondary,
|
||||
},
|
||||
socialSignInConnectorIds: ['BE8QXN0VsrOH7xdWFDJZ9', 'lcXT4o2GSjbV9kg2shZC7'],
|
||||
socialSignInConnectorTargets: ['BE8QXN0VsrOH7xdWFDJZ9', 'lcXT4o2GSjbV9kg2shZC7'],
|
||||
};
|
||||
|
||||
export const mockSignInExperienceSettings: SignInExperienceSettings = {
|
||||
|
|
Loading…
Add table
Reference in a new issue