feat(verification-plugin): support tencent sms (#1382)

* feat: support tencent sms

* feat: complete tencent sms

* refactor: clean

* feat: improve error message
This commit is contained in:
Dunqing 2023-01-30 21:16:03 +08:00 committed by GitHub
parent ddb174db1b
commit b88adc5c11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 174 additions and 17 deletions

View File

@ -12,7 +12,8 @@
"@nocobase/actions": "0.9.0-alpha.2",
"@nocobase/resourcer": "0.9.0-alpha.2",
"@nocobase/server": "0.9.0-alpha.2",
"@nocobase/utils": "0.9.0-alpha.2"
"@nocobase/utils": "0.9.0-alpha.2",
"tencentcloud-sdk-nodejs": "^4.0.525"
},
"devDependencies": {
"@nocobase/test": "0.9.0-alpha.2"

View File

@ -10,4 +10,11 @@ export default {
'Endpoint': '接入点',
'Sign': '签名',
'Template code': '模板代码',
'Secret Id': 'Secret Id',
'Secret Key': 'Secret Key',
'Region': '地域',
'Sign name': '短信签名内容',
'Sms sdk app id': '短信应用 ID',
'Template Id': '短信模板 ID'
};

View File

@ -1,9 +1,11 @@
import { ISchema } from '@formily/react';
import { Registry } from "@nocobase/utils/client";
import SMSAliyun from './sms-aliyun';
import SMSTencent from './sms-tencent';
const providerTypes: Registry<ISchema> = new Registry();
providerTypes.register('sms-aliyun', SMSAliyun);
providerTypes.register('sms-tencent', SMSTencent);
export default providerTypes;

View File

@ -0,0 +1,52 @@
import { ISchema } from '@formily/react';
import { NAMESPACE } from '../locale';
export default {
type: 'object',
properties: {
secretId: {
title: `{{t("Secret Id", { ns: "${NAMESPACE}" })}}`,
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
secretKey: {
title: `{{t("Secret Key", { ns: "${NAMESPACE}" })}}`,
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Password',
},
region: {
title: `{{t("Region", { ns: "${NAMESPACE}" })}}`,
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
endpoint: {
title: `{{t("Endpoint", { ns: "${NAMESPACE}" })}}`,
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
default: 'sms.tencentcloudapi.com',
},
SignName: {
title: `{{t("Sign name", { ns: "${NAMESPACE}" })}}`,
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
SmsSdkAppId: {
title: `{{t("Sms sdk app id", { ns: "${NAMESPACE}" })}}`,
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
TemplateId: {
title: `{{t("Template Id", { ns: "${NAMESPACE}" })}}`,
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
}
}
} as ISchema;

View File

@ -38,6 +38,7 @@ const collection = {
required: true,
enum: [
{ label: `{{t("Aliyun SMS", { ns: "${NAMESPACE}" })}}`, value: 'sms-aliyun' },
{ label: `{{t("Tencent SMS", { ns: "${NAMESPACE}" })}}`, value: 'sms-tencent' },
],
},
},

View File

@ -19,12 +19,7 @@ export async function create(context: Context, next: Next) {
return context.throw(400, 'Invalid action type');
}
const ProviderRepo = context.db.getRepository('verifications_providers');
const providerItem = await ProviderRepo.findOne({
filter: {
default: true
}
});
const providerItem = await plugin.getDefault()
if (!providerItem) {
console.error(`[verification] no provider for action (${values.type}) provided`);
return context.throw(500);
@ -32,7 +27,7 @@ export async function create(context: Context, next: Next) {
const receiver = interceptor.getReceiver(context);
if (!receiver) {
return context.throw(400, { code: 'InvalidReceiver', message: context.t('Not a valid cellphone number, please re-enter', {ns: namespace }) });
return context.throw(400, { code: 'InvalidReceiver', message: context.t('Not a valid cellphone number, please re-enter', { ns: namespace }) });
}
const VerificationModel = context.db.getModel('verifications');
const record = await VerificationModel.findOne({
@ -70,7 +65,7 @@ export async function create(context: Context, next: Next) {
switch (error.name) {
case 'InvalidReceiver':
// TODO: message should consider email and other providers, maybe use "receiver"
return context.throw(400, { code: 'InvalidReceiver', message: context.t('Not a valid cellphone number, please re-enter', {ns: namespace })});
return context.throw(400, { code: 'InvalidReceiver', message: context.t('Not a valid cellphone number, please re-enter', { ns: namespace }) });
case 'RateLimit':
return context.throw(429, context.t('You are trying so frequently, please slow down', { ns: namespace }));
default:

View File

@ -1,4 +1,5 @@
export const PROVIDER_TYPE_SMS_ALIYUN = 'sms-aliyun';
export const PROVIDER_TYPE_SMS_TENCENT = 'sms-tencent';
export const CODE_STATUS_UNUSED = 0;
export const CODE_STATUS_USED = 1;

View File

@ -3,5 +3,5 @@ export default {
'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入',
"Please don't retry in {{time}} seconds": '请 {{time}} 秒后再试',
'You are trying so frequently, please slow down': '您的操作太频繁,请稍后再试',
'Verification code is invalid': '无效的验证码'
'Verification code is invalid': '无效的验证码',
};

View File

@ -3,25 +3,26 @@ import path from 'path';
import { requireModule } from '@nocobase/utils';
import Plugin from '../Plugin';
import { PROVIDER_TYPE_SMS_ALIYUN } from '../constants';
import { PROVIDER_TYPE_SMS_ALIYUN, PROVIDER_TYPE_SMS_TENCENT } from '../constants';
export class Provider {
constructor(protected plugin: Plugin, protected options) {}
constructor(protected plugin: Plugin, protected options) { }
async send(receiver: string, data: { [key: string]: any }): Promise<any>{}
async send(receiver: string, data: { [key: string]: any }): Promise<any> { }
}
interface Providers {
[key: string]: typeof Provider
}
export default function(plugin: Plugin, more: Providers = {}) {
export default function (plugin: Plugin, more: Providers = {}) {
const { providers } = plugin;
const natives = [
PROVIDER_TYPE_SMS_ALIYUN
PROVIDER_TYPE_SMS_ALIYUN,
PROVIDER_TYPE_SMS_TENCENT
].reduce((result, key) => Object.assign(result, {
[key]: requireModule(path.isAbsolute(key) ? key : path.join(__dirname, key)) as typeof Provider
}), {} as Providers);

View File

@ -0,0 +1,57 @@
import { Provider } from '.';
import * as tencentcloud from "tencentcloud-sdk-nodejs"
// 导入对应产品模块的client models。
const smsClient = tencentcloud.sms.v20210111.Client;
export default class extends Provider {
client: InstanceType<typeof smsClient>;
constructor(plugin, options) {
super(plugin, options);
const { secretId, secretKey, region, endpoint } = this.options;
/* 实例化要请求产品(以sms为例)的client对象 */
this.client = new smsClient({
credential: {
secretId,
secretKey
},
region,
profile: {
httpProfile: {
endpoint
},
},
})
}
async send(phoneNumbers, data: { code: string }) {
const { SignName, TemplateId, SmsSdkAppId } = this.options
const result = await this.client.SendSms({
PhoneNumberSet: [phoneNumbers],
SignName,
TemplateId,
SmsSdkAppId,
TemplateParamSet: [data.code]
})
const errCode = result.SendStatusSet[0].Code
const error = new Error(`${errCode}:${result.SendStatusSet[0].Message}`)
switch (errCode) {
case 'Ok':
return result.RequestId
case 'InvalidParameterValue.IncorrectPhoneNumber':
error.name = 'InvalidReceiver'
case 'LimitExceeded.DeliveryFrequencyLimit':
case 'LimitExceeded.PhoneNumberDailyLimit':
case 'LimitExceeded.PhoneNumberThirtySecondLimit':
case 'LimitExceeded.PhoneNumberOneHourLimit':
error.name = 'RateLimit'
}
throw error
}
}

View File

@ -6442,7 +6442,14 @@
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react-dom@^16.9.8", "@types/react-dom@^17.0.0":
"@types/react-dom@^16.9.8":
version "16.9.17"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.17.tgz#29100cbcc422d7b7dba7de24bb906de56680dd34"
integrity sha512-qSRyxEsrm5btPXnowDOs5jSkgT8ldAA0j6Qp+otHUh+xHzy3sXmgNfyhucZjAjkgpdAUw9rJe0QRtX/l+yaS4g==
dependencies:
"@types/react" "^16"
"@types/react-dom@^17.0.0":
version "17.0.11"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
@ -6502,7 +6509,7 @@
"@types/history" "*"
"@types/react" "*"
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^16.9.43", "@types/react@^17.0.0":
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^17.0.0":
version "17.0.34"
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
@ -6511,6 +6518,15 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^16", "@types/react@^16.9.43":
version "16.14.34"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.34.tgz#d129324ffda312044e1c47aab18696e4ed493282"
integrity sha512-b99nWeGGReLh6aKBppghVqp93dFJtgtDOzc8NXM6hewD8PQ2zZG5kBLgbx+VJr7Q7WBMjHxaIl3dwpwwPIUgyA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@ -17453,6 +17469,13 @@ node-fetch@2.6.7, node-fetch@^2.6.7:
dependencies:
whatwg-url "^5.0.0"
node-fetch@^2.2.0:
version "2.6.8"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e"
integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==
dependencies:
whatwg-url "^5.0.0"
node-fetch@^2.6.1:
version "2.6.6"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
@ -23241,6 +23264,18 @@ temp@^0.8.1:
dependencies:
rimraf "~2.6.2"
tencentcloud-sdk-nodejs@^4.0.525:
version "4.0.525"
resolved "https://registry.yarnpkg.com/tencentcloud-sdk-nodejs/-/tencentcloud-sdk-nodejs-4.0.525.tgz#b0b6d6caf6de5a5aeeda4f1f172ea7ecc447b9be"
integrity sha512-Ym7DfnWsxtXpAd0sL3108dykO7q4hyTxrLeumPMHnzndGGWDqEcj6UMaZsylBOigYb2i423oJ5ePyPkDP5ot1g==
dependencies:
form-data "^3.0.0"
get-stream "^6.0.0"
https-proxy-agent "^5.0.0"
is-stream "^2.0.0"
node-fetch "^2.2.0"
tslib "1.13.0"
term-size@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
@ -23714,6 +23749,11 @@ tsconfig@^7.0.0:
strip-bom "^3.0.0"
strip-json-comments "^2.0.0"
tslib@1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
tslib@1.9.3:
version "1.9.3"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"