mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
refactor: sign up settings schema (#2559)
This commit is contained in:
parent
c7c98aa179
commit
bb53b32c1d
56 changed files with 573 additions and 434 deletions
|
@ -1,5 +1,5 @@
|
|||
import type { SignInExperience } from '@logto/schemas';
|
||||
import { SignUpIdentifier, SignInIdentifier, ConnectorType } from '@logto/schemas';
|
||||
import { SignInIdentifier, ConnectorType } from '@logto/schemas';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import type { RequestError } from './use-api';
|
||||
|
@ -17,7 +17,7 @@ const useConnectorInUse = (type?: ConnectorType, target?: string): boolean | und
|
|||
({ identifier, verificationCode }) =>
|
||||
verificationCode && identifier === SignInIdentifier.Email
|
||||
) ||
|
||||
(data.signUp.identifier === SignUpIdentifier.Email && data.signUp.verify)
|
||||
(data.signUp.identifiers.includes(SignInIdentifier.Email) && data.signUp.verify)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ const useConnectorInUse = (type?: ConnectorType, target?: string): boolean | und
|
|||
({ identifier, verificationCode }) =>
|
||||
verificationCode && identifier === SignInIdentifier.Sms
|
||||
) ||
|
||||
(data.signUp.identifier === SignUpIdentifier.Sms && data.signUp.verify)
|
||||
(data.signUp.identifiers.includes(SignInIdentifier.Sms) && data.signUp.verify)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,53 +37,53 @@ const SignInDiffSection = ({ before, after, isAfter = false }: Props) => {
|
|||
get(signInDiff, `updated.${identifierKey.toLocaleLowerCase()}.${authenticationKey}`) !==
|
||||
undefined;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const displayedIdentifiers = Object.keys(displaySignInMethodsObject)
|
||||
.slice()
|
||||
.sort() as SignInIdentifier[];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.title}>{t('sign_in_exp.save_alert.sign_in')}</div>
|
||||
<ul className={styles.list}>
|
||||
{
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
(Object.keys(displaySignInMethodsObject).slice().sort() as SignInIdentifier[]).map(
|
||||
(identifierKey) => {
|
||||
const { password, verificationCode } = displaySignInMethodsObject[identifierKey];
|
||||
const hasAuthentication = password || verificationCode;
|
||||
const needDisjunction = password && verificationCode;
|
||||
{displayedIdentifiers.map((identifierKey) => {
|
||||
const { password, verificationCode } = displaySignInMethodsObject[identifierKey];
|
||||
const hasAuthentication = password || verificationCode;
|
||||
const needDisjunction = password && verificationCode;
|
||||
|
||||
return (
|
||||
<li key={identifierKey}>
|
||||
<DiffSegment hasChanged={hasIdentifierChanged(identifierKey)} isAfter={isAfter}>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.identifiers', {
|
||||
context: identifierKey.toLocaleLowerCase(),
|
||||
})}
|
||||
{hasAuthentication && ' ('}
|
||||
{password && (
|
||||
<DiffSegment
|
||||
hasChanged={hasAuthenticationChanged(identifierKey, 'password')}
|
||||
isAfter={isAfter}
|
||||
>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.sign_in.password_auth')}
|
||||
</DiffSegment>
|
||||
)}
|
||||
{needDisjunction && ` ${String(t('sign_in_exp.sign_up_and_sign_in.or'))} `}
|
||||
{verificationCode && (
|
||||
<DiffSegment
|
||||
hasChanged={hasAuthenticationChanged(identifierKey, 'verificationCode')}
|
||||
isAfter={isAfter}
|
||||
>
|
||||
{needDisjunction
|
||||
? t(
|
||||
'sign_in_exp.sign_up_and_sign_in.sign_in.verification_code_auth'
|
||||
).toLocaleLowerCase()
|
||||
: t('sign_in_exp.sign_up_and_sign_in.sign_in.verification_code_auth')}
|
||||
</DiffSegment>
|
||||
)}
|
||||
{hasAuthentication && ')'}
|
||||
return (
|
||||
<li key={identifierKey}>
|
||||
<DiffSegment hasChanged={hasIdentifierChanged(identifierKey)} isAfter={isAfter}>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.identifiers', {
|
||||
context: identifierKey.toLocaleLowerCase(),
|
||||
})}
|
||||
{hasAuthentication && ' ('}
|
||||
{password && (
|
||||
<DiffSegment
|
||||
hasChanged={hasAuthenticationChanged(identifierKey, 'password')}
|
||||
isAfter={isAfter}
|
||||
>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.sign_in.password_auth')}
|
||||
</DiffSegment>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
)}
|
||||
{needDisjunction && ` ${String(t('sign_in_exp.sign_up_and_sign_in.or'))} `}
|
||||
{verificationCode && (
|
||||
<DiffSegment
|
||||
hasChanged={hasAuthenticationChanged(identifierKey, 'verificationCode')}
|
||||
isAfter={isAfter}
|
||||
>
|
||||
{needDisjunction
|
||||
? t(
|
||||
'sign_in_exp.sign_up_and_sign_in.sign_in.verification_code_auth'
|
||||
).toLocaleLowerCase()
|
||||
: t('sign_in_exp.sign_up_and_sign_in.sign_in.verification_code_auth')}
|
||||
</DiffSegment>
|
||||
)}
|
||||
{hasAuthentication && ')'}
|
||||
</DiffSegment>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -4,6 +4,9 @@ import get from 'lodash.get';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { snakeCase } from 'snake-case';
|
||||
|
||||
import type { SignUpForm } from '@/pages/SignInExperience/types';
|
||||
import { signInExperienceParser } from '@/pages/SignInExperience/utils/form';
|
||||
|
||||
import DiffSegment from './DiffSegment';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
@ -15,9 +18,11 @@ type Props = {
|
|||
|
||||
const SignUpDiffSection = ({ before, after, isAfter = false }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const signUpDiff = isAfter ? diff(before, after) : diff(after, before);
|
||||
const signUp = isAfter ? after : before;
|
||||
const hasChanged = (path: keyof SignUp) => get(signUpDiff, path) !== undefined;
|
||||
const parsedBefore = signInExperienceParser.toLocalSignUp(before);
|
||||
const parsedAfter = signInExperienceParser.toLocalSignUp(after);
|
||||
const signUpDiff = isAfter ? diff(parsedBefore, parsedAfter) : diff(parsedAfter, parsedBefore);
|
||||
const signUp = isAfter ? parsedAfter : parsedBefore;
|
||||
const hasChanged = (path: keyof SignUpForm) => get(signUpDiff, path) !== undefined;
|
||||
|
||||
const { identifier, password, verify } = signUp;
|
||||
const hasAuthentication = password || verify;
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import type { SignInExperience } from '@logto/schemas';
|
||||
import type { SignUp } from '@logto/schemas';
|
||||
import { diff } from 'deep-object-diff';
|
||||
|
||||
import type { SignInMethod, SignInMethodsObject } from '@/pages/SignInExperience/types';
|
||||
|
||||
export const isSignUpDifferent = (
|
||||
before: SignInExperience['signUp'],
|
||||
after: SignInExperience['signUp']
|
||||
) => Object.keys(diff(before, after)).length > 0;
|
||||
export const hasSignUpSettingsChanged = (before: SignUp, after: SignUp) =>
|
||||
Object.keys(diff(before, after)).length > 0;
|
||||
|
||||
export const convertToSignInMethodsObject = (signInMethods: SignInMethod[]): SignInMethodsObject =>
|
||||
signInMethods.reduce<SignInMethodsObject>(
|
||||
|
@ -18,9 +16,9 @@ export const convertToSignInMethodsObject = (signInMethods: SignInMethod[]): Sig
|
|||
{} as SignInMethodsObject
|
||||
);
|
||||
|
||||
export const isSignInMethodsDifferent = (before: SignInMethod[], after: SignInMethod[]) =>
|
||||
export const hasSignInMethodsChanged = (before: SignInMethod[], after: SignInMethod[]) =>
|
||||
Object.keys(diff(convertToSignInMethodsObject(before), convertToSignInMethodsObject(after)))
|
||||
.length > 0;
|
||||
|
||||
export const isSocialTargetsDifferent = (before: string[], after: string[]) =>
|
||||
export const hasSocialTargetsChanged = (before: string[], after: string[]) =>
|
||||
Object.keys(diff(before.slice().sort(), after.slice().sort())).length > 0;
|
||||
|
|
|
@ -22,7 +22,7 @@ import ColorForm from '../../tabs/Branding/ColorForm';
|
|||
import LanguagesForm from '../../tabs/Others/LanguagesForm';
|
||||
import TermsForm from '../../tabs/Others/TermsForm';
|
||||
import type { SignInExperienceForm } from '../../types';
|
||||
import { signInExperienceParser } from '../../utilities';
|
||||
import { signInExperienceParser } from '../../utils/form';
|
||||
import Preview from '../Preview';
|
||||
import * as styles from './GuideModal.module.scss';
|
||||
|
||||
|
|
22
packages/console/src/pages/SignInExperience/constants.ts
Normal file
22
packages/console/src/pages/SignInExperience/constants.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { ConnectorType, SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
import { SignUpIdentifier } from './types';
|
||||
|
||||
export const signUpIdentifiers = Object.values(SignUpIdentifier);
|
||||
|
||||
export const signInIdentifiers = Object.values(SignInIdentifier);
|
||||
|
||||
export const signUpIdentifiersMapping: { [key in SignUpIdentifier]: SignInIdentifier[] } = {
|
||||
[SignUpIdentifier.Username]: [SignInIdentifier.Username],
|
||||
[SignUpIdentifier.Email]: [SignInIdentifier.Email],
|
||||
[SignUpIdentifier.Sms]: [SignInIdentifier.Sms],
|
||||
[SignUpIdentifier.EmailOrSms]: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
[SignUpIdentifier.None]: [],
|
||||
};
|
||||
|
||||
export const identifierRequiredConnectorMapping: {
|
||||
[key in SignInIdentifier]?: ConnectorType;
|
||||
} = {
|
||||
[SignInIdentifier.Email]: ConnectorType.Email,
|
||||
[SignInIdentifier.Sms]: ConnectorType.Sms,
|
||||
};
|
|
@ -2,7 +2,7 @@ import type { SignInExperience } from '@logto/schemas';
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import type { SignInExperienceForm } from '../types';
|
||||
import { signInExperienceParser } from '../utilities';
|
||||
import { signInExperienceParser } from '../utils/form';
|
||||
|
||||
const usePreviewConfigs = (
|
||||
formData: SignInExperienceForm,
|
||||
|
|
|
@ -28,12 +28,12 @@ import Others from './tabs/Others';
|
|||
import SignUpAndSignIn from './tabs/SignUpAndSignIn';
|
||||
import type { SignInExperienceForm } from './types';
|
||||
import {
|
||||
compareSignUpAndSignInConfigs,
|
||||
hasSignUpAndSignInConfigChanged,
|
||||
getBrandingErrorCount,
|
||||
getOthersErrorCount,
|
||||
getSignUpAndSignInErrorCount,
|
||||
signInExperienceParser,
|
||||
} from './utilities';
|
||||
} from './utils/form';
|
||||
|
||||
const SignInExperience = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
@ -90,7 +90,7 @@ const SignInExperience = () => {
|
|||
const formatted = signInExperienceParser.toRemoteModel(formData);
|
||||
|
||||
// Sign-in methods changed, need to show confirm modal first.
|
||||
if (!compareSignUpAndSignInConfigs(data, formatted)) {
|
||||
if (!hasSignUpAndSignInConfigChanged(data, formatted)) {
|
||||
setDataToCompare(formatted);
|
||||
|
||||
return;
|
||||
|
|
|
@ -18,9 +18,12 @@ import ConfirmModal from '@/components/ConfirmModal';
|
|||
import IconButton from '@/components/IconButton';
|
||||
import useApi, { RequestError } from '@/hooks/use-api';
|
||||
import useUiLanguages from '@/hooks/use-ui-languages';
|
||||
import {
|
||||
createEmptyUiTranslation,
|
||||
flattenTranslation,
|
||||
} from '@/pages/SignInExperience/utils/language';
|
||||
import type { CustomPhraseResponse } from '@/types/custom-phrase';
|
||||
|
||||
import { createEmptyUiTranslation, flattenTranslation } from '../../../../../utilities';
|
||||
import EditSection from './EditSection';
|
||||
import * as style from './LanguageDetails.module.scss';
|
||||
import { LanguageEditorContext } from './use-language-editor-context';
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { SignUpIdentifier } from '@logto/schemas';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { snakeCase } from 'snake-case';
|
||||
|
@ -9,19 +8,19 @@ import FormField from '@/components/FormField';
|
|||
import Select from '@/components/Select';
|
||||
import useEnabledConnectorTypes from '@/hooks/use-enabled-connector-types';
|
||||
|
||||
import { signUpIdentifiers, signUpIdentifiersMapping } from '../../constants';
|
||||
import type { SignInExperienceForm } from '../../types';
|
||||
import { SignUpIdentifier } from '../../types';
|
||||
import {
|
||||
getSignUpRequiredConnectorTypes,
|
||||
isVerificationRequiredSignUpIdentifiers,
|
||||
} from '../../utils/identifier';
|
||||
import * as styles from '../index.module.scss';
|
||||
import ConnectorSetupWarning from './components/ConnectorSetupWarning';
|
||||
import {
|
||||
getSignInMethodPasswordCheckState,
|
||||
getSignInMethodVerificationCodeCheckState,
|
||||
} from './components/SignInMethodEditBox/utilities';
|
||||
import {
|
||||
requiredVerifySignUpIdentifiers,
|
||||
signUpIdentifiers,
|
||||
signUpIdentifierToRequiredConnectorMapping,
|
||||
signUpToSignInIdentifierMapping,
|
||||
} from './constants';
|
||||
|
||||
const SignUpForm = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
@ -56,7 +55,7 @@ const SignUpForm = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (requiredVerifySignUpIdentifiers.includes(signUpIdentifier)) {
|
||||
if (isVerificationRequiredSignUpIdentifiers(signUpIdentifier)) {
|
||||
setValue('signUp.verify', true);
|
||||
}
|
||||
};
|
||||
|
@ -67,7 +66,7 @@ const SignUpForm = () => {
|
|||
const isSignUpPasswordRequired = getValues('signUp.password');
|
||||
|
||||
// Note: append required sign-in methods according to the sign-up identifier config
|
||||
const requiredSignInIdentifiers = signUpToSignInIdentifierMapping[signUpIdentifier];
|
||||
const requiredSignInIdentifiers = signUpIdentifiersMapping[signUpIdentifier];
|
||||
const allSignInMethods = requiredSignInIdentifiers.reduce((methods, requiredIdentifier) => {
|
||||
if (signInMethods.some(({ identifier }) => identifier === requiredIdentifier)) {
|
||||
return methods;
|
||||
|
@ -120,7 +119,7 @@ const SignUpForm = () => {
|
|||
control={control}
|
||||
rules={{
|
||||
validate: (value) => {
|
||||
return signUpIdentifierToRequiredConnectorMapping[value].every((connectorType) =>
|
||||
return getSignUpRequiredConnectorTypes(value).every((connectorType) =>
|
||||
isConnectorTypeEnabled(connectorType)
|
||||
);
|
||||
},
|
||||
|
@ -158,7 +157,7 @@ const SignUpForm = () => {
|
|||
)}
|
||||
/>
|
||||
<ConnectorSetupWarning
|
||||
requiredConnectors={signUpIdentifierToRequiredConnectorMapping[signUpIdentifier]}
|
||||
requiredConnectors={getSignUpRequiredConnectorTypes(signUpIdentifier)}
|
||||
/>
|
||||
</FormField>
|
||||
{signUpIdentifier !== SignUpIdentifier.None && (
|
||||
|
@ -191,7 +190,7 @@ const SignUpForm = () => {
|
|||
<Checkbox
|
||||
label={t('sign_in_exp.sign_up_and_sign_in.sign_up.verify_at_sign_up_option')}
|
||||
value={value}
|
||||
disabled={requiredVerifySignUpIdentifiers.includes(signUpIdentifier)}
|
||||
disabled={isVerificationRequiredSignUpIdentifiers(signUpIdentifier)}
|
||||
disabledTooltip={t('sign_in_exp.sign_up_and_sign_in.tip.verify_at_sign_up')}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
|
|
|
@ -6,14 +6,14 @@ import { useTranslation } from 'react-i18next';
|
|||
import DragDropProvider from '@/components/Transfer/DragDropProvider';
|
||||
import DraggableItem from '@/components/Transfer/DraggableItem';
|
||||
import useEnabledConnectorTypes from '@/hooks/use-enabled-connector-types';
|
||||
import type { SignInExperienceForm } from '@/pages/SignInExperience/types';
|
||||
|
||||
import {
|
||||
identifierRequiredConnectorMapping,
|
||||
signInIdentifiers,
|
||||
signInIdentifierToRequiredConnectorMapping,
|
||||
signUpIdentifierToRequiredConnectorMapping,
|
||||
signUpToSignInIdentifierMapping,
|
||||
} from '../../constants';
|
||||
signUpIdentifiersMapping,
|
||||
} from '@/pages/SignInExperience/constants';
|
||||
import type { SignInExperienceForm } from '@/pages/SignInExperience/types';
|
||||
import { getSignUpRequiredConnectorTypes } from '@/pages/SignInExperience/utils/identifier';
|
||||
|
||||
import AddButton from './AddButton';
|
||||
import SignInMethodItem from './SignInMethodItem';
|
||||
import * as styles from './index.module.scss';
|
||||
|
@ -55,8 +55,8 @@ const SignInMethodEditBox = () => {
|
|||
verify: isSignUpVerificationRequired,
|
||||
} = signUp;
|
||||
|
||||
const requiredSignInIdentifiers = signUpToSignInIdentifierMapping[signUpIdentifier];
|
||||
const ignoredWarningConnectors = signUpIdentifierToRequiredConnectorMapping[signUpIdentifier];
|
||||
const requiredSignInIdentifiers = signUpIdentifiersMapping[signUpIdentifier];
|
||||
const ignoredWarningConnectors = getSignUpRequiredConnectorTypes(signUpIdentifier);
|
||||
|
||||
const signInIdentifierOptions = signInIdentifiers.filter((candidateIdentifier) =>
|
||||
fields.every(({ identifier }) => identifier !== candidateIdentifier)
|
||||
|
@ -67,13 +67,14 @@ const SignInMethodEditBox = () => {
|
|||
<DragDropProvider>
|
||||
{fields.map((signInMethod, index) => {
|
||||
const { id, identifier, verificationCode, isPasswordPrimary } = signInMethod;
|
||||
|
||||
const signInRelatedConnector = identifierRequiredConnectorMapping[identifier];
|
||||
const requiredConnectors =
|
||||
conditional(
|
||||
verificationCode &&
|
||||
signInIdentifierToRequiredConnectorMapping[identifier].filter(
|
||||
(connector) => !ignoredWarningConnectors.includes(connector)
|
||||
)
|
||||
signInRelatedConnector &&
|
||||
!ignoredWarningConnectors.includes(signInRelatedConnector) && [
|
||||
signInRelatedConnector,
|
||||
]
|
||||
) ?? [];
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import { SignUpIdentifier, SignInIdentifier, ConnectorType } from '@logto/schemas';
|
||||
|
||||
export const signUpIdentifiers = Object.values(SignUpIdentifier);
|
||||
|
||||
export const signInIdentifiers = Object.values(SignInIdentifier);
|
||||
|
||||
export const requiredVerifySignUpIdentifiers = [
|
||||
SignUpIdentifier.Email,
|
||||
SignUpIdentifier.Sms,
|
||||
SignUpIdentifier.EmailOrSms,
|
||||
];
|
||||
|
||||
export const signUpToSignInIdentifierMapping: { [key in SignUpIdentifier]: SignInIdentifier[] } = {
|
||||
[SignUpIdentifier.Username]: [SignInIdentifier.Username],
|
||||
[SignUpIdentifier.Email]: [SignInIdentifier.Email],
|
||||
[SignUpIdentifier.Sms]: [SignInIdentifier.Sms],
|
||||
[SignUpIdentifier.EmailOrSms]: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
[SignUpIdentifier.None]: [],
|
||||
};
|
||||
|
||||
export const signUpIdentifierToRequiredConnectorMapping: {
|
||||
[key in SignUpIdentifier]: ConnectorType[];
|
||||
} = {
|
||||
[SignUpIdentifier.Username]: [],
|
||||
[SignUpIdentifier.Email]: [ConnectorType.Email],
|
||||
[SignUpIdentifier.Sms]: [ConnectorType.Sms],
|
||||
[SignUpIdentifier.EmailOrSms]: [ConnectorType.Email, ConnectorType.Sms],
|
||||
[SignUpIdentifier.None]: [],
|
||||
};
|
||||
|
||||
export const signInIdentifierToRequiredConnectorMapping: {
|
||||
[key in SignInIdentifier]: ConnectorType[];
|
||||
} = {
|
||||
[SignInIdentifier.Username]: [],
|
||||
[SignInIdentifier.Email]: [ConnectorType.Email],
|
||||
[SignInIdentifier.Sms]: [ConnectorType.Sms],
|
||||
};
|
|
@ -1,7 +1,19 @@
|
|||
import type { SignInExperience, SignInIdentifier, SignUp } from '@logto/schemas';
|
||||
|
||||
export enum SignUpIdentifier {
|
||||
Email = 'email',
|
||||
Sms = 'sms',
|
||||
Username = 'username',
|
||||
EmailOrSms = 'emailOrSms',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export type SignUpForm = Omit<SignUp, 'identifiers'> & {
|
||||
identifier: SignUpIdentifier;
|
||||
};
|
||||
|
||||
export type SignInExperienceForm = Omit<SignInExperience, 'signUp'> & {
|
||||
signUp?: SignUp;
|
||||
signUp?: SignUpForm;
|
||||
createAccountEnabled: boolean;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,22 +1,41 @@
|
|||
import en from '@logto/phrases-ui/lib/locales/en';
|
||||
import type { SignInExperience, Translation } from '@logto/schemas';
|
||||
import { SignUpIdentifier, SignInMode } from '@logto/schemas';
|
||||
import type { SignInExperience, SignUp } from '@logto/schemas';
|
||||
import { SignInMode, SignInIdentifier } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import type { DeepRequired, FieldErrorsImpl } from 'react-hook-form';
|
||||
|
||||
import {
|
||||
isSignInMethodsDifferent,
|
||||
isSignUpDifferent,
|
||||
isSocialTargetsDifferent,
|
||||
} from './components/SignUpAndSignInChangePreview/SignUpAndSignInDiffSection/utilities';
|
||||
import type { SignInExperienceForm } from './types';
|
||||
hasSignInMethodsChanged,
|
||||
hasSignUpSettingsChanged,
|
||||
hasSocialTargetsChanged,
|
||||
} from '../components/SignUpAndSignInChangePreview/SignUpAndSignInDiffSection/utilities';
|
||||
import { signUpIdentifiersMapping } from '../constants';
|
||||
import { SignUpIdentifier } from '../types';
|
||||
import type { SignInExperienceForm, SignUpForm } from '../types';
|
||||
import { mapIdentifiersToSignUpIdentifier } from './identifier';
|
||||
|
||||
export const signInExperienceParser = {
|
||||
toLocalSignUp: (signUp: SignUp): SignUpForm => {
|
||||
const { identifiers, ...signUpData } = signUp;
|
||||
|
||||
return {
|
||||
identifier: mapIdentifiersToSignUpIdentifier(identifiers),
|
||||
...signUpData,
|
||||
};
|
||||
},
|
||||
toRemoteSignUp: (signUpForm: SignUpForm): SignUp => {
|
||||
const { identifier, ...signUpFormData } = signUpForm;
|
||||
|
||||
return {
|
||||
identifiers: signUpIdentifiersMapping[identifier],
|
||||
...signUpFormData,
|
||||
};
|
||||
},
|
||||
toLocalForm: (signInExperience: SignInExperience): SignInExperienceForm => {
|
||||
const { signInMode } = signInExperience;
|
||||
const { signUp, signInMode } = signInExperience;
|
||||
|
||||
return {
|
||||
...signInExperience,
|
||||
signUp: signInExperienceParser.toLocalSignUp(signUp),
|
||||
createAccountEnabled: signInMode !== SignInMode.SignIn,
|
||||
};
|
||||
},
|
||||
|
@ -31,61 +50,32 @@ export const signInExperienceParser = {
|
|||
darkLogoUrl: conditional(branding.darkLogoUrl?.length && branding.darkLogoUrl),
|
||||
slogan: conditional(branding.slogan?.length && branding.slogan),
|
||||
},
|
||||
signUp: signUp ?? {
|
||||
identifier: SignUpIdentifier.Username,
|
||||
password: true,
|
||||
verify: false,
|
||||
},
|
||||
signUp: signUp
|
||||
? signInExperienceParser.toRemoteSignUp(signUp)
|
||||
: {
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
verify: false,
|
||||
},
|
||||
signInMode: createAccountEnabled ? SignInMode.SignInAndRegister : SignInMode.SignIn,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const compareSignUpAndSignInConfigs = (
|
||||
export const hasSignUpAndSignInConfigChanged = (
|
||||
before: SignInExperience,
|
||||
after: SignInExperience
|
||||
): boolean => {
|
||||
return (
|
||||
!isSignUpDifferent(before.signUp, after.signUp) &&
|
||||
!isSignInMethodsDifferent(before.signIn.methods, after.signIn.methods) &&
|
||||
!isSocialTargetsDifferent(
|
||||
!hasSignUpSettingsChanged(before.signUp, after.signUp) &&
|
||||
!hasSignInMethodsChanged(before.signIn.methods, after.signIn.methods) &&
|
||||
!hasSocialTargetsChanged(
|
||||
before.socialSignInConnectorTargets,
|
||||
after.socialSignInConnectorTargets
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const flattenTranslation = (
|
||||
translation: Translation,
|
||||
keyPrefix = ''
|
||||
): Record<string, string> =>
|
||||
Object.keys(translation).reduce((result, key) => {
|
||||
const prefix = keyPrefix ? `${keyPrefix}.` : keyPrefix;
|
||||
const unwrappedKey = `${prefix}${key}`;
|
||||
const unwrapped = translation[key];
|
||||
|
||||
return unwrapped === undefined
|
||||
? result
|
||||
: {
|
||||
...result,
|
||||
...(typeof unwrapped === 'string'
|
||||
? { [unwrappedKey]: unwrapped }
|
||||
: flattenTranslation(unwrapped, unwrappedKey)),
|
||||
};
|
||||
}, {});
|
||||
|
||||
const emptyTranslation = (translation: Translation): Translation =>
|
||||
Object.entries(translation).reduce((result, [key, value]) => {
|
||||
return typeof value === 'string'
|
||||
? { ...result, [key]: '' }
|
||||
: {
|
||||
...result,
|
||||
[key]: emptyTranslation(value),
|
||||
};
|
||||
}, {});
|
||||
|
||||
export const createEmptyUiTranslation = () => emptyTranslation(en.translation);
|
||||
|
||||
export const getBrandingErrorCount = (
|
||||
errors: FieldErrorsImpl<DeepRequired<SignInExperienceForm>>
|
||||
) => {
|
|
@ -0,0 +1,32 @@
|
|||
import type { ConnectorType } from '@logto/schemas';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { isSameArray } from '@silverhand/essentials';
|
||||
|
||||
import { identifierRequiredConnectorMapping, signUpIdentifiersMapping } from '../constants';
|
||||
import type { SignUpIdentifier } from '../types';
|
||||
|
||||
export const isVerificationRequiredSignUpIdentifiers = (signUpIdentifier: SignUpIdentifier) => {
|
||||
const identifiers = signUpIdentifiersMapping[signUpIdentifier];
|
||||
|
||||
return identifiers.includes(SignInIdentifier.Email) || identifiers.includes(SignInIdentifier.Sms);
|
||||
};
|
||||
|
||||
export const mapIdentifiersToSignUpIdentifier = (
|
||||
identifiers: SignInIdentifier[]
|
||||
): SignUpIdentifier => {
|
||||
for (const [signUpIdentifier, mappedIdentifiers] of Object.entries(signUpIdentifiersMapping)) {
|
||||
if (isSameArray(identifiers, mappedIdentifiers)) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return signUpIdentifier as SignUpIdentifier;
|
||||
}
|
||||
}
|
||||
throw new Error('Invalid identifiers in the sign up settings.');
|
||||
};
|
||||
|
||||
export const getSignUpRequiredConnectorTypes = (
|
||||
signUpIdentifier: SignUpIdentifier
|
||||
): ConnectorType[] =>
|
||||
signUpIdentifiersMapping[signUpIdentifier]
|
||||
.map((identifier) => identifierRequiredConnectorMapping[identifier])
|
||||
// eslint-disable-next-line unicorn/prefer-native-coercion-functions
|
||||
.filter((connectorType): connectorType is ConnectorType => Boolean(connectorType));
|
|
@ -0,0 +1,33 @@
|
|||
import en from '@logto/phrases-ui/lib/locales/en';
|
||||
import type { Translation } from '@logto/schemas';
|
||||
|
||||
export const flattenTranslation = (
|
||||
translation: Translation,
|
||||
keyPrefix = ''
|
||||
): Record<string, string> =>
|
||||
Object.keys(translation).reduce((result, key) => {
|
||||
const prefix = keyPrefix ? `${keyPrefix}.` : keyPrefix;
|
||||
const unwrappedKey = `${prefix}${key}`;
|
||||
const unwrapped = translation[key];
|
||||
|
||||
return unwrapped === undefined
|
||||
? result
|
||||
: {
|
||||
...result,
|
||||
...(typeof unwrapped === 'string'
|
||||
? { [unwrappedKey]: unwrapped }
|
||||
: flattenTranslation(unwrapped, unwrappedKey)),
|
||||
};
|
||||
}, {});
|
||||
|
||||
const emptyTranslation = (translation: Translation): Translation =>
|
||||
Object.entries(translation).reduce((result, [key, value]) => {
|
||||
return typeof value === 'string'
|
||||
? { ...result, [key]: '' }
|
||||
: {
|
||||
...result,
|
||||
[key]: emptyTranslation(value),
|
||||
};
|
||||
}, {});
|
||||
|
||||
export const createEmptyUiTranslation = () => emptyTranslation(en.translation);
|
|
@ -7,7 +7,7 @@ import type {
|
|||
SignUp,
|
||||
SignIn,
|
||||
} from '@logto/schemas';
|
||||
import { BrandingStyle, SignInMode, SignUpIdentifier, SignInIdentifier } from '@logto/schemas';
|
||||
import { BrandingStyle, SignInMode, SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
export const mockSignInExperience: SignInExperience = {
|
||||
id: 'foo',
|
||||
|
@ -29,7 +29,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
fallbackLanguage: 'en',
|
||||
},
|
||||
signUp: {
|
||||
identifier: SignUpIdentifier.Username,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
verify: false,
|
||||
},
|
||||
|
@ -82,7 +82,7 @@ export const mockLanguageInfo: LanguageInfo = {
|
|||
};
|
||||
|
||||
export const mockSignUp: SignUp = {
|
||||
identifier: SignUpIdentifier.Username,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
verify: false,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ConnectorType, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { ConnectorType, SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
import {
|
||||
mockAliyunDmConnector,
|
||||
|
@ -33,7 +33,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.EmailOrSms,
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
password: false,
|
||||
verify: true,
|
||||
},
|
||||
|
@ -56,7 +56,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.Username,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
},
|
||||
[]
|
||||
|
@ -127,7 +127,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.Username,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
},
|
||||
enabledConnectors
|
||||
);
|
||||
|
@ -151,7 +151,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
},
|
||||
enabledConnectors
|
||||
);
|
||||
|
@ -175,7 +175,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.Sms,
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
},
|
||||
enabledConnectors
|
||||
);
|
||||
|
@ -199,7 +199,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.EmailOrSms,
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
},
|
||||
enabledConnectors
|
||||
);
|
||||
|
@ -226,7 +226,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: true,
|
||||
},
|
||||
enabledConnectors
|
||||
|
@ -252,7 +252,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: false,
|
||||
verify: true,
|
||||
},
|
||||
|
@ -286,7 +286,7 @@ describe('validate sign-in', () => {
|
|||
},
|
||||
{
|
||||
...mockSignUp,
|
||||
identifier: SignUpIdentifier.Sms,
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
password: false,
|
||||
verify: true,
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { SignIn, SignUp } from '@logto/schemas';
|
||||
import { ConnectorType, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { ConnectorType, SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
import type { LogtoConnector } from '#src/connectors/types.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
|
@ -47,56 +47,33 @@ export const validateSignIn = (
|
|||
})
|
||||
);
|
||||
|
||||
switch (signUp.identifier) {
|
||||
case SignUpIdentifier.Username: {
|
||||
for (const identifier of signUp.identifiers) {
|
||||
if (identifier === SignInIdentifier.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: {
|
||||
if (identifier === SignInIdentifier.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.Sms: {
|
||||
if (identifier === SignInIdentifier.Sms) {
|
||||
assertThat(
|
||||
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Sms),
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
|
||||
})
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SignUpIdentifier.EmailOrSms: {
|
||||
assertThat(
|
||||
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Email) &&
|
||||
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Sms),
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
|
||||
})
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SignUpIdentifier.None: {
|
||||
// No requirement
|
||||
}
|
||||
// No default
|
||||
}
|
||||
|
||||
if (signUp.password) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ConnectorType, SignUpIdentifier } from '@logto/schemas';
|
||||
import { ConnectorType, SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
import { mockAliyunDmConnector, mockAliyunSmsConnector, mockSignUp } from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
|
@ -16,7 +16,7 @@ describe('validate sign-up', () => {
|
|||
describe('There must be at least one connector for the specific identifier.', () => {
|
||||
test('should throw when there is no email connector and identifier is email', async () => {
|
||||
expect(() => {
|
||||
validateSignUp({ ...mockSignUp, identifier: SignUpIdentifier.Email }, []);
|
||||
validateSignUp({ ...mockSignUp, identifiers: [SignInIdentifier.Email] }, []);
|
||||
}).toMatchError(
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.enabled_connector_not_found',
|
||||
|
@ -27,7 +27,13 @@ describe('validate sign-up', () => {
|
|||
|
||||
test('should throw when there is no email connector and identifier is email or phone', async () => {
|
||||
expect(() => {
|
||||
validateSignUp({ ...mockSignUp, identifier: SignUpIdentifier.EmailOrSms }, []);
|
||||
validateSignUp(
|
||||
{
|
||||
...mockSignUp,
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
},
|
||||
[]
|
||||
);
|
||||
}).toMatchError(
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.enabled_connector_not_found',
|
||||
|
@ -38,7 +44,7 @@ describe('validate sign-up', () => {
|
|||
|
||||
test('should throw when there is no sms connector and identifier is phone', async () => {
|
||||
expect(() => {
|
||||
validateSignUp({ ...mockSignUp, identifier: SignUpIdentifier.Sms }, []);
|
||||
validateSignUp({ ...mockSignUp, identifiers: [SignInIdentifier.Sms] }, []);
|
||||
}).toMatchError(
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.enabled_connector_not_found',
|
||||
|
@ -49,9 +55,14 @@ describe('validate sign-up', () => {
|
|||
|
||||
test('should throw when there is no email connector and identifier is email or phone', async () => {
|
||||
expect(() => {
|
||||
validateSignUp({ ...mockSignUp, identifier: SignUpIdentifier.EmailOrSms }, [
|
||||
mockAliyunDmConnector,
|
||||
]);
|
||||
validateSignUp(
|
||||
{
|
||||
...mockSignUp,
|
||||
verify: true,
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
},
|
||||
[mockAliyunSmsConnector]
|
||||
);
|
||||
}).toMatchError(
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.enabled_connector_not_found',
|
||||
|
@ -64,7 +75,7 @@ describe('validate sign-up', () => {
|
|||
test('should throw when identifier is username and password is false', async () => {
|
||||
expect(() => {
|
||||
validateSignUp(
|
||||
{ ...mockSignUp, identifier: SignUpIdentifier.Username, password: false },
|
||||
{ ...mockSignUp, identifiers: [SignInIdentifier.Username], password: false },
|
||||
enabledConnectors
|
||||
);
|
||||
}).toMatchError(
|
||||
|
@ -78,7 +89,7 @@ describe('validate sign-up', () => {
|
|||
test('should throw when identifier is email', async () => {
|
||||
expect(() => {
|
||||
validateSignUp(
|
||||
{ ...mockSignUp, identifier: SignUpIdentifier.Email, verify: false },
|
||||
{ ...mockSignUp, identifiers: [SignInIdentifier.Email], verify: false },
|
||||
enabledConnectors
|
||||
);
|
||||
}).toMatchError(
|
||||
|
@ -91,7 +102,7 @@ describe('validate sign-up', () => {
|
|||
test('should throw when identifier is phone', async () => {
|
||||
expect(() => {
|
||||
validateSignUp(
|
||||
{ ...mockSignUp, identifier: SignUpIdentifier.Email, verify: false },
|
||||
{ ...mockSignUp, identifiers: [SignInIdentifier.Email], verify: false },
|
||||
enabledConnectors
|
||||
);
|
||||
}).toMatchError(
|
||||
|
@ -104,7 +115,11 @@ describe('validate sign-up', () => {
|
|||
test('should throw when identifier is email or phone', async () => {
|
||||
expect(() => {
|
||||
validateSignUp(
|
||||
{ ...mockSignUp, identifier: SignUpIdentifier.EmailOrSms, verify: false },
|
||||
{
|
||||
...mockSignUp,
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
verify: false,
|
||||
},
|
||||
enabledConnectors
|
||||
);
|
||||
}).toMatchError(
|
||||
|
|
|
@ -1,56 +1,48 @@
|
|||
import type { SignUp } from '@logto/schemas';
|
||||
import { ConnectorType, SignUpIdentifier } from '@logto/schemas';
|
||||
import { SignInIdentifier, ConnectorType } from '@logto/schemas';
|
||||
|
||||
import type { LogtoConnector } from '#src/connectors/types.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
export const validateSignUp = (signUp: SignUp, enabledConnectors: LogtoConnector[]) => {
|
||||
if (
|
||||
signUp.identifier === SignUpIdentifier.Email ||
|
||||
signUp.identifier === SignUpIdentifier.EmailOrSms
|
||||
) {
|
||||
assertThat(
|
||||
enabledConnectors.some((item) => item.type === ConnectorType.Email),
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.enabled_connector_not_found',
|
||||
type: ConnectorType.Email,
|
||||
})
|
||||
);
|
||||
}
|
||||
for (const identifier of signUp.identifiers) {
|
||||
if (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 (
|
||||
signUp.identifier === SignUpIdentifier.Sms ||
|
||||
signUp.identifier === SignUpIdentifier.EmailOrSms
|
||||
) {
|
||||
assertThat(
|
||||
enabledConnectors.some((item) => item.type === ConnectorType.Sms),
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.enabled_connector_not_found',
|
||||
type: ConnectorType.Sms,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (identifier === SignInIdentifier.Sms) {
|
||||
assertThat(
|
||||
enabledConnectors.some((item) => item.type === ConnectorType.Sms),
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.enabled_connector_not_found',
|
||||
type: ConnectorType.Sms,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (signUp.identifier === SignUpIdentifier.Username) {
|
||||
assertThat(
|
||||
signUp.password,
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.username_requires_password',
|
||||
})
|
||||
);
|
||||
}
|
||||
if (identifier === SignInIdentifier.Username) {
|
||||
assertThat(
|
||||
signUp.password,
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.username_requires_password',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
[SignUpIdentifier.Sms, SignUpIdentifier.Email, SignUpIdentifier.EmailOrSms].includes(
|
||||
signUp.identifier
|
||||
)
|
||||
) {
|
||||
assertThat(
|
||||
signUp.verify,
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.passwordless_requires_verify',
|
||||
})
|
||||
);
|
||||
if (identifier === SignInIdentifier.Email || identifier === SignInIdentifier.Sms) {
|
||||
assertThat(
|
||||
signUp.verify,
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.passwordless_requires_verify',
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { CreateUser, Role, User } from '@logto/schemas';
|
||||
import { SignUpIdentifier, userInfoSelectFields } from '@logto/schemas';
|
||||
import { userInfoSelectFields } from '@logto/schemas';
|
||||
import pick from 'lodash.pick';
|
||||
|
||||
import {
|
||||
|
@ -30,7 +30,7 @@ const filterUsersWithSearch = (users: User[], search: string) =>
|
|||
|
||||
const mockFindDefaultSignInExperience = jest.fn(async () => ({
|
||||
signUp: {
|
||||
identifier: SignUpIdentifier.None,
|
||||
identifiers: [],
|
||||
password: false,
|
||||
verify: false,
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { SignInExperience } from '@logto/schemas';
|
||||
import { SignUpIdentifier, SignInIdentifier, SignInMode, Event } from '@logto/schemas';
|
||||
import { SignInIdentifier, SignInMode, Event } from '@logto/schemas';
|
||||
|
||||
import { mockSignInExperience } from '#src/__mocks__/sign-in-experience.js';
|
||||
|
||||
|
@ -146,7 +146,7 @@ describe('identifier validation', () => {
|
|||
identifierValidation(identifier, {
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: false,
|
||||
verify: true,
|
||||
},
|
||||
|
@ -237,7 +237,7 @@ describe('identifier validation', () => {
|
|||
identifierValidation(identifier, {
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
identifier: SignUpIdentifier.Sms,
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
password: false,
|
||||
verify: true,
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { SignInExperience, Profile } from '@logto/schemas';
|
||||
import { SignUpIdentifier, SignInMode, SignInIdentifier, Event } from '@logto/schemas';
|
||||
import { SignInMode, SignInIdentifier, Event } from '@logto/schemas';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
@ -59,7 +59,7 @@ export const identifierValidation = (
|
|||
if (
|
||||
'passcode' in identifier &&
|
||||
!verificationCode &&
|
||||
![SignUpIdentifier.Email, SignUpIdentifier.EmailOrSms].includes(signUp.identifier) &&
|
||||
!signUp.identifiers.includes(SignInIdentifier.Email) &&
|
||||
!signUp.verify
|
||||
) {
|
||||
return false;
|
||||
|
@ -91,7 +91,7 @@ export const identifierValidation = (
|
|||
if (
|
||||
'passcode' in identifier &&
|
||||
!verificationCode &&
|
||||
![SignUpIdentifier.Sms, SignUpIdentifier.EmailOrSms].includes(signUp.identifier) &&
|
||||
!signUp.identifiers.includes(SignInIdentifier.Sms) &&
|
||||
!signUp.verify
|
||||
) {
|
||||
return false;
|
||||
|
@ -108,23 +108,15 @@ export const identifierValidation = (
|
|||
|
||||
export const profileValidation = (profile: Profile, { signUp }: SignInExperience) => {
|
||||
if (profile.phone) {
|
||||
assertThat(
|
||||
signUp.identifier === SignUpIdentifier.Sms ||
|
||||
signUp.identifier === SignUpIdentifier.EmailOrSms,
|
||||
forbiddenIdentifierError
|
||||
);
|
||||
assertThat(signUp.identifiers.includes(SignInIdentifier.Sms), forbiddenIdentifierError);
|
||||
}
|
||||
|
||||
if (profile.email) {
|
||||
assertThat(
|
||||
signUp.identifier === SignUpIdentifier.Email ||
|
||||
signUp.identifier === SignUpIdentifier.EmailOrSms,
|
||||
forbiddenIdentifierError
|
||||
);
|
||||
assertThat(signUp.identifiers.includes(SignInIdentifier.Email), forbiddenIdentifierError);
|
||||
}
|
||||
|
||||
if (profile.username) {
|
||||
assertThat(signUp.identifier === SignUpIdentifier.Username, forbiddenIdentifierError);
|
||||
assertThat(signUp.identifiers.includes(SignInIdentifier.Username), forbiddenIdentifierError);
|
||||
}
|
||||
|
||||
if (profile.password) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { PasscodeType, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { PasscodeType, SignInIdentifier } from '@logto/schemas';
|
||||
import type { MiddlewareType } from 'koa';
|
||||
import type { Provider } from 'oidc-provider';
|
||||
|
||||
|
@ -121,9 +121,9 @@ export const smsRegisterAction = <StateT, ContextT extends WithLogContext, Respo
|
|||
const signInExperience = await getSignInExperienceForApplication(
|
||||
await getApplicationIdFromInteraction(ctx, provider)
|
||||
);
|
||||
|
||||
assertThat(
|
||||
signInExperience.signUp.identifier === SignUpIdentifier.Sms ||
|
||||
signInExperience.signUp.identifier === SignUpIdentifier.EmailOrSms,
|
||||
signInExperience.signUp.identifiers.includes(SignInIdentifier.Sms),
|
||||
new RequestError({
|
||||
code: 'user.sign_up_method_not_enabled',
|
||||
status: 422,
|
||||
|
@ -165,9 +165,9 @@ export const emailRegisterAction = <StateT, ContextT extends WithLogContext, Res
|
|||
const signInExperience = await getSignInExperienceForApplication(
|
||||
await getApplicationIdFromInteraction(ctx, provider)
|
||||
);
|
||||
|
||||
assertThat(
|
||||
signInExperience.signUp.identifier === SignUpIdentifier.Email ||
|
||||
signInExperience.signUp.identifier === SignUpIdentifier.EmailOrSms,
|
||||
signInExperience.signUp.identifiers.includes(SignInIdentifier.Email),
|
||||
new RequestError({
|
||||
code: 'user.sign_up_method_not_enabled',
|
||||
status: 422,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { UserRole, SignUpIdentifier } from '@logto/schemas';
|
||||
import { UserRole, SignInIdentifier } from '@logto/schemas';
|
||||
import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds/index.js';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
|
@ -239,7 +239,7 @@ describe('session -> password routes', () => {
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -283,7 +283,7 @@ describe('session -> password routes', () => {
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { passwordRegEx, usernameRegEx } from '@logto/core-kit';
|
||||
import { SignInIdentifier, SignUpIdentifier, UserRole } from '@logto/schemas';
|
||||
import { SignInIdentifier, UserRole } from '@logto/schemas';
|
||||
import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds/index.js';
|
||||
import type { Provider } from 'oidc-provider';
|
||||
import { object, string } from 'zod';
|
||||
|
@ -108,7 +108,7 @@ export default function passwordRoutes<T extends AnonymousRouter>(router: T, pro
|
|||
await getApplicationIdFromInteraction(ctx, provider)
|
||||
);
|
||||
assertThat(
|
||||
signInExperience.signUp.identifier === SignUpIdentifier.Username,
|
||||
signInExperience.signUp.identifiers.includes(SignInIdentifier.Username),
|
||||
new RequestError({
|
||||
code: 'user.sign_up_method_not_enabled',
|
||||
status: 422,
|
||||
|
@ -146,7 +146,7 @@ export default function passwordRoutes<T extends AnonymousRouter>(router: T, pro
|
|||
await getApplicationIdFromInteraction(ctx, provider)
|
||||
);
|
||||
assertThat(
|
||||
signInExperience.signUp.identifier === SignUpIdentifier.Username,
|
||||
signInExperience.signUp.identifiers.includes(SignInIdentifier.Username),
|
||||
new RequestError({
|
||||
code: 'user.sign_up_method_not_enabled',
|
||||
status: 422,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable max-lines */
|
||||
import type { User } from '@logto/schemas';
|
||||
import { PasscodeType, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { PasscodeType, SignInIdentifier } from '@logto/schemas';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
import { addDays, addSeconds, subDays } from 'date-fns';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
@ -22,7 +22,7 @@ const findDefaultSignInExperience = jest.fn(async () => ({
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Username,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: false,
|
||||
verify: true,
|
||||
},
|
||||
|
@ -554,7 +554,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: false,
|
||||
verify: true,
|
||||
},
|
||||
|
@ -709,7 +709,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Sms,
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
password: false,
|
||||
},
|
||||
});
|
||||
|
@ -822,7 +822,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -837,7 +837,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Email,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: false,
|
||||
},
|
||||
});
|
||||
|
@ -950,7 +950,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Sms,
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable max-lines */
|
||||
import type { CreateUser, User } from '@logto/schemas';
|
||||
import { ConnectorType, SignUpIdentifier } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { getUnixTime } from 'date-fns';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
|
@ -80,7 +80,7 @@ jest.mock('#src/queries/user.js', () => ({
|
|||
|
||||
const mockFindDefaultSignInExperience = jest.fn(async () => ({
|
||||
signUp: {
|
||||
identifier: SignUpIdentifier.None,
|
||||
identifier: [],
|
||||
password: false,
|
||||
verify: false,
|
||||
},
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { ConnectorType } from '@logto/connector-kit';
|
||||
import type { User } from '@logto/schemas';
|
||||
import { SignUpIdentifier } from '@logto/schemas';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockLogtoConnectorList, mockSignInExperience, mockUser } from '#src/__mocks__/index.js';
|
||||
|
@ -58,7 +57,7 @@ jest.mock('#src/queries/sign-in-experience.js', () => ({
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.None,
|
||||
identifiers: [],
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { ConnectorType } from '@logto/connector-kit';
|
||||
import type { User } from '@logto/schemas';
|
||||
import { SignUpIdentifier } from '@logto/schemas';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockLogtoConnectorList, mockSignInExperience, mockUser } from '#src/__mocks__/index.js';
|
||||
|
@ -65,7 +64,7 @@ jest.mock('#src/queries/sign-in-experience.js', () => ({
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.None,
|
||||
identifiers: [],
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { UserRole, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { UserRole, SignInIdentifier } from '@logto/schemas';
|
||||
import { createMockContext } from '@shopify/jest-koa-mocks';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
@ -17,7 +17,7 @@ const findDefaultSignInExperience = jest.fn(async () => ({
|
|||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Username,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
import type {
|
||||
LogPayload,
|
||||
LogType,
|
||||
PasscodeType,
|
||||
SignInExperience,
|
||||
SignInIdentifier,
|
||||
User,
|
||||
} from '@logto/schemas';
|
||||
import { SignUpIdentifier, logTypeGuard } from '@logto/schemas';
|
||||
import type { LogPayload, LogType, PasscodeType, SignInExperience, User } from '@logto/schemas';
|
||||
import { SignInIdentifier, logTypeGuard } from '@logto/schemas';
|
||||
import type { Nullable, Truthy } from '@silverhand/essentials';
|
||||
import { addSeconds, isAfter, isValid } from 'date-fns';
|
||||
import type { Context } from 'koa';
|
||||
|
@ -176,25 +169,30 @@ export const checkRequiredProfile = async (
|
|||
throw new RequestError({ code: 'user.require_password', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifier === SignUpIdentifier.Username && !username) {
|
||||
if (signUp.identifiers.includes(SignInIdentifier.Username) && !username) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_username', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifier === SignUpIdentifier.Email && !primaryEmail) {
|
||||
if (
|
||||
signUp.identifiers.includes(SignInIdentifier.Email) &&
|
||||
signUp.identifiers.includes(SignInIdentifier.Sms) &&
|
||||
!primaryEmail &&
|
||||
!primaryPhone
|
||||
) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_email_or_sms', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifiers.includes(SignInIdentifier.Email) && !primaryEmail) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_email', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifier === SignUpIdentifier.Sms && !primaryPhone) {
|
||||
if (signUp.identifiers.includes(SignInIdentifier.Sms) && !primaryPhone) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_sms', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifier === SignUpIdentifier.EmailOrSms && !primaryEmail && !primaryPhone) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_email_or_sms', status: 422 });
|
||||
}
|
||||
};
|
||||
|
||||
export const checkMissingRequiredSignUpIdentifiers = async (identifiers: {
|
||||
|
@ -206,17 +204,22 @@ export const checkMissingRequiredSignUpIdentifiers = async (identifiers: {
|
|||
|
||||
const { signUp } = await getSignInExperienceForApplication();
|
||||
|
||||
if (signUp.identifier === SignUpIdentifier.Email && !primaryEmail) {
|
||||
if (
|
||||
signUp.identifiers.includes(SignInIdentifier.Email) &&
|
||||
signUp.identifiers.includes(SignInIdentifier.Sms) &&
|
||||
!primaryEmail &&
|
||||
!primaryPhone
|
||||
) {
|
||||
throw new RequestError({ code: 'user.require_email_or_sms', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifiers.includes(SignInIdentifier.Email) && !primaryEmail) {
|
||||
throw new RequestError({ code: 'user.require_email', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifier === SignUpIdentifier.Sms && !primaryPhone) {
|
||||
if (signUp.identifiers.includes(SignInIdentifier.Sms) && !primaryPhone) {
|
||||
throw new RequestError({ code: 'user.require_sms', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifier === SignUpIdentifier.EmailOrSms && !primaryEmail && !primaryPhone) {
|
||||
throw new RequestError({ code: 'user.require_email_or_sms', status: 422 });
|
||||
}
|
||||
};
|
||||
/* eslint-enable complexity */
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { demoAppApplicationId } from '@logto/schemas/lib/seeds';
|
||||
import { getEnv } from '@silverhand/essentials';
|
||||
|
||||
|
@ -7,3 +8,11 @@ export const discoveryUrl = `${logtoUrl}/oidc/.well-known/openid-configuration`;
|
|||
|
||||
export const demoAppRedirectUri = `${logtoUrl}/${demoAppApplicationId}`;
|
||||
export const adminConsoleRedirectUri = `${logtoUrl}/console/callback`;
|
||||
|
||||
export const signUpIdentifiers = {
|
||||
username: [SignInIdentifier.Username],
|
||||
email: [SignInIdentifier.Email],
|
||||
sms: [SignInIdentifier.Sms],
|
||||
emailOrSms: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
none: [],
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import type { User, SignUpIdentifier, SignIn } from '@logto/schemas';
|
||||
import type { User, SignIn, SignInIdentifier } from '@logto/schemas';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
import { HTTPError } from 'got';
|
||||
|
||||
|
@ -68,11 +68,11 @@ export const signIn = async ({ username, email, password }: SignInHelper) => {
|
|||
};
|
||||
|
||||
export const setSignUpIdentifier = async (
|
||||
identifier: SignUpIdentifier,
|
||||
identifiers: SignInIdentifier[],
|
||||
password = true,
|
||||
verify = true
|
||||
) => {
|
||||
await updateSignInExperience({ signUp: { identifier, password, verify } });
|
||||
await updateSignInExperience({ signUp: { identifiers, password, verify } });
|
||||
};
|
||||
|
||||
export const setSignInMethod = async (methods: SignIn['methods']) => {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { SignUpIdentifier } from '@logto/schemas';
|
||||
|
||||
import type { StatisticsData } from '@/api';
|
||||
import { getTotalUsersCount, getNewUsersData, getActiveUsersData } from '@/api';
|
||||
import { signUpIdentifiers } from '@/constants';
|
||||
import { createUserByAdmin, registerNewUser, setSignUpIdentifier, signIn } from '@/helpers';
|
||||
import { generateUsername, generatePassword } from '@/utils';
|
||||
|
||||
describe('admin console dashboard', () => {
|
||||
beforeAll(async () => {
|
||||
await setSignUpIdentifier(SignUpIdentifier.Username);
|
||||
await setSignUpIdentifier(signUpIdentifiers.username);
|
||||
});
|
||||
|
||||
it('should get total user count successfully', async () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SignUpIdentifier } from '@logto/schemas';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
|
||||
import { getLogs, getLog } from '@/api';
|
||||
import { signUpIdentifiers } from '@/constants';
|
||||
import { registerNewUser, setSignUpIdentifier } from '@/helpers';
|
||||
import { generateUsername, generatePassword } from '@/utils';
|
||||
|
||||
|
@ -10,7 +10,7 @@ describe('admin console logs', () => {
|
|||
const password = generatePassword();
|
||||
|
||||
beforeAll(async () => {
|
||||
await setSignUpIdentifier(SignUpIdentifier.Username);
|
||||
await setSignUpIdentifier(signUpIdentifiers.username);
|
||||
});
|
||||
|
||||
it('should get logs and visit log details successfully', async () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
|
||||
|
@ -25,6 +25,7 @@ import {
|
|||
updateConnectorConfig,
|
||||
} from '@/api';
|
||||
import MockClient from '@/client';
|
||||
import { signUpIdentifiers } from '@/constants';
|
||||
import {
|
||||
registerNewUser,
|
||||
signIn,
|
||||
|
@ -42,7 +43,7 @@ describe('username and password flow', () => {
|
|||
const password = generatePassword();
|
||||
|
||||
beforeAll(async () => {
|
||||
await setSignUpIdentifier(SignUpIdentifier.Username, true);
|
||||
await setSignUpIdentifier(signUpIdentifiers.username, true);
|
||||
await setSignInMethod([
|
||||
{
|
||||
identifier: SignInIdentifier.Username,
|
||||
|
@ -71,7 +72,7 @@ describe('email and password flow', () => {
|
|||
await updateConnectorConfig(id, mockEmailConnectorConfig);
|
||||
connectorIdMap.set(mockEmailConnectorId, id);
|
||||
|
||||
await setSignUpIdentifier(SignUpIdentifier.Email, true);
|
||||
await setSignUpIdentifier(signUpIdentifiers.email, true);
|
||||
await setSignInMethod([
|
||||
{
|
||||
identifier: SignInIdentifier.Email,
|
||||
|
@ -112,7 +113,7 @@ describe('email passwordless flow', () => {
|
|||
await updateConnectorConfig(id, mockEmailConnectorConfig);
|
||||
connectorIdMap.set(mockEmailConnectorId, id);
|
||||
|
||||
await setSignUpIdentifier(SignUpIdentifier.Email, false);
|
||||
await setSignUpIdentifier(signUpIdentifiers.email, false);
|
||||
await setSignInMethod([
|
||||
{
|
||||
identifier: SignInIdentifier.Username,
|
||||
|
@ -211,7 +212,7 @@ describe('sms passwordless flow', () => {
|
|||
await updateConnectorConfig(id, mockSmsConnectorConfig);
|
||||
connectorIdMap.set(mockSmsConnectorId, id);
|
||||
|
||||
await setSignUpIdentifier(SignUpIdentifier.Sms, false);
|
||||
await setSignUpIdentifier(signUpIdentifiers.sms, false);
|
||||
await setSignInMethod([
|
||||
{
|
||||
identifier: SignInIdentifier.Username,
|
||||
|
@ -302,7 +303,7 @@ describe('sign-in and sign-out', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
await createUserByAdmin(username, password);
|
||||
await setSignUpIdentifier(SignUpIdentifier.Username);
|
||||
await setSignUpIdentifier(signUpIdentifiers.username);
|
||||
});
|
||||
|
||||
it('verify sign-in and then sign-out', async () => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { SignUpIdentifier } from '@logto/schemas';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
import { HTTPError } from 'got';
|
||||
|
||||
|
@ -18,6 +17,7 @@ import {
|
|||
updateConnectorConfig,
|
||||
} from '@/api';
|
||||
import MockClient from '@/client';
|
||||
import { signUpIdentifiers } from '@/constants';
|
||||
import { createUserByAdmin, setSignUpIdentifier } from '@/helpers';
|
||||
import { generateUsername, generatePassword } from '@/utils';
|
||||
|
||||
|
@ -35,7 +35,7 @@ describe('social sign-in and register', () => {
|
|||
connectorIdMap.set(mockSocialConnectorId, id);
|
||||
await updateConnectorConfig(id, mockSocialConnectorConfig);
|
||||
|
||||
await setSignUpIdentifier(SignUpIdentifier.None, false);
|
||||
await setSignUpIdentifier(signUpIdentifiers.none, false);
|
||||
});
|
||||
|
||||
it('register with social', async () => {
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('wellknown api', () => {
|
|||
|
||||
expect(response).toMatchObject({
|
||||
signUp: {
|
||||
identifier: 'username',
|
||||
identifiers: ['username'],
|
||||
password: true,
|
||||
verify: false,
|
||||
},
|
||||
|
|
124
packages/schemas/alterations/next-1669702299-sign-up.ts
Normal file
124
packages/schemas/alterations/next-1669702299-sign-up.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { isSameArray } from '@silverhand/essentials';
|
||||
import type { DatabaseTransactionConnection } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
enum DeprecatedSignUpIdentifier {
|
||||
Email = 'email',
|
||||
Sms = 'sms',
|
||||
Username = 'username',
|
||||
EmailOrSms = 'emailOrSms',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
type DeprecatedSignUp = {
|
||||
identifier: DeprecatedSignUpIdentifier;
|
||||
password: boolean;
|
||||
verify: boolean;
|
||||
};
|
||||
|
||||
type DeprecatedSignInExperience = {
|
||||
id: string;
|
||||
signUp: DeprecatedSignUp;
|
||||
};
|
||||
|
||||
enum SignInIdentifier {
|
||||
Username = 'username',
|
||||
Email = 'email',
|
||||
Sms = 'sms',
|
||||
}
|
||||
|
||||
type SignUp = {
|
||||
identifiers: SignInIdentifier[];
|
||||
password: boolean;
|
||||
verify: boolean;
|
||||
};
|
||||
|
||||
type SignInExperience = {
|
||||
id: string;
|
||||
signUp: SignUp;
|
||||
};
|
||||
|
||||
const signUpIdentifierMapping: {
|
||||
[key in DeprecatedSignUpIdentifier]: SignInIdentifier[];
|
||||
} = {
|
||||
[DeprecatedSignUpIdentifier.Email]: [SignInIdentifier.Email],
|
||||
[DeprecatedSignUpIdentifier.Sms]: [SignInIdentifier.Sms],
|
||||
[DeprecatedSignUpIdentifier.Username]: [SignInIdentifier.Username],
|
||||
[DeprecatedSignUpIdentifier.EmailOrSms]: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
[DeprecatedSignUpIdentifier.None]: [],
|
||||
};
|
||||
|
||||
const mapDeprecatedSignUpIdentifierToIdentifiers = (signUpIdentifier: DeprecatedSignUpIdentifier) =>
|
||||
signUpIdentifierMapping[signUpIdentifier];
|
||||
|
||||
const alterSignUp = async (
|
||||
signInExperience: DeprecatedSignInExperience,
|
||||
pool: DatabaseTransactionConnection
|
||||
) => {
|
||||
const {
|
||||
id,
|
||||
signUp: { identifier, ...signUpSettings },
|
||||
} = signInExperience;
|
||||
|
||||
const signUpIdentifiers = mapDeprecatedSignUpIdentifierToIdentifiers(identifier);
|
||||
|
||||
const signUp: SignUp = {
|
||||
identifiers: signUpIdentifiers,
|
||||
...signUpSettings,
|
||||
};
|
||||
|
||||
await pool.query(
|
||||
sql`update sign_in_experiences set sign_up = ${JSON.stringify(signUp)} where id = ${id}`
|
||||
);
|
||||
};
|
||||
|
||||
const mapIdentifiersToDeprecatedSignUpIdentifier = (
|
||||
identifiers: SignInIdentifier[]
|
||||
): DeprecatedSignUpIdentifier => {
|
||||
for (const [key, mappedIdentifiers] of Object.entries(signUpIdentifierMapping)) {
|
||||
if (isSameArray(identifiers, mappedIdentifiers)) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return key as DeprecatedSignUpIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Invalid identifiers in the sign up settings.');
|
||||
};
|
||||
|
||||
const rollbackSignUp = async (
|
||||
signInExperience: SignInExperience,
|
||||
pool: DatabaseTransactionConnection
|
||||
) => {
|
||||
const {
|
||||
id,
|
||||
signUp: { identifiers, ...signUpSettings },
|
||||
} = signInExperience;
|
||||
|
||||
const signUpIdentifier = mapIdentifiersToDeprecatedSignUpIdentifier(identifiers);
|
||||
|
||||
const signUp: DeprecatedSignUp = {
|
||||
identifier: signUpIdentifier,
|
||||
...signUpSettings,
|
||||
};
|
||||
|
||||
await pool.query(
|
||||
sql`update sign_in_experiences set sign_up = ${JSON.stringify(signUp)} where id = ${id}`
|
||||
);
|
||||
};
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
const rows = await pool.many<DeprecatedSignInExperience>(
|
||||
sql`select * from sign_in_experiences`
|
||||
);
|
||||
await Promise.all(rows.map(async (row) => alterSignUp(row, pool)));
|
||||
},
|
||||
down: async (pool) => {
|
||||
const rows = await pool.many<SignInExperience>(sql`select * from sign_in_experiences`);
|
||||
await Promise.all(rows.map(async (row) => rollbackSignUp(row, pool)));
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -133,28 +133,20 @@ export const languageInfoGuard = z.object({
|
|||
|
||||
export type LanguageInfo = z.infer<typeof languageInfoGuard>;
|
||||
|
||||
export enum SignUpIdentifier {
|
||||
export enum SignInIdentifier {
|
||||
Username = 'username',
|
||||
Email = 'email',
|
||||
Sms = 'sms',
|
||||
Username = 'username',
|
||||
EmailOrSms = 'emailOrSms',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export const signUpGuard = z.object({
|
||||
identifier: z.nativeEnum(SignUpIdentifier),
|
||||
identifiers: z.nativeEnum(SignInIdentifier).array(),
|
||||
password: z.boolean(),
|
||||
verify: z.boolean(),
|
||||
});
|
||||
|
||||
export type SignUp = z.infer<typeof signUpGuard>;
|
||||
|
||||
export enum SignInIdentifier {
|
||||
Email = 'email',
|
||||
Sms = 'sms',
|
||||
Username = 'username',
|
||||
}
|
||||
|
||||
export const signInGuard = z.object({
|
||||
methods: z
|
||||
.object({
|
||||
|
|
|
@ -2,7 +2,7 @@ import { generateDarkColor } from '@logto/core-kit';
|
|||
|
||||
import type { CreateSignInExperience } from '../db-entries/index.js';
|
||||
import { SignInMode } from '../db-entries/index.js';
|
||||
import { BrandingStyle, SignInIdentifier, SignUpIdentifier } from '../foundations/index.js';
|
||||
import { BrandingStyle, SignInIdentifier } from '../foundations/index.js';
|
||||
|
||||
const defaultPrimaryColor = '#6139F6';
|
||||
|
||||
|
@ -26,7 +26,7 @@ export const defaultSignInExperience: Readonly<CreateSignInExperience> = {
|
|||
enabled: false,
|
||||
},
|
||||
signUp: {
|
||||
identifier: SignUpIdentifier.Username,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
verify: false,
|
||||
},
|
||||
|
|
|
@ -2,12 +2,12 @@ import type { ReactElement } from 'react';
|
|||
import { useContext, useEffect } from 'react';
|
||||
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
import type { SignInExperienceSettings } from '@/types';
|
||||
import type { SignInExperienceResponse } from '@/types';
|
||||
|
||||
import { mockSignInExperienceSettings } from '../logto';
|
||||
|
||||
type Props = {
|
||||
settings?: SignInExperienceSettings;
|
||||
settings?: SignInExperienceResponse;
|
||||
children: ReactElement;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,10 +5,9 @@ import {
|
|||
ConnectorType,
|
||||
SignInIdentifier,
|
||||
SignInMode,
|
||||
SignUpIdentifier,
|
||||
} from '@logto/schemas';
|
||||
|
||||
import type { SignInExperienceSettings } from '@/types';
|
||||
import type { SignInExperienceResponse } from '@/types';
|
||||
|
||||
export const appLogo = 'https://avatars.githubusercontent.com/u/88327661?s=200&v=4';
|
||||
export const appHeadline = 'Build user identity in a modern way';
|
||||
|
@ -202,7 +201,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
fallbackLanguage: 'en',
|
||||
},
|
||||
signUp: {
|
||||
identifier: SignUpIdentifier.Username,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
verify: true,
|
||||
},
|
||||
|
@ -213,7 +212,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
signInMode: SignInMode.SignInAndRegister,
|
||||
};
|
||||
|
||||
export const mockSignInExperienceSettings: SignInExperienceSettings = {
|
||||
export const mockSignInExperienceSettings: SignInExperienceResponse = {
|
||||
id: mockSignInExperience.id,
|
||||
color: mockSignInExperience.color,
|
||||
branding: mockSignInExperience.branding,
|
||||
|
@ -221,7 +220,7 @@ export const mockSignInExperienceSettings: SignInExperienceSettings = {
|
|||
languageInfo: mockSignInExperience.languageInfo,
|
||||
signIn: mockSignInExperience.signIn,
|
||||
signUp: {
|
||||
methods: [SignInIdentifier.Username],
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
verify: true,
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useMemo, createContext } from 'react';
|
||||
import { isMobile } from 'react-device-detect';
|
||||
|
||||
import type { SignInExperienceSettings, Platform, Theme } from '@/types';
|
||||
import type { SignInExperienceResponse, Platform, Theme } from '@/types';
|
||||
|
||||
export type Context = {
|
||||
theme: Theme;
|
||||
|
@ -9,13 +9,13 @@ export type Context = {
|
|||
loading: boolean;
|
||||
platform: Platform;
|
||||
termsAgreement: boolean;
|
||||
experienceSettings: SignInExperienceSettings | undefined;
|
||||
experienceSettings: SignInExperienceResponse | undefined;
|
||||
setTheme: React.Dispatch<React.SetStateAction<Theme>>;
|
||||
setToast: React.Dispatch<React.SetStateAction<string>>;
|
||||
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setPlatform: React.Dispatch<React.SetStateAction<Platform>>;
|
||||
setTermsAgreement: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setExperienceSettings: React.Dispatch<React.SetStateAction<SignInExperienceSettings | undefined>>;
|
||||
setExperienceSettings: React.Dispatch<React.SetStateAction<SignInExperienceResponse | undefined>>;
|
||||
};
|
||||
|
||||
const noop = () => {
|
||||
|
@ -42,7 +42,7 @@ const usePageContext = () => {
|
|||
const [toast, setToast] = useState('');
|
||||
const [theme, setTheme] = useState<Theme>('light');
|
||||
const [platform, setPlatform] = useState<Platform>(isMobile ? 'mobile' : 'web');
|
||||
const [experienceSettings, setExperienceSettings] = useState<SignInExperienceSettings>();
|
||||
const [experienceSettings, setExperienceSettings] = useState<SignInExperienceResponse>();
|
||||
const [termsAgreement, setTermsAgreement] = useState(false);
|
||||
|
||||
const context = useMemo(
|
||||
|
|
|
@ -6,9 +6,8 @@ import * as styles from '@/containers/AppContent/index.module.scss';
|
|||
import type { Context } from '@/hooks/use-page-context';
|
||||
import initI18n from '@/i18n/init';
|
||||
import { changeLanguage } from '@/i18n/utils';
|
||||
import type { SignInExperienceSettings, PreviewConfig } from '@/types';
|
||||
import type { SignInExperienceResponse, PreviewConfig } from '@/types';
|
||||
import { parseQueryParameters } from '@/utils';
|
||||
import { signUpIdentifierMap } from '@/utils/sign-in-experience';
|
||||
import { filterPreviewSocialConnectors } from '@/utils/social-connectors';
|
||||
|
||||
const usePreview = (context: Context): [boolean, PreviewConfig?] => {
|
||||
|
@ -54,25 +53,19 @@ const usePreview = (context: Context): [boolean, PreviewConfig?] => {
|
|||
}
|
||||
|
||||
const {
|
||||
signInExperience: { signUp, socialConnectors, color, ...rest },
|
||||
signInExperience: { socialConnectors, color, ...rest },
|
||||
language,
|
||||
mode,
|
||||
platform,
|
||||
isNative,
|
||||
} = previewConfig;
|
||||
|
||||
const { identifier, ...signUpSettings } = signUp;
|
||||
|
||||
const experienceSettings: SignInExperienceSettings = {
|
||||
const experienceSettings: SignInExperienceResponse = {
|
||||
...rest,
|
||||
color: {
|
||||
...color,
|
||||
isDarkModeEnabled: false, // Disable theme mode auto detection on preview
|
||||
},
|
||||
signUp: {
|
||||
methods: signUpIdentifierMap[identifier],
|
||||
...signUpSettings,
|
||||
},
|
||||
socialConnectors: filterPreviewSocialConnectors(
|
||||
isNative ? ConnectorPlatform.Native : ConnectorPlatform.Web,
|
||||
socialConnectors
|
||||
|
|
|
@ -4,10 +4,10 @@ import { PageContext } from './use-page-context';
|
|||
|
||||
export const useSieMethods = () => {
|
||||
const { experienceSettings } = useContext(PageContext);
|
||||
const { methods, password, verify } = experienceSettings?.signUp ?? {};
|
||||
const { identifiers, password, verify } = experienceSettings?.signUp ?? {};
|
||||
|
||||
return {
|
||||
signUpMethods: methods ?? [],
|
||||
signUpMethods: identifiers ?? [],
|
||||
signUpSettings: { password, verify },
|
||||
signInMethods:
|
||||
experienceSettings?.signIn.methods.filter(
|
||||
|
|
|
@ -20,7 +20,10 @@ describe('SetEmail', () => {
|
|||
<SettingsProvider
|
||||
settings={{
|
||||
...mockSignInExperienceSettings,
|
||||
signUp: { ...mockSignInExperienceSettings.signUp, methods: [SignInIdentifier.Email] },
|
||||
signUp: {
|
||||
...mockSignInExperienceSettings.signUp,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SetEmail />
|
||||
|
|
|
@ -24,7 +24,10 @@ describe('SetPhone', () => {
|
|||
<SettingsProvider
|
||||
settings={{
|
||||
...mockSignInExperienceSettings,
|
||||
signUp: { ...mockSignInExperienceSettings.signUp, methods: [SignInIdentifier.Sms] },
|
||||
signUp: {
|
||||
...mockSignInExperienceSettings.signUp,
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SetPhone />
|
||||
|
|
|
@ -59,7 +59,10 @@ describe('<PasswordRegisterWithUsername />', () => {
|
|||
<SettingsProvider
|
||||
settings={{
|
||||
...mockSignInExperienceSettings,
|
||||
signUp: { ...mockSignInExperienceSettings.signUp, methods: [SignInIdentifier.Email] },
|
||||
signUp: {
|
||||
...mockSignInExperienceSettings.signUp,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PasswordRegisterWithUsername />
|
||||
|
|
|
@ -32,7 +32,10 @@ describe('<Register />', () => {
|
|||
<SettingsProvider
|
||||
settings={{
|
||||
...mockSignInExperienceSettings,
|
||||
signUp: { ...mockSignInExperienceSettings.signUp, methods: [SignInIdentifier.Email] },
|
||||
signUp: {
|
||||
...mockSignInExperienceSettings.signUp,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MemoryRouter>
|
||||
|
@ -49,7 +52,10 @@ describe('<Register />', () => {
|
|||
<SettingsProvider
|
||||
settings={{
|
||||
...mockSignInExperienceSettings,
|
||||
signUp: { ...mockSignInExperienceSettings.signUp, methods: [SignInIdentifier.Sms] },
|
||||
signUp: {
|
||||
...mockSignInExperienceSettings.signUp,
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MemoryRouter>
|
||||
|
@ -68,7 +74,7 @@ describe('<Register />', () => {
|
|||
...mockSignInExperienceSettings,
|
||||
signUp: {
|
||||
...mockSignInExperienceSettings.signUp,
|
||||
methods: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
@ -86,7 +92,7 @@ describe('<Register />', () => {
|
|||
<SettingsProvider
|
||||
settings={{
|
||||
...mockSignInExperienceSettings,
|
||||
signUp: { ...mockSignInExperienceSettings.signUp, methods: [] },
|
||||
signUp: { ...mockSignInExperienceSettings.signUp, identifiers: [] },
|
||||
}}
|
||||
>
|
||||
<MemoryRouter>
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('<SecondaryRegister />', () => {
|
|||
...mockSignInExperienceSettings,
|
||||
signUp: {
|
||||
...mockSignInExperienceSettings.signUp,
|
||||
methods: [SignInIdentifier.Sms],
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
@ -51,7 +51,7 @@ describe('<SecondaryRegister />', () => {
|
|||
...mockSignInExperienceSettings,
|
||||
signUp: {
|
||||
...mockSignInExperienceSettings.signUp,
|
||||
methods: [SignInIdentifier.Email],
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
@ -115,7 +115,7 @@ describe('<SecondaryRegister />', () => {
|
|||
settings={{
|
||||
...mockSignInExperienceSettings,
|
||||
signUp: {
|
||||
methods: [SignInIdentifier.Email],
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: true,
|
||||
verify: false,
|
||||
},
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import type {
|
||||
SignInExperience,
|
||||
ConnectorMetadata,
|
||||
AppearanceMode,
|
||||
SignInIdentifier,
|
||||
} from '@logto/schemas';
|
||||
import type { SignInExperience, ConnectorMetadata, AppearanceMode } from '@logto/schemas';
|
||||
|
||||
export enum UserFlow {
|
||||
signIn = 'sign-in',
|
||||
|
@ -23,12 +18,8 @@ export type Platform = 'web' | 'mobile';
|
|||
// TODO: @simeng, @sijie, @charles should we combine this with admin console?
|
||||
export type Theme = 'dark' | 'light';
|
||||
|
||||
// Omit signInMethods property since it is deprecated,
|
||||
// Omit socialSignInConnectorTargets since it is being translated into socialConnectors
|
||||
export type SignInExperienceResponse = Omit<
|
||||
SignInExperience,
|
||||
'signInMethods' | 'socialSignInConnectorTargets'
|
||||
> & {
|
||||
export type SignInExperienceResponse = Omit<SignInExperience, 'socialSignInConnectorTargets'> & {
|
||||
socialConnectors: ConnectorMetadata[];
|
||||
notification?: string;
|
||||
forgotPassword: {
|
||||
|
@ -37,12 +28,6 @@ export type SignInExperienceResponse = Omit<
|
|||
};
|
||||
};
|
||||
|
||||
export type SignInExperienceSettings = Omit<SignInExperienceResponse, 'signUp'> & {
|
||||
signUp: Omit<SignInExperienceResponse['signUp'], 'identifier'> & {
|
||||
methods: SignInIdentifier[];
|
||||
};
|
||||
};
|
||||
|
||||
export enum ConfirmModalMessage {
|
||||
SHOW_TERMS_DETAIL_MODAL = 'SHOW_TERMS_DETAIL_MODAL',
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('getSignInExperienceSettings', () => {
|
|||
expect(settings.branding).toEqual(mockSignInExperience.branding);
|
||||
expect(settings.languageInfo).toEqual(mockSignInExperience.languageInfo);
|
||||
expect(settings.termsOfUse).toEqual(mockSignInExperience.termsOfUse);
|
||||
expect(settings.signUp.methods).toContain('username');
|
||||
expect(settings.signUp.identifiers).toContain('username');
|
||||
expect(settings.signIn.methods).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,37 +3,24 @@
|
|||
* Remove this once we have a better way to get the sign in experience through SSR
|
||||
*/
|
||||
|
||||
import { SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
import { getSignInExperience } from '@/apis/settings';
|
||||
import type { SignInExperienceSettings, SignInExperienceResponse } from '@/types';
|
||||
import type { SignInExperienceResponse } from '@/types';
|
||||
import { filterSocialConnectors } from '@/utils/social-connectors';
|
||||
|
||||
export const signUpIdentifierMap: Record<SignUpIdentifier, SignInIdentifier[]> = {
|
||||
[SignUpIdentifier.Username]: [SignInIdentifier.Username],
|
||||
[SignUpIdentifier.Email]: [SignInIdentifier.Email],
|
||||
[SignUpIdentifier.Sms]: [SignInIdentifier.Sms],
|
||||
[SignUpIdentifier.EmailOrSms]: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
[SignUpIdentifier.None]: [],
|
||||
};
|
||||
|
||||
const parseSignInExperienceResponse = (
|
||||
response: SignInExperienceResponse
|
||||
): SignInExperienceSettings => {
|
||||
const { socialConnectors, signUp, ...rest } = response;
|
||||
const { identifier, ...signUpSettings } = signUp;
|
||||
): SignInExperienceResponse => {
|
||||
const { socialConnectors, ...rest } = response;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
socialConnectors: filterSocialConnectors(socialConnectors),
|
||||
signUp: {
|
||||
methods: signUpIdentifierMap[identifier],
|
||||
...signUpSettings,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getSignInExperienceSettings = async (): Promise<SignInExperienceSettings> => {
|
||||
export const getSignInExperienceSettings = async (): Promise<SignInExperienceResponse> => {
|
||||
const response = await getSignInExperience<SignInExperienceResponse>();
|
||||
|
||||
return parseSignInExperienceResponse(response);
|
||||
|
|
Loading…
Add table
Reference in a new issue