mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
refactor(connector): refactor passwordless connector
This commit is contained in:
parent
e09a4703cc
commit
0439ce9dc8
9 changed files with 142 additions and 35 deletions
|
@ -2,6 +2,7 @@ import {
|
|||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
ConnectorMetadata,
|
||||
EmailMessageTypes,
|
||||
EmailSendMessageFunction,
|
||||
ValidateConfig,
|
||||
EmailConnector,
|
||||
|
@ -32,12 +33,30 @@ export default class AliyunDmConnector implements EmailConnector {
|
|||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
public sendMessage: EmailSendMessageFunction = async (address, type, data, config) => {
|
||||
const emailConfig =
|
||||
(config as AliyunDmConfig | undefined) ?? (await this.getConfig(this.metadata.id));
|
||||
public sendMessage: EmailSendMessageFunction = async (address, type, data) => {
|
||||
const emailConfig = await this.getConfig(this.metadata.id);
|
||||
await this.validateConfig(emailConfig);
|
||||
const { accessKeyId, accessKeySecret, accountName, fromAlias, templates } = emailConfig;
|
||||
|
||||
return this.sendMessageCore(address, type, data, emailConfig);
|
||||
};
|
||||
|
||||
public sendTestMessage: EmailSendMessageFunction = async (address, type, data, config) => {
|
||||
if (!config) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InsufficientRequestParameters);
|
||||
}
|
||||
|
||||
await this.validateConfig(config);
|
||||
|
||||
return this.sendMessageCore(address, type, data, config as AliyunDmConfig);
|
||||
};
|
||||
|
||||
private readonly sendMessageCore = async (
|
||||
address: string,
|
||||
type: keyof EmailMessageTypes,
|
||||
data: EmailMessageTypes[typeof type],
|
||||
config: AliyunDmConfig
|
||||
) => {
|
||||
const { accessKeyId, accessKeySecret, accountName, fromAlias, templates } = config;
|
||||
const template = templates.find((template) => template.usageType === type);
|
||||
|
||||
assert(
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
ConnectorMetadata,
|
||||
SmsMessageTypes,
|
||||
SmsSendMessageFunction,
|
||||
ValidateConfig,
|
||||
SmsConnector,
|
||||
|
@ -27,11 +28,30 @@ export default class AliyunSmsConnector implements SmsConnector {
|
|||
}
|
||||
};
|
||||
|
||||
public sendMessage: SmsSendMessageFunction = async (phone, type, { code }, config) => {
|
||||
const smsConfig =
|
||||
(config as AliyunSmsConfig | undefined) ?? (await this.getConfig(this.metadata.id));
|
||||
public sendMessage: SmsSendMessageFunction = async (phone, type, { code }) => {
|
||||
const smsConfig = await this.getConfig(this.metadata.id);
|
||||
await this.validateConfig(smsConfig);
|
||||
const { accessKeyId, accessKeySecret, signName, templates } = smsConfig;
|
||||
|
||||
return this.sendMessageCore(phone, type, { code }, smsConfig);
|
||||
};
|
||||
|
||||
public sendTestMessage: SmsSendMessageFunction = async (phone, type, { code }, config) => {
|
||||
if (!config) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InsufficientRequestParameters);
|
||||
}
|
||||
|
||||
await this.validateConfig(config);
|
||||
|
||||
return this.sendMessageCore(phone, type, { code }, config as AliyunSmsConfig);
|
||||
};
|
||||
|
||||
private readonly sendMessageCore = async (
|
||||
phone: string,
|
||||
type: keyof SmsMessageTypes,
|
||||
data: SmsMessageTypes[typeof type],
|
||||
config: AliyunSmsConfig
|
||||
) => {
|
||||
const { accessKeyId, accessKeySecret, signName, templates } = config;
|
||||
const template = templates.find(({ usageType }) => usageType === type);
|
||||
|
||||
assert(
|
||||
|
@ -46,7 +66,7 @@ export default class AliyunSmsConnector implements SmsConnector {
|
|||
PhoneNumbers: phone,
|
||||
SignName: signName,
|
||||
TemplateCode: template.templateCode,
|
||||
TemplateParam: JSON.stringify({ code }),
|
||||
TemplateParam: JSON.stringify(data),
|
||||
},
|
||||
accessKeySecret
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
ConnectorMetadata,
|
||||
EmailMessageTypes,
|
||||
EmailSendMessageFunction,
|
||||
ValidateConfig,
|
||||
EmailConnector,
|
||||
|
@ -33,12 +34,30 @@ export default class SendGridMailConnector implements EmailConnector {
|
|||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
public sendMessage: EmailSendMessageFunction = async (address, type, data, config) => {
|
||||
const emailConfig =
|
||||
(config as SendGridMailConfig | undefined) ?? (await this.getConfig(this.metadata.id));
|
||||
public sendMessage: EmailSendMessageFunction = async (address, type, data) => {
|
||||
const emailConfig = await this.getConfig(this.metadata.id);
|
||||
await this.validateConfig(emailConfig);
|
||||
const { apiKey, fromEmail, fromName, templates } = emailConfig;
|
||||
|
||||
return this.sendMessageCore(address, type, data, emailConfig);
|
||||
};
|
||||
|
||||
public sendTestMessage: EmailSendMessageFunction = async (address, type, data, config) => {
|
||||
if (!config) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InsufficientRequestParameters);
|
||||
}
|
||||
|
||||
await this.validateConfig(config);
|
||||
|
||||
return this.sendMessageCore(address, type, data, config as SendGridMailConfig);
|
||||
};
|
||||
|
||||
private readonly sendMessageCore = async (
|
||||
address: string,
|
||||
type: keyof EmailMessageTypes,
|
||||
data: EmailMessageTypes[typeof type],
|
||||
config: SendGridMailConfig
|
||||
) => {
|
||||
const { apiKey, fromEmail, fromName, templates } = config;
|
||||
const template = templates.find((template) => template.usageType === type);
|
||||
|
||||
assert(
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
ConnectorMetadata,
|
||||
EmailMessageTypes,
|
||||
EmailSendMessageFunction,
|
||||
ValidateConfig,
|
||||
EmailConnector,
|
||||
|
@ -27,11 +28,30 @@ export default class SmtpConnector implements EmailConnector {
|
|||
}
|
||||
};
|
||||
|
||||
public sendMessage: EmailSendMessageFunction = async (address, type, data, config) => {
|
||||
const emailConfig =
|
||||
(config as SmtpConfig | undefined) ?? (await this.getConfig(this.metadata.id));
|
||||
public sendMessage: EmailSendMessageFunction = async (address, type, data) => {
|
||||
const emailConfig = await this.getConfig(this.metadata.id);
|
||||
await this.validateConfig(emailConfig);
|
||||
const { host, port, username, password, fromEmail, replyTo, templates } = emailConfig;
|
||||
|
||||
return this.sendMessageCore(address, type, data, emailConfig);
|
||||
};
|
||||
|
||||
public sendTestMessage: EmailSendMessageFunction = async (address, type, data, config) => {
|
||||
if (!config) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InsufficientRequestParameters);
|
||||
}
|
||||
|
||||
await this.validateConfig(config);
|
||||
|
||||
return this.sendMessageCore(address, type, data, config as SmtpConfig);
|
||||
};
|
||||
|
||||
private readonly sendMessageCore = async (
|
||||
address: string,
|
||||
type: keyof EmailMessageTypes,
|
||||
data: EmailMessageTypes[typeof type],
|
||||
config: SmtpConfig
|
||||
) => {
|
||||
const { host, port, username, password, fromEmail, replyTo, templates } = config;
|
||||
const template = templates.find((template) => template.usageType === type);
|
||||
|
||||
assert(
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
ConnectorMetadata,
|
||||
SmsMessageTypes,
|
||||
SmsSendMessageFunction,
|
||||
ValidateConfig,
|
||||
SmsConnector,
|
||||
|
@ -26,11 +27,30 @@ export default class TwilioSmsConnector implements SmsConnector {
|
|||
}
|
||||
};
|
||||
|
||||
public sendMessage: SmsSendMessageFunction = async (address, type, data, config) => {
|
||||
const smsConfig =
|
||||
(config as TwilioSmsConfig | undefined) ?? (await this.getConfig(this.metadata.id));
|
||||
public sendMessage: SmsSendMessageFunction = async (address, type, data) => {
|
||||
const smsConfig = await this.getConfig(this.metadata.id);
|
||||
await this.validateConfig(smsConfig);
|
||||
const { accountSID, authToken, fromMessagingServiceSID, templates } = smsConfig;
|
||||
|
||||
return this.sendMessageCore(address, type, data, smsConfig);
|
||||
};
|
||||
|
||||
public sendTestMessage: SmsSendMessageFunction = async (address, type, data, config) => {
|
||||
if (!config) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InsufficientRequestParameters);
|
||||
}
|
||||
|
||||
await this.validateConfig(config);
|
||||
|
||||
return this.sendMessageCore(address, type, data, config as TwilioSmsConfig);
|
||||
};
|
||||
|
||||
private readonly sendMessageCore = async (
|
||||
phone: string,
|
||||
type: keyof SmsMessageTypes,
|
||||
data: SmsMessageTypes[typeof type],
|
||||
config: TwilioSmsConfig
|
||||
) => {
|
||||
const { accountSID, authToken, fromMessagingServiceSID, templates } = config;
|
||||
const template = templates.find((template) => template.usageType === type);
|
||||
|
||||
assert(
|
||||
|
@ -42,7 +62,7 @@ export default class TwilioSmsConnector implements SmsConnector {
|
|||
);
|
||||
|
||||
const parameters: PublicParameters = {
|
||||
To: address,
|
||||
To: phone,
|
||||
MessagingServiceSid: fromMessagingServiceSID,
|
||||
Body:
|
||||
typeof data.code === 'string'
|
||||
|
|
|
@ -66,19 +66,19 @@ export type EmailMessageTypes = {
|
|||
|
||||
export type SmsMessageTypes = EmailMessageTypes;
|
||||
|
||||
export type EmailSendMessageFunction<T = unknown> = (
|
||||
export type EmailSendMessageFunction<T = Record<string, unknown>, U = unknown> = (
|
||||
address: string,
|
||||
type: keyof EmailMessageTypes,
|
||||
payload: EmailMessageTypes[typeof type],
|
||||
config?: Record<string, unknown>
|
||||
) => Promise<T>;
|
||||
config?: T
|
||||
) => Promise<U>;
|
||||
|
||||
export type SmsSendMessageFunction<T = unknown> = (
|
||||
export type SmsSendMessageFunction<T = Record<string, unknown>, U = unknown> = (
|
||||
phone: string,
|
||||
type: keyof SmsMessageTypes,
|
||||
payload: SmsMessageTypes[typeof type],
|
||||
config?: Record<string, unknown>
|
||||
) => Promise<T>;
|
||||
config?: T
|
||||
) => Promise<U>;
|
||||
|
||||
export interface BaseConnector {
|
||||
metadata: ConnectorMetadata;
|
||||
|
@ -88,10 +88,12 @@ export interface BaseConnector {
|
|||
|
||||
export interface SmsConnector extends BaseConnector {
|
||||
sendMessage: SmsSendMessageFunction;
|
||||
sendTestMessage: SmsSendMessageFunction;
|
||||
}
|
||||
|
||||
export interface EmailConnector extends BaseConnector {
|
||||
sendMessage: EmailSendMessageFunction;
|
||||
sendTestMessage: EmailSendMessageFunction;
|
||||
}
|
||||
|
||||
export interface SocialConnector extends BaseConnector {
|
||||
|
|
|
@ -119,6 +119,7 @@ describe('sendPasscode', () => {
|
|||
|
||||
it('should throw error when email or sms connector can not be found', async () => {
|
||||
const sendMessage = jest.fn();
|
||||
const sendTestMessage = jest.fn();
|
||||
const validateConfig = jest.fn();
|
||||
const getConfig = jest.fn();
|
||||
mockedGetConnectorInstances.mockResolvedValueOnce([
|
||||
|
@ -133,6 +134,7 @@ describe('sendPasscode', () => {
|
|||
platform: null,
|
||||
},
|
||||
sendMessage,
|
||||
sendTestMessage,
|
||||
validateConfig,
|
||||
getConfig,
|
||||
},
|
||||
|
@ -158,6 +160,7 @@ describe('sendPasscode', () => {
|
|||
|
||||
it('should call sendPasscode with params matching', async () => {
|
||||
const sendMessage = jest.fn();
|
||||
const sendTestMessage = jest.fn();
|
||||
const validateConfig = jest.fn();
|
||||
const getConfig = jest.fn();
|
||||
mockedGetConnectorInstances.mockResolvedValueOnce([
|
||||
|
@ -172,6 +175,7 @@ describe('sendPasscode', () => {
|
|||
platform: null,
|
||||
},
|
||||
sendMessage,
|
||||
sendTestMessage,
|
||||
validateConfig,
|
||||
getConfig,
|
||||
},
|
||||
|
@ -186,6 +190,7 @@ describe('sendPasscode', () => {
|
|||
platform: null,
|
||||
},
|
||||
sendMessage,
|
||||
sendTestMessage,
|
||||
validateConfig,
|
||||
getConfig,
|
||||
},
|
||||
|
|
|
@ -116,15 +116,16 @@ describe('connector route', () => {
|
|||
metadata: mockedMetadata,
|
||||
validateConfig: jest.fn(),
|
||||
getConfig: jest.fn(),
|
||||
sendMessage: async (
|
||||
sendTestMessage: async (
|
||||
address: string,
|
||||
type: keyof EmailMessageTypes,
|
||||
_payload: EmailMessageTypes[typeof type]
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
): Promise<any> => {},
|
||||
sendMessage: jest.fn(),
|
||||
};
|
||||
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([mockedSmsConnectorInstance]);
|
||||
const sendMessageSpy = jest.spyOn(mockedSmsConnectorInstance, 'sendMessage');
|
||||
const sendMessageSpy = jest.spyOn(mockedSmsConnectorInstance, 'sendTestMessage');
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ phone: '12345678901', config: { test: 123 } });
|
||||
|
@ -146,15 +147,16 @@ describe('connector route', () => {
|
|||
metadata: mockMetadata,
|
||||
validateConfig: jest.fn(),
|
||||
getConfig: jest.fn(),
|
||||
sendMessage: async (
|
||||
sendTestMessage: async (
|
||||
address: string,
|
||||
type: keyof EmailMessageTypes,
|
||||
_payload: EmailMessageTypes[typeof type]
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
): Promise<any> => {},
|
||||
sendMessage: jest.fn(),
|
||||
};
|
||||
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([mockedEmailConnector]);
|
||||
const sendMessageSpy = jest.spyOn(mockedEmailConnector, 'sendMessage');
|
||||
const sendMessageSpy = jest.spyOn(mockedEmailConnector, 'sendTestMessage');
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ email: 'test@email.com', config: { test: 123 } });
|
||||
|
|
|
@ -191,7 +191,7 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
|
|||
await connector.validateConfig(config);
|
||||
}
|
||||
|
||||
await connector.sendMessage(
|
||||
await connector.sendTestMessage(
|
||||
subject,
|
||||
'Test',
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue