diff --git a/object/application.go b/object/application.go index 768d8a62..ce217025 100644 --- a/object/application.go +++ b/object/application.go @@ -32,6 +32,7 @@ type Application struct { EnablePassword bool `json:"enablePassword"` EnableSignUp bool `json:"enableSignUp"` Providers []*ProviderItem `xorm:"varchar(10000)" json:"providers"` + SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"` OrganizationObj *Organization `xorm:"-" json:"organizationObj"` ClientId string `xorm:"varchar(100)" json:"clientId"` diff --git a/object/init.go b/object/init.go index ce6f5a30..799d823e 100644 --- a/object/init.go +++ b/object/init.go @@ -67,6 +67,7 @@ func initBuiltInApplication() { EnablePassword: true, EnableSignUp: true, Providers: []*ProviderItem{}, + SignupItems: []*SignupItem{}, RedirectUris: []string{}, ExpireInHours: 168, } diff --git a/object/signup_item.go b/object/signup_item.go new file mode 100644 index 00000000..e86b6413 --- /dev/null +++ b/object/signup_item.go @@ -0,0 +1,22 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package object + +type SignupItem struct { + Name string `json:"name"` + Visible bool `json:"visible"` + Required bool `json:"required"` + Rule string `json:"rule"` +} diff --git a/web/src/ApplicationEditPage.js b/web/src/ApplicationEditPage.js index 06fdc039..5f6dc3a2 100644 --- a/web/src/ApplicationEditPage.js +++ b/web/src/ApplicationEditPage.js @@ -24,6 +24,7 @@ import LoginPage from "./auth/LoginPage"; import i18next from "i18next"; import UrlTable from "./UrlTable"; import ProviderTable from "./ProviderTable"; +import SignupTable from "./SignupTable"; const { Option } = Select; @@ -299,6 +300,18 @@ class ApplicationEditPage extends React.Component { this.renderPreview() } + + + {i18next.t("application:Signup items")}: + + + { this.updateApplicationField('signupItems', value)}} + /> + + ) } diff --git a/web/src/ProviderTable.js b/web/src/ProviderTable.js index 2d3e70b6..64ea547a 100644 --- a/web/src/ProviderTable.js +++ b/web/src/ProviderTable.js @@ -13,8 +13,8 @@ // limitations under the License. import React from "react"; -import {DownOutlined, DeleteOutlined, UpOutlined, LinkOutlined} from '@ant-design/icons'; -import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from 'antd'; +import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons'; +import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd'; import * as Setting from "./Setting"; import i18next from "i18next"; @@ -81,11 +81,6 @@ class ProviderTable extends React.Component { } ) - return ( - } value={text} onChange={e => { - this.updateField(table, index, 'name', e.target.value); - }} /> - ) } }, { diff --git a/web/src/SignupTable.js b/web/src/SignupTable.js new file mode 100644 index 00000000..20efc33c --- /dev/null +++ b/web/src/SignupTable.js @@ -0,0 +1,197 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from "react"; +import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons'; +import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd'; +import * as Setting from "./Setting"; +import i18next from "i18next"; + +const { Option } = Select; + +class SignupTable extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + }; + } + + updateTable(table) { + this.props.onUpdateTable(table); + } + + updateField(table, index, key, value) { + table[index][key] = value; + this.updateTable(table); + } + + addRow(table) { + let row = {name: "Please select a signup item", visible: true, required: true, rule: "None"}; + if (table === undefined) { + table = []; + } + table = Setting.addRow(table, row); + this.updateTable(table); + } + + deleteRow(table, i) { + table = Setting.deleteRow(table, i); + this.updateTable(table); + } + + upRow(table, i) { + table = Setting.swapRow(table, i - 1, i); + this.updateTable(table); + } + + downRow(table, i) { + table = Setting.swapRow(table, i, i + 1); + this.updateTable(table); + } + + renderTable(table) { + const columns = [ + { + title: i18next.t("provider:Name"), + dataIndex: 'name', + key: 'name', + render: (text, record, index) => { + return ( + + ) + } + }, + { + title: i18next.t("provider:visible"), + dataIndex: 'visible', + key: 'visible', + width: '120px', + render: (text, record, index) => { + return ( + { + this.updateField(table, index, 'visible', checked); + if (!checked) { + this.updateField(table, index, 'required', false); + } + }} /> + ) + } + }, + { + title: i18next.t("provider:required"), + dataIndex: 'required', + key: 'required', + width: '120px', + render: (text, record, index) => { + if (!record.visible) { + return null; + } + + return ( + { + this.updateField(table, index, 'required', checked); + }} /> + ) + } + }, + { + title: i18next.t("provider:rule"), + dataIndex: 'rule', + key: 'rule', + width: '120px', + render: (text, record, index) => { + return ( + + ) + } + }, + { + title: i18next.t("general:Action"), + key: 'action', + width: '100px', + render: (text, record, index) => { + return ( +
+ +
+ ); + } + }, + ]; + + return ( + ( +
+ {this.props.title}     + +
+ )} + /> + ); + } + + render() { + return ( +
+ +
+ { + this.renderTable(this.props.table) + } + + + + ) + } +} + +export default SignupTable; diff --git a/web/src/auth/SignupPage.js b/web/src/auth/SignupPage.js index 71bffe27..b48f4a45 100644 --- a/web/src/auth/SignupPage.js +++ b/web/src/auth/SignupPage.js @@ -126,6 +126,194 @@ class SignupPage extends React.Component { this.form.current.scrollToField(errorFields[0].name); } + renderFormItem(application, signupItem) { + if (!signupItem.visible) { + return null; + } + + if (signupItem.name === "Username") { + return ( + + + + ) + } else if (signupItem.name === "Display name") { + return ( + + + + ) + } else if (signupItem.name === "Affiliation") { + return ( + + + + ) + } else if (signupItem.name === "Email") { + return ( + + + this.setState({email: e.target.value})} /> + + + + + + ) + } else if (signupItem.name === "Password") { + return ( + + + + + ({ + validator(rule, value) { + if (!value || getFieldValue('password') === value) { + return Promise.resolve(); + } + + return Promise.reject(i18next.t("signup:Your confirmed password is inconsistent with the password!")); + }, + }), + ]} + > + + + + ) + } else if (signupItem.name === "Phone") { + return ( + + + this.setState({phone: e.target.value})} + /> + + + + + + ) + } else if (signupItem.name === "Agreement") { + return ( + + + {i18next.t("signup:Accept")}  + + {i18next.t("signup:Terms of Use")} + + + + ) + } + } + renderForm(application) { if (!application.enableSignUp) { return ( @@ -146,7 +334,6 @@ class SignupPage extends React.Component { ) } - return ( - - - - - - - - - - - this.setState({email: e.target.value})} /> - - - - - - - - ({ - validator(rule, value) { - if (!value || getFieldValue('password') === value) { - return Promise.resolve(); - } - - return Promise.reject(i18next.t("signup:Your confirmed password is inconsistent with the password!")); - }, - }), - ]} - > - - - - this.setState({phone: e.target.value})} - /> - - - - - - - {i18next.t("signup:Accept")}  - - {i18next.t("signup:Terms of Use")} - - - + { + application.signupItems.map(signupItem => this.renderFormItem(application, signupItem)) + }