diff --git a/packages/connectors/connector-logto-email/src/constant.ts b/packages/connectors/connector-logto-email/src/constant.ts index 7591d7e05..c178e4451 100644 --- a/packages/connectors/connector-logto-email/src/constant.ts +++ b/packages/connectors/connector-logto-email/src/constant.ts @@ -95,6 +95,11 @@ export const defaultMetadata: ConnectorMetadata = { type: ConnectorConfigFormItemType.Text, }, ], + /** + * This is the email address that will be used as the sender of the email, should align with + * the `fromEmail` of emailServiceProvider stored in cloud.systems table. + */ + fromEmail: 'no-reply@logto.email', }; export const scope = ['send:email']; diff --git a/packages/connectors/connector-logto-email/src/index.ts b/packages/connectors/connector-logto-email/src/index.ts index c95852ac0..b68144331 100644 --- a/packages/connectors/connector-logto-email/src/index.ts +++ b/packages/connectors/connector-logto-email/src/index.ts @@ -20,8 +20,6 @@ import { grantAccessToken } from './grant-access-token.js'; import type { LogtoEmailConfig } from './types.js'; import { logtoEmailConfigGuard } from './types.js'; -export type { EmailServiceBasicConfig } from './types.js'; - const sendMessage = (getConfig: GetConnectorConfig): SendMessageFunction => async (data, inputConfig) => { diff --git a/packages/core/src/routes/connector/index.ts b/packages/core/src/routes/connector/index.ts index 68f1e152b..5c846b130 100644 --- a/packages/core/src/routes/connector/index.ts +++ b/packages/core/src/routes/connector/index.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ import { buildRawConnector } from '@logto/cli/lib/connector/index.js'; import { demoConnectorIds, validateConfig } from '@logto/connector-kit'; import { @@ -13,13 +12,12 @@ import { string, object } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; import koaGuard from '#src/middleware/koa-guard.js'; -import SystemContext from '#src/tenants/SystemContext.js'; import assertThat from '#src/utils/assert-that.js'; +import { buildExtraInfo } from '#src/utils/connectors/extra-information.js'; import { loadConnectorFactories, transpileConnectorFactory, transpileLogtoConnector, - buildExtraInfoFromEmailServiceData, } from '#src/utils/connectors/index.js'; import { checkSocialConnectorTargetAndPlatformUniqueness } from '#src/utils/connectors/platform.js'; @@ -46,15 +44,6 @@ export default function connectorRoutes( signInExperiences: { removeUnavailableSocialConnectorTargets }, } = tenant.libraries; - // Will accept other source of `extraInfo` in the future. - const { emailServiceProviderConfig } = SystemContext.shared; - const buildExtraInfo = (connectorFactoryId: string) => { - const extraInfo = { - ...buildExtraInfoFromEmailServiceData(connectorFactoryId, emailServiceProviderConfig), - }; - return cleanDeep(extraInfo, { emptyObjects: false }); - }; - router.post( '/connectors', koaGuard({ @@ -164,7 +153,7 @@ export default function connectorRoutes( } const connector = await getLogtoConnectorById(insertConnectorId); - ctx.body = await transpileLogtoConnector(connector, buildExtraInfo(connector.metadata.id)); + ctx.body = await transpileLogtoConnector(connector, buildExtraInfo(connector.metadata)); return next(); } @@ -200,7 +189,7 @@ export default function connectorRoutes( ctx.body = await Promise.all( filteredConnectors.map(async (connector) => - transpileLogtoConnector(connector, buildExtraInfo(connector.metadata.id)) + transpileLogtoConnector(connector, buildExtraInfo(connector.metadata)) ) ); @@ -224,7 +213,7 @@ export default function connectorRoutes( // Hide demo connector assertThat(!demoConnectorIds.includes(connector.metadata.id), 'connector.not_found'); - ctx.body = await transpileLogtoConnector(connector, buildExtraInfo(connector.metadata.id)); + ctx.body = await transpileLogtoConnector(connector, buildExtraInfo(connector.metadata)); return next(); } @@ -324,7 +313,7 @@ export default function connectorRoutes( jsonbMode: 'replace', }); const connector = await getLogtoConnectorById(id); - ctx.body = await transpileLogtoConnector(connector, buildExtraInfo(connector.metadata.id)); + ctx.body = await transpileLogtoConnector(connector, buildExtraInfo(connector.metadata)); return next(); } @@ -360,5 +349,3 @@ export default function connectorRoutes( connectorConfigTestingRoutes(router, tenant); connectorAuthorizationUriRoutes(router, tenant); } -/** TODO @Darcy: refactor this file later. */ -/* eslint-enable max-lines */ diff --git a/packages/core/src/tenants/SystemContext.ts b/packages/core/src/tenants/SystemContext.ts index ff7cddd9e..421d992b6 100644 --- a/packages/core/src/tenants/SystemContext.ts +++ b/packages/core/src/tenants/SystemContext.ts @@ -1,12 +1,9 @@ import { CloudflareKey, - EmailServiceProviderKey, type HostnameProviderData, type StorageProviderData, - type EmailServiceData, hostnameProviderDataGuard, storageProviderDataGuard, - emailServiceDataGuard, StorageProviderKey, type SystemKey, } from '@logto/schemas'; @@ -20,7 +17,6 @@ export default class SystemContext { static shared = new SystemContext(); public storageProviderConfig?: StorageProviderData; public hostnameProviderConfig?: HostnameProviderData; - public emailServiceProviderConfig?: EmailServiceData; async loadProviderConfigs(pool: CommonQueryMethods) { await Promise.all([ @@ -38,13 +34,6 @@ export default class SystemContext { hostnameProviderDataGuard ); })(), - (async () => { - this.emailServiceProviderConfig = await this.loadConfig( - pool, - EmailServiceProviderKey.EmailServiceProvider, - emailServiceDataGuard - ); - })(), ]); } diff --git a/packages/core/src/utils/connectors/extra-information.ts b/packages/core/src/utils/connectors/extra-information.ts new file mode 100644 index 000000000..40c7af3e6 --- /dev/null +++ b/packages/core/src/utils/connectors/extra-information.ts @@ -0,0 +1,18 @@ +import { type ConnectorMetadata, ServiceConnector } from '@logto/connector-kit'; +import { conditional } from '@silverhand/essentials'; +import cleanDeep from 'clean-deep'; +import { string, object } from 'zod'; + +const getFromEmailFromMetadata = (metadata: ConnectorMetadata) => { + const result = object({ fromEmail: string() }).safeParse(metadata); + return conditional(result.success && result.data.fromEmail); +}; + +// Will accept other source of `extraInfo` in the future. +export const buildExtraInfo = (metadata: ConnectorMetadata) => { + const fromEmail = getFromEmailFromMetadata(metadata); + const extraInfo = { + ...conditional(fromEmail && metadata.id === ServiceConnector.Email && { fromEmail }), + }; + return cleanDeep(extraInfo, { emptyObjects: false }); +}; diff --git a/packages/core/src/utils/connectors/index.ts b/packages/core/src/utils/connectors/index.ts index 19428e78b..71146d6bd 100644 --- a/packages/core/src/utils/connectors/index.ts +++ b/packages/core/src/utils/connectors/index.ts @@ -12,11 +12,10 @@ import { ConnectorType, type EmailConnector, type SmsConnector, - ServiceConnector, } from '@logto/connector-kit'; -import type { ConnectorFactoryResponse, ConnectorResponse, EmailServiceData } from '@logto/schemas'; +import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas'; import { findPackage } from '@logto/shared'; -import { conditional, deduplicate, pick, trySafe, type Optional } from '@silverhand/essentials'; +import { conditional, deduplicate, pick, trySafe } from '@silverhand/essentials'; import { EnvSet } from '#src/env-set/index.js'; import RequestError from '#src/errors/RequestError/index.js'; @@ -76,20 +75,6 @@ export const transpileConnectorFactory = ({ }; }; -/** - * `extraInfo` is only used to expose email service vendors `fromEmail` setup to Logto email connector. - * Can extend this method in the future for other use cases. - */ -export const buildExtraInfoFromEmailServiceData = ( - connectorFactoryId: string, - emailServiceProviderConfig?: EmailServiceData -): Optional> => { - return conditional( - connectorFactoryId === ServiceConnector.Email && - emailServiceProviderConfig?.fromEmail && { fromEmail: emailServiceProviderConfig.fromEmail } - ); -}; - const checkDuplicateConnectorFactoriesId = (connectorFactories: ConnectorFactory[]) => { const connectorFactoryIds = connectorFactories.map(({ metadata }) => metadata.id); const deduplicatedConnectorFactoryIds = deduplicate(connectorFactoryIds); diff --git a/packages/toolkit/connector-kit/src/types.ts b/packages/toolkit/connector-kit/src/types.ts index 54dc891f4..27329c8b5 100644 --- a/packages/toolkit/connector-kit/src/types.ts +++ b/packages/toolkit/connector-kit/src/types.ts @@ -138,19 +138,21 @@ const connectorConfigFormItemGuard = z.discriminatedUnion('type', [ export type ConnectorConfigFormItem = z.infer; -export const connectorMetadataGuard = z.object({ - id: z.string(), - target: z.string(), - platform: z.nativeEnum(ConnectorPlatform).nullable(), - name: i18nPhrasesGuard, - logo: z.string(), - logoDark: z.string().nullable(), - description: i18nPhrasesGuard, - isStandard: z.boolean().optional(), - readme: z.string(), - configTemplate: z.string().optional(), - formItems: connectorConfigFormItemGuard.array().optional(), -}); +export const connectorMetadataGuard = z + .object({ + id: z.string(), + target: z.string(), + platform: z.nativeEnum(ConnectorPlatform).nullable(), + name: i18nPhrasesGuard, + logo: z.string(), + logoDark: z.string().nullable(), + description: i18nPhrasesGuard, + isStandard: z.boolean().optional(), + readme: z.string(), + configTemplate: z.string().optional(), + formItems: connectorConfigFormItemGuard.array().optional(), + }) + .catchall(z.unknown()); export const configurableConnectorMetadataGuard = connectorMetadataGuard .pick({