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:
parent
ddb174db1b
commit
b88adc5c11
@ -12,7 +12,8 @@
|
|||||||
"@nocobase/actions": "0.9.0-alpha.2",
|
"@nocobase/actions": "0.9.0-alpha.2",
|
||||||
"@nocobase/resourcer": "0.9.0-alpha.2",
|
"@nocobase/resourcer": "0.9.0-alpha.2",
|
||||||
"@nocobase/server": "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": {
|
"devDependencies": {
|
||||||
"@nocobase/test": "0.9.0-alpha.2"
|
"@nocobase/test": "0.9.0-alpha.2"
|
||||||
|
@ -10,4 +10,11 @@ export default {
|
|||||||
'Endpoint': '接入点',
|
'Endpoint': '接入点',
|
||||||
'Sign': '签名',
|
'Sign': '签名',
|
||||||
'Template code': '模板代码',
|
'Template code': '模板代码',
|
||||||
|
|
||||||
|
'Secret Id': 'Secret Id',
|
||||||
|
'Secret Key': 'Secret Key',
|
||||||
|
'Region': '地域',
|
||||||
|
'Sign name': '短信签名内容',
|
||||||
|
'Sms sdk app id': '短信应用 ID',
|
||||||
|
'Template Id': '短信模板 ID'
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { Registry } from "@nocobase/utils/client";
|
import { Registry } from "@nocobase/utils/client";
|
||||||
import SMSAliyun from './sms-aliyun';
|
import SMSAliyun from './sms-aliyun';
|
||||||
|
import SMSTencent from './sms-tencent';
|
||||||
|
|
||||||
const providerTypes: Registry<ISchema> = new Registry();
|
const providerTypes: Registry<ISchema> = new Registry();
|
||||||
|
|
||||||
providerTypes.register('sms-aliyun', SMSAliyun);
|
providerTypes.register('sms-aliyun', SMSAliyun);
|
||||||
|
providerTypes.register('sms-tencent', SMSTencent);
|
||||||
|
|
||||||
export default providerTypes;
|
export default providerTypes;
|
||||||
|
@ -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;
|
@ -38,6 +38,7 @@ const collection = {
|
|||||||
required: true,
|
required: true,
|
||||||
enum: [
|
enum: [
|
||||||
{ label: `{{t("Aliyun SMS", { ns: "${NAMESPACE}" })}}`, value: 'sms-aliyun' },
|
{ label: `{{t("Aliyun SMS", { ns: "${NAMESPACE}" })}}`, value: 'sms-aliyun' },
|
||||||
|
{ label: `{{t("Tencent SMS", { ns: "${NAMESPACE}" })}}`, value: 'sms-tencent' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -19,12 +19,7 @@ export async function create(context: Context, next: Next) {
|
|||||||
return context.throw(400, 'Invalid action type');
|
return context.throw(400, 'Invalid action type');
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProviderRepo = context.db.getRepository('verifications_providers');
|
const providerItem = await plugin.getDefault()
|
||||||
const providerItem = await ProviderRepo.findOne({
|
|
||||||
filter: {
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!providerItem) {
|
if (!providerItem) {
|
||||||
console.error(`[verification] no provider for action (${values.type}) provided`);
|
console.error(`[verification] no provider for action (${values.type}) provided`);
|
||||||
return context.throw(500);
|
return context.throw(500);
|
||||||
@ -32,7 +27,7 @@ export async function create(context: Context, next: Next) {
|
|||||||
|
|
||||||
const receiver = interceptor.getReceiver(context);
|
const receiver = interceptor.getReceiver(context);
|
||||||
if (!receiver) {
|
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 VerificationModel = context.db.getModel('verifications');
|
||||||
const record = await VerificationModel.findOne({
|
const record = await VerificationModel.findOne({
|
||||||
@ -70,7 +65,7 @@ export async function create(context: Context, next: Next) {
|
|||||||
switch (error.name) {
|
switch (error.name) {
|
||||||
case 'InvalidReceiver':
|
case 'InvalidReceiver':
|
||||||
// TODO: message should consider email and other providers, maybe use "receiver"
|
// 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':
|
case 'RateLimit':
|
||||||
return context.throw(429, context.t('You are trying so frequently, please slow down', { ns: namespace }));
|
return context.throw(429, context.t('You are trying so frequently, please slow down', { ns: namespace }));
|
||||||
default:
|
default:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export const PROVIDER_TYPE_SMS_ALIYUN = 'sms-aliyun';
|
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_UNUSED = 0;
|
||||||
export const CODE_STATUS_USED = 1;
|
export const CODE_STATUS_USED = 1;
|
||||||
|
@ -3,5 +3,5 @@ export default {
|
|||||||
'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入',
|
'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入',
|
||||||
"Please don't retry in {{time}} seconds": '请 {{time}} 秒后再试',
|
"Please don't retry in {{time}} seconds": '请 {{time}} 秒后再试',
|
||||||
'You are trying so frequently, please slow down': '您的操作太频繁,请稍后再试',
|
'You are trying so frequently, please slow down': '您的操作太频繁,请稍后再试',
|
||||||
'Verification code is invalid': '无效的验证码'
|
'Verification code is invalid': '无效的验证码',
|
||||||
};
|
};
|
||||||
|
@ -3,25 +3,26 @@ import path from 'path';
|
|||||||
import { requireModule } from '@nocobase/utils';
|
import { requireModule } from '@nocobase/utils';
|
||||||
|
|
||||||
import Plugin from '../Plugin';
|
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 {
|
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 {
|
interface Providers {
|
||||||
[key: string]: typeof Provider
|
[key: string]: typeof Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(plugin: Plugin, more: Providers = {}) {
|
export default function (plugin: Plugin, more: Providers = {}) {
|
||||||
const { providers } = plugin;
|
const { providers } = plugin;
|
||||||
|
|
||||||
const natives = [
|
const natives = [
|
||||||
PROVIDER_TYPE_SMS_ALIYUN
|
PROVIDER_TYPE_SMS_ALIYUN,
|
||||||
|
PROVIDER_TYPE_SMS_TENCENT
|
||||||
].reduce((result, key) => Object.assign(result, {
|
].reduce((result, key) => Object.assign(result, {
|
||||||
[key]: requireModule(path.isAbsolute(key) ? key : path.join(__dirname, key)) as typeof Provider
|
[key]: requireModule(path.isAbsolute(key) ? key : path.join(__dirname, key)) as typeof Provider
|
||||||
}), {} as Providers);
|
}), {} as Providers);
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
44
yarn.lock
44
yarn.lock
@ -6442,7 +6442,14 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
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"
|
version "17.0.11"
|
||||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
|
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
|
||||||
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
|
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
|
||||||
@ -6502,7 +6509,7 @@
|
|||||||
"@types/history" "*"
|
"@types/history" "*"
|
||||||
"@types/react" "*"
|
"@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"
|
version "17.0.34"
|
||||||
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
|
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
|
||||||
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
|
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
|
||||||
@ -6511,6 +6518,15 @@
|
|||||||
"@types/scheduler" "*"
|
"@types/scheduler" "*"
|
||||||
csstype "^3.0.2"
|
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":
|
"@types/resolve@1.17.1":
|
||||||
version "1.17.1"
|
version "1.17.1"
|
||||||
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
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:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
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:
|
node-fetch@^2.6.1:
|
||||||
version "2.6.6"
|
version "2.6.6"
|
||||||
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
|
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
|
||||||
@ -23241,6 +23264,18 @@ temp@^0.8.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
rimraf "~2.6.2"
|
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:
|
term-size@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
|
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-bom "^3.0.0"
|
||||||
strip-json-comments "^2.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:
|
tslib@1.9.3:
|
||||||
version "1.9.3"
|
version "1.9.3"
|
||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||||
|
Loading…
Reference in New Issue
Block a user