mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(core,phrases): validate social sign-in method with social connector IDs (#485)
This commit is contained in:
parent
44eb14fd6b
commit
58d03ae715
6 changed files with 88 additions and 21 deletions
|
@ -1,6 +1,6 @@
|
|||
import { BrandingStyle, SignInMethodState } from '@logto/schemas';
|
||||
import { BrandingStyle, SignInMethodState, ConnectorType } from '@logto/schemas';
|
||||
|
||||
import { ConnectorType } from '@/connectors/types';
|
||||
import { ConnectorInstance } from '@/connectors/types';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import {
|
||||
validateBranding,
|
||||
|
@ -62,7 +62,11 @@ describe('validate sign-in methods', () => {
|
|||
describe('There must be one and only one primary sign-in method.', () => {
|
||||
test('should throw when there is no primary sign-in method', () => {
|
||||
expect(() => {
|
||||
validateSignInMethods({ ...mockSignInMethods, username: SignInMethodState.disabled }, []);
|
||||
validateSignInMethods(
|
||||
{ ...mockSignInMethods, username: SignInMethodState.disabled },
|
||||
[],
|
||||
[]
|
||||
);
|
||||
}).toMatchError(
|
||||
new RequestError('sign_in_experiences.not_one_and_only_one_primary_sign_in_method')
|
||||
);
|
||||
|
@ -70,7 +74,7 @@ describe('validate sign-in methods', () => {
|
|||
|
||||
test('should throw when there are more than one primary sign-in methods', () => {
|
||||
expect(() => {
|
||||
validateSignInMethods({ ...mockSignInMethods, social: SignInMethodState.primary }, []);
|
||||
validateSignInMethods({ ...mockSignInMethods, social: SignInMethodState.primary }, [], []);
|
||||
}).toMatchError(
|
||||
new RequestError('sign_in_experiences.not_one_and_only_one_primary_sign_in_method')
|
||||
);
|
||||
|
@ -82,8 +86,8 @@ describe('validate sign-in methods', () => {
|
|||
expect(() => {
|
||||
validateSignInMethods(
|
||||
{ ...mockSignInMethods, email: SignInMethodState.secondary },
|
||||
// @ts-expect-error-this-line
|
||||
enabledConnectorInstances
|
||||
[],
|
||||
enabledConnectorInstances as ConnectorInstance[]
|
||||
);
|
||||
}).toMatchError(
|
||||
new RequestError({
|
||||
|
@ -97,8 +101,8 @@ describe('validate sign-in methods', () => {
|
|||
expect(() => {
|
||||
validateSignInMethods(
|
||||
{ ...mockSignInMethods, sms: SignInMethodState.secondary },
|
||||
// @ts-expect-error-this-line
|
||||
enabledConnectorInstances
|
||||
[],
|
||||
enabledConnectorInstances as ConnectorInstance[]
|
||||
);
|
||||
}).toMatchError(
|
||||
new RequestError({
|
||||
|
@ -110,10 +114,9 @@ describe('validate sign-in methods', () => {
|
|||
|
||||
test('should throw when there is no enabled social connector and social sign-in method is enabled', () => {
|
||||
expect(() => {
|
||||
validateSignInMethods({ ...mockSignInMethods, social: SignInMethodState.secondary }, [
|
||||
// @ts-expect-error-this-line
|
||||
validateSignInMethods({ ...mockSignInMethods, social: SignInMethodState.secondary }, [], [
|
||||
mockAliyunDmConnectorInstance,
|
||||
]);
|
||||
] as ConnectorInstance[]);
|
||||
}).toMatchError(
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.enabled_connector_not_found',
|
||||
|
@ -122,4 +125,46 @@ describe('validate sign-in methods', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw when the social connector IDs are empty and social sign-in method is enabled', () => {
|
||||
expect(() => {
|
||||
validateSignInMethods(
|
||||
{ ...mockSignInMethods, social: SignInMethodState.secondary },
|
||||
[],
|
||||
enabledConnectorInstances as ConnectorInstance[]
|
||||
);
|
||||
}).toMatchError(new RequestError('sign_in_experiences.empty_social_connectors'));
|
||||
});
|
||||
|
||||
describe('Selected social connectors must be enabled only when social sign-in method is enabled.', () => {
|
||||
test('should not validate selected social connectors when social sign-in method is disabled', () => {
|
||||
expect(() => {
|
||||
validateSignInMethods(
|
||||
{ ...mockSignInMethods, social: SignInMethodState.disabled },
|
||||
['google', 'facebook'],
|
||||
enabledConnectorInstances as ConnectorInstance[]
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('should throw when some selected social connector are disabled and social sign-in method is enabled', () => {
|
||||
expect(() => {
|
||||
validateSignInMethods(
|
||||
{ ...mockSignInMethods, social: SignInMethodState.secondary },
|
||||
['google', 'facebook'],
|
||||
enabledConnectorInstances as ConnectorInstance[]
|
||||
);
|
||||
}).toMatchError(new RequestError('sign_in_experiences.invalid_social_connectors'));
|
||||
});
|
||||
|
||||
test('should not throw when all selected social connectors are enabled and social sign-in method is enabled', () => {
|
||||
expect(() => {
|
||||
validateSignInMethods(
|
||||
{ ...mockSignInMethods, social: SignInMethodState.secondary },
|
||||
['facebook', 'github'],
|
||||
enabledConnectorInstances as ConnectorInstance[]
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
SignInMethodState,
|
||||
TermsOfUse,
|
||||
} from '@logto/schemas';
|
||||
import { Optional } from '@silverhand/essentials';
|
||||
|
||||
import { ConnectorInstance, ConnectorType } from '@/connectors/types';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
@ -26,6 +27,7 @@ const isEnabled = (state: SignInMethodState) => state !== SignInMethodState.disa
|
|||
|
||||
export const validateSignInMethods = (
|
||||
signInMethods: SignInMethods,
|
||||
socialSignInConnectorIds: Optional<string[]>,
|
||||
enabledConnectorInstances: ConnectorInstance[]
|
||||
) => {
|
||||
const signInMethodStates = Object.values(signInMethods);
|
||||
|
@ -56,7 +58,20 @@ export const validateSignInMethods = (
|
|||
'sign_in_experiences.enabled_connector_not_found',
|
||||
{ type: ConnectorType.Social }
|
||||
);
|
||||
// TODO: assertNonemptySocialConnectorIds
|
||||
// TODO: assertEnabledSocialConnectorIds
|
||||
|
||||
assertThat(
|
||||
socialSignInConnectorIds && socialSignInConnectorIds.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)),
|
||||
'sign_in_experiences.invalid_social_connectors'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -67,10 +67,11 @@ describe('signInExperiences routes', () => {
|
|||
|
||||
expect(validateBranding).toHaveBeenCalledWith(mockBranding);
|
||||
expect(validateTermsOfUse).toHaveBeenCalledWith(termsOfUse);
|
||||
expect(validateSignInMethods).toHaveBeenCalledWith(mockSignInMethods, [
|
||||
mockFacebookConnectorInstance,
|
||||
mockGithubConnectorInstance,
|
||||
]);
|
||||
expect(validateSignInMethods).toHaveBeenCalledWith(
|
||||
mockSignInMethods,
|
||||
socialSignInConnectorIds,
|
||||
[mockFacebookConnectorInstance, mockGithubConnectorInstance]
|
||||
);
|
||||
// TODO: only update socialSignInConnectorIds when social sign-in is enabled.
|
||||
|
||||
expect(response).toMatchObject({
|
||||
|
|
|
@ -46,7 +46,7 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
|
|||
body: SignInExperiences.createGuard.omit({ id: true }).partial(),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { branding, termsOfUse, signInMethods } = ctx.guard.body;
|
||||
const { branding, termsOfUse, signInMethods, socialSignInConnectorIds } = ctx.guard.body;
|
||||
|
||||
if (branding) {
|
||||
validateBranding(branding);
|
||||
|
@ -63,11 +63,9 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
|
|||
(instance) => instance.connector.enabled
|
||||
);
|
||||
|
||||
validateSignInMethods(signInMethods, enabledConnectorInstances);
|
||||
validateSignInMethods(signInMethods, socialSignInConnectorIds, enabledConnectorInstances);
|
||||
}
|
||||
|
||||
// TODO: validate socialConnectorIds
|
||||
|
||||
// TODO: Only update socialSignInConnectorIds when social sign-in is enabled.
|
||||
ctx.body = await updateDefaultSignInExperience({
|
||||
...ctx.guard.body,
|
||||
|
|
|
@ -347,7 +347,11 @@ const errors = {
|
|||
'Empty "Terms of use" content URL. Please add the content URL if "Terms of use" is enabled.',
|
||||
empty_slogan:
|
||||
'Empty branding slogan. Please add a branding slogan if a UI style containing the slogan is selected.',
|
||||
empty_social_connectors:
|
||||
'Empty social connectors. Please add enabled social connectors when the social sign-in method is enabled.',
|
||||
enabled_connector_not_found: 'Enabled {{type}} connector not found.',
|
||||
invalid_social_connectors:
|
||||
'Invalid social connectors, Please confirm all your selected social connectors are enabled.',
|
||||
not_one_and_only_one_primary_sign_in_method:
|
||||
'There must be one and only one primary sign-in method. Please check your input.',
|
||||
},
|
||||
|
|
|
@ -344,7 +344,11 @@ const errors = {
|
|||
empty_content_url_of_terms_of_use:
|
||||
'空的《用户协议》内容链接。当启用《用户协议》时,请添加其内容链接。',
|
||||
empty_slogan: '空的标语。当使用包含标语的 UI 风格时,请添加标语。',
|
||||
empty_social_connectors:
|
||||
'空的 social 连接器。当启用社交网络连接器的登录方式时,请添加可用的 social 连接器。',
|
||||
enabled_connector_not_found: '未找到可用的 {{type}} 类型的连接器。',
|
||||
invalid_social_connectors:
|
||||
'无效的 social 连接器。请确认你选择的所有连接器都可用,并且是 social 类型的。',
|
||||
not_one_and_only_one_primary_sign_in_method: '主要的登录方式必须有且仅有一个,请检查你的输入。',
|
||||
},
|
||||
swagger: {
|
||||
|
|
Loading…
Reference in a new issue