0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-23 20:33:16 -05:00

feat(core,phrases): validate there must be one and only one primary sign-in method (#475)

This commit is contained in:
IceHe.xyz 2022-04-01 13:52:01 +08:00 committed by GitHub
parent b416ee877e
commit bf94ee2d10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 8 deletions

View file

@ -1,8 +1,12 @@
import { BrandingStyle } from '@logto/schemas';
import { BrandingStyle, SignInMethodState } from '@logto/schemas';
import RequestError from '@/errors/RequestError';
import { validateBranding, validateTermsOfUse } from '@/lib/sign-in-experience';
import { mockBranding } from '@/utils/mock';
import {
validateBranding,
validateSignInMethods,
validateTermsOfUse,
} from '@/lib/sign-in-experience';
import { mockBranding, mockSignInMethods } from '@/utils/mock';
describe('validate branding', () => {
test('should throw when the UI style contains the slogan and slogan is empty', () => {
@ -44,3 +48,29 @@ describe('validate terms of use', () => {
}).toMatchError(new RequestError('sign_in_experiences.empty_content_url_of_terms_of_use'));
});
});
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', async () => {
expect(() => {
validateSignInMethods({
...mockSignInMethods,
username: SignInMethodState.disabled,
});
}).toMatchError(
new RequestError('sign_in_experiences.not_one_and_only_one_primary_sign_in_method')
);
});
test('should throw when there are more than one primary sign-in methods', async () => {
expect(() => {
validateSignInMethods({
...mockSignInMethods,
social: SignInMethodState.primary,
});
}).toMatchError(
new RequestError('sign_in_experiences.not_one_and_only_one_primary_sign_in_method')
);
});
});
});

View file

@ -1,4 +1,10 @@
import { Branding, BrandingStyle, TermsOfUse } from '@logto/schemas';
import {
Branding,
BrandingStyle,
SignInMethods,
SignInMethodState,
TermsOfUse,
} from '@logto/schemas';
import assertThat from '@/utils/assert-that';
@ -14,3 +20,13 @@ export const validateTermsOfUse = (termsOfUse: TermsOfUse) => {
'sign_in_experiences.empty_content_url_of_terms_of_use'
);
};
export const validateSignInMethods = (signInMethods: SignInMethods) => {
const signInMethodStates = Object.values(signInMethods);
assertThat(
signInMethodStates.filter((state) => state === SignInMethodState.primary).length === 1,
'sign_in_experiences.not_one_and_only_one_primary_sign_in_method'
);
// TODO: assert others next PR
};

View file

@ -1,7 +1,7 @@
import { SignInExperience, CreateSignInExperience, TermsOfUse } from '@logto/schemas';
import * as signInExpLib from '@/lib/sign-in-experience';
import { mockBranding, mockSignInExperience } from '@/utils/mock';
import { mockBranding, mockSignInExperience, mockSignInMethods } from '@/utils/mock';
import { createRequester } from '@/utils/test-utils';
import signInExperiencesRoutes from './sign-in-experience';
@ -38,15 +38,19 @@ describe('signInExperiences routes', () => {
const validateBranding = jest.spyOn(signInExpLib, 'validateBranding');
const validateTermsOfUse = jest.spyOn(signInExpLib, 'validateTermsOfUse');
const validateSignInMethods = jest.spyOn(signInExpLib, 'validateSignInMethods');
const response = await signInExperienceRequester.patch('/sign-in-exp').send({
branding: mockBranding,
termsOfUse,
signInMethods: mockSignInMethods,
socialSignInConnectorIds,
});
expect(validateBranding).toHaveBeenCalledWith(mockBranding);
expect(validateTermsOfUse).toHaveBeenCalledWith(termsOfUse);
expect(validateSignInMethods).toHaveBeenCalledWith(mockSignInMethods);
// TODO: only update socialSignInConnectorIds when social sign-in is enabled.
expect(response).toMatchObject({
status: 200,
@ -54,6 +58,7 @@ describe('signInExperiences routes', () => {
...mockSignInExperience,
branding: mockBranding,
termsOfUse,
signInMethods: mockSignInMethods,
socialSignInConnectorIds,
},
});

View file

@ -1,7 +1,11 @@
import { SignInExperiences } from '@logto/schemas';
import { getEnabledSocialConnectorIds } from '@/connectors';
import { validateBranding, validateTermsOfUse } from '@/lib/sign-in-experience';
import {
validateBranding,
validateTermsOfUse,
validateSignInMethods,
} from '@/lib/sign-in-experience';
import koaGuard from '@/middleware/koa-guard';
import {
findDefaultSignInExperience,
@ -42,7 +46,7 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
body: SignInExperiences.createGuard.omit({ id: true }).partial(),
}),
async (ctx, next) => {
const { branding, termsOfUse } = ctx.guard.body;
const { branding, termsOfUse, signInMethods } = ctx.guard.body;
if (branding) {
validateBranding(branding);
@ -52,7 +56,10 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
validateTermsOfUse(termsOfUse);
}
// TODO: validate SignInMethods
if (signInMethods) {
validateSignInMethods(signInMethods);
}
// TODO: validate socialConnectorIds
// TODO: Only update socialSignInConnectorIds when social sign-in is enabled.

View file

@ -21,6 +21,7 @@ import {
ConnectorType,
SignInMethodState,
Branding,
SignInMethods,
} from '@logto/schemas';
import pick from 'lodash.pick';
@ -382,4 +383,11 @@ export const mockBranding: Branding = {
logoUrl: 'http://silverhand.png',
slogan: 'Silverhand.',
};
export const mockSignInMethods: SignInMethods = {
username: SignInMethodState.primary,
email: SignInMethodState.disabled,
sms: SignInMethodState.disabled,
social: SignInMethodState.disabled,
};
/* eslint-enable max-lines */

View file

@ -347,6 +347,8 @@ 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.',
not_one_and_only_one_primary_sign_in_method:
'There must be one and only one primary sign-in method. Please check your input.',
},
swagger: {
invalid_zod_type: 'Invalid Zod type, please check route guard config.',

View file

@ -344,6 +344,7 @@ const errors = {
empty_content_url_of_terms_of_use:
'空的《用户协议》内容链接。当启用《用户协议》时,请添加其内容链接。',
empty_slogan: '空的标语。当使用包含标语的 UI 风格时,请添加标语。',
not_one_and_only_one_primary_sign_in_method: '主要的登录方式必须有且仅有一个。请检查你的输入。',
},
swagger: {
invalid_zod_type: '无效的 Zod 类型,请检查路由 guard 配置。',