diff --git a/docker/app-postgres/docker-compose.yml b/docker/app-postgres/docker-compose.yml
index 37790dc0f..d3c12c903 100644
--- a/docker/app-postgres/docker-compose.yml
+++ b/docker/app-postgres/docker-compose.yml
@@ -1,7 +1,4 @@
version: "3"
-networks:
- nocobase:
- driver: bridge
services:
app:
image: nocobase/nocobase:0.8.0-alpha.13
@@ -23,10 +20,8 @@ services:
postgres:
image: postgres:10
restart: always
- networks:
- - nocobase
command: postgres -c wal_level=logical
environment:
POSTGRES_USER: nocobase
POSTGRES_DB: nocobase
- POSTGRES_PASSWORD: nocobase
\ No newline at end of file
+ POSTGRES_PASSWORD: nocobase
diff --git a/packages/app/client/src/plugins/verification.ts b/packages/app/client/src/plugins/verification.ts
new file mode 100644
index 000000000..e987ff3c9
--- /dev/null
+++ b/packages/app/client/src/plugins/verification.ts
@@ -0,0 +1 @@
+export { default } from '@nocobase/plugin-verification/client';
diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts
index 554885439..12aac6676 100644
--- a/packages/core/client/src/locale/zh_CN.ts
+++ b/packages/core/client/src/locale/zh_CN.ts
@@ -425,6 +425,7 @@ export default {
'DESC': '降序',
'Add sort field': '添加排序字段',
'ID': 'ID',
+ 'Identifier for program usage. Support letters, numbers and underscores, must start with an letter.': '用于程序使用的标识符,支持字母、数字和下划线,必须以字母开头。',
'Drawer': '抽屉',
'Dialog': '对话框',
'Delete action': '删除操作',
diff --git a/packages/core/client/src/user/EditProfile.tsx b/packages/core/client/src/user/EditProfile.tsx
index ff4d7f48d..03161d02a 100644
--- a/packages/core/client/src/user/EditProfile.tsx
+++ b/packages/core/client/src/user/EditProfile.tsx
@@ -72,6 +72,22 @@ const schema: ISchema = {
'x-component': 'Input',
required: true,
},
+ email: {
+ type: 'string',
+ title: '{{t("Email")}}',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ 'x-validator': 'email',
+ required: true,
+ },
+ phone: {
+ type: 'string',
+ title: '{{t("Phone")}}',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ 'x-validator': 'phone',
+ required: true,
+ },
footer: {
'x-component': 'Action.Drawer.Footer',
type: 'void',
diff --git a/packages/plugins/file-manager/src/client/FileStorage.tsx b/packages/plugins/file-manager/src/client/FileStorage.tsx
index 9ab11cf16..46a260cff 100644
--- a/packages/plugins/file-manager/src/client/FileStorage.tsx
+++ b/packages/plugins/file-manager/src/client/FileStorage.tsx
@@ -6,19 +6,12 @@ import { useTranslation } from 'react-i18next';
import { storageSchema } from './schemas/storage';
import { StorageOptions } from './StorageOptions';
-const schema = {
- type: 'object',
- properties: {
- [uid()]: storageSchema,
- },
-};
-
export const FileStoragePane = () => {
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
return (
-
+
);
};
diff --git a/packages/plugins/users/src/collections/users.ts b/packages/plugins/users/src/collections/users.ts
index 6d4828261..17efb0557 100644
--- a/packages/plugins/users/src/collections/users.ts
+++ b/packages/plugins/users/src/collections/users.ts
@@ -38,7 +38,7 @@ export default {
title: '{{t("Email")}}',
'x-component': 'Input',
'x-validator': 'email',
- require: true,
+ required: true,
},
},
{
@@ -51,7 +51,7 @@ export default {
title: '{{t("Phone")}}',
'x-component': 'Input',
'x-validator': 'phone',
- require: true,
+ required: true,
},
},
{
diff --git a/packages/plugins/users/src/server.ts b/packages/plugins/users/src/server.ts
index 96f66567d..808c8bb99 100644
--- a/packages/plugins/users/src/server.ts
+++ b/packages/plugins/users/src/server.ts
@@ -97,6 +97,8 @@ export default class UsersPlugin extends Plugin {
publicActions.forEach((action) => this.app.acl.allow('users', action));
loggedInActions.forEach((action) => this.app.acl.allow('users', action, 'loggedIn'));
+
+ this.app.on('beforeStart', () => this.initVerification());
}
async load() {
@@ -113,79 +115,6 @@ export default class UsersPlugin extends Plugin {
});
initAuthenticators(this);
-
- // TODO(module): should move to preset
- const verificationPlugin = this.app.getPlugin('verification') as any;
- if (verificationPlugin && process.env.DEFAULT_SMS_VERIFY_CODE_PROVIDER) {
- verificationPlugin.interceptors.register('users:signin', {
- manual: true,
- provider: process.env.DEFAULT_SMS_VERIFY_CODE_PROVIDER,
- getReceiver(ctx) {
- return ctx.action.params.values.phone;
- },
- expiresIn: 120,
- validate: async (ctx, phone) => {
- if (!phone) {
- throw new Error(ctx.t('Not a valid cellphone number, please re-enter'));
- }
- const User = this.db.getCollection('users');
- const exists = await User.model.count({
- where: {
- phone,
- },
- });
- if (!exists) {
- throw new Error(ctx.t('The phone number is not registered, please register first', { ns: namespace }));
- }
-
- return true;
- },
- });
-
- verificationPlugin.interceptors.register('users:signup', {
- provider: process.env.DEFAULT_SMS_VERIFY_CODE_PROVIDER,
- getReceiver(ctx) {
- return ctx.action.params.values.phone;
- },
- expiresIn: 120,
- validate: async (ctx, phone) => {
- if (!phone) {
- throw new Error(ctx.t('Not a valid cellphone number, please re-enter', { ns: namespace }));
- }
- const User = this.db.getCollection('users');
- const exists = await User.model.count({
- where: {
- phone,
- },
- });
- if (exists) {
- throw new Error(ctx.t('The phone number has been registered, please login directly', { ns: namespace }));
- }
-
- return true;
- },
- });
-
- this.authenticators.register('sms', (ctx, next) =>
- verificationPlugin.intercept(ctx, async () => {
- const { values } = ctx.action.params;
-
- const User = ctx.db.getCollection('users');
- const user = await User.model.findOne({
- where: {
- phone: values.phone,
- },
- });
- if (!user) {
- return ctx.throw(404, ctx.t('The phone number is incorrect, please re-enter', { ns: namespace }));
- }
-
- ctx.state.currentUser = user;
-
- return next();
- }),
- );
- }
}
getInstallingData(options: any = {}) {
@@ -218,4 +147,79 @@ export default class UsersPlugin extends Plugin {
await repo.db2cm('users');
}
}
+
+ // TODO(module): should move to preset or dynamic configuration panel
+ async initVerification() {
+ const verificationPlugin = this.app.getPlugin('verification') as any;
+ if (!verificationPlugin) {
+ return;
+ }
+
+ verificationPlugin.interceptors.register('users:signin', {
+ manual: true,
+ getReceiver(ctx) {
+ return ctx.action.params.values.phone;
+ },
+ expiresIn: 120,
+ validate: async (ctx, phone) => {
+ if (!phone) {
+ throw new Error(ctx.t('Not a valid cellphone number, please re-enter'));
+ }
+ const User = this.db.getCollection('users');
+ const exists = await User.model.count({
+ where: {
+ phone,
+ },
+ });
+ if (!exists) {
+ throw new Error(ctx.t('The phone number is not registered, please register first', { ns: namespace }));
+ }
+
+ return true;
+ },
+ });
+
+ verificationPlugin.interceptors.register('users:signup', {
+ getReceiver(ctx) {
+ return ctx.action.params.values.phone;
+ },
+ expiresIn: 120,
+ validate: async (ctx, phone) => {
+ if (!phone) {
+ throw new Error(ctx.t('Not a valid cellphone number, please re-enter', { ns: namespace }));
+ }
+ const User = this.db.getCollection('users');
+ const exists = await User.model.count({
+ where: {
+ phone,
+ },
+ });
+ if (exists) {
+ throw new Error(ctx.t('The phone number has been registered, please login directly', { ns: namespace }));
+ }
+
+ return true;
+ },
+ });
+
+ this.authenticators.register('sms', (ctx, next) =>
+ verificationPlugin.intercept(ctx, async () => {
+ const { values } = ctx.action.params;
+
+ const User = ctx.db.getCollection('users');
+ const user = await User.model.findOne({
+ where: {
+ phone: values.phone,
+ },
+ });
+ if (!user) {
+ return ctx.throw(404, ctx.t('The phone number is incorrect, please re-enter', { ns: namespace }));
+ }
+
+ ctx.state.currentUser = user;
+
+ return next();
+ }),
+ );
+ }
}
diff --git a/packages/plugins/verification/client.d.ts b/packages/plugins/verification/client.d.ts
new file mode 100644
index 000000000..765db9222
--- /dev/null
+++ b/packages/plugins/verification/client.d.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+export * from './lib/client';
+export { default } from './lib/client';
+
diff --git a/packages/plugins/verification/client.js b/packages/plugins/verification/client.js
new file mode 100644
index 000000000..238820257
--- /dev/null
+++ b/packages/plugins/verification/client.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+var _index = _interopRequireWildcard(require("./lib/client"));
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {};
+Object.defineProperty(exports, "default", {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ }
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ }
+ });
+});
diff --git a/packages/plugins/verification/server.d.ts b/packages/plugins/verification/server.d.ts
new file mode 100644
index 000000000..e70edb928
--- /dev/null
+++ b/packages/plugins/verification/server.d.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+export * from './lib/server';
+export { default } from './lib/server';
+
diff --git a/packages/plugins/verification/server.js b/packages/plugins/verification/server.js
new file mode 100644
index 000000000..d06a7eb92
--- /dev/null
+++ b/packages/plugins/verification/server.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+var _index = _interopRequireWildcard(require("./lib/server"));
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {};
+Object.defineProperty(exports, "default", {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ }
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ }
+ });
+});
diff --git a/packages/plugins/verification/src/client/ProviderOptions.tsx b/packages/plugins/verification/src/client/ProviderOptions.tsx
new file mode 100644
index 000000000..67a74ecf2
--- /dev/null
+++ b/packages/plugins/verification/src/client/ProviderOptions.tsx
@@ -0,0 +1,22 @@
+import React, { useEffect, useState } from 'react';
+import { FormLayout } from '@formily/antd';
+import { Field } from '@formily/core';
+import { observer, RecursionField, Schema, useField, useForm } from '@formily/react';
+
+import providerTypes from './providerTypes';
+
+
+export default observer((props) => {
+ const form = useForm();
+ const field = useField();
+ const [s, setSchema] = useState(new Schema({}));
+ useEffect(() => {
+ form.clearFormGraph('options.*');
+ setSchema(new Schema(providerTypes.get(form.values.type) || {}));
+ }, [form.values.type]);
+ return (
+
+
+
+ );
+});
diff --git a/packages/plugins/verification/src/client/Shortcut.tsx b/packages/plugins/verification/src/client/Shortcut.tsx
new file mode 100644
index 000000000..faca2c388
--- /dev/null
+++ b/packages/plugins/verification/src/client/Shortcut.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { CheckCircleOutlined } from '@ant-design/icons';
+import { PluginManager } from '@nocobase/client';
+import { NAMESPACE } from './locale';
+
+export const Shortcut = () => {
+ const { t } = useTranslation();
+ const history = useHistory();
+ return (
+ }
+ title={t('Verification', { ns: NAMESPACE })}
+ onClick={() => {
+ history.push('/admin/settings/verification/providers');
+ }}
+ />
+ );
+};
diff --git a/packages/plugins/verification/src/client/VerificationProviders.tsx b/packages/plugins/verification/src/client/VerificationProviders.tsx
new file mode 100644
index 000000000..18f730780
--- /dev/null
+++ b/packages/plugins/verification/src/client/VerificationProviders.tsx
@@ -0,0 +1,19 @@
+import { SchemaComponent } from "@nocobase/client";
+import React from "react";
+import { Card } from 'antd';
+
+import providers from "./schemas/providers";
+import ProviderOptions from "./ProviderOptions";
+
+export function VerificationProviders() {
+ return (
+
+
+
+ );
+};
diff --git a/packages/plugins/verification/src/client/index.tsx b/packages/plugins/verification/src/client/index.tsx
new file mode 100644
index 000000000..b59c4e067
--- /dev/null
+++ b/packages/plugins/verification/src/client/index.tsx
@@ -0,0 +1,41 @@
+import React, { useContext } from 'react';
+
+import { PluginManagerContext, SettingsCenterProvider } from '@nocobase/client';
+
+import { NAMESPACE } from './locale';
+
+import { VerificationProviders } from './VerificationProviders';
+
+export { default as verificationProviderTypes } from './providerTypes';
+
+export default function(props) {
+ const ctx = useContext(PluginManagerContext);
+ return (
+
+
+ {props.children}
+
+
+ );
+};
+
+
diff --git a/packages/plugins/verification/src/client/locale/index.ts b/packages/plugins/verification/src/client/locale/index.ts
new file mode 100644
index 000000000..e8f0d5007
--- /dev/null
+++ b/packages/plugins/verification/src/client/locale/index.ts
@@ -0,0 +1,16 @@
+import { useTranslation } from 'react-i18next';
+import { i18n } from '@nocobase/client';
+
+import zhCN from './zh-CN';
+
+export const NAMESPACE = 'verification';
+
+i18n.addResources('zh-CN', NAMESPACE, zhCN);
+
+export function lang(key: string) {
+ return i18n.t(key, { ns: NAMESPACE });
+}
+
+export function useVerificationTranslation() {
+ return useTranslation(NAMESPACE);
+}
diff --git a/packages/plugins/verification/src/client/locale/zh-CN.ts b/packages/plugins/verification/src/client/locale/zh-CN.ts
new file mode 100644
index 000000000..e1ba7540b
--- /dev/null
+++ b/packages/plugins/verification/src/client/locale/zh-CN.ts
@@ -0,0 +1,13 @@
+export default {
+ 'Verification': '验证码',
+ 'Verification providers': '验证码提供商',
+ 'Provider type': '提供商类型',
+
+ // aliyun sms
+ 'Aliyun SMS': '阿里云短信服务',
+ 'Access Key ID': 'Access Key ID',
+ 'Access Key Secret': 'Access Key Secret',
+ 'Endpoint': '接入点',
+ 'Sign': '签名',
+ 'Template code': '模板代码',
+};
diff --git a/packages/plugins/verification/src/client/providerTypes/index.ts b/packages/plugins/verification/src/client/providerTypes/index.ts
new file mode 100644
index 000000000..ad9628e0b
--- /dev/null
+++ b/packages/plugins/verification/src/client/providerTypes/index.ts
@@ -0,0 +1,9 @@
+import { ISchema } from '@formily/react';
+import { Registry } from "@nocobase/utils/client";
+import SMSAliyun from './sms-aliyun';
+
+const providerTypes: Registry = new Registry();
+
+providerTypes.register('sms-aliyun', SMSAliyun);
+
+export default providerTypes;
diff --git a/packages/plugins/verification/src/client/providerTypes/sms-aliyun.ts b/packages/plugins/verification/src/client/providerTypes/sms-aliyun.ts
new file mode 100644
index 000000000..4d29c546c
--- /dev/null
+++ b/packages/plugins/verification/src/client/providerTypes/sms-aliyun.ts
@@ -0,0 +1,39 @@
+import { ISchema } from '@formily/react';
+
+import { NAMESPACE } from '../locale';
+
+export default {
+ type: 'object',
+ properties: {
+ accessKeyId: {
+ title: `{{t("Access Key ID", { ns: "${NAMESPACE}" })}}`,
+ type: 'string',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ accessKeySecret: {
+ title: `{{t("Access Key Secret", { ns: "${NAMESPACE}" })}}`,
+ type: 'string',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Password',
+ },
+ endpoint: {
+ title: `{{t("Endpoint", { ns: "${NAMESPACE}" })}}`,
+ type: 'string',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ sign: {
+ title: `{{t("Sign", { ns: "${NAMESPACE}" })}}`,
+ type: 'string',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ template: {
+ title: `{{t("Template code", { ns: "${NAMESPACE}" })}}`,
+ type: 'string',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ }
+ }
+} as ISchema;
diff --git a/packages/plugins/verification/src/client/schemas/providers.ts b/packages/plugins/verification/src/client/schemas/providers.ts
new file mode 100644
index 000000000..9f59533de
--- /dev/null
+++ b/packages/plugins/verification/src/client/schemas/providers.ts
@@ -0,0 +1,330 @@
+import { uid } from '@formily/shared';
+import { useActionContext, useRequest } from '@nocobase/client';
+import { NAMESPACE } from '../locale';
+
+const collection = {
+ name: 'verifications_providers',
+ fields: [
+ {
+ type: 'string',
+ name: 'id',
+ interface: 'input',
+ uiSchema: {
+ title: '{{t("ID")}}',
+ type: 'string',
+ 'x-component': 'Input',
+ required: true,
+ }
+ },
+ {
+ type: 'string',
+ name: 'title',
+ interface: 'input',
+ uiSchema: {
+ title: '{{t("Title")}}',
+ type: 'string',
+ 'x-component': 'Input',
+ required: true,
+ }
+ },
+ {
+ type: 'string',
+ name: 'type',
+ interface: 'select',
+ uiSchema: {
+ title: `{{t("Provider type", { ns: "${NAMESPACE}" })}}`,
+ type: 'string',
+ 'x-component': 'Select',
+ required: true,
+ enum: [
+ { label: `{{t("Aliyun SMS", { ns: "${NAMESPACE}" })}}`, value: 'sms-aliyun' },
+ ],
+ },
+ },
+ {
+ type: 'radio',
+ name: 'default',
+ interface: 'checkbox',
+ uiSchema: {
+ title: '{{t("Default")}}',
+ type: 'boolean',
+ 'x-component': 'Checkbox',
+ }
+ }
+ ]
+};
+
+export default {
+ type: 'void',
+ name: 'providers',
+ 'x-decorator': 'ResourceActionProvider',
+ 'x-decorator-props': {
+ collection,
+ resourceName: 'verifications_providers',
+ request: {
+ resource: 'verifications_providers',
+ action: 'list',
+ params: {
+ pageSize: 50,
+ sort: ['-default', 'id'],
+ appends: [],
+ },
+ },
+ },
+ 'x-component': 'CollectionProvider',
+ 'x-component-props': {
+ collection,
+ },
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {
+ style: {
+ marginBottom: 16,
+ },
+ },
+ properties: {
+ delete: {
+ type: 'void',
+ title: '{{t("Delete")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useBulkDestroyAction }}',
+ confirm: {
+ title: "{{t('Delete')}}",
+ content: "{{t('Are you sure you want to delete it?')}}",
+ },
+ },
+ },
+ create: {
+ type: 'void',
+ title: '{{t("Add new")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ },
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'Form',
+ 'x-decorator-props': {
+ useValues(options) {
+ const ctx = useActionContext();
+ return useRequest(
+ () =>
+ Promise.resolve({
+ data: {
+ name: `s_${uid()}`,
+ },
+ }),
+ { ...options, refreshDeps: [ctx.visible] },
+ );
+ },
+ },
+ title: '{{t("Add new")}}',
+ properties: {
+ id: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ description: '{{t("Identifier for program usage. Support letters, numbers and underscores, must start with an letter.")}}',
+ },
+ title: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ type: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ options: {
+ type: 'object',
+ 'x-component': 'ProviderOptions',
+ },
+ default: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ submit: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ cm.useCreateAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ table: {
+ type: 'void',
+ 'x-uid': 'input',
+ 'x-component': 'Table.Void',
+ 'x-component-props': {
+ rowKey: 'id',
+ rowSelection: {
+ type: 'checkbox',
+ },
+ useDataSource: '{{ cm.useDataSourceFromRAC }}',
+ },
+ properties: {
+ id: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ id: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ title: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ title: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ type: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ type: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ default: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ default: {
+ type: 'boolean',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ }
+ }
+ },
+ actions: {
+ type: 'void',
+ title: '{{t("Actions")}}',
+ 'x-component': 'Table.Column',
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'Space',
+ 'x-component-props': {
+ split: '|',
+ },
+ properties: {
+ update: {
+ type: 'void',
+ title: '{{t("Edit")}}',
+ 'x-component': 'Action.Link',
+ 'x-component-props': {
+ type: 'primary',
+ },
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'Form',
+ 'x-decorator-props': {
+ useValues: '{{ cm.useValuesFromRecord }}',
+ },
+ title: '{{t("Edit")}}',
+ properties: {
+ id: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ title: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ type: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ 'x-disabled': true,
+ },
+ options: {
+ type: 'object',
+ 'x-component': 'ProviderOptions',
+ },
+ default: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ submit: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ cm.useUpdateAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ delete: {
+ type: 'void',
+ title: '{{ t("Delete") }}',
+ 'x-component': 'Action.Link',
+ 'x-component-props': {
+ confirm: {
+ title: "{{t('Delete record')}}",
+ content: "{{t('Are you sure you want to delete it?')}}",
+ },
+ useAction: '{{cm.useDestroyAction}}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
diff --git a/packages/plugins/verification/src/index.ts b/packages/plugins/verification/src/index.ts
index 0f8a6e2f7..7ddad5814 100644
--- a/packages/plugins/verification/src/index.ts
+++ b/packages/plugins/verification/src/index.ts
@@ -1,5 +1 @@
-export * from './constants';
-export { Provider } from './providers';
-export { Interceptor, default } from './Plugin';
-
-export const namespace = require('../package.json').name;
+export { default } from './server';
diff --git a/packages/plugins/verification/src/Plugin.ts b/packages/plugins/verification/src/server/Plugin.ts
similarity index 89%
rename from packages/plugins/verification/src/Plugin.ts
rename to packages/plugins/verification/src/server/Plugin.ts
index 6dbec4579..c487a7c75 100644
--- a/packages/plugins/verification/src/Plugin.ts
+++ b/packages/plugins/verification/src/server/Plugin.ts
@@ -14,7 +14,6 @@ import initProviders, { Provider } from './providers';
export interface Interceptor {
manual?: boolean;
- provider: string;
expiresIn?: number;
getReceiver(ctx): string;
getCode?(ctx): string;
@@ -56,7 +55,7 @@ export default class VerificationPlugin extends Plugin {
});
if (!item) {
- return context.throw(400, { code: 'InvalidSMSCode', message: 'verify by sms code failed' });
+ return context.throw(400, { code: 'InvalidVerificationCode', message: context.t('Verification code is invalid', { ns: namespace }) });
}
// TODO: code should be removed if exists in values
@@ -103,6 +102,7 @@ export default class VerificationPlugin extends Plugin {
sign: INIT_ALI_SMS_VERIFY_CODE_SIGN,
template: INIT_ALI_SMS_VERIFY_CODE_TEMPLATE,
},
+ default: true
},
});
}
@@ -133,5 +133,15 @@ export default class VerificationPlugin extends Plugin {
});
app.acl.allow('verifications', 'create');
+ app.acl.allow('verifications_providers', '*', 'allowConfigure');
+ }
+
+ async getDefault() {
+ const providerRepo = this.db.getRepository('verifications_providers');
+ return providerRepo.findOne({
+ filter: {
+ default: true,
+ }
+ });
}
}
diff --git a/packages/plugins/verification/src/__tests__/Plugin.test.ts b/packages/plugins/verification/src/server/__tests__/Plugin.test.ts
similarity index 98%
rename from packages/plugins/verification/src/__tests__/Plugin.test.ts
rename to packages/plugins/verification/src/server/__tests__/Plugin.test.ts
index b98285dca..d27ea159c 100644
--- a/packages/plugins/verification/src/__tests__/Plugin.test.ts
+++ b/packages/plugins/verification/src/server/__tests__/Plugin.test.ts
@@ -32,6 +32,7 @@ describe('verification > Plugin', () => {
provider = await VerificationProviderModel.create({
id: 'fake1',
type: 'fake',
+ default: true
});
});
@@ -40,7 +41,6 @@ describe('verification > Plugin', () => {
describe('auto intercept', () => {
beforeEach(async () => {
plugin.interceptors.register('authors:create', {
- provider: 'fake1',
getReceiver(ctx) {
return ctx.action.params.values.phone;
},
@@ -103,7 +103,6 @@ describe('verification > Plugin', () => {
beforeEach(async () => {
plugin.interceptors.register('authors:create', {
manual: true,
- provider: 'fake1',
getReceiver(ctx) {
return ctx.action.params.values.phone;
},
@@ -131,7 +130,6 @@ describe('verification > Plugin', () => {
describe('validate', () => {
beforeEach(async () => {
plugin.interceptors.register('authors:create', {
- provider: 'fake1',
getReceiver(ctx) {
return ctx.action.params.values.phone;
},
diff --git a/packages/plugins/verification/src/__tests__/collections/authors.ts b/packages/plugins/verification/src/server/__tests__/collections/authors.ts
similarity index 100%
rename from packages/plugins/verification/src/__tests__/collections/authors.ts
rename to packages/plugins/verification/src/server/__tests__/collections/authors.ts
diff --git a/packages/plugins/verification/src/__tests__/index.ts b/packages/plugins/verification/src/server/__tests__/index.ts
similarity index 100%
rename from packages/plugins/verification/src/__tests__/index.ts
rename to packages/plugins/verification/src/server/__tests__/index.ts
diff --git a/packages/plugins/verification/src/actions/index.ts b/packages/plugins/verification/src/server/actions/index.ts
similarity index 100%
rename from packages/plugins/verification/src/actions/index.ts
rename to packages/plugins/verification/src/server/actions/index.ts
diff --git a/packages/plugins/verification/src/actions/verifications.ts b/packages/plugins/verification/src/server/actions/verifications.ts
similarity index 92%
rename from packages/plugins/verification/src/actions/verifications.ts
rename to packages/plugins/verification/src/server/actions/verifications.ts
index 301c18e1f..055e1763e 100644
--- a/packages/plugins/verification/src/actions/verifications.ts
+++ b/packages/plugins/verification/src/server/actions/verifications.ts
@@ -21,7 +21,9 @@ export async function create(context: Context, next: Next) {
const ProviderRepo = context.db.getRepository('verifications_providers');
const providerItem = await ProviderRepo.findOne({
- filterByTk: interceptor.provider
+ filter: {
+ default: true
+ }
});
if (!providerItem) {
console.error(`[verification] no provider for action (${values.type}) provided`);
@@ -30,7 +32,7 @@ export async function create(context: Context, next: Next) {
const receiver = interceptor.getReceiver(context);
if (!receiver) {
- return context.throw(400, { code: 'InvalidReceiver', message: 'Invalid receiver' });
+ 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({
@@ -68,7 +70,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, 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:
diff --git a/packages/plugins/verification/src/collections/verifications.ts b/packages/plugins/verification/src/server/collections/verifications.ts
similarity index 100%
rename from packages/plugins/verification/src/collections/verifications.ts
rename to packages/plugins/verification/src/server/collections/verifications.ts
diff --git a/packages/plugins/verification/src/collections/verifications_providers.ts b/packages/plugins/verification/src/server/collections/verifications_providers.ts
similarity index 84%
rename from packages/plugins/verification/src/collections/verifications_providers.ts
rename to packages/plugins/verification/src/server/collections/verifications_providers.ts
index 6e37f643d..fa4c4b075 100644
--- a/packages/plugins/verification/src/collections/verifications_providers.ts
+++ b/packages/plugins/verification/src/server/collections/verifications_providers.ts
@@ -17,6 +17,10 @@ export default {
{
type: 'jsonb',
name: 'options'
+ },
+ {
+ type: 'radio',
+ name: 'default'
}
]
};
diff --git a/packages/plugins/verification/src/constants.ts b/packages/plugins/verification/src/server/constants.ts
similarity index 100%
rename from packages/plugins/verification/src/constants.ts
rename to packages/plugins/verification/src/server/constants.ts
diff --git a/packages/plugins/verification/src/server/index.ts b/packages/plugins/verification/src/server/index.ts
new file mode 100644
index 000000000..5f205b2f2
--- /dev/null
+++ b/packages/plugins/verification/src/server/index.ts
@@ -0,0 +1,5 @@
+export * from './constants';
+export { Provider } from './providers';
+export { Interceptor, default } from './Plugin';
+
+export const namespace = require('../../package.json').name;
diff --git a/packages/plugins/verification/src/locale/index.ts b/packages/plugins/verification/src/server/locale/index.ts
similarity index 100%
rename from packages/plugins/verification/src/locale/index.ts
rename to packages/plugins/verification/src/server/locale/index.ts
diff --git a/packages/plugins/verification/src/locale/zh-CN.ts b/packages/plugins/verification/src/server/locale/zh-CN.ts
similarity index 81%
rename from packages/plugins/verification/src/locale/zh-CN.ts
rename to packages/plugins/verification/src/server/locale/zh-CN.ts
index 213a21608..a05b96826 100644
--- a/packages/plugins/verification/src/locale/zh-CN.ts
+++ b/packages/plugins/verification/src/server/locale/zh-CN.ts
@@ -2,5 +2,6 @@ export default {
'Verification send failed, please try later or contact to administrator': '验证码发送失败,请稍后重试或联系管理员',
'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入',
"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': '无效的验证码'
};
diff --git a/packages/plugins/verification/src/providers/index.ts b/packages/plugins/verification/src/server/providers/index.ts
similarity index 100%
rename from packages/plugins/verification/src/providers/index.ts
rename to packages/plugins/verification/src/server/providers/index.ts
diff --git a/packages/plugins/verification/src/providers/sms-aliyun.ts b/packages/plugins/verification/src/server/providers/sms-aliyun.ts
similarity index 97%
rename from packages/plugins/verification/src/providers/sms-aliyun.ts
rename to packages/plugins/verification/src/server/providers/sms-aliyun.ts
index f2f0c1fcd..4ed440dfb 100644
--- a/packages/plugins/verification/src/providers/sms-aliyun.ts
+++ b/packages/plugins/verification/src/server/providers/sms-aliyun.ts
@@ -49,6 +49,7 @@ export default class extends Provider {
console.error(body);
return Promise.reject(err);
+ // case 'isp.RAM_PERMISSION_DENY':
default:
// should not let user to know
console.error(body);
diff --git a/packages/plugins/workflow/src/client/WorkflowShortcut.tsx b/packages/plugins/workflow/src/client/WorkflowShortcut.tsx
index 885393ae0..ae070ba61 100644
--- a/packages/plugins/workflow/src/client/WorkflowShortcut.tsx
+++ b/packages/plugins/workflow/src/client/WorkflowShortcut.tsx
@@ -13,7 +13,6 @@ import { ExecutionLink } from './ExecutionLink';
import { lang } from './locale';
-
export const WorkflowPane = () => {
return (