Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(connector): add vonage connector #6768

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions packages/connectors/connector-vonage-sms/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Vonage SMS connector

The official Logto connector for Vonage SMS.

## Get started

Vonage is a global communications provider, offering robust cloud-based communication services, including SMS (short message service). The Vonage SMS Connector is a plugin provided by the Logto team to enable Logto end-users to register and sign in to their Logto account via SMS verification codes.

## Set up in Vonage

> 💡 **Tip**
>
> You can skip this step if you have already completed them.

To work with this connector, you will need to [sign up for an account](https://developer.vonage.com/en/account/guides/dashboard-management#create-and-configure-a-vonage-account) in Vonage. This will give you an API key and secret that you can use to access the APIs through this connector.

Once you have an account, you can find your API key and API secret at the top of the Vonage API Dashboard.

And you may need to [rant a virtual number](https://developer.vonage.com/en/numbers/guides/number-management#rent-a-virtual-number) to send SMS messages.

See the [Vonage SMS API](https://developer.vonage.com/en/messaging/sms/overview) for more information.

## Set up in Logto

1. **API Key**: Your Vonage API key.
2. **API Secret**: Your Vonage API secret.
3. **Brand Name**: The brand name you want to use to send the SMS, this is also called the `from` field, see the [Sender Identity](https://developer.vonage.com/en/messaging/sms/guides/custom-sender-id) for more information.
4. **Templates**: The templates you want to use to send the SMS, you can use the default templates or modify them as needed.
9 changes: 9 additions & 0 deletions packages/connectors/connector-vonage-sms/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions packages/connectors/connector-vonage-sms/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"name": "@logto/connector-vonage-sms",
"version": "0.0.0",
"description": "Vonage SMS connector implementation.",
"author": "Silverhand Inc. <[email protected]>",
"dependencies": {
"@logto/connector-kit": "workspace:^4.0.0",
"@silverhand/essentials": "^2.9.1",
"@vonage/auth": "^1.12.0",
"@vonage/server-sdk": "^3.19.0",
"zod": "^3.23.8"
},
"main": "./lib/index.js",
"module": "./lib/index.js",
"exports": "./lib/index.js",
"license": "MPL-2.0",
"type": "module",
"files": [
"lib",
"docs",
"logo.svg",
"logo-dark.svg"
],
"scripts": {
"precommit": "lint-staged",
"check": "tsc --noEmit",
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint --ext .ts src",
"lint:report": "pnpm lint --format json --output-file report.json",
"test": "vitest src",
"test:ci": "pnpm run test --silent --coverage",
"prepublishOnly": "pnpm build"
},
"engines": {
"node": "^20.9.0"
},
"eslintConfig": {
"extends": "@silverhand",
"settings": {
"import/core-modules": [
"@silverhand/essentials",
"got",
"nock",
"snakecase-keys",
"zod"
]
}
},
"prettier": "@silverhand/eslint-config/.prettierrc",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@silverhand/eslint-config": "6.0.1",
"@silverhand/ts-config": "6.0.0",
"@types/node": "^20.11.20",
"@types/supertest": "^6.0.2",
"@vitest/coverage-v8": "^2.0.0",
"eslint": "^8.56.0",
"lint-staged": "^15.0.2",
"prettier": "^3.0.0",
"supertest": "^7.0.0",
"tsup": "^8.3.0",
"typescript": "^5.5.3",
"vitest": "^2.0.0"
}
}
65 changes: 65 additions & 0 deletions packages/connectors/connector-vonage-sms/src/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { ConnectorMetadata } from '@logto/connector-kit';
import { ConnectorConfigFormItemType } from '@logto/connector-kit';

export const defaultMetadata: ConnectorMetadata = {
id: 'vonage-sms',
target: 'vonage-sms',
platform: null,
name: {
en: 'Vonage SMS Service',
},
logo: './logo.svg',
logoDark: null,
description: {
en: 'Communications APIs to connect the world',
},
readme: './README.md',
formItems: [
{
key: 'apiKey',
label: 'API Key',
type: ConnectorConfigFormItemType.Text,
required: true,
},
{
key: 'apiSecret',
label: 'API Secret',
type: ConnectorConfigFormItemType.Text,
required: true,
},
{
key: 'brandName',
label: 'Brand Name',
type: ConnectorConfigFormItemType.Text,
required: true,
},
{
key: 'templates',
label: 'Templates',
type: ConnectorConfigFormItemType.Json,
required: true,
defaultValue: [
{
usageType: 'SignIn',
content:
'Your Logto sign-in verification code is {{code}}. The code will remain active for 10 minutes.',
},
{
usageType: 'Register',
content:
'Your Logto sign-up verification code is {{code}}. The code will remain active for 10 minutes.',
},
{
usageType: 'ForgotPassword',
content:
'Your Logto password change verification code is {{code}}. The code will remain active for 10 minutes.',
},
{
usageType: 'Generic',
content:
'Your Logto verification code is {{code}}. The code will remain active for 10 minutes.',
},
],
},
],
};
10 changes: 10 additions & 0 deletions packages/connectors/connector-vonage-sms/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import createConnector from './index.js';
import { mockedConfig } from './mock.js';

const getConfig = vi.fn().mockResolvedValue(mockedConfig);

describe('Vonage SMS connector', () => {
it('init without throwing errors', async () => {
await expect(createConnector({ getConfig })).resolves.not.toThrow();
});
});
69 changes: 69 additions & 0 deletions packages/connectors/connector-vonage-sms/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { assert } from '@silverhand/essentials';

import type {
GetConnectorConfig,
SendMessageFunction,
CreateConnector,
SmsConnector,
} from '@logto/connector-kit';
import {
ConnectorError,
ConnectorErrorCodes,
validateConfig,
ConnectorType,
replaceSendMessageHandlebars,
} from '@logto/connector-kit';
import { Auth } from '@vonage/auth';
import { Vonage } from '@vonage/server-sdk';

import { defaultMetadata } from './constant.js';
import { vonageSmsConfigGuard } from './types.js';

const sendMessage =
(getConfig: GetConnectorConfig): SendMessageFunction =>
async (data, inputConfig) => {
const { to, type, payload } = data;
const config = inputConfig ?? (await getConfig(defaultMetadata.id));
validateConfig(config, vonageSmsConfigGuard);
const { apiKey, apiSecret, brandName, templates } = config;
const template = templates.find((template) => template.usageType === type);

assert(
template,
new ConnectorError(
ConnectorErrorCodes.TemplateNotFound,
`Cannot find template for type: ${type}`
)
);

const vonageAuth = new Auth({
apiKey,
apiSecret,
});
const vonage = new Vonage(vonageAuth);

try {
return await vonage.sms.send({
from: brandName,
to,
text: replaceSendMessageHandlebars(template.content, payload),
});
} catch (error: unknown) {
if (error instanceof Error) {
throw new ConnectorError(ConnectorErrorCodes.General, error.message);
}

throw error;
}
};

const createVonageSmsConnector: CreateConnector<SmsConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Sms,
configGuard: vonageSmsConfigGuard,
sendMessage: sendMessage(getConfig),
};
};

export default createVonageSmsConnector;
17 changes: 17 additions & 0 deletions packages/connectors/connector-vonage-sms/src/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { VonageSmsConfig } from './types.js';

const mockedApiKey = 'api-key';
const mockedApiSecret = 'api-secret';
const mockedBrandName = 'brand name';

export const mockedConfig: VonageSmsConfig = {
apiKey: mockedApiKey,
apiSecret: mockedApiSecret,
brandName: mockedBrandName,
templates: [
{
usageType: 'Generic',
content: 'This is for testing purposes only. Your verification code is {{code}}.',
},
],
};
33 changes: 33 additions & 0 deletions packages/connectors/connector-vonage-sms/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { z } from 'zod';

/**
* UsageType here is used to specify the use case of the template, can be either
* 'Register', 'SignIn', 'ForgotPassword', 'Generic'.
*/
const requiredTemplateUsageTypes = ['Register', 'SignIn', 'ForgotPassword', 'Generic'];

const templateGuard = z.object({
usageType: z.string(),
content: z.string(),
});

export const vonageSmsConfigGuard = z.object({
apiKey: z.string(),
apiSecret: z.string(),
brandName: z.string(),
templates: z.array(templateGuard).refine(
(templates) =>
requiredTemplateUsageTypes.every((requiredType) =>
templates.map((template) => template.usageType).includes(requiredType)
),
(templates) => ({
message: `Template with UsageType (${requiredTemplateUsageTypes
.filter(
(requiredType) => !templates.map((template) => template.usageType).includes(requiredType)
)
.join(', ')}) should be provided!`,
})
),
});

export type VonageSmsConfig = z.infer<typeof vonageSmsConfigGuard>;
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ function CreateConnectorForm({ onClose, isOpen: isFormOpen, type }: Props) {
.filter(({ id }) => id !== 'saml')
// Hide the entrance of adding HTTP Email connector
.filter(({ id }) => id !== 'http-email')
// Hide the entrance of adding Vonage SMS connector
.filter(({ id }) => id !== 'vonage')
);

return allGroups
Expand Down
Loading
Loading