mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
Merge branch 'feature/sie-v2' into merge/sie-v2
This commit is contained in:
commit
c8a7efb0cc
20 changed files with 405 additions and 143 deletions
|
@ -3,7 +3,7 @@ import { AppearanceMode, ConnectorType } from '@logto/schemas';
|
|||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import useSWR, { useSWRConfig } from 'swr';
|
||||
|
||||
|
@ -14,6 +14,7 @@ import Reset from '@/assets/images/reset.svg';
|
|||
import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import ConfirmModal from '@/components/ConfirmModal';
|
||||
import CopyToClipboard from '@/components/CopyToClipboard';
|
||||
import DetailsSkeleton from '@/components/DetailsSkeleton';
|
||||
import Drawer from '@/components/Drawer';
|
||||
|
@ -49,6 +50,18 @@ const ConnectorDetails = () => {
|
|||
const api = useApi();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isSocial = data?.type === ConnectorType.Social;
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false);
|
||||
|
||||
const onDeleteClick = async () => {
|
||||
if (!isSocial || !inUse) {
|
||||
await handleDelete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDeleteAlertOpen(true);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!connectorId) {
|
||||
|
@ -65,7 +78,7 @@ const ConnectorDetails = () => {
|
|||
await mutateGlobal('/api/connectors');
|
||||
setIsDeleted(true);
|
||||
|
||||
if (data?.type === ConnectorType.Social) {
|
||||
if (isSocial) {
|
||||
navigate(`/connectors/social`, { replace: true });
|
||||
} else {
|
||||
navigate(`/connectors`, { replace: true });
|
||||
|
@ -75,16 +88,14 @@ const ConnectorDetails = () => {
|
|||
return (
|
||||
<div className={detailsStyles.container}>
|
||||
<LinkButton
|
||||
to={data?.type === ConnectorType.Social ? '/connectors/social' : '/connectors'}
|
||||
to={isSocial ? '/connectors/social' : '/connectors'}
|
||||
icon={<Back />}
|
||||
title="connector_details.back_to_connectors"
|
||||
className={styles.backLink}
|
||||
/>
|
||||
{isLoading && <DetailsSkeleton />}
|
||||
{!data && error && <div>{`error occurred: ${error.body?.message ?? error.message}`}</div>}
|
||||
{data?.type === ConnectorType.Social && (
|
||||
<ConnectorTabs target={data.target} connectorId={data.id} />
|
||||
)}
|
||||
{isSocial && <ConnectorTabs target={data.target} connectorId={data.id} />}
|
||||
{data && (
|
||||
<Card className={styles.header}>
|
||||
<div className={styles.logoContainer}>
|
||||
|
@ -137,7 +148,7 @@ const ConnectorDetails = () => {
|
|||
buttonProps={{ icon: <More className={styles.moreIcon} />, size: 'large' }}
|
||||
title={t('general.more_options')}
|
||||
>
|
||||
{data.type !== ConnectorType.Social && (
|
||||
{!isSocial && (
|
||||
<ActionMenuItem
|
||||
icon={<Reset />}
|
||||
iconClassName={styles.resetIcon}
|
||||
|
@ -152,7 +163,7 @@ const ConnectorDetails = () => {
|
|||
)}
|
||||
</ActionMenuItem>
|
||||
)}
|
||||
<ActionMenuItem icon={<Delete />} type="danger" onClick={handleDelete}>
|
||||
<ActionMenuItem icon={<Delete />} type="danger" onClick={onDeleteClick}>
|
||||
{t('general.delete')}
|
||||
</ActionMenuItem>
|
||||
</ActionMenu>
|
||||
|
@ -183,6 +194,22 @@ const ConnectorDetails = () => {
|
|||
/>
|
||||
</Card>
|
||||
)}
|
||||
{data && (
|
||||
<ConfirmModal
|
||||
isOpen={isDeleteAlertOpen}
|
||||
confirmButtonText="general.delete"
|
||||
onCancel={() => {
|
||||
setIsDeleteAlertOpen(false);
|
||||
}}
|
||||
onConfirm={handleDelete}
|
||||
>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="connector_details.in_use_deletion_description"
|
||||
components={{ name: <UnnamedTrans resource={data.name} /> }}
|
||||
/>
|
||||
</ConfirmModal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
125
packages/core/src/__mocks__/connector-base-data.ts
Normal file
125
packages/core/src/__mocks__/connector-base-data.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
import type { ConnectorMetadata } from '@logto/connector-kit';
|
||||
import { ConnectorPlatform } from '@logto/connector-kit';
|
||||
import type { Connector } from '@logto/schemas';
|
||||
|
||||
export const mockMetadata: ConnectorMetadata = {
|
||||
id: 'id',
|
||||
target: 'connector',
|
||||
platform: null,
|
||||
name: {
|
||||
en: 'Connector',
|
||||
'pt-PT': 'Conector',
|
||||
'zh-CN': '连接器',
|
||||
'tr-TR': 'Connector',
|
||||
ko: 'Connector',
|
||||
},
|
||||
logo: './logo.png',
|
||||
logoDark: './logo-dark.png',
|
||||
description: {
|
||||
en: 'Connector',
|
||||
'pt-PT': 'Conector',
|
||||
'zh-CN': '连接器',
|
||||
'tr-TR': 'Connector',
|
||||
ko: 'Connector',
|
||||
},
|
||||
readme: 'README.md',
|
||||
configTemplate: 'config-template.json',
|
||||
};
|
||||
|
||||
export const mockMetadata0: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id0',
|
||||
target: 'connector_0',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
export const mockMetadata1: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id1',
|
||||
target: 'connector_1',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
export const mockMetadata2: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id2',
|
||||
target: 'connector_2',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
export const mockMetadata3: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id3',
|
||||
target: 'connector_3',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
export const mockMetadata4: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id4',
|
||||
target: 'connector_4',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
export const mockMetadata5: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id5',
|
||||
target: 'connector_5',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
export const mockMetadata6: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id6',
|
||||
target: 'connector_6',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
export const mockConnector0: Connector = {
|
||||
id: 'id0',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
};
|
||||
|
||||
export const mockConnector1: Connector = {
|
||||
id: 'id1',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
};
|
||||
|
||||
export const mockConnector2: Connector = {
|
||||
id: 'id2',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_345,
|
||||
};
|
||||
|
||||
export const mockConnector3: Connector = {
|
||||
id: 'id3',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_456,
|
||||
};
|
||||
|
||||
export const mockConnector4: Connector = {
|
||||
id: 'id4',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
};
|
||||
|
||||
export const mockConnector5: Connector = {
|
||||
id: 'id5',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
};
|
||||
|
||||
export const mockConnector6: Connector = {
|
||||
id: 'id6',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
};
|
|
@ -1,33 +1,29 @@
|
|||
import { ConnectorPlatform } from '@logto/connector-kit';
|
||||
import type { Connector, ConnectorMetadata } from '@logto/schemas';
|
||||
import type { Connector } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { any } from 'zod';
|
||||
|
||||
import type { LogtoConnector } from '@/connectors/types';
|
||||
|
||||
export const mockMetadata: ConnectorMetadata = {
|
||||
id: 'id',
|
||||
target: 'connector',
|
||||
platform: null,
|
||||
name: {
|
||||
en: 'Connector',
|
||||
'pt-PT': 'Conector',
|
||||
'zh-CN': '连接器',
|
||||
'tr-TR': 'Connector',
|
||||
ko: 'Connector',
|
||||
},
|
||||
logo: './logo.png',
|
||||
logoDark: './logo-dark.png',
|
||||
description: {
|
||||
en: 'Connector',
|
||||
'pt-PT': 'Conector',
|
||||
'zh-CN': '连接器',
|
||||
'tr-TR': 'Connector',
|
||||
ko: 'Connector',
|
||||
},
|
||||
readme: 'README.md',
|
||||
configTemplate: 'config-template.json',
|
||||
};
|
||||
import {
|
||||
mockConnector0,
|
||||
mockConnector1,
|
||||
mockConnector2,
|
||||
mockConnector3,
|
||||
mockConnector4,
|
||||
mockConnector5,
|
||||
mockConnector6,
|
||||
mockMetadata,
|
||||
mockMetadata0,
|
||||
mockMetadata1,
|
||||
mockMetadata2,
|
||||
mockMetadata3,
|
||||
mockMetadata4,
|
||||
mockMetadata5,
|
||||
mockMetadata6,
|
||||
} from './connector-base-data';
|
||||
|
||||
export { mockMetadata } from './connector-base-data';
|
||||
|
||||
export const mockConnector: Connector = {
|
||||
id: 'id',
|
||||
|
@ -44,104 +40,6 @@ export const mockLogtoConnector = {
|
|||
configGuard: any(),
|
||||
};
|
||||
|
||||
const mockMetadata0: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id0',
|
||||
target: 'connector_0',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
const mockMetadata1: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id1',
|
||||
target: 'connector_1',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
const mockMetadata2: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id2',
|
||||
target: 'connector_2',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
const mockMetadata3: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id3',
|
||||
target: 'connector_3',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
const mockMetadata4: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id4',
|
||||
target: 'connector_4',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
const mockMetadata5: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id5',
|
||||
target: 'connector_5',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
const mockMetadata6: ConnectorMetadata = {
|
||||
...mockMetadata,
|
||||
id: 'id6',
|
||||
target: 'connector_6',
|
||||
platform: ConnectorPlatform.Universal,
|
||||
};
|
||||
|
||||
const mockConnector0: Connector = {
|
||||
id: 'id0',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
};
|
||||
|
||||
const mockConnector1: Connector = {
|
||||
id: 'id1',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
};
|
||||
|
||||
const mockConnector2: Connector = {
|
||||
id: 'id2',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_345,
|
||||
};
|
||||
|
||||
const mockConnector3: Connector = {
|
||||
id: 'id3',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_456,
|
||||
};
|
||||
|
||||
const mockConnector4: Connector = {
|
||||
id: 'id4',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
};
|
||||
|
||||
const mockConnector5: Connector = {
|
||||
id: 'id5',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
};
|
||||
|
||||
const mockConnector6: Connector = {
|
||||
id: 'id6',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
};
|
||||
|
||||
export const mockConnectorList: Connector[] = [
|
||||
mockConnector0,
|
||||
mockConnector1,
|
||||
|
@ -312,3 +210,52 @@ export const mockLogtoConnectors = [
|
|||
mockWechatConnector,
|
||||
mockWechatNativeConnector,
|
||||
];
|
||||
|
||||
export const disabledSocialTarget01 = 'disableSocialTarget-id01';
|
||||
export const disabledSocialTarget02 = 'disableSocialTarget-id02';
|
||||
export const enabledSocialTarget01 = 'enabledSocialTarget-id01';
|
||||
|
||||
export const mockSocialConnectors: LogtoConnector[] = [
|
||||
{
|
||||
dbEntry: {
|
||||
id: 'id0',
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
metadata: {
|
||||
...mockMetadata,
|
||||
target: disabledSocialTarget01,
|
||||
},
|
||||
type: ConnectorType.Social,
|
||||
...mockLogtoConnector,
|
||||
},
|
||||
{
|
||||
dbEntry: {
|
||||
id: 'id1',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
metadata: {
|
||||
...mockMetadata,
|
||||
target: enabledSocialTarget01,
|
||||
},
|
||||
type: ConnectorType.Social,
|
||||
...mockLogtoConnector,
|
||||
},
|
||||
{
|
||||
dbEntry: {
|
||||
id: 'id2',
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
metadata: {
|
||||
...mockMetadata,
|
||||
target: disabledSocialTarget02,
|
||||
},
|
||||
type: ConnectorType.Social,
|
||||
...mockLogtoConnector,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,22 +1,53 @@
|
|||
import type { LanguageTag } from '@logto/language-kit';
|
||||
import { builtInLanguages } from '@logto/phrases-ui';
|
||||
import type { CreateSignInExperience, SignInExperience } from '@logto/schemas';
|
||||
import { BrandingStyle } from '@logto/schemas';
|
||||
|
||||
import { mockBranding } from '@/__mocks__';
|
||||
import {
|
||||
disabledSocialTarget01,
|
||||
disabledSocialTarget02,
|
||||
enabledSocialTarget01,
|
||||
mockBranding,
|
||||
mockSignInExperience,
|
||||
mockSocialConnectors,
|
||||
} from '@/__mocks__';
|
||||
import type { LogtoConnector } from '@/connectors/types';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import {
|
||||
validateBranding,
|
||||
validateTermsOfUse,
|
||||
validateLanguageInfo,
|
||||
removeUnavailableSocialConnectorTargets,
|
||||
} from '@/lib/sign-in-experience';
|
||||
import { updateDefaultSignInExperience } from '@/queries/sign-in-experience';
|
||||
|
||||
const allCustomLanguageTags: LanguageTag[] = [];
|
||||
const findAllCustomLanguageTags = jest.fn(async () => allCustomLanguageTags);
|
||||
const getLogtoConnectorsPlaceHolder = jest.fn() as jest.MockedFunction<
|
||||
() => Promise<LogtoConnector[]>
|
||||
>;
|
||||
const findDefaultSignInExperience = jest.fn() as jest.MockedFunction<
|
||||
() => Promise<SignInExperience>
|
||||
>;
|
||||
|
||||
jest.mock('@/queries/custom-phrase', () => ({
|
||||
findAllCustomLanguageTags: async () => findAllCustomLanguageTags(),
|
||||
}));
|
||||
|
||||
jest.mock('@/connectors', () => ({
|
||||
getLogtoConnectors: async () => getLogtoConnectorsPlaceHolder(),
|
||||
}));
|
||||
|
||||
jest.mock('@/queries/sign-in-experience', () => ({
|
||||
findDefaultSignInExperience: async () => findDefaultSignInExperience(),
|
||||
updateDefaultSignInExperience: jest.fn(
|
||||
async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
})
|
||||
),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
@ -123,3 +154,25 @@ describe('validate terms of use', () => {
|
|||
}).toMatchError(new RequestError('sign_in_experiences.empty_content_url_of_terms_of_use'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove unavailable social connector targets', () => {
|
||||
test('should remove unavailable social connector targets in sign-in experience', async () => {
|
||||
const mockSocialConnectorTargets = mockSocialConnectors.map(
|
||||
({ metadata: { target } }) => target
|
||||
);
|
||||
findDefaultSignInExperience.mockResolvedValueOnce({
|
||||
...mockSignInExperience,
|
||||
socialSignInConnectorTargets: mockSocialConnectorTargets,
|
||||
});
|
||||
getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(mockSocialConnectors);
|
||||
expect(mockSocialConnectorTargets).toEqual([
|
||||
disabledSocialTarget01,
|
||||
enabledSocialTarget01,
|
||||
disabledSocialTarget02,
|
||||
]);
|
||||
await removeUnavailableSocialConnectorTargets();
|
||||
expect(updateDefaultSignInExperience).toBeCalledWith({
|
||||
socialSignInConnectorTargets: [enabledSocialTarget01],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import { builtInLanguages } from '@logto/phrases-ui';
|
||||
import type { Branding, LanguageInfo, TermsOfUse } from '@logto/schemas';
|
||||
import { BrandingStyle } from '@logto/schemas';
|
||||
import { ConnectorType, BrandingStyle } from '@logto/schemas';
|
||||
|
||||
import { getLogtoConnectors } from '@/connectors';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { findAllCustomLanguageTags } from '@/queries/custom-phrase';
|
||||
import {
|
||||
findDefaultSignInExperience,
|
||||
updateDefaultSignInExperience,
|
||||
} from '@/queries/sign-in-experience';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
export * from './sign-up';
|
||||
|
@ -35,3 +40,19 @@ export const validateTermsOfUse = (termsOfUse: TermsOfUse) => {
|
|||
'sign_in_experiences.empty_content_url_of_terms_of_use'
|
||||
);
|
||||
};
|
||||
|
||||
export const removeUnavailableSocialConnectorTargets = async () => {
|
||||
const connectors = await getLogtoConnectors();
|
||||
const availableSocialConnectorTargets = new Set(
|
||||
connectors
|
||||
.filter(({ type, dbEntry: { enabled } }) => enabled && type === ConnectorType.Social)
|
||||
.map(({ metadata: { target } }) => target)
|
||||
);
|
||||
|
||||
const { socialSignInConnectorTargets } = await findDefaultSignInExperience();
|
||||
await updateDefaultSignInExperience({
|
||||
socialSignInConnectorTargets: socialSignInConnectorTargets.filter((target) =>
|
||||
availableSocialConnectorTargets.has(target)
|
||||
),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import { object, string } from 'zod';
|
|||
import { getLogtoConnectorById, getLogtoConnectors } from '@/connectors';
|
||||
import type { LogtoConnector } from '@/connectors/types';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { removeUnavailableSocialConnectorTargets } from '@/lib/sign-in-experience';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import { updateConnector } from '@/queries/connector';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
@ -115,6 +116,12 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
|
|||
where: { id },
|
||||
jsonbMode: 'merge',
|
||||
});
|
||||
|
||||
// Delete the social connector in the sign-in experience if it is disabled.
|
||||
if (!enabled && type === ConnectorType.Social) {
|
||||
await removeUnavailableSocialConnectorTargets();
|
||||
}
|
||||
|
||||
ctx.body = { ...connector, metadata, type };
|
||||
|
||||
return next();
|
||||
|
|
|
@ -46,6 +46,10 @@ jest.mock('@/connectors', () => ({
|
|||
getLogtoConnectorById: async (connectorId: string) =>
|
||||
getLogtoConnectorByIdPlaceholder(connectorId),
|
||||
}));
|
||||
jest.mock('@/lib/sign-in-experience', () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
removeUnavailableSocialConnectorTargets: async () => {},
|
||||
}));
|
||||
|
||||
describe('connector PATCH routes', () => {
|
||||
const connectorRequest = createRequester({ authedRoutes: connectorRoutes });
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { PasscodeType } from '@logto/schemas';
|
||||
import { addDays, subSeconds } from 'date-fns';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
|
@ -138,6 +139,7 @@ describe('session -> continueRoutes', () => {
|
|||
continueSignIn: {
|
||||
userId: mockUser.id,
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
type: PasscodeType.Continue,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -169,6 +171,7 @@ describe('session -> continueRoutes', () => {
|
|||
continueSignIn: {
|
||||
userId: mockUser.id,
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
type: PasscodeType.Continue,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
import type { AnonymousRouter } from '../types';
|
||||
import { emailSessionResultGuard, smsSessionResultGuard } from './types';
|
||||
import { continueEmailSessionResultGuard, continueSmsSessionResultGuard } from './types';
|
||||
import {
|
||||
checkRequiredProfile,
|
||||
getContinueSignInResult,
|
||||
|
@ -104,7 +104,7 @@ export default function continueRoutes<T extends AnonymousRouter>(router: T, pro
|
|||
const { email } = await getVerificationStorageFromInteraction(
|
||||
ctx,
|
||||
provider,
|
||||
emailSessionResultGuard
|
||||
continueEmailSessionResultGuard
|
||||
);
|
||||
const user = await findUserById(userId);
|
||||
|
||||
|
@ -138,7 +138,7 @@ export default function continueRoutes<T extends AnonymousRouter>(router: T, pro
|
|||
const { phone } = await getVerificationStorageFromInteraction(
|
||||
ctx,
|
||||
provider,
|
||||
smsSessionResultGuard
|
||||
continueSmsSessionResultGuard
|
||||
);
|
||||
const user = await findUserById(userId);
|
||||
|
||||
|
|
|
@ -117,13 +117,21 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
|||
return next();
|
||||
}
|
||||
|
||||
await assignVerificationResult(ctx, provider, { flow, phone });
|
||||
|
||||
if (flow === PasscodeType.SignIn) {
|
||||
await assignVerificationResult(ctx, provider, { flow, phone });
|
||||
|
||||
return smsSignInAction(provider)(ctx, next);
|
||||
}
|
||||
|
||||
return smsRegisterAction(provider)(ctx, next);
|
||||
if (flow === PasscodeType.Register) {
|
||||
await assignVerificationResult(ctx, provider, { flow, phone });
|
||||
|
||||
return smsRegisterAction(provider)(ctx, next);
|
||||
}
|
||||
|
||||
await assignVerificationResult(ctx, provider, { flow, phone });
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -161,13 +169,21 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
|||
return next();
|
||||
}
|
||||
|
||||
await assignVerificationResult(ctx, provider, { flow, email });
|
||||
|
||||
if (flow === PasscodeType.SignIn) {
|
||||
await assignVerificationResult(ctx, provider, { flow, email });
|
||||
|
||||
return emailSignInAction(provider)(ctx, next);
|
||||
}
|
||||
|
||||
return emailRegisterAction(provider)(ctx, next);
|
||||
if (flow === PasscodeType.Register) {
|
||||
await assignVerificationResult(ctx, provider, { flow, email });
|
||||
|
||||
return emailRegisterAction(provider)(ctx, next);
|
||||
}
|
||||
|
||||
await assignVerificationResult(ctx, provider, { flow, email });
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -45,10 +45,36 @@ export const forgotPasswordSessionResultGuard = z.object({
|
|||
verification: forgotPasswordSessionStorageGuard,
|
||||
});
|
||||
|
||||
const continueEmailSessionStorageGuard = z.object({
|
||||
flow: z.literal(PasscodeType.Continue),
|
||||
expiresAt: z.string(),
|
||||
email: z.string(),
|
||||
});
|
||||
|
||||
export type ContinueEmailSessionStorage = z.infer<typeof continueEmailSessionStorageGuard>;
|
||||
|
||||
export const continueEmailSessionResultGuard = z.object({
|
||||
verification: continueEmailSessionStorageGuard,
|
||||
});
|
||||
|
||||
const continueSmsSessionStorageGuard = z.object({
|
||||
flow: z.literal(PasscodeType.Continue),
|
||||
expiresAt: z.string(),
|
||||
phone: z.string(),
|
||||
});
|
||||
|
||||
export type ContinueSmsSessionStorage = z.infer<typeof continueSmsSessionStorageGuard>;
|
||||
|
||||
export const continueSmsSessionResultGuard = z.object({
|
||||
verification: continueSmsSessionStorageGuard,
|
||||
});
|
||||
|
||||
export type VerificationStorage =
|
||||
| SmsSessionStorage
|
||||
| EmailSessionStorage
|
||||
| ForgotPasswordSessionStorage;
|
||||
| ForgotPasswordSessionStorage
|
||||
| ContinueEmailSessionStorage
|
||||
| ContinueSmsSessionStorage;
|
||||
|
||||
export type VerificationResult<T = VerificationStorage> = { verification: T };
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ const connector_details = {
|
|||
type_email: 'E-Mail connector',
|
||||
type_sms: 'SMS connector',
|
||||
type_social: 'Social connector',
|
||||
in_use_deletion_description:
|
||||
'This connector is in use in your sign in experience. By deleting, <name/> sign in experience will be deleted in sign in experience settings.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default connector_details;
|
||||
|
|
|
@ -17,6 +17,8 @@ const connector_details = {
|
|||
type_email: 'Email connector',
|
||||
type_sms: 'SMS connector',
|
||||
type_social: 'Social connector',
|
||||
in_use_deletion_description:
|
||||
'This connector is in use in your sign in experience. By deleting, <name/> sign in experience will be deleted in sign in experience settings.',
|
||||
};
|
||||
|
||||
export default connector_details;
|
||||
|
|
|
@ -17,6 +17,8 @@ const connector_details = {
|
|||
type_email: 'Connecteur Email',
|
||||
type_sms: 'Connecteur SMS',
|
||||
type_social: 'Connecteur Social',
|
||||
in_use_deletion_description:
|
||||
'This connector is in use in your sign in experience. By deleting, <name/> sign in experience will be deleted in sign in experience settings.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default connector_details;
|
||||
|
|
|
@ -17,6 +17,8 @@ const connector_details = {
|
|||
type_email: '이메일 연동',
|
||||
type_sms: 'SMS 연동',
|
||||
type_social: '소셜 연동',
|
||||
in_use_deletion_description:
|
||||
'This connector is in use in your sign in experience. By deleting, <name/> sign in experience will be deleted in sign in experience settings.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default connector_details;
|
||||
|
|
|
@ -17,6 +17,8 @@ const connector_details = {
|
|||
type_email: 'Conector de Email',
|
||||
type_sms: 'Conector de SMS',
|
||||
type_social: 'Conector Social',
|
||||
in_use_deletion_description:
|
||||
'This connector is in use in your sign in experience. By deleting, <name/> sign in experience will be deleted in sign in experience settings.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default connector_details;
|
||||
|
|
|
@ -17,6 +17,8 @@ const connector_details = {
|
|||
type_email: 'Eposta connectorı',
|
||||
type_sms: 'SMS connectorı',
|
||||
type_social: 'Social connector',
|
||||
in_use_deletion_description:
|
||||
'This connector is in use in your sign in experience. By deleting, <name/> sign in experience will be deleted in sign in experience settings.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default connector_details;
|
||||
|
|
|
@ -17,6 +17,8 @@ const connector_details = {
|
|||
type_email: '邮件连接器',
|
||||
type_sms: '短信连接器',
|
||||
type_social: '社交连接器',
|
||||
in_use_deletion_description:
|
||||
'This connector is in use in your sign in experience. By deleting, <name/> sign in experience will be deleted in sign in experience settings.', // UNTRANSLATED
|
||||
};
|
||||
|
||||
export default connector_details;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { sql } from 'slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter type passcode_type add value 'Continue'
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
drop type passcode_type
|
||||
create type passcode_type as enum ('SignIn', 'Register', 'ForgotPassword');
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -1,4 +1,4 @@
|
|||
create type passcode_type as enum ('SignIn', 'Register', 'ForgotPassword');
|
||||
create type passcode_type as enum ('SignIn', 'Register', 'ForgotPassword', 'Continue');
|
||||
|
||||
create table passcodes (
|
||||
id varchar(21) not null,
|
||||
|
|
Loading…
Add table
Reference in a new issue