diff --git a/packages/core/src/routes/connector.ts b/packages/core/src/routes/connector.ts index 81c4edd83..e16f091fe 100644 --- a/packages/core/src/routes/connector.ts +++ b/packages/core/src/routes/connector.ts @@ -1,10 +1,11 @@ -import { ConnectorDTO, Connectors } from '@logto/schemas'; +import { ConnectorDTO, Connectors, ConnectorType } from '@logto/schemas'; import { object, string } from 'zod'; import { getConnectorInstances, getConnectorInstanceById } from '@/connectors'; import { ConnectorInstance } from '@/connectors/types'; import koaGuard from '@/middleware/koa-guard'; import { updateConnector } from '@/queries/connector'; +import assertThat from '@/utils/assert-that'; import { AuthedRouter } from './types'; @@ -16,6 +17,21 @@ const transpileConnectorInstance = ({ connector, metadata }: ConnectorInstance): export default function connectorRoutes(router: T) { router.get('/connectors', async (ctx, next) => { const connectorInstances = await getConnectorInstances(); + + assertThat( + connectorInstances.filter( + (connector) => + connector.connector.enabled && connector.metadata.type === ConnectorType.Email + ).length <= 1, + 'connector.more_than_one_email' + ); + assertThat( + connectorInstances.filter( + (connector) => connector.connector.enabled && connector.metadata.type === ConnectorType.SMS + ).length <= 1, + 'connector.more_than_one_sms' + ); + ctx.body = connectorInstances.map((connectorInstance) => { return transpileConnectorInstance(connectorInstance); }); @@ -49,6 +65,26 @@ export default function connectorRoutes(router: T) { body: { enabled }, } = ctx.guard; const { metadata } = await getConnectorInstanceById(id); + + // 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) + ) { + const connectors = await getConnectorInstances(); + await Promise.all( + connectors + .filter( + (connector) => + connector.metadata.type === metadata.type && connector.connector.enabled + ) + .map(async ({ connector: { id } }) => + updateConnector({ set: { enabled: false }, where: { id } }) + ) + ); + } + const connector = await updateConnector({ set: { enabled }, where: { id } }); ctx.body = { ...connector, metadata }; diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index 98316b6c1..5b2f05d53 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -163,6 +163,8 @@ const errors = { template_not_found: 'Unable to find correct template in connector config.', access_token_invalid: "Connector's access token is invalid.", oauth_code_invalid: 'Unable to get access token, please check authorization code.', + more_than_one_sms: 'The number of SMS connectors is larger then 1.', + more_than_one_email: 'The number of Email connectors is larger then 1.', }, passcode: { phone_email_empty: 'Both phone and email are empty.', diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index 0e4fd888e..d8b52b760 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -163,6 +163,8 @@ const errors = { template_not_found: '无法从连接器配置中找到对应的模板。', access_token_invalid: '当前连接器的 access_token 无效。', oauth_code_invalid: '无法获取 access_token,请检查授权 code 是否有效。', + more_than_one_sms: '同时存在超过 1 个短信连接器。', + more_than_one_email: '同时存在超过 1 个邮件连接器。', }, passcode: { phone_email_empty: '手机号与邮箱地址均为空。',