fix(public-form): authorization validation and content optimization (#5330)

* fix: public form

* refactor: locale improve

* refactor: markdown locale

* fix: translation

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
Katherine 2024-09-26 22:06:34 +08:00 committed by GitHub
parent 51423a37ac
commit 4a5aa7542b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 87 additions and 92 deletions

View File

@ -5,8 +5,8 @@
"dependencies": {},
"displayName": "Public forms",
"displayName.zh-CN": "公开表单",
"description": "Provides a public form that allows users to submit information without requiring registration or login.",
"description.zh-CN": "提供了一种无需用户注册或登录即可提交信息的表单",
"description": "Share public forms externally to collect information from anonymous users",
"description.zh-CN": "对外分享公开表单,向匿名用户收集信息。",
"license": "AGPL-3.0",
"homepage": "https://docs.nocobase.com/handbook/public-form",
"homepage.zh-CN": "https://docs-cn.nocobase.com/public-form",

View File

@ -55,17 +55,6 @@ export const publicFormsCollection = {
'x-component': 'DataSourceCollectionCascader',
},
},
{
type: 'boolean',
name: 'enabledPassword',
interface: 'checkbox',
uiSchema: {
type: 'string',
title: `{{t("Enable password",{ns:"${NAMESPACE}"})}}`,
'x-component': 'Checkbox',
default: true,
},
},
{
type: 'password',
name: 'password',

View File

@ -79,25 +79,10 @@ export function AdminPublicFormPage() {
<SchemaComponent
schema={{
properties: {
enabledPassword: {
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
title: t('Enabled password'),
},
password: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input.Password',
title: t('Password'),
'x-reactions': {
dependencies: ['enabledPassword'],
fulfill: {
state: {
required: '{{$deps[0]}}',
},
},
},
},
},
}}
@ -110,8 +95,8 @@ export function AdminPublicFormPage() {
).open({
initialValues: { ...others },
});
const { enabledPassword, password } = values;
await handleEditPublicForm({ enabledPassword, password });
const { password } = values;
await handleEditPublicForm({ password });
};
const handleCopyLink = () => {

View File

@ -87,7 +87,6 @@ const PublicFormMessageProvider = ({ children }) => {
if (f) {
f.visible = visible;
f.hidden = !visible;
f.decoratorProps.title = null;
}
});
};
@ -95,6 +94,14 @@ const PublicFormMessageProvider = ({ children }) => {
useEffect(() => {
toggleFieldVisibility('success', showMessage);
toggleFieldVisibility('form', !showMessage);
if (!showMessage) {
field.form.query('promptMessage').take((f) => {
if (f) {
f.visible = false;
f.hidden = true;
}
});
}
}, [showMessage]);
return (

View File

@ -12,38 +12,44 @@ import { uid } from '@formily/shared';
import {
useActionContext,
useAPIClient,
useBlockRequestContext,
useCollection,
useDataBlockRequest,
useDataBlockResource,
usePlugin,
useBlockRequestContext,
} from '@nocobase/client';
import { App as AntdApp } from 'antd';
import PluginPublicFormsClient from '..';
const initialSchema = (values, formSchema) => {
const initialSchema = (values, formSchema, t) => {
return {
type: 'void',
name: uid(),
'x-decorator': 'PublicFormMessageProvider',
properties: {
form: formSchema,
promptMessage: {
type: 'void',
'x-component': 'h3',
'x-component-props': {
style: { margin: '10px 0px 10px' },
children: '{{ t("Prompt After successful submission",{ns:"public-forms"})}}',
},
},
success: {
type: 'void',
'x-editable': false,
'x-toolbar-props': {
draggable: false,
},
'x-settings': 'blockSettings:markdown',
'x-settings': 'blockSettings:publicMarkdown',
'x-component': 'Markdown.Void',
'x-decorator': 'CardItem',
'x-component-props': {
content: 'Submitted Successfully',
content: t('# Submitted successfully!\nThis is a demo text, **supports Markdown syntax**.'),
},
'x-decorator-props': {
name: 'markdown',
engine: 'handlebars',
title: '{{ t("After successful submission",{ns:"public-forms"})}}',
},
},
},
@ -82,6 +88,7 @@ export const useSubmitActionProps = () => {
collection,
dataSource,
}),
plugin.t.bind(plugin),
);
schema['x-uid'] = key;
await resource.create({

View File

@ -12,7 +12,7 @@ import { AdminPublicFormList } from './components/AdminPublicFormList';
import { AdminPublicFormPage } from './components/AdminPublicFormPage';
import { PublicFormPage } from './components/PublicFormPage';
import { formSchemaCallback } from './schemas/formSchemaCallback';
import { publicFormBlockSettings } from './settings';
import { publicFormBlockSettings, publicMarkdownBlockSettings } from './settings';
import { NAMESPACE } from './locale';
export class PluginPublicFormsClient extends Plugin {
protected formTypes = new Map();
@ -40,6 +40,8 @@ export class PluginPublicFormsClient extends Plugin {
async load() {
this.app.schemaSettingsManager.add(publicFormBlockSettings);
this.app.schemaSettingsManager.add(publicMarkdownBlockSettings);
this.registerFormType('form', {
label: 'Form',
uiSchema: formSchemaCallback,

View File

@ -50,24 +50,10 @@ export const createActionSchema = {
'x-decorator': 'FormItem',
'x-component': 'CollectionField',
},
enabledPassword: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'CollectionField',
default: false,
},
password: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'CollectionField',
'x-reactions': {
dependencies: ['enabledPassword'],
fulfill: {
state: {
required: '{{$deps[0]}}',
},
},
},
},
enabled: {
type: 'string',

View File

@ -52,24 +52,11 @@ export const editActionSchema = {
'x-decorator': 'FormItem',
'x-component': 'CollectionField',
},
enabledPassword: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'CollectionField',
default: false,
},
password: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'CollectionField',
'x-reactions': {
dependencies: ['enabledPassword'],
fulfill: {
state: {
required: '{{$deps[0]}}',
},
},
},
},
enabled: {
type: 'string',

View File

@ -13,7 +13,10 @@ import {
SchemaSettingsBlockTitleItem,
SchemaSettingsLinkageRules,
useCollection,
SchemaSettingsRenderEngine,
} from '@nocobase/client';
import { useField } from '@formily/react';
import { useTranslation } from 'react-i18next';
export const publicFormBlockSettings = new SchemaSettings({
name: 'blockSettings:publicForm',
@ -38,3 +41,28 @@ export const publicFormBlockSettings = new SchemaSettings({
},
],
});
export const publicMarkdownBlockSettings = new SchemaSettings({
name: 'blockSettings:publicMarkdown',
items: [
{
name: 'EditMarkdown',
type: 'item',
useComponentProps() {
const field = useField();
const { t } = useTranslation();
return {
title: t('Edit markdown'),
onClick: () => {
field.editable = true;
},
};
},
},
{
name: 'setBlockTemplate',
Component: SchemaSettingsRenderEngine,
},
],
});

View File

@ -10,5 +10,8 @@
"The form is not enabled and cannot be accessed": "该表单未启用,无法访问",
"Link copied successfully": "复制地址成功",
"After successful submission": "提交成功后",
"Enable password": "启用密码"
"Enable password": "启用密码",
"Prompt After successful submission": "提交成功后的信息提示",
"Form": "普通表单",
"# Submitted successfully!\nThis is a demo text, **supports Markdown syntax**.": "# 提交成功!\n这是一段演示文本**支持 Markdown 语法**。"
}

View File

@ -40,10 +40,6 @@ export default defineCollection({
type: 'boolean',
name: 'enabled',
},
{
type: 'boolean',
name: 'enabledPassword',
},
{
type: 'string',
name: 'password',

View File

@ -57,7 +57,6 @@ export const parseAssociationNames = (dataSourceKey: string, collectionName: str
return initialValue;
};
// 定义自定义的 reducer 函数,模仿你的原始逻辑
const customReducer = (pre, s, key) => {
const prefix = pre || str;
const collection = dataSource.collectionManager.getCollection(

View File

@ -56,7 +56,7 @@ export class PluginPublicFormsServer extends Plugin {
return null;
}
if (!token) {
if (instance.get('password') && instance.get('enabledPassword')) {
if (instance.get('password')) {
if (password === undefined) {
return {
passwordRequired: true,
@ -80,11 +80,16 @@ export class PluginPublicFormsServer extends Plugin {
displayName: dataSourceKey,
collections,
},
token: this.app.authManager.jwt.sign({
collectionName,
formKey: filterByTk,
targetCollections: appends,
}),
token: this.app.authManager.jwt.sign(
{
collectionName,
formKey: filterByTk,
targetCollections: appends,
},
{
expiresIn: '1h',
},
),
schema,
};
}
@ -131,6 +136,9 @@ export class PluginPublicFormsServer extends Plugin {
key: tokenData.formKey,
},
});
if (!instance) {
throw new Error('The form is not found');
}
if (!instance.get('enabled')) {
throw new Error('The form is not enabled');
}
@ -147,21 +155,19 @@ export class PluginPublicFormsServer extends Plugin {
};
parseACL = async (ctx, next) => {
const { resourceName, actionName } = ctx.action;
if (ctx.PublicForm && ['create', 'list'].includes(actionName)) {
if (actionName === 'create') {
ctx.permission = {
skip:
ctx.PublicForm['collectionName'] === resourceName ||
ctx.PublicForm['targetCollections'].includes(resourceName),
};
} else {
ctx.permission = {
skip: ctx.PublicForm['targetCollections'].includes(resourceName),
};
}
if (!ctx.PublicForm) {
return next();
}
const { resourceName, actionName } = ctx.action;
if (actionName === 'create' && ctx.PublicForm['collectionName'] === resourceName) {
ctx.permission = {
skip: true,
};
} else if (actionName === 'list' && ctx.PublicForm['targetCollections'].includes(resourceName)) {
ctx.permission = {
skip: true,
};
}
await next();
};