0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-14 23:11:31 -05:00

refactor: connector-core types (#1843)

* refactor: connector-core types

* refactor: fix rebase issues

* refactor: remove unused types

* refactor: fix connector error code

* refactor(core): add comments

* refactor: remove unused types
This commit is contained in:
Gao Sun 2022-09-01 12:21:42 +08:00 committed by GitHub
parent 8c2fba81fd
commit 139dec727d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 441 additions and 235 deletions

View file

@ -1,4 +1,4 @@
import { ConnectorType, ConnectorMetadata, ConnectorPlatform } from '@logto/connector-core';
import { ConnectorMetadata, ConnectorPlatform } from '@logto/connector-core';
export const authorizationEndpoint = 'alipay://'; // This is used to arouse the native Alipay App
export const alipayEndpoint = 'https://openapi.alipay.com/gateway.do';
@ -18,7 +18,6 @@ export const invalidAccessTokenSubCode = ['isv.code-invalid'];
export const defaultMetadata: ConnectorMetadata = {
id: 'alipay-native',
target: 'alipay',
type: ConnectorType.Social,
platform: ConnectorPlatform.Native,
name: {
en: 'Alipay',

View file

@ -16,6 +16,7 @@ import {
CreateConnector,
SocialConnector,
validateConfig,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import dayjs from 'dayjs';
@ -182,6 +183,7 @@ const authorizationCallbackHandler = async (parameterObject: unknown) => {
const createAlipayNativeConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: alipayNativeConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -21,7 +21,6 @@ export const invalidAccessTokenSubCode = ['isv.code-invalid'];
export const defaultMetadata: ConnectorMetadata = {
id: 'alipay-web',
target: 'alipay',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
name: {
en: 'Alipay',

View file

@ -14,6 +14,7 @@ import {
CreateConnector,
SocialConnector,
validateConfig,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import dayjs from 'dayjs';
@ -192,6 +193,7 @@ const authorizationCallbackHandler = async (parameterObject: unknown) => {
const createAlipayConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: alipayConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -12,7 +12,6 @@ export const staticConfigs = {
export const defaultMetadata: ConnectorMetadata = {
id: 'aliyun-direct-mail',
target: 'aliyun-dm',
type: ConnectorType.Email,
platform: null,
name: {
en: 'Aliyun Direct Mail',

View file

@ -1,6 +1,7 @@
import {
ConnectorError,
ConnectorErrorCodes,
ConnectorType,
CreateConnector,
EmailConnector,
GetConnectorConfig,
@ -95,6 +96,7 @@ const errorHandler = (errorResponseBody: string) => {
const createAliyunDmConnector: CreateConnector<EmailConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Email,
configGuard: aliyunDmConfigGuard,
sendMessage: sendMessage(getConfig),
};

View file

@ -28,7 +28,6 @@ export enum SmsTemplateType {
export const defaultMetadata: ConnectorMetadata = {
id: 'aliyun-short-message-service',
target: 'aliyun-sms',
type: ConnectorType.SMS,
platform: null,
name: {
en: 'Aliyun Short Message Service',

View file

@ -1,7 +1,7 @@
import { MessageTypes } from '@logto/connector-core';
import createConnector from '.';
import { mockedConnectorConfig, mockedValidConnectorConfig, phoneTest, codeTest } from './mock';
import { mockedConnectorConfig, phoneTest, codeTest } from './mock';
import { sendSms } from './single-send-text';
const getConfig = jest.fn().mockResolvedValue(mockedConnectorConfig);

View file

@ -6,6 +6,7 @@ import {
SmsConnector,
CreateConnector,
validateConfig,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import { HTTPError } from 'got';
@ -87,6 +88,7 @@ const parseResponseString = (response: string) => {
const createAliyunSmsConnector: CreateConnector<SmsConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Sms,
configGuard: aliyunSmsConfigGuard,
sendMessage: sendMessage(getConfig),
};

View file

@ -11,13 +11,6 @@ export const mockedConnectorConfig = {
],
};
export const mockedValidConnectorConfig = {
accessKeyId: 'accessKeyId',
accessKeySecret: 'accessKeySecret',
signName: 'signName',
templates: [],
};
export const phoneTest = '13012345678';
export const codeTest = '1234';

View file

@ -12,7 +12,6 @@ export const scope = ''; // Note: `openid` is required when adding more scope(s)
export const defaultMetadata: ConnectorMetadata = {
id: 'apple-universal',
target: 'apple',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
name: {
en: 'Apple',

View file

@ -7,6 +7,7 @@ import {
validateConfig,
CreateConnector,
SocialConnector,
ConnectorType,
} from '@logto/connector-core';
import { createRemoteJWKSet, jwtVerify } from 'jose';
@ -80,6 +81,7 @@ const authorizationCallbackHandler = async (parameterObject: unknown) => {
const createAppleConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: appleConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -6,7 +6,6 @@ export const scopes = ['User.Read'];
export const defaultMetadata: ConnectorMetadata = {
id: 'azuread-universal',
target: 'azuread',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
name: {
en: 'Azure Active Directory',

View file

@ -15,6 +15,7 @@ import {
validateConfig,
CreateConnector,
SocialConnector,
ConnectorType,
} from '@logto/connector-core';
import { assert, conditional } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
@ -152,6 +153,7 @@ const authorizationCallbackHandler = async (parameterObject: unknown) => {
const createAzureAdConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: azureADConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -2,9 +2,10 @@ import type { LanguageKey } from '@logto/shared';
import { Nullable } from '@silverhand/essentials';
import { z, ZodType } from 'zod';
// MARK: Foundation
export enum ConnectorType {
Email = 'Email',
SMS = 'SMS',
Sms = 'Sms',
Social = 'Social',
}
@ -18,22 +19,10 @@ type I18nPhrases = { en: string } & {
[K in Exclude<LanguageKey, 'en'>]?: string;
};
export type ConnectorMetadata = {
id: string;
target: string;
type: ConnectorType;
platform: Nullable<ConnectorPlatform>;
name: I18nPhrases;
logo: string;
logoDark: Nullable<string>;
description: I18nPhrases;
readme: string;
configTemplate: string;
};
export enum ConnectorErrorCodes {
General = 'general',
InvalidMetadata = 'invalid_metadata',
UnexpectedType = 'unexpected_type',
InvalidConfigGuard = 'invalid_config_guard',
InsufficientRequestParameters = 'insufficient_request_parameters',
InvalidConfig = 'invalid_config',
@ -67,34 +56,52 @@ export enum MessageTypes {
export const messageTypesGuard = z.nativeEnum(MessageTypes);
export type BaseConnector = {
export type ConnectorMetadata = {
id: string;
target: string;
platform: Nullable<ConnectorPlatform>;
name: I18nPhrases;
logo: string;
logoDark: Nullable<string>;
description: I18nPhrases;
readme: string;
configTemplate: string;
};
export type BaseConnector<Type extends ConnectorType> = {
type: Type;
metadata: ConnectorMetadata;
configGuard: ZodType;
};
export type SmsConnector = {
export type CreateConnector<T extends BaseConnector<ConnectorType>> = (options: {
getConfig: GetConnectorConfig;
}) => Promise<T>;
export type GetConnectorConfig = (id: string) => Promise<unknown>;
export type AllConnector = SmsConnector | EmailConnector | SocialConnector;
// MARK: SMS + Email connector
export type SmsConnector = BaseConnector<ConnectorType.Sms> & {
sendMessage: SendMessageFunction;
} & BaseConnector;
};
export type EmailConnector = SmsConnector;
export type SocialConnector = {
getAuthorizationUri: GetAuthorizationUri;
getUserInfo: GetUserInfo;
} & BaseConnector;
export type GeneralConnector = BaseConnector &
Partial<SocialConnector & EmailConnector & SmsConnector>;
export type CreateConnector<
T extends SocialConnector | EmailConnector | SmsConnector | GeneralConnector
> = (options: { getConfig: GetConnectorConfig }) => Promise<T>;
export type EmailConnector = BaseConnector<ConnectorType.Email> & {
sendMessage: SendMessageFunction;
};
export type SendMessageFunction = (
data: { to: string; type: MessageTypes; payload: { code: string } },
config?: unknown
) => Promise<unknown>;
// MARK: Social connector
export type SocialConnector = BaseConnector<ConnectorType.Social> & {
getAuthorizationUri: GetAuthorizationUri;
getUserInfo: GetUserInfo;
};
export type GetAuthorizationUri = (payload: {
state: string;
redirectUri: string;
@ -103,5 +110,3 @@ export type GetAuthorizationUri = (payload: {
export type GetUserInfo = (
data: unknown
) => Promise<{ id: string } & Record<string, string | undefined>>;
export type GetConnectorConfig = (id: string) => Promise<unknown>;

View file

@ -17,7 +17,6 @@ export const scope = 'email,public_profile';
export const defaultMetadata: ConnectorMetadata = {
id: 'facebook-universal',
target: 'facebook',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
name: {
en: 'Facebook',

View file

@ -12,6 +12,7 @@ import {
GetUserInfo,
GetConnectorConfig,
validateConfig,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
@ -160,6 +161,7 @@ const authorizationCallbackHandler = async (parameterObject: unknown) => {
const createFacebookConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: facebookConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -8,7 +8,6 @@ export const userInfoEndpoint = 'https://api.github.com/user';
export const defaultMetadata: ConnectorMetadata = {
id: 'github-universal',
target: 'github',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
name: {
en: 'GitHub',

View file

@ -7,6 +7,7 @@ import {
CreateConnector,
GetConnectorConfig,
validateConfig,
ConnectorType,
} from '@logto/connector-core';
import { assert, conditional } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
@ -145,6 +146,7 @@ const getUserInfo =
const createGithubConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: githubConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -8,7 +8,6 @@ export const scope = 'openid profile email';
export const defaultMetadata: ConnectorMetadata = {
id: 'google-universal',
target: 'google',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
name: {
en: 'Google',

View file

@ -11,6 +11,7 @@ import {
validateConfig,
CreateConnector,
SocialConnector,
ConnectorType,
} from '@logto/connector-core';
import { conditional, assert } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
@ -143,6 +144,7 @@ const getUserInfoErrorHandler = (error: unknown) => {
const createGoogleConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: googleConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -1,4 +1,4 @@
import { ConnectorMetadata, ConnectorPlatform, ConnectorType } from '@logto/connector-core';
import { ConnectorMetadata, ConnectorPlatform } from '@logto/connector-core';
export const authorizationEndpoint = 'https://kauth.kakao.com/oauth/authorize';
export const accessTokenEndpoint = 'https://kauth.kakao.com/oauth/token';
@ -7,7 +7,6 @@ export const userInfoEndpoint = 'https://kapi.kakao.com/v2/user/me';
export const defaultMetadata: ConnectorMetadata = {
id: 'kakao-universal',
target: 'kakao',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
name: {
en: 'Kakao',

View file

@ -5,6 +5,7 @@
import {
ConnectorError,
ConnectorErrorCodes,
ConnectorType,
CreateConnector,
GetAuthorizationUri,
GetConnectorConfig,
@ -147,6 +148,7 @@ const getUserInfoErrorHandler = (error: unknown) => {
const createKakaoConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: kakaoConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -3,7 +3,6 @@ import { ConnectorType, ConnectorMetadata } from '@logto/connector-core';
export const defaultMetadata: ConnectorMetadata = {
id: 'mock-email-service',
target: 'mock-mail',
type: ConnectorType.Email,
platform: null,
name: {
en: 'Mock Mail Service',

View file

@ -9,6 +9,7 @@ import {
CreateConnector,
EmailConnector,
validateConfig,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
@ -43,6 +44,7 @@ const sendMessage =
const createMockEmailConnector: CreateConnector<EmailConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Email,
configGuard: mockMailConfigGuard,
sendMessage: sendMessage(getConfig),
};

View file

@ -3,7 +3,6 @@ import { ConnectorType, ConnectorMetadata } from '@logto/connector-core';
export const defaultMetadata: ConnectorMetadata = {
id: 'mock-short-message-service',
target: 'mock-sms',
type: ConnectorType.SMS,
platform: null,
name: {
en: 'Mock SMS Service',

View file

@ -9,6 +9,7 @@ import {
validateConfig,
CreateConnector,
SmsConnector,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
@ -43,6 +44,7 @@ const sendMessage =
const createMockSmsConnector: CreateConnector<SmsConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Sms,
configGuard: mockSmsConfigGuard,
sendMessage: sendMessage(getConfig),
};

View file

@ -3,7 +3,6 @@ import { ConnectorMetadata, ConnectorType, ConnectorPlatform } from '@logto/conn
export const defaultMetadata: ConnectorMetadata = {
id: 'mock-social-connector',
target: 'mock-social',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
name: {
en: 'Mock Social',

View file

@ -7,6 +7,7 @@ import {
GetUserInfo,
CreateConnector,
SocialConnector,
ConnectorType,
} from '@logto/connector-core';
import { z } from 'zod';
@ -36,6 +37,7 @@ const getUserInfo: GetUserInfo = async (data) => {
const createMockSocialConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: mockSocialConfigGuard,
getAuthorizationUri,
getUserInfo,

View file

@ -5,7 +5,6 @@ export const endpoint = 'https://api.sendgrid.com/v3/mail/send';
export const defaultMetadata: ConnectorMetadata = {
id: 'sendgrid-email-service',
target: 'sendgrid-mail',
type: ConnectorType.Email,
platform: null,
name: {
en: 'SendGrid Mail Service',

View file

@ -6,6 +6,7 @@ import {
validateConfig,
CreateConnector,
EmailConnector,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
@ -87,6 +88,7 @@ const sendMessage =
const createSendGridMailConnector: CreateConnector<EmailConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Email,
configGuard: sendGridMailConfigGuard,
sendMessage: sendMessage(getConfig),
};

View file

@ -3,7 +3,6 @@ import { ConnectorType, ConnectorMetadata } from '@logto/connector-core';
export const defaultMetadata: ConnectorMetadata = {
id: 'simple-mail-transfer-protocol',
target: 'smtp',
type: ConnectorType.Email,
platform: null,
name: {
en: 'SMTP',

View file

@ -6,6 +6,7 @@ import {
EmailConnector,
SendMessageFunction,
validateConfig,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import nodemailer from 'nodemailer';
@ -89,6 +90,7 @@ const parseContents = (contents: string, contentType: ContextType) => {
const createSmtpConnector: CreateConnector<EmailConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Email,
configGuard: smtpConfigGuard,
sendMessage: sendMessage(getConfig),
};

View file

@ -5,7 +5,6 @@ export const endpoint = 'https://api.twilio.com/2010-04-01/Accounts/{{accountSID
export const defaultMetadata: ConnectorMetadata = {
id: 'twilio-short-message-service',
target: 'twilio-sms',
type: ConnectorType.SMS,
platform: null,
name: {
en: 'Twilio SMS Service',

View file

@ -6,6 +6,7 @@ import {
validateConfig,
CreateConnector,
SmsConnector,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
@ -68,6 +69,7 @@ const sendMessage =
const createTwilioSmsConnector: CreateConnector<SmsConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Sms,
configGuard: twilioSmsConfigGuard,
sendMessage: sendMessage(getConfig),
};

View file

@ -13,7 +13,6 @@ export const invalidAccessTokenErrcode = [40_001, 40_014];
export const defaultMetadata: ConnectorMetadata = {
id: 'wechat-native',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Native,
name: {
en: 'WeChat',

View file

@ -12,6 +12,7 @@ import {
validateConfig,
CreateConnector,
SocialConnector,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
@ -167,6 +168,7 @@ const authorizationCallbackHandler = async (parameterObject: unknown) => {
const createWechatNativeConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: wechatNativeConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -13,7 +13,6 @@ export const invalidAccessTokenErrcode = [40_001, 40_014];
export const defaultMetadata: ConnectorMetadata = {
id: 'wechat-web',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
name: {
en: 'WeChat',

View file

@ -12,6 +12,7 @@ import {
validateConfig,
CreateConnector,
SocialConnector,
ConnectorType,
} from '@logto/connector-core';
import { assert } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
@ -168,6 +169,7 @@ const authorizationCallbackHandler = async (parameterObject: unknown) => {
const createWechatConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Social,
configGuard: wechatConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),

View file

@ -9,7 +9,7 @@ type TitlePlaceHolder = {
};
export const connectorTitlePlaceHolder: TitlePlaceHolder = Object.freeze({
[ConnectorType.SMS]: 'connectors.type.sms',
[ConnectorType.Sms]: 'connectors.type.sms',
[ConnectorType.Email]: 'connectors.type.email',
[ConnectorType.Social]: 'connectors.type.social',
});
@ -29,6 +29,6 @@ type ConnectorPlaceholderIcon = {
};
export const connectorPlaceholderIcon: ConnectorPlaceholderIcon = Object.freeze({
[ConnectorType.SMS]: SmsConnectorIcon,
[ConnectorType.Sms]: SmsConnectorIcon,
[ConnectorType.Email]: EmailConnector,
} as const);

View file

@ -14,7 +14,7 @@ const useConnectorInUse = (type?: ConnectorType, target?: string): boolean | und
return data.signInMethods.email !== SignInMethodState.Disabled;
}
if (type === ConnectorType.SMS) {
if (type === ConnectorType.Sms) {
return data.signInMethods.sms !== SignInMethodState.Disabled;
}

View file

@ -56,7 +56,7 @@ const ConnectorContent = ({ isDeleted, connectorData, onConnectorUpdated }: Prop
const { metadata, ...rest } = await api
.patch(`/api/connectors/${connectorData.id}`, { json: { config: result.data } })
.json<Connector & { metadata: ConnectorMetadata }>();
.json<Connector & { metadata: ConnectorMetadata; type: ConnectorType }>();
onConnectorUpdated({ ...rest, ...metadata });
reset({ configJson: JSON.stringify(result.data, null, 2) });

View file

@ -13,7 +13,7 @@ const ConnectorTypeName = ({ type }: Props) => {
return (
<div className={styles.connectorType}>
{type === ConnectorType.Email && t('connector_details.type_email')}
{type === ConnectorType.SMS && t('connector_details.type_sms')}
{type === ConnectorType.Sms && t('connector_details.type_sms')}
{type === ConnectorType.Social && t('connector_details.type_social')}
</div>
);

View file

@ -39,7 +39,7 @@ const SenderTester = ({ connectorId, connectorType, config, className }: Props)
} = useForm<FormData>();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useApi();
const isSms = connectorType === ConnectorType.SMS;
const isSms = connectorType === ConnectorType.Sms;
useEffect(() => {
if (!showTooltip) {

View file

@ -143,7 +143,7 @@ const ConnectorDetails = () => {
}}
>
{t(
data.type === ConnectorType.SMS
data.type === ConnectorType.Sms
? 'connector_details.options_change_sms'
: 'connector_details.options_change_email'
)}

View file

@ -46,7 +46,7 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
return 'connectors.setup_title.email';
}
if (type === ConnectorType.SMS) {
if (type === ConnectorType.Sms) {
return 'connectors.setup_title.sms';
}

View file

@ -37,7 +37,7 @@ const Guide = ({ connector, onClose }: Props) => {
const connectorName = result.success ? name[result.data] : name.en;
const isSocialConnector =
connectorType !== ConnectorType.SMS && connectorType !== ConnectorType.Email;
connectorType !== ConnectorType.Sms && connectorType !== ConnectorType.Email;
const methods = useForm<GuideForm>({ reValidateMode: 'onBlur' });
const {
control,

View file

@ -42,7 +42,7 @@ const Connectors = () => {
const smsConnector = useMemo(() => {
const smsConnectorGroup = data?.find(
({ enabled, type }) => enabled && type === ConnectorType.SMS
({ enabled, type }) => enabled && type === ConnectorType.Sms
);
return smsConnectorGroup?.connectors[0];
@ -119,9 +119,9 @@ const Connectors = () => {
{!isLoading && !isSocial && (
<ConnectorRow
connectors={smsConnector ? [smsConnector] : []}
type={ConnectorType.SMS}
type={ConnectorType.Sms}
onClickSetup={() => {
setCreateType(ConnectorType.SMS);
setCreateType(ConnectorType.Sms);
}}
/>
)}

View file

@ -19,8 +19,8 @@ const ConnectorSetupWarning = ({ method }: Props) => {
return;
}
if (method === SignInMethodKey.SMS) {
return ConnectorType.SMS;
if (method === SignInMethodKey.Sms) {
return ConnectorType.Sms;
}
if (method === SignInMethodKey.Email) {

View file

@ -55,7 +55,7 @@ const SignInMethodsForm = () => {
const enabled =
(method === SignInMethodKey.Email && email) ||
(method === SignInMethodKey.SMS && sms) ||
(method === SignInMethodKey.Sms && sms) ||
(method === SignInMethodKey.Social && social);
return (

View file

@ -51,7 +51,7 @@ export const signInExperienceParser = {
primary: primaryMethod,
enableSecondary: secondaryMethods.length > 0,
username: secondaryMethods.includes(SignInMethodKey.Username),
sms: secondaryMethods.includes(SignInMethodKey.SMS),
sms: secondaryMethods.includes(SignInMethodKey.Sms),
email: secondaryMethods.includes(SignInMethodKey.Email),
social: secondaryMethods.includes(SignInMethodKey.Social),
},

View file

@ -1,6 +1,6 @@
{
"name": "@logto/core",
"version": "1.0.0-beta.5",
"version": "1.0.0-beta.6",
"description": "The open source identity solution.",
"main": "build/index.js",
"author": "Silverhand Inc. <contact@silverhand.io>",

View file

@ -7,7 +7,6 @@ import { LogtoConnector } from '@/connectors/types';
export const mockMetadata: ConnectorMetadata = {
id: 'id',
target: 'connector',
type: ConnectorType.Email,
platform: null,
name: {
en: 'Connector',
@ -34,7 +33,7 @@ export const mockConnector: Connector = {
createdAt: 1_234_567_890_123,
};
export const mockLogtoConnector: Omit<LogtoConnector, 'metadata' | 'dbEntry'> = {
export const mockLogtoConnector = {
getAuthorizationUri: jest.fn(),
getUserInfo: jest.fn(),
sendMessage: jest.fn(),
@ -46,7 +45,6 @@ const mockMetadata0: ConnectorMetadata = {
...mockMetadata,
id: 'id0',
target: 'connector_0',
type: ConnectorType.Email,
platform: ConnectorPlatform.Universal,
};
@ -54,7 +52,6 @@ const mockMetadata1: ConnectorMetadata = {
...mockMetadata,
id: 'id1',
target: 'connector_1',
type: ConnectorType.SMS,
platform: ConnectorPlatform.Universal,
};
@ -62,7 +59,6 @@ const mockMetadata2: ConnectorMetadata = {
...mockMetadata,
id: 'id2',
target: 'connector_2',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
};
@ -70,7 +66,6 @@ const mockMetadata3: ConnectorMetadata = {
...mockMetadata,
id: 'id3',
target: 'connector_3',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
};
@ -78,7 +73,6 @@ const mockMetadata4: ConnectorMetadata = {
...mockMetadata,
id: 'id4',
target: 'connector_4',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
};
@ -86,7 +80,6 @@ const mockMetadata5: ConnectorMetadata = {
...mockMetadata,
id: 'id5',
target: 'connector_5',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
};
@ -94,7 +87,6 @@ const mockMetadata6: ConnectorMetadata = {
...mockMetadata,
id: 'id6',
target: 'connector_6',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
};
@ -160,37 +152,44 @@ export const mockConnectorList: Connector[] = [
export const mockLogtoConnectorList: LogtoConnector[] = [
{
dbEntry: mockConnector0,
metadata: { ...mockMetadata0, type: ConnectorType.Social },
metadata: { ...mockMetadata0 },
type: ConnectorType.Social,
...mockLogtoConnector,
},
{
dbEntry: mockConnector1,
metadata: mockMetadata1,
type: ConnectorType.Sms,
...mockLogtoConnector,
},
{
dbEntry: mockConnector2,
metadata: mockMetadata2,
type: ConnectorType.Social,
...mockLogtoConnector,
},
{
dbEntry: mockConnector3,
metadata: mockMetadata3,
type: ConnectorType.Social,
...mockLogtoConnector,
},
{
dbEntry: mockConnector4,
metadata: { ...mockMetadata4, type: ConnectorType.Email, platform: null },
metadata: { ...mockMetadata4, platform: null },
type: ConnectorType.Email,
...mockLogtoConnector,
},
{
dbEntry: mockConnector5,
metadata: { ...mockMetadata5, type: ConnectorType.SMS, platform: null },
metadata: { ...mockMetadata5, platform: null },
type: ConnectorType.Sms,
...mockLogtoConnector,
},
{
dbEntry: mockConnector6,
metadata: { ...mockMetadata6, type: ConnectorType.Email, platform: null },
metadata: { ...mockMetadata6, platform: null },
type: ConnectorType.Email,
...mockLogtoConnector,
},
];
@ -204,9 +203,9 @@ export const mockAliyunDmConnector: LogtoConnector = {
...mockMetadata,
id: 'aliyun-dm',
target: 'aliyun-dm',
type: ConnectorType.Email,
platform: null,
},
type: ConnectorType.Email,
...mockLogtoConnector,
};
@ -219,9 +218,9 @@ export const mockAliyunSmsConnector: LogtoConnector = {
...mockMetadata,
id: 'aliyun-sms',
target: 'aliyun-sms',
type: ConnectorType.SMS,
platform: null,
},
type: ConnectorType.Sms,
...mockLogtoConnector,
};
@ -234,9 +233,9 @@ export const mockFacebookConnector: LogtoConnector = {
...mockMetadata,
id: 'facebook',
target: 'facebook',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
},
type: ConnectorType.Social,
...mockLogtoConnector,
};
@ -249,9 +248,9 @@ export const mockGithubConnector: LogtoConnector = {
...mockMetadata,
id: 'github',
target: 'github',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
},
type: ConnectorType.Social,
...mockLogtoConnector,
};
@ -264,9 +263,9 @@ export const mockWechatConnector: LogtoConnector = {
...mockMetadata,
id: 'wechat-web',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
},
type: ConnectorType.Social,
...mockLogtoConnector,
};
@ -279,9 +278,9 @@ export const mockWechatNativeConnector: LogtoConnector = {
...mockMetadata,
id: 'wechat-native',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Native,
},
type: ConnectorType.Social,
...mockLogtoConnector,
};
@ -295,9 +294,9 @@ export const mockGoogleConnector: LogtoConnector = {
...mockMetadata,
id: 'google',
target: 'google',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
},
type: ConnectorType.Social,
...mockLogtoConnector,
};

View file

@ -1,6 +1,4 @@
import { ConnectorError, ConnectorErrorCodes, GeneralConnector } from '@logto/connector-core';
import { LogtoConnector } from './types';
import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-core';
export const defaultConnectorPackages = [
'@logto/connector-alipay-web',
@ -24,10 +22,9 @@ const notImplemented = () => {
throw new ConnectorError(ConnectorErrorCodes.NotImplemented);
};
export const defaultConnectorMethods: Omit<LogtoConnector, 'metadata' | 'configGuard' | 'dbEntry'> =
{
getAuthorizationUri: notImplemented,
getUserInfo: notImplemented,
sendMessage: notImplemented,
validateConfig: notImplemented,
};
export const defaultConnectorMethods = {
getAuthorizationUri: notImplemented,
getUserInfo: notImplemented,
sendMessage: notImplemented,
validateConfig: notImplemented,
};

View file

@ -1,7 +1,7 @@
import { existsSync, readFileSync } from 'fs';
import path from 'path';
import { CreateConnector, GeneralConnector, validateConfig } from '@logto/connector-core';
import { AllConnector, CreateConnector, validateConfig } from '@logto/connector-core';
import resolvePackagePath from 'resolve-package-path';
import envSet from '@/env-set';
@ -9,11 +9,11 @@ import RequestError from '@/errors/RequestError';
import { findAllConnectors, insertConnector } from '@/queries/connector';
import { defaultConnectorMethods, defaultConnectorPackages } from './consts';
import { LogtoConnector } from './types';
import { LoadConnector, LogtoConnector } from './types';
import { getConnectorConfig, validateConnectorModule } from './utilities';
// eslint-disable-next-line @silverhand/fp/no-let
let cachedConnectors: Array<Omit<LogtoConnector, 'dbEntry'>> | undefined;
let cachedConnectors: LoadConnector[] | undefined;
const loadConnectors = async () => {
if (cachedConnectors) {
@ -31,12 +31,12 @@ const loadConnectors = async () => {
connectorPackages.map(async (packageName) => {
// eslint-disable-next-line no-restricted-syntax
const { default: createConnector } = (await import(packageName)) as {
default: CreateConnector<GeneralConnector>;
default: CreateConnector<AllConnector>;
};
const rawConnector = await createConnector({ getConfig: getConnectorConfig });
validateConnectorModule(rawConnector);
const connector: Omit<LogtoConnector, 'dbEntry'> = {
const connector: LoadConnector = {
...defaultConnectorMethods,
...rawConnector,
validateConfig: (config: unknown) => {

View file

@ -1,4 +1,4 @@
import { GeneralConnector } from '@logto/connector-core';
import { AllConnector } from '@logto/connector-core';
import { Connector, PasscodeType } from '@logto/schemas';
import { z } from 'zod';
@ -16,7 +16,16 @@ export const socialUserInfoGuard = z.object({
export type SocialUserInfo = z.infer<typeof socialUserInfoGuard>;
export type LogtoConnector = Required<GeneralConnector> & {
dbEntry: Connector;
/**
* Dynamic loaded connector type.
*/
export type LoadConnector<T extends AllConnector = AllConnector> = T & {
validateConfig: (config: unknown) => void;
};
/**
* The connector type with full context.
*/
export type LogtoConnector<T extends AllConnector = AllConnector> = LoadConnector<T> & {
dbEntry: Connector;
};

View file

@ -1,4 +1,9 @@
import { ConnectorError, ConnectorErrorCodes, GeneralConnector } from '@logto/connector-core';
import {
BaseConnector,
ConnectorError,
ConnectorErrorCodes,
ConnectorType,
} from '@logto/connector-core';
import RequestError from '@/errors/RequestError';
import { findAllConnectors } from '@/queries/connector';
@ -14,8 +19,8 @@ export const getConnectorConfig = async (id: string): Promise<unknown> => {
};
export function validateConnectorModule(
connector: Partial<GeneralConnector>
): asserts connector is GeneralConnector {
connector: Partial<BaseConnector<ConnectorType>>
): asserts connector is BaseConnector<ConnectorType> {
if (!connector.metadata) {
throw new ConnectorError(ConnectorErrorCodes.InvalidMetadata);
}
@ -23,4 +28,8 @@ export function validateConnectorModule(
if (!connector.configGuard) {
throw new ConnectorError(ConnectorErrorCodes.InvalidConfigGuard);
}
if (!connector.type || !Object.values(ConnectorType).includes(connector.type)) {
throw new ConnectorError(ConnectorErrorCodes.UnexpectedType);
}
}

View file

@ -135,9 +135,10 @@ describe('sendPasscode', () => {
},
metadata: {
...mockMetadata,
type: ConnectorType.Email,
platform: null,
},
type: ConnectorType.Email,
sendMessage: jest.fn(),
configGuard: any(),
},
]);
@ -155,7 +156,7 @@ describe('sendPasscode', () => {
await expect(sendPasscode(passcode)).rejects.toThrowError(
new RequestError({
code: 'connector.not_found',
type: ConnectorType.SMS,
type: ConnectorType.Sms,
})
);
});
@ -172,9 +173,9 @@ describe('sendPasscode', () => {
},
metadata: {
...mockMetadata,
type: ConnectorType.SMS,
platform: null,
},
type: ConnectorType.Sms,
sendMessage,
},
{
@ -186,9 +187,9 @@ describe('sendPasscode', () => {
},
metadata: {
...mockMetadata,
type: ConnectorType.Email,
platform: null,
},
type: ConnectorType.Email,
sendMessage,
},
]);

View file

@ -1,9 +1,15 @@
import { messageTypesGuard, ConnectorError, ConnectorErrorCodes } from '@logto/connector-core';
import {
messageTypesGuard,
ConnectorError,
ConnectorErrorCodes,
EmailConnector,
SmsConnector,
} from '@logto/connector-core';
import { Passcode, PasscodeType } from '@logto/schemas';
import { customAlphabet, nanoid } from 'nanoid';
import { getLogtoConnectors } from '@/connectors';
import { ConnectorType } from '@/connectors/types';
import { ConnectorType, LogtoConnector } from '@/connectors/types';
import RequestError from '@/errors/RequestError';
import {
consumePasscode,
@ -46,22 +52,19 @@ export const sendPasscode = async (passcode: Passcode) => {
throw new RequestError('passcode.phone_email_empty');
}
const expectType = passcode.phone ? ConnectorType.Sms : ConnectorType.Email;
const connectors = await getLogtoConnectors();
const emailConnector = connectors.find(
(connector) => connector.dbEntry.enabled && connector.metadata.type === ConnectorType.Email
const connector = connectors.find(
(connector): connector is LogtoConnector<SmsConnector | EmailConnector> =>
connector.dbEntry.enabled && connector.type === expectType
);
const smsConnector = connectors.find(
(connector) => connector.dbEntry.enabled && connector.metadata.type === ConnectorType.SMS
);
const connector = passcode.email ? emailConnector : smsConnector;
assertThat(
connector,
new RequestError({
code: 'connector.not_found',
type: passcode.email ? ConnectorType.Email : ConnectorType.SMS,
type: expectType,
})
);

View file

@ -132,7 +132,7 @@ describe('validate sign-in methods', () => {
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.SMS,
type: ConnectorType.Sms,
})
);
});

View file

@ -41,7 +41,7 @@ export const validateSignInMethods = (
if (isEnabled(signInMethods.email)) {
assertThat(
enabledConnectors.some((item) => item.metadata.type === ConnectorType.Email),
enabledConnectors.some((item) => item.type === ConnectorType.Email),
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.Email,
@ -51,17 +51,17 @@ export const validateSignInMethods = (
if (isEnabled(signInMethods.sms)) {
assertThat(
enabledConnectors.some((item) => item.metadata.type === ConnectorType.SMS),
enabledConnectors.some((item) => item.type === ConnectorType.Sms),
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.SMS,
type: ConnectorType.Sms,
})
);
}
if (isEnabled(signInMethods.social)) {
assertThat(
enabledConnectors.some((item) => item.metadata.type === ConnectorType.Social),
enabledConnectors.some((item) => item.type === ConnectorType.Social),
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.Social,

View file

@ -1,4 +1,4 @@
import { User } from '@logto/schemas';
import { ConnectorType, User } from '@logto/schemas';
import { Nullable } from '@silverhand/essentials';
import { InteractionResults } from 'oidc-provider';
import { z } from 'zod';
@ -41,6 +41,15 @@ export const getUserInfoByAuthCode = async (
): Promise<SocialUserInfo> => {
const connector = await getConnector(connectorId);
assertThat(
connector.type === ConnectorType.Social,
new RequestError({
code: 'session.invalid_connector_id',
status: 422,
connectorId,
})
);
return connector.getUserInfo(data);
};

View file

@ -1,4 +1,4 @@
import { MessageTypes } from '@logto/connector-core';
import { EmailConnector, MessageTypes, SmsConnector } from '@logto/connector-core';
import { Connector, ConnectorType } from '@logto/schemas';
import { any } from 'zod';
@ -49,9 +49,7 @@ describe('connector route', () => {
it('throws if more than one SMS connector is enabled', async () => {
getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(
mockLogtoConnectorList.filter(
(connector) => connector.metadata.type !== ConnectorType.Email
)
mockLogtoConnectorList.filter((connector) => connector.type !== ConnectorType.Email)
);
const response = await connectorRequest.get('/connectors').send({});
expect(response).toHaveProperty('statusCode', 400);
@ -59,9 +57,7 @@ describe('connector route', () => {
it('shows all connectors', async () => {
getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(
mockLogtoConnectorList.filter(
(connector) => connector.metadata.type === ConnectorType.Social
)
mockLogtoConnectorList.filter((connector) => connector.type === ConnectorType.Social)
);
const response = await connectorRequest.get('/connectors').send({});
expect(response).toHaveProperty('statusCode', 200);
@ -100,12 +96,12 @@ describe('connector route', () => {
it('should get SMS connector and send test message', async () => {
const mockedMetadata = {
...mockMetadata,
type: ConnectorType.SMS,
};
const sendMessage = jest.fn();
const mockedSmsConnector: LogtoConnector = {
const mockedSmsConnector: LogtoConnector<SmsConnector> = {
dbEntry: mockConnector,
metadata: mockedMetadata,
type: ConnectorType.Sms,
configGuard: any(),
...defaultConnectorMethods,
sendMessage,
@ -130,9 +126,10 @@ describe('connector route', () => {
it('should get email connector and send test message', async () => {
const sendMessage = jest.fn();
const mockedEmailConnector: LogtoConnector = {
const mockedEmailConnector: LogtoConnector<EmailConnector> = {
dbEntry: mockConnector,
metadata: mockMetadata,
type: ConnectorType.Email,
configGuard: any(),
...defaultConnectorMethods,
sendMessage,

View file

@ -12,9 +12,10 @@ import assertThat from '@/utils/assert-that';
import { AuthedRouter } from './types';
const transpileLogtoConnector = ({ dbEntry, metadata }: LogtoConnector): ConnectorDto => ({
...dbEntry,
const transpileLogtoConnector = ({ dbEntry, metadata, type }: LogtoConnector): ConnectorDto => ({
type,
...metadata,
...dbEntry,
});
export default function connectorRoutes<T extends AuthedRouter>(router: T) {
@ -31,14 +32,13 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
assertThat(
connectors.filter(
(connector) =>
connector.dbEntry.enabled && connector.metadata.type === ConnectorType.Email
(connector) => connector.dbEntry.enabled && connector.type === ConnectorType.Email
).length <= 1,
'connector.more_than_one_email'
);
assertThat(
connectors.filter(
(connector) => connector.dbEntry.enabled && connector.metadata.type === ConnectorType.SMS
(connector) => connector.dbEntry.enabled && connector.type === ConnectorType.Sms
).length <= 1,
'connector.more_than_one_sms'
);
@ -80,6 +80,7 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
} = ctx.guard;
const {
type,
dbEntry: { config },
metadata,
validateConfig,
@ -91,15 +92,12 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
// Only allow one enabled connector for SMS and Email.
// disable other connectors before enable this one.
if (
enabled &&
(metadata.type === ConnectorType.SMS || metadata.type === ConnectorType.Email)
) {
if (enabled && (type === ConnectorType.Sms || type === ConnectorType.Email)) {
const connectors = await getLogtoConnectors();
await Promise.all(
connectors
.filter(
({ dbEntry: { enabled }, metadata: { type } }) => type === metadata.type && enabled
({ dbEntry: { enabled }, type: currentType }) => type === currentType && enabled
)
.map(async ({ dbEntry: { id } }) =>
updateConnector({ set: { enabled: false }, where: { id }, jsonbMode: 'merge' })
@ -112,7 +110,7 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
where: { id },
jsonbMode: 'merge',
});
ctx.body = { ...connector, metadata };
ctx.body = { ...connector, metadata, type };
return next();
}
@ -130,14 +128,14 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
body,
} = ctx.guard;
const { metadata, validateConfig } = await getLogtoConnectorById(id);
const { metadata, type, validateConfig } = await getLogtoConnectorById(id);
if (body.config) {
validateConfig(body.config);
}
const connector = await updateConnector({ set: body, where: { id }, jsonbMode: 'replace' });
ctx.body = { ...connector, metadata };
ctx.body = { ...connector, metadata, type };
return next();
}
@ -164,21 +162,17 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
const subject = phone ?? email;
assertThat(subject, new RequestError({ code: 'guard.invalid_input' }));
const connector = phone
? logtoConnectors.find(
({ metadata: { id: id_, type } }) => id_ === id && type === ConnectorType.SMS
)
: logtoConnectors.find(
({ metadata: { id: id_, type } }) => id_ === id && type === ConnectorType.Email
);
const connector = logtoConnectors.find(({ metadata: { id: currentId } }) => currentId === id);
const expectType = phone ? ConnectorType.Sms : ConnectorType.Email;
assertThat(
connector,
new RequestError({
code: 'connector.not_found',
type: phone ? ConnectorType.SMS : ConnectorType.Email,
type: expectType,
})
);
assertThat(connector.type === expectType, 'connector.unexpected_type');
const { sendMessage } = connector;

View file

@ -75,7 +75,8 @@ describe('connector PATCH routes', () => {
getLogtoConnectorsPlaceholder.mockResolvedValueOnce([
{
dbEntry: mockConnector,
metadata: { ...mockMetadata, type: ConnectorType.Social },
metadata: mockMetadata,
type: ConnectorType.Social,
...mockLogtoConnector,
},
]);
@ -90,7 +91,8 @@ describe('connector PATCH routes', () => {
})
);
expect(response.body).toMatchObject({
metadata: { ...mockMetadata, type: ConnectorType.Social },
metadata: mockMetadata,
type: ConnectorType.Social,
});
expect(response).toHaveProperty('statusCode', 200);
});
@ -100,6 +102,7 @@ describe('connector PATCH routes', () => {
{
dbEntry: mockConnector,
metadata: mockMetadata,
type: ConnectorType.Social,
...mockLogtoConnector,
validateConfig: () => {
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig);
@ -117,6 +120,7 @@ describe('connector PATCH routes', () => {
{
dbEntry: mockConnector,
metadata: mockMetadata,
type: ConnectorType.Social,
...mockLogtoConnector,
},
]);
@ -141,7 +145,6 @@ describe('connector PATCH routes', () => {
const mockedMetadata = {
...mockMetadata,
id: 'id1',
type: ConnectorType.SMS,
};
const mockedConnector = {
...mockConnector,
@ -150,6 +153,7 @@ describe('connector PATCH routes', () => {
getLogtoConnectorByIdPlaceholder.mockResolvedValueOnce({
dbEntry: mockedConnector,
metadata: mockedMetadata,
type: ConnectorType.Sms,
...mockLogtoConnector,
});
const response = await connectorRequest
@ -189,10 +193,8 @@ describe('connector PATCH routes', () => {
getLogtoConnectorsPlaceholder.mockResolvedValueOnce([
{
dbEntry: mockConnector,
metadata: {
...mockMetadata,
type: ConnectorType.SMS,
},
metadata: mockMetadata,
type: ConnectorType.Sms,
...mockLogtoConnector,
validateConfig: () => {
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig);
@ -210,6 +212,7 @@ describe('connector PATCH routes', () => {
{
dbEntry: mockConnector,
metadata: mockMetadata,
type: ConnectorType.Sms,
...mockLogtoConnector,
},
]);
@ -252,6 +255,7 @@ describe('connector PATCH routes', () => {
{
dbEntry: mockConnector,
metadata: mockMetadata,
type: ConnectorType.Sms,
...mockLogtoConnector,
validateConfig: () => {
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig);
@ -269,6 +273,7 @@ describe('connector PATCH routes', () => {
{
dbEntry: mockConnector,
metadata: mockMetadata,
type: ConnectorType.Sms,
...mockLogtoConnector,
},
]);

View file

@ -61,10 +61,14 @@ const getLogtoConnectorByIdHelper = jest.fn(async (connectorId: string) => {
: connectorId === 'social_disabled'
? 'social_disabled'
: 'others',
type: connectorId.startsWith('social') ? ConnectorType.Social : ConnectorType.SMS,
};
return { dbEntry: database, metadata, getAuthorizationUri: jest.fn(async () => '') };
return {
dbEntry: database,
metadata,
type: connectorId.startsWith('social') ? ConnectorType.Social : ConnectorType.Sms,
getAuthorizationUri: jest.fn(async () => ''),
};
});
jest.mock('@/connectors', () => ({
@ -72,7 +76,7 @@ jest.mock('@/connectors', () => ({
getLogtoConnectorById: jest.fn(async (connectorId: string) => {
const connector = await getLogtoConnectorByIdHelper(connectorId);
if (connector.metadata.type !== ConnectorType.Social) {
if (connector.type !== ConnectorType.Social) {
throw new RequestError({
code: 'entity.not_found',
status: 404,

View file

@ -1,4 +1,4 @@
import { userInfoSelectFields } from '@logto/schemas';
import { ConnectorType, userInfoSelectFields } from '@logto/schemas';
import { redirectUriRegEx } from '@logto/shared';
import pick from 'lodash.pick';
import { Provider } from 'oidc-provider';
@ -45,6 +45,7 @@ export default function socialRoutes<T extends AnonymousRouter>(router: T, provi
assertThat(state && redirectUri, 'session.insufficient_info');
const connector = await getLogtoConnectorById(connectorId);
assertThat(connector.dbEntry.enabled, 'connector.not_enabled');
assertThat(connector.type === ConnectorType.Social, 'connector.unexpected_type');
const redirectTo = await connector.getAuthorizationUri({ state, redirectUri });
ctx.body = { redirectTo };

View file

@ -50,7 +50,7 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
const filteredSocialSignInConnectorTargets = socialSignInConnectorTargets?.filter((target) =>
enabledConnectors.some(
(connector) =>
connector.metadata.target === target && connector.metadata.type === ConnectorType.Social
connector.metadata.target === target && connector.type === ConnectorType.Social
)
);

View file

@ -67,7 +67,7 @@ test('connector set-up flow', async () => {
*/
await Promise.all(
[
{ id: mockSmsConnectorId, config: mockSmsConnectorConfig, type: ConnectorType.SMS },
{ id: mockSmsConnectorId, config: mockSmsConnectorConfig, type: ConnectorType.Sms },
{ id: mockEmailConnectorId, config: mockEmailConnectorConfig, type: ConnectorType.Email },
].map(async ({ id, config, type }) => {
const updatedConnector = await updateConnectorConfig(id, config);

View file

@ -64,6 +64,7 @@ const errors = {
not_enabled: 'The connector is not enabled.',
invalid_metadata: "The connector's metadata is invalid.",
invalid_config_guard: "The connector's config guard is invalid.",
unexpected_type: "The connector's type is unexpected.",
insufficient_request_parameters: 'The request might miss some input parameters.',
invalid_config: "The connector's config is invalid.",
invalid_response: "The connector's response is invalid.",

View file

@ -69,6 +69,7 @@ const errors = {
not_enabled: "Le connecteur n'est pas activé.",
invalid_metadata: "The connector's metadata is invalid.", // UNTRANSLATED
invalid_config_guard: "The connector's config guard is invalid.", // UNTRANSLATED
unexpected_type: "The connector's type is unexpected.", // UNTRANSLATED
insufficient_request_parameters: 'Certains paramètres peuvent manquer dans la requête.',
invalid_config: "La configuration du connecteur n'est pas valide.",
invalid_response: "La réponse du connecteur n'est pas valide.",

View file

@ -63,6 +63,7 @@ const errors = {
not_enabled: '연동이 활성화 되지 않았어요.',
invalid_metadata: "The connector's metadata is invalid.", // UNTRANSLATED
invalid_config_guard: "The connector's config guard is invalid.", // UNTRANSLATED
unexpected_type: "The connector's type is unexpected.", // UNTRANSLATED
insufficient_request_parameters: '요청 데이터에서 일부 정보가 없어요.',
invalid_config: '연동 설정이 유효하지 않아요.',
invalid_response: '연동 응답이 유효하지 않아요.',

View file

@ -65,6 +65,7 @@ const errors = {
not_enabled: 'Bağlayıcı etkin değil.',
invalid_metadata: "The connector's metadata is invalid.", // UNTRANSLATED
invalid_config_guard: "The connector's config guard is invalid.", // UNTRANSLATED
unexpected_type: "The connector's type is unexpected.", // UNTRANSLATED
insufficient_request_parameters: 'İstek, bazı input parametrelerini atlayabilir.',
invalid_config: 'Bağlayıcının ayarları geçersiz.',
invalid_response: 'Bağlayıcının yanıtı geçersiz.',

View file

@ -63,6 +63,7 @@ const errors = {
not_enabled: '连接器尚未启用',
invalid_metadata: '连接器 metadata 参数错误',
invalid_config_guard: '连接器配置 guard 错误',
unexpected_type: '连接器类型错误',
insufficient_request_parameters: '请求参数缺失',
invalid_config: '连接器配置错误',
invalid_response: '连接器错误响应',

View file

@ -111,7 +111,7 @@ export type LanguageInfo = z.infer<typeof languageInfoGuard>;
export enum SignInMethodKey {
Username = 'username',
Email = 'email',
SMS = 'sms',
Sms = 'sms',
Social = 'social',
}
@ -124,7 +124,7 @@ export enum SignInMethodState {
export const signInMethodsGuard = z.object({
[SignInMethodKey.Username]: z.nativeEnum(SignInMethodState),
[SignInMethodKey.Email]: z.nativeEnum(SignInMethodState),
[SignInMethodKey.SMS]: z.nativeEnum(SignInMethodState),
[SignInMethodKey.Sms]: z.nativeEnum(SignInMethodState),
[SignInMethodKey.Social]: z.nativeEnum(SignInMethodState),
});

View file

@ -1,8 +1,10 @@
import { ConnectorMetadata } from '@logto/connector-core';
import { BaseConnector, ConnectorMetadata, ConnectorType } from '@logto/connector-core';
import { Connector } from '../db-entries';
export type { ConnectorMetadata } from '@logto/connector-core';
export { ConnectorType, ConnectorPlatform } from '@logto/connector-core';
export type ConnectorDto = Connector & ConnectorMetadata;
export type ConnectorDto = Connector &
Omit<BaseConnector<ConnectorType>, 'configGuard' | 'metadata'> &
ConnectorMetadata;

253
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff