0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(core): update connector APIs for parsing config

This commit is contained in:
Darcy Ye 2023-07-04 14:13:38 +08:00
parent 44c09baba9
commit 5a9e85986b
No known key found for this signature in database
GPG key ID: B46F4C07EDEFC610
4 changed files with 84 additions and 16 deletions

View file

@ -4,22 +4,36 @@ import {
type SmsConnector,
type EmailConnector,
demoConnectorIds,
ServiceConnector,
VerificationCodeType,
} from '@logto/connector-kit';
import { phoneRegEx, emailRegEx } from '@logto/core-kit';
import { jsonObjectGuard, ConnectorType } from '@logto/schemas';
import { jsonObjectGuard, ConnectorType, type JsonObject } from '@logto/schemas';
import { string, object } from 'zod';
import RequestError from '#src/errors/RequestError/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import assertThat from '#src/utils/assert-that.js';
import { loadConnectorFactories } from '#src/utils/connectors/index.js';
import { getCloudConnectionEndpoints } from '#src/utils/endpoint.js';
import type { AuthedRouter, RouterInitArgs } from '../types.js';
export default function connectorConfigTestingRoutes<T extends AuthedRouter>(
...[router]: RouterInitArgs<T>
...[router, { libraries }]: RouterInitArgs<T>
) {
const {
logtoConfigs: { getCloudConnectionData },
} = libraries;
const configPatcher = async (factoryId: string, config?: JsonObject) => {
if (!config || ServiceConnector.Email !== factoryId) {
return config;
}
const endpoints = getCloudConnectionEndpoints();
const credentials = await getCloudConnectionData();
return { ...endpoints, ...credentials, ...config };
};
router.post(
'/connectors/:factoryId/test',
koaGuard({
@ -35,7 +49,7 @@ export default function connectorConfigTestingRoutes<T extends AuthedRouter>(
params: { factoryId },
body,
} = ctx.guard;
const { phone, email, config } = body;
const { phone, email, config: originalConfig } = body;
const subject = phone ?? email;
assertThat(subject, new RequestError({ code: 'guard.invalid_input' }));
@ -64,6 +78,7 @@ export default function connectorConfigTestingRoutes<T extends AuthedRouter>(
rawConnector: { sendMessage },
} = await buildRawConnector<SmsConnector | EmailConnector>(connectorFactory);
const config = await configPatcher(factoryId, originalConfig);
await sendMessage(
{
to: subject,

View file

@ -1,11 +1,17 @@
/* eslint-disable max-lines */
import { buildRawConnector } from '@logto/cli/lib/connector/index.js';
import { demoConnectorIds, validateConfig } from '@logto/connector-kit';
import {
ServiceConnector,
demoConnectorIds,
emailServiceBrandingGuard,
validateConfig,
} from '@logto/connector-kit';
import {
connectorFactoryResponseGuard,
Connectors,
ConnectorType,
connectorResponseGuard,
type JsonObject,
} from '@logto/schemas';
import { buildIdGenerator } from '@logto/shared';
import cleanDeep from 'clean-deep';
@ -22,6 +28,7 @@ import {
buildExtraInfoFromEmailServiceData,
} from '#src/utils/connectors/index.js';
import { checkSocialConnectorTargetAndPlatformUniqueness } from '#src/utils/connectors/platform.js';
import { getCloudConnectionEndpoints } from '#src/utils/endpoint.js';
import type { AuthedRouter, RouterInitArgs } from '../types.js';
@ -30,6 +37,20 @@ import connectorConfigTestingRoutes from './config-testing.js';
const generateConnectorId = buildIdGenerator(12);
const configPruner = (factoryId: string, config: JsonObject): JsonObject => {
if (ServiceConnector.Email !== factoryId) {
return config;
}
/**
* Can not use `pick()` from @silverhand/essentials since the pick returns 'undefined' and
* this is value is not accepted by `config` return type JsonObject.
*/
return Object.fromEntries(
Object.keys(emailServiceBrandingGuard.shape).map((key) => [key, config[key] ?? null])
);
};
export default function connectorRoutes<T extends AuthedRouter>(
...[router, tenant]: RouterInitArgs<T>
) {
@ -44,6 +65,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
const { getLogtoConnectorById, getLogtoConnectors } = tenant.connectors;
const {
signInExperiences: { removeUnavailableSocialConnectorTargets },
logtoConfigs: { getCloudConnectionData },
} = tenant.libraries;
// Will accept other source of `extraInfo` in the future.
@ -55,6 +77,15 @@ export default function connectorRoutes<T extends AuthedRouter>(
return cleanDeep(extraInfo, { emptyObjects: false });
};
const configPatcher = async (factoryId: string, config?: JsonObject) => {
if (!config || ServiceConnector.Email !== factoryId) {
return config;
}
const endpoints = getCloudConnectionEndpoints();
const credentials = await getCloudConnectionData();
return { ...endpoints, ...credentials, ...config };
};
router.post(
'/connectors',
koaGuard({
@ -71,7 +102,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
}),
async (ctx, next) => {
const {
body: { id: proposedId, connectorId, metadata, config, syncProfile },
body: { id: proposedId, connectorId, metadata, config: originalConfig, syncProfile },
} = ctx.guard;
const connectorFactories = await loadConnectorFactories();
@ -130,6 +161,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
);
}
const config = await configPatcher(connectorFactory.metadata.id, originalConfig);
if (config) {
const { rawConnector } = await buildRawConnector(connectorFactory);
validateConfig(config, rawConnector.configGuard);
@ -164,7 +196,10 @@ export default function connectorRoutes<T extends AuthedRouter>(
}
const connector = await getLogtoConnectorById(insertConnectorId);
ctx.body = await transpileLogtoConnector(connector, buildExtraInfo(connector.metadata.id));
ctx.body = await transpileLogtoConnector(connector, {
extraInfo: buildExtraInfo(connector.metadata.id),
configPruner: (config) => configPruner(connectorFactory.metadata.id, config),
});
return next();
}
@ -200,7 +235,10 @@ export default function connectorRoutes<T extends AuthedRouter>(
ctx.body = await Promise.all(
filteredConnectors.map(async (connector) =>
transpileLogtoConnector(connector, buildExtraInfo(connector.metadata.id))
transpileLogtoConnector(connector, {
extraInfo: buildExtraInfo(connector.metadata.id),
configPruner: (config) => configPruner(connector.metadata.id, config),
})
)
);
@ -224,7 +262,10 @@ export default function connectorRoutes<T extends AuthedRouter>(
// 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, {
extraInfo: buildExtraInfo(connector.metadata.id),
configPruner: (config) => configPruner(connector.metadata.id, config),
});
return next();
}
@ -287,7 +328,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
async (ctx, next) => {
const {
params: { id },
body: { config, metadata, syncProfile },
body: { config: originalConfig, metadata, syncProfile },
} = ctx.guard;
const { type, validateConfig, metadata: originalMetadata } = await getLogtoConnectorById(id);
@ -314,6 +355,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
);
}
const config = await configPatcher(originalMetadata.id, originalConfig);
if (config) {
validateConfig(config);
}
@ -324,7 +366,10 @@ export default function connectorRoutes<T extends AuthedRouter>(
jsonbMode: 'replace',
});
const connector = await getLogtoConnectorById(id);
ctx.body = await transpileLogtoConnector(connector, buildExtraInfo(connector.metadata.id));
ctx.body = await transpileLogtoConnector(connector, {
extraInfo: buildExtraInfo(connector.metadata.id),
configPruner: (config) => configPruner(connector.metadata.id, config),
});
return next();
}

View file

@ -14,7 +14,12 @@ import {
type SmsConnector,
ServiceConnector,
} from '@logto/connector-kit';
import type { ConnectorFactoryResponse, ConnectorResponse, EmailServiceData } from '@logto/schemas';
import type {
ConnectorFactoryResponse,
ConnectorResponse,
EmailServiceData,
JsonObject,
} from '@logto/schemas';
import { findPackage } from '@logto/shared';
import { conditional, deduplicate, pick, trySafe, type Optional } from '@silverhand/essentials';
@ -30,7 +35,10 @@ export const isPasswordlessLogtoConnector = (
export const transpileLogtoConnector = async (
connector: LogtoConnector,
extraInfo?: ConnectorResponse['extraInfo']
payload?: {
extraInfo?: ConnectorResponse['extraInfo'];
configPruner?: (config: JsonObject) => JsonObject;
}
): Promise<ConnectorResponse> => {
const usagePayload = conditional(
/** Should do the check in advance since only passwordless connectors could have `getUsage` method. */
@ -44,6 +52,8 @@ export const transpileLogtoConnector = async (
/** Temporarily block entering Logto email connector as well until this feature is ready for prod. */
const isDemo = demoConnectorIds.includes(id) || serviceConnectorIds.includes(id);
const { extraInfo, configPruner } = payload ?? {};
return {
type,
...metadata,
@ -51,7 +61,7 @@ export const transpileLogtoConnector = async (
isDemo,
extraInfo,
// Hide demo connector config
config: isDemo ? {} : config,
config: isDemo ? {} : configPruner ? configPruner(config) : config,
...usagePayload,
};
};

View file

@ -2,9 +2,7 @@ import { appendPath } from '@silverhand/essentials';
import { EnvSet } from '#src/env-set/index.js';
/** Will use this method in upcoming changes. */
// eslint-disable-next-line import/no-unused-modules
export const getCloudConnectionEndpoints = async () => {
export const getCloudConnectionEndpoints = () => {
const { cloudUrlSet, adminUrlSet } = EnvSet.values;
return {
tokenEndpoint: appendPath(adminUrlSet.endpoint, 'oidc/token').toString(),