0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

feat(core): validate sign in (#2114)

This commit is contained in:
wangsijie 2022-10-12 11:04:28 +08:00 committed by GitHub
parent 4a6f25cec6
commit 37c8e703da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 407 additions and 0 deletions

View file

@ -11,6 +11,7 @@ import {
SignUpIdentifier,
SignInIdentifier,
SignUp,
SignIn,
} from '@logto/schemas';
export const mockSignInExperience: SignInExperience = {
@ -94,3 +95,10 @@ export const mockSignUp: SignUp = {
password: true,
verify: false,
};
export const mockSignInMethod: SignIn['methods'][0] = {
identifier: SignInIdentifier.Username,
password: true,
verificationCode: false,
isPasswordPrimary: true,
};

View file

@ -4,6 +4,7 @@ import assertThat from '@/utils/assert-that';
export * from './sign-in-methods';
export * from './sign-up';
export * from './sign-in';
export const validateBranding = (branding: Branding) => {
if (branding.style === BrandingStyle.Logo_Slogan) {

View file

@ -0,0 +1,264 @@
import { ConnectorType, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
import {
mockAliyunDmConnector,
mockAliyunSmsConnector,
mockSignInMethod,
mockSignUp,
} from '@/__mocks__';
import RequestError from '@/errors/RequestError';
import { validateSignIn } from './sign-in';
const enabledConnectors = [mockAliyunDmConnector, mockAliyunSmsConnector];
describe('validate sign-in', () => {
describe('pass on valid cases', () => {
test('email or phone sign up', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Email,
verificationCode: true,
},
{
...mockSignInMethod,
identifier: SignInIdentifier.Phone,
verificationCode: true,
},
],
},
{
...mockSignUp,
identifier: SignUpIdentifier.EmailOrPhone,
password: false,
verify: true,
},
enabledConnectors
);
}).not.toThrow();
});
test('username sign up', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Username,
password: true,
},
],
},
{
...mockSignUp,
identifier: SignUpIdentifier.Username,
password: true,
},
[]
);
}).not.toThrow();
});
});
describe('There must be at least one enabled connector for the specific identifier.', () => {
it('throws when there is no enabled email connector and identifiers includes email', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Email,
},
],
},
mockSignUp,
[]
);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.Email,
})
);
});
it('throws when there is no enabled sms connector and identifiers includes phone', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Phone,
},
],
},
mockSignUp,
[]
);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.Sms,
})
);
});
});
describe('The sign up identifier must be included in sign in', () => {
it('throws when sign up is username and sign in methods does not include username', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Phone,
},
],
},
{
...mockSignUp,
identifier: SignUpIdentifier.Username,
},
enabledConnectors
);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})
);
});
it('throws when sign up is email and sign in methods does not include email', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Username,
},
],
},
{
...mockSignUp,
identifier: SignUpIdentifier.Email,
},
enabledConnectors
);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})
);
});
it('throws when sign up is phone and sign in methods does not include phone', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Username,
},
],
},
{
...mockSignUp,
identifier: SignUpIdentifier.Phone,
},
enabledConnectors
);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})
);
});
it('throws when sign up is `email or phone` and sign in methods does not include email and phone', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Email,
},
],
},
{
...mockSignUp,
identifier: SignUpIdentifier.EmailOrPhone,
},
enabledConnectors
);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})
);
});
});
it('throws when sign up requires set a password and sign in password is not enabled', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Email,
password: false,
},
],
},
{
...mockSignUp,
identifier: SignUpIdentifier.Email,
password: true,
},
enabledConnectors
);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.password_sign_in_must_be_enabled',
})
);
});
it('throws when sign up only requires verify and sign in verification code is not enabled', () => {
expect(() => {
validateSignIn(
{
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Email,
verificationCode: false,
},
],
},
{
...mockSignUp,
identifier: SignUpIdentifier.Email,
password: false,
verify: true,
},
enabledConnectors
);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.code_sign_in_must_be_enabled',
})
);
});
});

View file

@ -0,0 +1,104 @@
import { ConnectorType, SignIn, SignInIdentifier, SignUp, SignUpIdentifier } from '@logto/schemas';
import { LogtoConnector } from '@/connectors/types';
import RequestError from '@/errors/RequestError';
import assertThat from '@/utils/assert-that';
export const validateSignIn = (
signIn: SignIn,
signUp: SignUp,
enabledConnectors: LogtoConnector[]
) => {
if (signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Email)) {
assertThat(
enabledConnectors.some((item) => item.type === ConnectorType.Email),
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.Email,
})
);
}
if (signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Phone)) {
assertThat(
enabledConnectors.some((item) => item.type === ConnectorType.Sms),
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.Sms,
})
);
}
switch (signUp.identifier) {
case SignUpIdentifier.Username: {
assertThat(
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Username),
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})
);
break;
}
case SignUpIdentifier.Email: {
assertThat(
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Email),
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})
);
break;
}
case SignUpIdentifier.Phone: {
assertThat(
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Phone),
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})
);
break;
}
case SignUpIdentifier.EmailOrPhone: {
assertThat(
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Email) &&
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Phone),
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})
);
break;
}
case SignUpIdentifier.None: {
// No requirement
}
// No default
}
if (signUp.password) {
assertThat(
signIn.methods.every(({ password }) => password),
new RequestError({
code: 'sign_in_experiences.password_sign_in_must_be_enabled',
})
);
}
if (signUp.verify && !signUp.password) {
assertThat(
signIn.methods.every(
({ verificationCode, identifier }) =>
verificationCode || identifier === SignInIdentifier.Username
),
new RequestError({
code: 'sign_in_experiences.code_sign_in_must_be_enabled',
})
);
}
};

View file

@ -107,6 +107,11 @@ const errors = {
'There must be one and only one primary sign-in method. Please check your input.',
username_requires_password: 'Must enable set a password for username sign up identifier.',
passwordless_requires_verify: 'Must enable verify for email/phone sign up identifier.',
miss_sign_up_identifier_in_sign_in: 'Sign in methods must contain the sign up identifier.',
password_sign_in_must_be_enabled:
'Password sign in must be enabled when set a password is required in sign up.',
code_sign_in_must_be_enabled:
'Verification code sign in must be enabled when set a password is not required in sign up.',
},
localization: {
cannot_delete_default_language:

View file

@ -115,6 +115,11 @@ const errors = {
'Il doit y avoir une et une seule méthode de connexion primaire. Veuillez vérifier votre saisie.',
username_requires_password: 'Must enable set a password for username sign up identifier.', // UNTRANSLATED
passwordless_requires_verify: 'Must enable verify for email/phone sign up identifier.', // UNTRANSLATED
miss_sign_up_identifier_in_sign_in: 'Sign in methods must contain the sign up identifier.', // UNTRANSLATED
password_sign_in_must_be_enabled:
'Password sign in must be enabled when set a password is required in sign up.', // UNTRANSLATED
code_sign_in_must_be_enabled:
'Verification code sign in must be enabled when set a password is not required in sign up.', // UNTRANSLATED
},
localization: {
cannot_delete_default_language:

View file

@ -104,6 +104,11 @@ const errors = {
'반드시 하나의 메인 로그인 방법이 설정되어야 해요. 입력된 값을 확인해주세요.',
username_requires_password: 'Must enable set a password for username sign up identifier.', // UNTRANSLATED
passwordless_requires_verify: 'Must enable verify for email/phone sign up identifier.', // UNTRANSLATED
miss_sign_up_identifier_in_sign_in: 'Sign in methods must contain the sign up identifier.', // UNTRANSLATED
password_sign_in_must_be_enabled:
'Password sign in must be enabled when set a password is required in sign up.', // UNTRANSLATED
code_sign_in_must_be_enabled:
'Verification code sign in must be enabled when set a password is not required in sign up.', // UNTRANSLATED
},
localization: {
cannot_delete_default_language:

View file

@ -110,6 +110,11 @@ const errors = {
'Deve haver um e apenas um método de login principal. Por favor, verifique sua entrada.',
username_requires_password: 'Must enable set a password for username sign up identifier.', // UNTRANSLATED
passwordless_requires_verify: 'Must enable verify for email/phone sign up identifier.', // UNTRANSLATED
miss_sign_up_identifier_in_sign_in: 'Sign in methods must contain the sign up identifier.', // UNTRANSLATED
password_sign_in_must_be_enabled:
'Password sign in must be enabled when set a password is required in sign up.', // UNTRANSLATED
code_sign_in_must_be_enabled:
'Verification code sign in must be enabled when set a password is not required in sign up.', // UNTRANSLATED
},
localization: {
cannot_delete_default_language:

View file

@ -108,6 +108,11 @@ const errors = {
'Yalnızca bir tane birincil oturum açma yöntemi olmalıdır. Lütfen inputu kontrol ediniz.',
username_requires_password: 'Must enable set a password for username sign up identifier.', // UNTRANSLATED
passwordless_requires_verify: 'Must enable verify for email/phone sign up identifier.', // UNTRANSLATED
miss_sign_up_identifier_in_sign_in: 'Sign in methods must contain the sign up identifier.', // UNTRANSLATED
password_sign_in_must_be_enabled:
'Password sign in must be enabled when set a password is required in sign up.', // UNTRANSLATED
code_sign_in_must_be_enabled:
'Verification code sign in must be enabled when set a password is not required in sign up.', // UNTRANSLATED
},
localization: {
cannot_delete_default_language:

View file

@ -100,6 +100,11 @@ const errors = {
not_one_and_only_one_primary_sign_in_method: '主要的登录方式必须有且仅有一个,请检查你的输入。',
username_requires_password: 'Must enable set a password for username sign up identifier.', // UNTRANSLATED
passwordless_requires_verify: 'Must enable verify for email/phone sign up identifier.', // UNTRANSLATED
miss_sign_up_identifier_in_sign_in: 'Sign in methods must contain the sign up identifier.', // UNTRANSLATED
password_sign_in_must_be_enabled:
'Password sign in must be enabled when set a password is required in sign up.', // UNTRANSLATED
code_sign_in_must_be_enabled:
'Verification code sign in must be enabled when set a password is not required in sign up.', // UNTRANSLATED
},
localization: {
cannot_delete_default_language: '不能删除「登录体验」正在使用的默认语言 {{languageKey}}。', // UNTRANSLATED