mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-14 16:13:24 +08:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
31b586e391 | |||
249f83e764 | |||
16f5569e50 | |||
f99c1f44e8 | |||
c8c4dfbfb8 | |||
d9c6ff2507 | |||
e1664f2f60 | |||
460a4d4969 | |||
376bac15dc | |||
8d0e92edef | |||
0075b7af52 | |||
2c57bece39 | |||
2e42511bc4 | |||
ae4ab9902b | |||
065b235dc5 | |||
63c09a879f | |||
61c80e790f | |||
be91ff47aa | |||
b4c18eb7a4 | |||
0f483fb65b | |||
ebe9889d58 | |||
ee42fcac8e | |||
6187b48f61 | |||
2020955270 | |||
1b5a8f8e57 | |||
ff94e5164a | |||
15a6fd2b52 | |||
37b6b50751 | |||
efe5431f54 | |||
e9159902eb | |||
604e2757c8 | |||
88c5aae9e9 | |||
3d0cf8788b | |||
e78ea2546f | |||
f7705931f7 | |||
5d8b710bf7 | |||
b85ad896bf | |||
42c2210178 | |||
d52caed3a9 | |||
27d8cd758d | |||
98f77960de |
152
README.md
152
README.md
@ -42,166 +42,66 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Online demo
|
## Online demo
|
||||||
|
|
||||||
Deployed site: https://door.casdoor.com/
|
- International: https://door.casdoor.org (read-only)
|
||||||
|
- Asian mirror: https://door.casdoor.com (read-only)
|
||||||
|
- Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes)
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
Run your own casdoor program in a few minutes.
|
|
||||||
|
|
||||||
### Download
|
|
||||||
|
|
||||||
There are two methods, get code via go subcommand `get`:
|
## Documentation
|
||||||
|
|
||||||
```shell
|
- International: https://casdoor.org
|
||||||
go get github.com/casdoor/casdoor
|
- Asian mirror: https://docs.casdoor.cn
|
||||||
```
|
|
||||||
|
|
||||||
or `git`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/casdoor/casdoor
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, change directory:
|
## Install
|
||||||
|
|
||||||
```bash
|
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||||
cd casdoor/
|
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||||
```
|
|
||||||
|
|
||||||
We provide two start up methods for all kinds of users.
|
|
||||||
|
|
||||||
### Manual
|
|
||||||
|
|
||||||
#### Simple configuration
|
## How to connect to Casdoor?
|
||||||
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
|
|
||||||
|
|
||||||
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
|
https://casdoor.org/docs/how-to-connect/overview
|
||||||
|
|
||||||
```bash
|
|
||||||
username:password@tcp(database_ip:database_port)/
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create an empty schema (database) named `casdoor` in your relational database. After the program runs for the first time, it will automatically create tables in this schema.
|
|
||||||
|
|
||||||
You can also edit `main.go`, modify `false` to `true`. It will automatically create the schema (database) named `casdoor` in this database.
|
## Casdoor Public API
|
||||||
|
|
||||||
```bash
|
- Docs: https://casdoor.org/docs/basic/public-api
|
||||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
- Swagger: https://door.casdoor.com/swagger
|
||||||
```
|
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
|
||||||
|
|
||||||
##### Dev Mode
|
## Integrations
|
||||||
|
|
||||||
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
|
https://casdoor.org/docs/integration/apisix
|
||||||
|
|
||||||
```bash
|
|
||||||
cd web/ && yarn && yarn run start
|
|
||||||
```
|
|
||||||
*❗ A word of caution ❗: Casdoor's front-end is built using yarn. You should use `yarn` instead of `npm`. It has a potential failure during building the files if you use `npm`.*
|
|
||||||
|
|
||||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
## How to contact?
|
||||||
|
|
||||||
```bash
|
- Gitter: https://gitter.im/casbin/casdoor
|
||||||
go run main.go
|
- Forum: https://forum.casbin.com
|
||||||
```
|
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
|
||||||
|
|
||||||
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
|
|
||||||
**But make sure you always request the backend port 8000 when you are using SDKs.**
|
|
||||||
|
|
||||||
##### Production Mode
|
|
||||||
|
|
||||||
Edit `conf/app.conf`, set `runmode=prod`. Firstly build front-end files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd web/ && yarn && yarn run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build main.go && sudo ./main
|
|
||||||
```
|
|
||||||
|
|
||||||
> Notice, you should visit back-end port, default 8000. Now try to visit **http://SERVER_IP:8000/**
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
Casdoor provide 2 kinds of image:
|
|
||||||
- casbin/casdoor-all-in-one, in which casdoor binary, a mysql database and all necessary configurations are packed up. This image is for new user to have a trial on casdoor quickly. **With this image you can start a casdoor immediately with one single command (or two) without any complex configuration**. **Note: we DO NOT recommend you to use this image in productive environment**
|
|
||||||
|
|
||||||
- casbin/casdoor: normal & graceful casdoor image with only casdoor and environment installed.
|
|
||||||
|
|
||||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
|
||||||
|
|
||||||
### Start casdoor with casbin/casdoor-all-in-one
|
|
||||||
if the image is not pulled, pull it from dockerhub
|
|
||||||
```shell
|
|
||||||
docker pull casbin/casdoor-all-in-one
|
|
||||||
```
|
|
||||||
Start it with
|
|
||||||
```shell
|
|
||||||
docker run -p 8000:8000 casbin/casdoor-all-in-one
|
|
||||||
```
|
|
||||||
Now you can visit http://localhost:8000 and have a try. Default account and password is 'admin' and '123'. Go for it!
|
|
||||||
|
|
||||||
### Start casdoor with casbin/casdoor
|
|
||||||
#### modify the configurations
|
|
||||||
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
|
|
||||||
|
|
||||||
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dataSourceName = root:123456@tcp(db:3306)/
|
|
||||||
```
|
|
||||||
|
|
||||||
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
|
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
### K8S
|
|
||||||
You could use helm to deploy casdoor in k8s. At first, you should modify the [configmap](./manifests/casdoor/templates/configmap.yaml) for your application.
|
|
||||||
And then run bellow command to deploy it.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
IMG_TAG=latest make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
And undeploy it with:
|
|
||||||
```bash
|
|
||||||
make undeploy
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it! Try to visit http://localhost:8000/. :small_airplane:
|
|
||||||
|
|
||||||
## Detailed documentation
|
|
||||||
|
|
||||||
We also provide a complete [document](https://casdoor.org/) as a reference.
|
|
||||||
|
|
||||||
## Other examples
|
|
||||||
|
|
||||||
These all use casdoor as a centralized authentication platform.
|
|
||||||
|
|
||||||
- [Casnode](https://github.com/casbin/casnode): Next-generation forum software based on React + Golang.
|
|
||||||
- [Casbin-OA](https://github.com/casbin/casbin-oa): A full-featured OA(Office Assistant) system.
|
|
||||||
- ......
|
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
||||||
|
|
||||||
### I18n notice
|
### I18n translation
|
||||||
|
|
||||||
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
|
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||||
|
|
||||||
|
@ -95,7 +95,8 @@ p, *, *, GET, /api/get-providers, *, *
|
|||||||
p, *, *, POST, /api/unlink, *, *
|
p, *, *, POST, /api/unlink, *, *
|
||||||
p, *, *, POST, /api/set-password, *, *
|
p, *, *, POST, /api/set-password, *, *
|
||||||
p, *, *, POST, /api/send-verification-code, *, *
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
p, *, *, GET, /api/get-human-check, *, *
|
p, *, *, GET, /api/get-captcha, *, *
|
||||||
|
p, *, *, POST, /api/verify-captcha, *, *
|
||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,12 +12,18 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package object
|
package captcha
|
||||||
|
|
||||||
type SignupItem struct {
|
import "github.com/casdoor/casdoor/object"
|
||||||
Name string `json:"name"`
|
|
||||||
Visible bool `json:"visible"`
|
type DefaultCaptchaProvider struct {
|
||||||
Required bool `json:"required"`
|
}
|
||||||
Prompted bool `json:"prompted"`
|
|
||||||
Rule string `json:"rule"`
|
func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
|
||||||
|
captcha := &DefaultCaptchaProvider{}
|
||||||
|
return captcha
|
||||||
|
}
|
||||||
|
|
||||||
|
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
||||||
|
return object.VerifyCaptcha(clientSecret, token), nil
|
||||||
}
|
}
|
60
captcha/hcaptcha.go
Normal file
60
captcha/hcaptcha.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
const HCaptchaVerifyUrl = "https://hcaptcha.com/siteverify"
|
||||||
|
|
||||||
|
type HCaptchaProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHCaptchaProvider() *HCaptchaProvider {
|
||||||
|
captcha := &HCaptchaProvider{}
|
||||||
|
return captcha
|
||||||
|
}
|
||||||
|
|
||||||
|
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
||||||
|
reqData := url.Values{
|
||||||
|
"secret": {clientSecret},
|
||||||
|
"response": {token},
|
||||||
|
}
|
||||||
|
resp, err := http.PostForm(HCaptchaVerifyUrl, reqData)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type captchaResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
captchaResp := &captchaResponse{}
|
||||||
|
err = json.Unmarshal(body, captchaResp)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return captchaResp.Success, nil
|
||||||
|
}
|
30
captcha/provider.go
Normal file
30
captcha/provider.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 captcha
|
||||||
|
|
||||||
|
type CaptchaProvider interface {
|
||||||
|
VerifyCaptcha(token, clientSecret string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
||||||
|
if captchaType == "Default" {
|
||||||
|
return NewDefaultCaptchaProvider()
|
||||||
|
} else if captchaType == "reCAPTCHA" {
|
||||||
|
return NewReCaptchaProvider()
|
||||||
|
} else if captchaType == "hCaptcha" {
|
||||||
|
return NewHCaptchaProvider()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
60
captcha/recaptcha.go
Normal file
60
captcha/recaptcha.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ReCaptchaVerifyUrl = "https://recaptcha.net/recaptcha/api/siteverify"
|
||||||
|
|
||||||
|
type ReCaptchaProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReCaptchaProvider() *ReCaptchaProvider {
|
||||||
|
captcha := &ReCaptchaProvider{}
|
||||||
|
return captcha
|
||||||
|
}
|
||||||
|
|
||||||
|
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
||||||
|
reqData := url.Values{
|
||||||
|
"secret": {clientSecret},
|
||||||
|
"response": {token},
|
||||||
|
}
|
||||||
|
resp, err := http.PostForm(ReCaptchaVerifyUrl, reqData)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type captchaResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
captchaResp := &captchaResponse{}
|
||||||
|
err = json.Unmarshal(body, captchaResp)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return captchaResp.Success, nil
|
||||||
|
}
|
@ -17,6 +17,7 @@ package conf
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -61,7 +62,12 @@ func GetBeegoConfDataSourceName() string {
|
|||||||
|
|
||||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||||
if runningInDocker == "true" {
|
if runningInDocker == "true" {
|
||||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
// https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "172.17.0.1")
|
||||||
|
} else {
|
||||||
|
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataSourceName
|
return dataSourceName
|
||||||
|
@ -75,12 +75,14 @@ type Response struct {
|
|||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HumanCheck struct {
|
type Captcha struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
Scene string `json:"scene"`
|
Scene string `json:"scene"`
|
||||||
CaptchaId string `json:"captchaId"`
|
CaptchaId string `json:"captchaId"`
|
||||||
CaptchaImage interface{} `json:"captchaImage"`
|
CaptchaImage []byte `json:"captchaImage"`
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
|
ClientSecret string `json:"clientSecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signup
|
// Signup
|
||||||
@ -291,20 +293,30 @@ func (c *ApiController) GetUserinfo() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHumanCheck ...
|
// GetCaptcha ...
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Title GetHumancheck
|
// @Title GetCaptcha
|
||||||
// @router /api/get-human-check [get]
|
// @router /api/get-captcha [get]
|
||||||
func (c *ApiController) GetHumanCheck() {
|
func (c *ApiController) GetCaptcha() {
|
||||||
c.Data["json"] = HumanCheck{Type: "none"}
|
applicationId := c.Input().Get("applicationId")
|
||||||
|
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||||
|
|
||||||
provider := object.GetDefaultHumanCheckProvider()
|
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
|
||||||
if provider == nil {
|
if err != nil {
|
||||||
id, img := object.GetCaptcha()
|
c.ResponseError(err.Error())
|
||||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ServeJSON()
|
if captchaProvider != nil {
|
||||||
|
if captchaProvider.Type == "Default" {
|
||||||
|
id, img := object.GetCaptcha()
|
||||||
|
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
|
||||||
|
return
|
||||||
|
} else if captchaProvider.Type != "" {
|
||||||
|
c.ResponseOk(Captcha{Type: captchaProvider.Type, ClientId: captchaProvider.ClientId, ClientSecret: captchaProvider.ClientSecret})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(Captcha{Type: "none"})
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func codeToResponse(code *object.Code) *Response {
|
func codeToResponse(code *object.Code) *Response {
|
||||||
@ -132,7 +133,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
// @Param redirectUri query string true "redirect uri"
|
// @Param redirectUri query string true "redirect uri"
|
||||||
// @Param scope query string true "scope"
|
// @Param scope query string true "scope"
|
||||||
// @Param state query string true "state"
|
// @Param state query string true "state"
|
||||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
// @Success 200 {object} Response The Response object
|
||||||
// @router /get-app-login [get]
|
// @router /get-app-login [get]
|
||||||
func (c *ApiController) GetApplicationLogin() {
|
func (c *ApiController) GetApplicationLogin() {
|
||||||
clientId := c.Input().Get("clientId")
|
clientId := c.Input().Get("clientId")
|
||||||
@ -162,9 +163,16 @@ func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
|||||||
// @Title Login
|
// @Title Login
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Description login
|
// @Description login
|
||||||
// @Param oAuthParams query string true "oAuth parameters"
|
// @Param clientId query string true clientId
|
||||||
// @Param body body RequestForm true "Login information"
|
// @Param responseType query string true responseType
|
||||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
// @Param redirectUri query string true redirectUri
|
||||||
|
// @Param scope query string false scope
|
||||||
|
// @Param state query string false state
|
||||||
|
// @Param nonce query string false nonce
|
||||||
|
// @Param code_challenge_method query string false code_challenge_method
|
||||||
|
// @Param code_challenge query string false code_challenge
|
||||||
|
// @Param form body controllers.RequestForm true "Login information"
|
||||||
|
// @Success 200 {object} Response The Response object
|
||||||
// @router /login [post]
|
// @router /login [post]
|
||||||
func (c *ApiController) Login() {
|
func (c *ApiController) Login() {
|
||||||
resp := &Response{}
|
resp := &Response{}
|
||||||
@ -222,7 +230,11 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// disable the verification code
|
// disable the verification code
|
||||||
object.DisableVerificationCode(form.Username)
|
if strings.Contains(form.Username, "@") {
|
||||||
|
object.DisableVerificationCode(form.Username)
|
||||||
|
} else {
|
||||||
|
object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username))
|
||||||
|
}
|
||||||
|
|
||||||
user = object.GetUserByFields(form.Organization, form.Username)
|
user = object.GetUserByFields(form.Organization, form.Username)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@ -248,7 +260,7 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
util.SafeGoroutine(func() {object.AddRecord(record)})
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
}
|
}
|
||||||
} else if form.Provider != "" {
|
} else if form.Provider != "" {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
@ -321,12 +333,6 @@ func (c *ApiController) Login() {
|
|||||||
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
||||||
} else if provider.Category == "OAuth" {
|
} else if provider.Category == "OAuth" {
|
||||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||||
if user == nil {
|
|
||||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
|
||||||
}
|
|
||||||
if user == nil {
|
|
||||||
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil && user.IsDeleted == false {
|
if user != nil && user.IsDeleted == false {
|
||||||
@ -341,7 +347,7 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
util.SafeGoroutine(func() {object.AddRecord(record)})
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
} else if provider.Category == "OAuth" {
|
} else if provider.Category == "OAuth" {
|
||||||
// Sign up via OAuth
|
// Sign up via OAuth
|
||||||
if !application.EnableSignUp {
|
if !application.EnableSignUp {
|
||||||
@ -354,6 +360,19 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle username conflicts
|
||||||
|
tmpUser := object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Username))
|
||||||
|
if tmpUser != nil {
|
||||||
|
uid, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uidStr := strings.Split(uid.String(), "-")
|
||||||
|
userInfo.Username = fmt.Sprintf("%s_%s", userInfo.Username, uidStr[1])
|
||||||
|
}
|
||||||
|
|
||||||
properties := map[string]string{}
|
properties := map[string]string{}
|
||||||
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
||||||
user = &object.User{
|
user = &object.User{
|
||||||
@ -390,7 +409,13 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
util.SafeGoroutine(func() {object.AddRecord(record)})
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
|
|
||||||
|
record2 := object.NewRecord(c.Ctx)
|
||||||
|
record2.Action = "signup"
|
||||||
|
record2.Organization = application.Organization
|
||||||
|
record2.User = user.Name
|
||||||
|
util.SafeGoroutine(func() { object.AddRecord(record2) })
|
||||||
} else if provider.Category == "SAML" {
|
} else if provider.Category == "SAML" {
|
||||||
resp = &Response{Status: "error", Msg: "The account does not exist"}
|
resp = &Response{Status: "error", Msg: "The account does not exist"}
|
||||||
}
|
}
|
||||||
@ -403,9 +428,6 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||||
if oldUser == nil {
|
|
||||||
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
|
||||||
}
|
|
||||||
if oldUser != nil {
|
if oldUser != nil {
|
||||||
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
|
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
|
||||||
return
|
return
|
||||||
@ -434,6 +456,11 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
resp = c.HandleLoggedIn(application, user, &form)
|
||||||
|
|
||||||
|
record := object.NewRecord(c.Ctx)
|
||||||
|
record.Organization = application.Organization
|
||||||
|
record.User = user.Name
|
||||||
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
|
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
|
||||||
return
|
return
|
||||||
|
120
controllers/model.go
Normal file
120
controllers/model.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2021 The Casdoor 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModels
|
||||||
|
// @Title GetModels
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description get models
|
||||||
|
// @Param owner query string true "The owner of models"
|
||||||
|
// @Success 200 {array} object.Model The Response object
|
||||||
|
// @router /get-models [get]
|
||||||
|
func (c *ApiController) GetModels() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetModels(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetModelCount(owner, field, value)))
|
||||||
|
models := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(models, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModel
|
||||||
|
// @Title GetModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description get model
|
||||||
|
// @Param id query string true "The id of the model"
|
||||||
|
// @Success 200 {object} object.Model The Response object
|
||||||
|
// @router /get-model [get]
|
||||||
|
func (c *ApiController) GetModel() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetModel(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModel
|
||||||
|
// @Title UpdateModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description update model
|
||||||
|
// @Param id query string true "The id of the model"
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-model [post]
|
||||||
|
func (c *ApiController) UpdateModel() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddModel
|
||||||
|
// @Title AddModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description add model
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-model [post]
|
||||||
|
func (c *ApiController) AddModel() {
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddModel(&model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteModel
|
||||||
|
// @Title DeleteModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description delete model
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-model [post]
|
||||||
|
func (c *ApiController) DeleteModel() {
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteModel(&model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@ -18,6 +18,8 @@ import "github.com/casdoor/casdoor/object"
|
|||||||
|
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
|
// @Description Get Oidc Discovery
|
||||||
|
// @Success 200 {object} object.OidcDiscovery
|
||||||
// @router /.well-known/openid-configuration [get]
|
// @router /.well-known/openid-configuration [get]
|
||||||
func (c *RootController) GetOidcDiscovery() {
|
func (c *RootController) GetOidcDiscovery() {
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
@ -27,6 +29,7 @@ func (c *RootController) GetOidcDiscovery() {
|
|||||||
|
|
||||||
// @Title GetJwks
|
// @Title GetJwks
|
||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
|
// @Success 200 {object} jose.JSONWebKey
|
||||||
// @router /.well-known/jwks [get]
|
// @router /.well-known/jwks [get]
|
||||||
func (c *RootController) GetJwks() {
|
func (c *RootController) GetJwks() {
|
||||||
jwks, err := object.GetJsonWebKeySet()
|
jwks, err := object.GetJsonWebKeySet()
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
// @Description get all records
|
// @Description get all records
|
||||||
// @Param pageSize query string true "The size of each page"
|
// @Param pageSize query string true "The size of each page"
|
||||||
// @Param p query string true "The number of the page"
|
// @Param p query string true "The number of the page"
|
||||||
// @Success 200 {array} object.Records The Response object
|
// @Success 200 {object} object.Record The Response object
|
||||||
// @router /get-records [get]
|
// @router /get-records [get]
|
||||||
func (c *ApiController) GetRecords() {
|
func (c *ApiController) GetRecords() {
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
@ -50,8 +50,8 @@ func (c *ApiController) GetRecords() {
|
|||||||
// @Tag Record API
|
// @Tag Record API
|
||||||
// @Title GetRecordsByFilter
|
// @Title GetRecordsByFilter
|
||||||
// @Description get records by filter
|
// @Description get records by filter
|
||||||
// @Param body body object.Records true "filter Record message"
|
// @Param filter body string true "filter Record message"
|
||||||
// @Success 200 {array} object.Records The Response object
|
// @Success 200 {object} object.Record The Response object
|
||||||
// @router /get-records-filter [post]
|
// @router /get-records-filter [post]
|
||||||
func (c *ApiController) GetRecordsByFilter() {
|
func (c *ApiController) GetRecordsByFilter() {
|
||||||
body := string(c.Ctx.Input.RequestBody)
|
body := string(c.Ctx.Input.RequestBody)
|
||||||
|
@ -26,6 +26,7 @@ func (c *ApiController) GetSamlMeta() {
|
|||||||
application := object.GetApplication(paramApp)
|
application := object.GetApplication(paramApp)
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(fmt.Sprintf("err: application %s not found", paramApp))
|
c.ResponseError(fmt.Sprintf("err: application %s not found", paramApp))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
metadata, _ := object.GetSamlMeta(application, host)
|
metadata, _ := object.GetSamlMeta(application, host)
|
||||||
c.Data["xml"] = metadata
|
c.Data["xml"] = metadata
|
||||||
|
@ -25,33 +25,61 @@ import (
|
|||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type EmailForm struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Sender string `json:"sender"`
|
||||||
|
Receivers []string `json:"receivers"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SmsForm struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
Receivers []string `json:"receivers"`
|
||||||
|
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
||||||
|
}
|
||||||
|
|
||||||
// SendEmail
|
// SendEmail
|
||||||
// @Title SendEmail
|
// @Title SendEmail
|
||||||
// @Tag Service API
|
// @Tag Service API
|
||||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||||
// @Param clientId query string true "The clientId of the application"
|
// @Param clientId query string true "The clientId of the application"
|
||||||
// @Param clientSecret query string true "The clientSecret of the application"
|
// @Param clientSecret query string true "The clientSecret of the application"
|
||||||
// @Param body body emailForm true "Details of the email request"
|
// @Param from body controllers.EmailForm true "Details of the email request"
|
||||||
// @Success 200 {object} Response object
|
// @Success 200 {object} Response object
|
||||||
// @router /api/send-email [post]
|
// @router /api/send-email [post]
|
||||||
func (c *ApiController) SendEmail() {
|
func (c *ApiController) SendEmail() {
|
||||||
provider, _, ok := c.GetProviderFromContext("Email")
|
var emailForm EmailForm
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var emailForm struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Sender string `json:"sender"`
|
|
||||||
Receivers []string `json:"receivers"`
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var provider *object.Provider
|
||||||
|
if emailForm.Provider != "" {
|
||||||
|
// called by frontend's TestEmailWidget, provider name is set by frontend
|
||||||
|
provider = object.GetProvider(fmt.Sprintf("admin/%s", emailForm.Provider))
|
||||||
|
} else {
|
||||||
|
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
|
||||||
|
var ok bool
|
||||||
|
provider, _, ok = c.GetProviderFromContext("Email")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
|
||||||
|
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
|
||||||
|
err := object.DailSmtpServer(provider)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
||||||
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
||||||
return
|
return
|
||||||
@ -86,7 +114,7 @@ func (c *ApiController) SendEmail() {
|
|||||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||||
// @Param clientId query string true "The clientId of the application"
|
// @Param clientId query string true "The clientId of the application"
|
||||||
// @Param clientSecret query string true "The clientSecret of the application"
|
// @Param clientSecret query string true "The clientSecret of the application"
|
||||||
// @Param body body smsForm true "Details of the sms request"
|
// @Param from body controllers.SmsForm true "Details of the sms request"
|
||||||
// @Success 200 {object} Response object
|
// @Success 200 {object} Response object
|
||||||
// @router /api/send-sms [post]
|
// @router /api/send-sms [post]
|
||||||
func (c *ApiController) SendSms() {
|
func (c *ApiController) SendSms() {
|
||||||
@ -95,11 +123,7 @@ func (c *ApiController) SendSms() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var smsForm struct {
|
var smsForm SmsForm
|
||||||
Content string `json:"content"`
|
|
||||||
Receivers []string `json:"receivers"`
|
|
||||||
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/captcha"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@ -48,20 +49,28 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||||
|
|
||||||
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
|
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 {
|
||||||
c.ResponseError("Missing parameter.")
|
c.ResponseError("Missing parameter.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isHuman := false
|
captchaProvider := captcha.GetCaptchaProvider(checkType)
|
||||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
|
||||||
if captchaProvider == nil {
|
|
||||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isHuman {
|
if captchaProvider != nil {
|
||||||
c.ResponseError("Turing test failed.")
|
if checkKey == "" {
|
||||||
return
|
c.ResponseError("Missing parameter: checkKey.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError("Failed to verify captcha: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isHuman {
|
||||||
|
c.ResponseError("Turing test failed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
@ -173,3 +182,36 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
c.Data["json"] = Response{Status: "ok"}
|
c.Data["json"] = Response{Status: "ok"}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyCaptcha ...
|
||||||
|
// @Title VerifyCaptcha
|
||||||
|
// @Tag Verification API
|
||||||
|
// @router /verify-captcha [post]
|
||||||
|
func (c *ApiController) VerifyCaptcha() {
|
||||||
|
captchaType := c.Ctx.Request.Form.Get("captchaType")
|
||||||
|
|
||||||
|
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
||||||
|
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
||||||
|
if captchaToken == "" {
|
||||||
|
c.ResponseError("Missing parameter: captchaToken.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if clientSecret == "" {
|
||||||
|
c.ResponseError("Missing parameter: clientSecret.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := captcha.GetCaptchaProvider(captchaType)
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError("Invalid captcha provider.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError("Failed to verify captcha: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(isValid)
|
||||||
|
}
|
||||||
|
@ -38,8 +38,10 @@ func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||||
hash := getMd5HexDigest(password)
|
res := getMd5HexDigest(password)
|
||||||
res := getMd5HexDigest(hash + userSalt)
|
if userSalt != "" {
|
||||||
|
res = getMd5HexDigest(res + userSalt)
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +38,10 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||||
hash := getSha256HexDigest(password)
|
res := getSha256HexDigest(password)
|
||||||
res := getSha256HexDigest(hash + organizationSalt)
|
if organizationSalt != "" {
|
||||||
|
res = getSha256HexDigest(res + organizationSalt)
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,3 +25,10 @@ func TestGetSaltedPassword(t *testing.T) {
|
|||||||
cm := NewSha256SaltCredManager()
|
cm := NewSha256SaltCredManager()
|
||||||
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetPassword(t *testing.T) {
|
||||||
|
password := "123456"
|
||||||
|
cm := NewSha256SaltCredManager()
|
||||||
|
// https://passwordsgenerator.net/sha256-hash-generator/
|
||||||
|
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, "", ""))
|
||||||
|
}
|
||||||
|
@ -12,8 +12,6 @@ services:
|
|||||||
- db
|
- db
|
||||||
environment:
|
environment:
|
||||||
RUNNING_IN_DOCKER: "true"
|
RUNNING_IN_DOCKER: "true"
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./conf:/conf/
|
- ./conf:/conf/
|
||||||
db:
|
db:
|
||||||
|
@ -187,9 +187,10 @@ func (idp *BilibiliIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
userInfo := &UserInfo{
|
userInfo := &UserInfo{
|
||||||
Id: bUserInfoResponse.Data.OpenId,
|
Id: bUserInfoResponse.Data.OpenId,
|
||||||
Username: bUserInfoResponse.Data.Name,
|
Username: bUserInfoResponse.Data.Name,
|
||||||
AvatarUrl: bUserInfoResponse.Data.Face,
|
DisplayName: bUserInfoResponse.Data.Name,
|
||||||
|
AvatarUrl: bUserInfoResponse.Data.Face,
|
||||||
}
|
}
|
||||||
|
|
||||||
return userInfo, nil
|
return userInfo, nil
|
||||||
|
198
idp/douyin.go
Normal file
198
idp/douyin.go
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DouyinIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDouyinIdProvider(clientId string, clientSecret string, redirectUrl string) *DouyinIdProvider {
|
||||||
|
idp := &DouyinIdProvider{}
|
||||||
|
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DouyinIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DouyinIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: "https://open.douyin.com/oauth/access_token",
|
||||||
|
AuthURL: "https://open.douyin.com/platform/oauth/connect",
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Scopes: []string{"user_info"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"access_token": "access_token",
|
||||||
|
"description": "",
|
||||||
|
"error_code": "0",
|
||||||
|
"expires_in": "86400",
|
||||||
|
"open_id": "aaa-bbb-ccc",
|
||||||
|
"refresh_expires_in": "86400",
|
||||||
|
"refresh_token": "refresh_token",
|
||||||
|
"scope": "user_info"
|
||||||
|
},
|
||||||
|
"message": "<nil>"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DouyinTokenResp struct {
|
||||||
|
Data struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
} `json:"data"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
|
||||||
|
func (idp *DouyinIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
payload := url.Values{}
|
||||||
|
payload.Set("code", code)
|
||||||
|
payload.Set("grant_type", "authorization_code")
|
||||||
|
payload.Set("client_key", idp.Config.ClientID)
|
||||||
|
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||||
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tokenResp := &DouyinTokenResp{}
|
||||||
|
err = json.Unmarshal(data, tokenResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenResp.Data.AccessToken,
|
||||||
|
RefreshToken: tokenResp.Data.RefreshToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+tokenResp.Data.ExpiresIn, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
raw["open_id"] = tokenResp.Data.OpenId
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-management/get-account-open-info
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"avatar": "https://example.com/x.jpeg",
|
||||||
|
"city": "上海",
|
||||||
|
"country": "中国",
|
||||||
|
"description": "",
|
||||||
|
"e_account_role": "<nil>",
|
||||||
|
"error_code": "0",
|
||||||
|
"gender": "<nil>",
|
||||||
|
"nickname": "张伟",
|
||||||
|
"open_id": "0da22181-d833-447f-995f-1beefea5bef3",
|
||||||
|
"province": "上海",
|
||||||
|
"union_id": "1ad4e099-4a0c-47d1-a410-bffb4f2f64a4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DouyinUserInfo struct {
|
||||||
|
Data struct {
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
// 0->unknown, 1->male, 2->female
|
||||||
|
Gender int64 `json:"gender"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo use token to get user profile
|
||||||
|
func (idp *DouyinIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
body := &struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
}{token.AccessToken, token.Extra("open_id").(string)}
|
||||||
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("GET", "https://open.douyin.com/oauth/userinfo/", bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("access-token", token.AccessToken)
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var douyinUserInfo DouyinUserInfo
|
||||||
|
err = json.Unmarshal(respBody, &douyinUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: douyinUserInfo.Data.OpenId,
|
||||||
|
Username: douyinUserInfo.Data.Nickname,
|
||||||
|
DisplayName: douyinUserInfo.Data.Nickname,
|
||||||
|
AvatarUrl: douyinUserInfo.Data.Avatar,
|
||||||
|
}
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
@ -86,6 +86,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
} else if typ == "Okta" {
|
} else if typ == "Okta" {
|
||||||
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
|
} else if typ == "Douyin" {
|
||||||
|
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if isGothSupport(typ) {
|
} else if isGothSupport(typ) {
|
||||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||||
} else if typ == "Bilibili" {
|
} else if typ == "Bilibili" {
|
||||||
|
@ -138,6 +138,11 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Model))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Provider))
|
err = a.Engine.Sync2(new(Provider))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -22,6 +22,14 @@ import (
|
|||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SignupItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
Prompted bool `json:"prompted"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
@ -29,3 +29,16 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
|||||||
|
|
||||||
return dialer.DialAndSend(message)
|
return dialer.DialAndSend(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DailSmtpServer Dail Smtp server
|
||||||
|
func DailSmtpServer(provider *Provider) error {
|
||||||
|
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||||
|
|
||||||
|
sender, err := dialer.Dial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sender.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
func InitDb() {
|
func InitDb() {
|
||||||
existed := initBuiltInOrganization()
|
existed := initBuiltInOrganization()
|
||||||
if !existed {
|
if !existed {
|
||||||
|
initBuiltInProvider()
|
||||||
initBuiltInUser()
|
initBuiltInUser()
|
||||||
initBuiltInApplication()
|
initBuiltInApplication()
|
||||||
initBuiltInCert()
|
initBuiltInCert()
|
||||||
@ -47,6 +48,31 @@ func initBuiltInOrganization() bool {
|
|||||||
PhonePrefix: "86",
|
PhonePrefix: "86",
|
||||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||||
Tags: []string{},
|
Tags: []string{},
|
||||||
|
AccountItems: []*AccountItem{
|
||||||
|
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||||
|
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
|
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
|
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
AddOrganization(organization)
|
AddOrganization(organization)
|
||||||
return false
|
return false
|
||||||
@ -102,7 +128,9 @@ func initBuiltInApplication() {
|
|||||||
Cert: "cert-built-in",
|
Cert: "cert-built-in",
|
||||||
EnablePassword: true,
|
EnablePassword: true,
|
||||||
EnableSignUp: true,
|
EnableSignUp: true,
|
||||||
Providers: []*ProviderItem{},
|
Providers: []*ProviderItem{
|
||||||
|
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Provider: nil},
|
||||||
|
},
|
||||||
SignupItems: []*SignupItem{
|
SignupItems: []*SignupItem{
|
||||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||||
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
@ -176,3 +204,20 @@ func initBuiltInLdap() {
|
|||||||
}
|
}
|
||||||
AddLdap(ldap)
|
AddLdap(ldap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initBuiltInProvider() {
|
||||||
|
provider := GetProvider("admin/provider_captcha_default")
|
||||||
|
if provider != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider = &Provider{
|
||||||
|
Owner: "admin",
|
||||||
|
Name: "provider_captcha_default",
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
DisplayName: "Captcha Default",
|
||||||
|
Category: "Captcha",
|
||||||
|
Type: "Default",
|
||||||
|
}
|
||||||
|
AddProvider(provider)
|
||||||
|
}
|
||||||
|
122
object/model.go
Normal file
122
object/model.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2021 The Casdoor 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
|
||||||
|
ModelText string `xorm:"mediumtext" json:"modelText"`
|
||||||
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModelCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModels(owner string) []*Model {
|
||||||
|
models := []*Model{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&models, &Model{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationModels(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Model {
|
||||||
|
models := []*Model{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&models)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModel(owner string, name string) *Model {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
model := Model{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &model
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModel(id string) *Model {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getModel(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateModel(id string, model *Model) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getModel(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddModel(model *Model) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteModel(model *Model) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{model.Owner, model.Name}).Delete(&Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (model *Model) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", model.Owner, model.Name)
|
||||||
|
}
|
@ -20,6 +20,13 @@ import (
|
|||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AccountItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
|
ViewRule string `json:"viewRule"`
|
||||||
|
ModifyRule string `json:"modifyRule"`
|
||||||
|
}
|
||||||
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@ -36,6 +43,8 @@ type Organization struct {
|
|||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
|
|
||||||
|
AccountItems []*AccountItem `xorm:"varchar(2000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) int {
|
func GetOrganizationCount(owner, field, value string) int {
|
||||||
@ -121,11 +130,15 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if name != organization.Name {
|
if name != organization.Name {
|
||||||
applications := GetApplicationsByOrganizationName("admin", name)
|
go func() {
|
||||||
for _, application := range applications {
|
application := new(Application)
|
||||||
application.Organization = organization.Name
|
application.Organization = organization.Name
|
||||||
UpdateApplication(application.GetId(), application)
|
_, _ = adapter.Engine.Where("organization=?", name).Update(application)
|
||||||
}
|
|
||||||
|
user := new(User)
|
||||||
|
user.Owner = organization.Name
|
||||||
|
_, _ = adapter.Engine.Where("owner=?", name).Update(user)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
|
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
|
||||||
|
@ -30,6 +30,7 @@ type Permission struct {
|
|||||||
Users []string `xorm:"mediumtext" json:"users"`
|
Users []string `xorm:"mediumtext" json:"users"`
|
||||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||||
|
|
||||||
|
Model string `xorm:"varchar(100)" json:"model"`
|
||||||
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
||||||
Resources []string `xorm:"mediumtext" json:"resources"`
|
Resources []string `xorm:"mediumtext" json:"resources"`
|
||||||
Actions []string `xorm:"mediumtext" json:"actions"`
|
Actions []string `xorm:"mediumtext" json:"actions"`
|
||||||
|
@ -32,10 +32,10 @@ func TestProduct(t *testing.T) {
|
|||||||
cert := getCert(product.Owner, "cert-pay-alipay")
|
cert := getCert(product.Owner, "cert-pay-alipay")
|
||||||
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||||
|
|
||||||
paymentId := util.GenerateTimeId()
|
paymentName := util.GenerateTimeId()
|
||||||
returnUrl := ""
|
returnUrl := ""
|
||||||
notifyUrl := ""
|
notifyUrl := ""
|
||||||
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
|
payUrl, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, returnUrl, notifyUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -142,8 +142,8 @@ func GetProvider(id string) *Provider {
|
|||||||
return getProvider(owner, name)
|
return getProvider(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultHumanCheckProvider() *Provider {
|
func GetDefaultCaptchaProvider() *Provider {
|
||||||
provider := Provider{Owner: "admin", Category: "HumanCheck"}
|
provider := Provider{Owner: "admin", Category: "Captcha"}
|
||||||
existed, err := adapter.Engine.Get(&provider)
|
existed, err := adapter.Engine.Get(&provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -225,3 +225,37 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
|||||||
func (p *Provider) GetId() string {
|
func (p *Provider) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProviderByOwnerName(applicationId string) (*Provider, error) {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(applicationId)
|
||||||
|
provider := Provider{Owner: owner, Name: name, Category: "Captcha"}
|
||||||
|
existed, err := adapter.Engine.Get(&provider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil, fmt.Errorf("the provider: %s does not exist", applicationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProviderByApplication(applicationId, isCurrentProvider string) (*Provider, error) {
|
||||||
|
if isCurrentProvider == "true" {
|
||||||
|
return GetCaptchaProviderByOwnerName(applicationId)
|
||||||
|
}
|
||||||
|
application := GetApplication(applicationId)
|
||||||
|
if application == nil || len(application.Providers) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid application id")
|
||||||
|
}
|
||||||
|
for _, provider := range application.Providers {
|
||||||
|
if provider.Provider == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if provider.Provider.Category == "Captcha" {
|
||||||
|
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
@ -51,7 +51,7 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
|
|||||||
samlResponse.CreateAttr("Version", "2.0")
|
samlResponse.CreateAttr("Version", "2.0")
|
||||||
samlResponse.CreateAttr("IssueInstant", now)
|
samlResponse.CreateAttr("IssueInstant", now)
|
||||||
samlResponse.CreateAttr("Destination", destination)
|
samlResponse.CreateAttr("Destination", destination)
|
||||||
samlResponse.CreateAttr("InResponseTo", fmt.Sprintf("Casdoor_%s", arId))
|
samlResponse.CreateAttr("InResponseTo", fmt.Sprintf("_%s", arId))
|
||||||
samlResponse.CreateElement("saml:Issuer").SetText(host)
|
samlResponse.CreateElement("saml:Issuer").SetText(host)
|
||||||
|
|
||||||
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
|
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
|
||||||
@ -261,13 +261,15 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
}
|
}
|
||||||
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
||||||
ctx.Hash = crypto.SHA1
|
ctx.Hash = crypto.SHA1
|
||||||
signedXML, err := ctx.SignEnveloped(samlResponse)
|
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
// return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
}
|
//}
|
||||||
|
sig, err := ctx.ConstructSignature(samlResponse, true)
|
||||||
|
samlResponse.InsertChildAt(1, sig)
|
||||||
|
|
||||||
doc := etree.NewDocument()
|
doc := etree.NewDocument()
|
||||||
doc.SetRoot(signedXML)
|
doc.SetRoot(samlResponse)
|
||||||
xmlStr, err := doc.WriteToString()
|
xmlStr, err := doc.WriteToString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
@ -56,6 +56,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||||
host = util.UrlJoin(provider.Domain, "/files")
|
host = util.UrlJoin(provider.Domain, "/files")
|
||||||
}
|
}
|
||||||
|
if provider.Type == "Azure Blob" {
|
||||||
|
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
fileUrl := util.UrlJoin(host, objectKey)
|
fileUrl := util.UrlJoin(host, objectKey)
|
||||||
if hasTimestamp {
|
if hasTimestamp {
|
||||||
|
@ -96,6 +96,7 @@ type User struct {
|
|||||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||||
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||||
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
||||||
|
Douyin string `xorm:"douyin vachar(100)" json:"douyin"`
|
||||||
Custom string `xorm:"custom varchar(100)" json:"custom"`
|
Custom string `xorm:"custom varchar(100)" json:"custom"`
|
||||||
|
|
||||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||||
|
@ -65,5 +65,5 @@ func RecordMessage(ctx *context.Context) {
|
|||||||
record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
|
record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
util.SafeGoroutine(func() {object.AddRecord(record)})
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
}
|
}
|
||||||
|
@ -84,12 +84,19 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
||||||
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
||||||
|
|
||||||
|
beego.Router("/api/get-models", &controllers.ApiController{}, "GET:GetModels")
|
||||||
|
beego.Router("/api/get-model", &controllers.ApiController{}, "GET:GetModel")
|
||||||
|
beego.Router("/api/update-model", &controllers.ApiController{}, "POST:UpdateModel")
|
||||||
|
beego.Router("/api/add-model", &controllers.ApiController{}, "POST:AddModel")
|
||||||
|
beego.Router("/api/delete-model", &controllers.ApiController{}, "POST:DeleteModel")
|
||||||
|
|
||||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||||
|
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||||
beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
|
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||||
|
|
||||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
||||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -12,11 +12,22 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- OIDC API
|
- OIDC API
|
||||||
operationId: RootController.GetJwks
|
operationId: RootController.GetJwks
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/jose.JSONWebKey'
|
||||||
/.well-known/openid-configuration:
|
/.well-known/openid-configuration:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- OIDC API
|
- OIDC API
|
||||||
|
description: Get Oidc Discovery
|
||||||
operationId: RootController.GetOidcDiscovery
|
operationId: RootController.GetOidcDiscovery
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.OidcDiscovery'
|
||||||
/api/add-application:
|
/api/add-application:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -58,6 +69,24 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.AddLdap
|
operationId: ApiController.AddLdap
|
||||||
|
/api/add-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: add model
|
||||||
|
operationId: ApiController.AddModel
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/add-organization:
|
/api/add-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -243,11 +272,11 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/api/get-human-check:
|
/api/api/get-captcha:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Login API
|
- Login API
|
||||||
operationId: ApiController.GetHumancheck
|
operationId: ApiController.GetCaptcha
|
||||||
/api/api/reset-email-or-phone:
|
/api/api/reset-email-or-phone:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -271,11 +300,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: from
|
||||||
description: Details of the email request
|
description: Details of the email request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/emailForm'
|
$ref: '#/definitions/controllers.EmailForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: object
|
description: object
|
||||||
@ -299,11 +328,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: from
|
||||||
description: Details of the sms request
|
description: Details of the sms request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/smsForm'
|
$ref: '#/definitions/controllers.SmsForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: object
|
description: object
|
||||||
@ -382,6 +411,24 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.DeleteLdap
|
operationId: ApiController.DeleteLdap
|
||||||
|
/api/delete-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: delete model
|
||||||
|
operationId: ApiController.DeleteModel
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/delete-organization:
|
/api/delete-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -578,6 +625,43 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/get-app-login:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Login API
|
||||||
|
description: get application login
|
||||||
|
operationId: ApiController.GetApplicationLogin
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: clientId
|
||||||
|
description: client id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: responseType
|
||||||
|
description: response type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: redirectUri
|
||||||
|
description: redirect uri
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: scope
|
||||||
|
description: scope
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: state
|
||||||
|
description: state
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
/api/get-application:
|
/api/get-application:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -700,6 +784,42 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.GetLdaps
|
operationId: ApiController.GetLdaps
|
||||||
|
/api/get-model:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: get model
|
||||||
|
operationId: ApiController.GetModel
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the model
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
/api/get-models:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: get models
|
||||||
|
operationId: ApiController.GetModels
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: owner
|
||||||
|
description: The owner of models
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
/api/get-organization:
|
/api/get-organization:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -901,9 +1021,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/definitions/object.Record'
|
||||||
items:
|
|
||||||
$ref: '#/definitions/object.Records'
|
|
||||||
/api/get-records-filter:
|
/api/get-records-filter:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -912,18 +1030,17 @@ paths:
|
|||||||
operationId: ApiController.GetRecordsByFilter
|
operationId: ApiController.GetRecordsByFilter
|
||||||
parameters:
|
parameters:
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: filter
|
||||||
description: filter Record message
|
description: filter Record message
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Records'
|
type: string
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/definitions/object.Record'
|
||||||
items:
|
|
||||||
$ref: '#/definitions/object.Records'
|
|
||||||
/api/get-resource:
|
/api/get-resource:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1216,6 +1333,23 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.Webhook'
|
$ref: '#/definitions/object.Webhook'
|
||||||
|
/api/invoice-payment:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Payment API
|
||||||
|
description: invoice payment
|
||||||
|
operationId: ApiController.InvoicePayment
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the payment
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/login:
|
/api/login:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1224,21 +1358,51 @@ paths:
|
|||||||
operationId: ApiController.Login
|
operationId: ApiController.Login
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- in: query
|
||||||
name: oAuthParams
|
name: clientId
|
||||||
description: oAuth parameters
|
description: clientId
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: responseType
|
||||||
|
description: responseType
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: redirectUri
|
||||||
|
description: redirectUri
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: scope
|
||||||
|
description: scope
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: state
|
||||||
|
description: state
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: nonce
|
||||||
|
description: nonce
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: code_challenge_method
|
||||||
|
description: code_challenge_method
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: code_challenge
|
||||||
|
description: code_challenge
|
||||||
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: form
|
||||||
description: Login information
|
description: Login information
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/RequestForm'
|
$ref: '#/definitions/controllers.RequestForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.api_controller.Response'
|
$ref: '#/definitions/Response'
|
||||||
/api/login/oauth/access_token:
|
/api/login/oauth/access_token:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1424,6 +1588,24 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/run-syncer:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Syncer API
|
||||||
|
description: run syncer
|
||||||
|
operationId: ApiController.RunSyncer
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the syncer
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Syncer'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/send-verification-code:
|
/api/send-verification-code:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1493,42 +1675,6 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Login API
|
- Login API
|
||||||
/api/update-application:
|
/api/update-application:
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- Login API
|
|
||||||
description: get application login
|
|
||||||
operationId: ApiController.GetApplicationLogin
|
|
||||||
parameters:
|
|
||||||
- in: query
|
|
||||||
name: clientId
|
|
||||||
description: client id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: responseType
|
|
||||||
description: response type
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: redirectUri
|
|
||||||
description: redirect uri
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: scope
|
|
||||||
description: scope
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: state
|
|
||||||
description: state
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The Response object
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/controllers.api_controller.Response'
|
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Application API
|
- Application API
|
||||||
@ -1579,6 +1725,29 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.UpdateLdap
|
operationId: ApiController.UpdateLdap
|
||||||
|
/api/update-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: update model
|
||||||
|
operationId: ApiController.UpdateModel
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the model
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/update-organization:
|
/api/update-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1830,27 +1999,97 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Userinfo'
|
$ref: '#/definitions/object.Userinfo'
|
||||||
|
/api/verify-captcha:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Verification API
|
||||||
|
operationId: ApiController.VerifyCaptcha
|
||||||
definitions:
|
definitions:
|
||||||
2127.0xc00036c600.false:
|
2200.0xc0003c4b70.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
2161.0xc00036c630.false:
|
2235.0xc0003c4ba0.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
RequestForm:
|
|
||||||
title: RequestForm
|
|
||||||
type: object
|
|
||||||
Response:
|
Response:
|
||||||
title: Response
|
title: Response
|
||||||
type: object
|
type: object
|
||||||
|
controllers.EmailForm:
|
||||||
|
title: EmailForm
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
receivers:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
sender:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
controllers.RequestForm:
|
||||||
|
title: RequestForm
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
affiliation:
|
||||||
|
type: string
|
||||||
|
application:
|
||||||
|
type: string
|
||||||
|
autoSignin:
|
||||||
|
type: boolean
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
emailCode:
|
||||||
|
type: string
|
||||||
|
firstName:
|
||||||
|
type: string
|
||||||
|
idCard:
|
||||||
|
type: string
|
||||||
|
lastName:
|
||||||
|
type: string
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
organization:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
phoneCode:
|
||||||
|
type: string
|
||||||
|
phonePrefix:
|
||||||
|
type: string
|
||||||
|
provider:
|
||||||
|
type: string
|
||||||
|
redirectUri:
|
||||||
|
type: string
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
relayState:
|
||||||
|
type: string
|
||||||
|
samlRequest:
|
||||||
|
type: string
|
||||||
|
samlResponse:
|
||||||
|
type: string
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
controllers.Response:
|
controllers.Response:
|
||||||
title: Response
|
title: Response
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/2127.0xc00036c600.false'
|
$ref: '#/definitions/2200.0xc0003c4b70.false'
|
||||||
data2:
|
data2:
|
||||||
$ref: '#/definitions/2161.0xc00036c630.false'
|
$ref: '#/definitions/2235.0xc0003c4ba0.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -1859,25 +2098,33 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
sub:
|
sub:
|
||||||
type: string
|
type: string
|
||||||
controllers.api_controller.Response:
|
controllers.SmsForm:
|
||||||
title: Response
|
title: SmsForm
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
content:
|
||||||
$ref: '#/definitions/2127.0xc00036c600.false'
|
type: string
|
||||||
data2:
|
organizationId:
|
||||||
$ref: '#/definitions/2161.0xc00036c630.false'
|
type: string
|
||||||
msg:
|
receivers:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
jose.JSONWebKey:
|
||||||
|
title: JSONWebKey
|
||||||
|
type: object
|
||||||
|
object.AccountItem:
|
||||||
|
title: AccountItem
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
modifyRule:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
status:
|
viewRule:
|
||||||
type: string
|
type: string
|
||||||
sub:
|
visible:
|
||||||
type: string
|
type: boolean
|
||||||
emailForm:
|
|
||||||
title: emailForm
|
|
||||||
type: object
|
|
||||||
object.Adapter:
|
object.Adapter:
|
||||||
title: Adapter
|
title: Adapter
|
||||||
type: object
|
type: object
|
||||||
@ -2037,10 +2284,80 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
|
object.Model:
|
||||||
|
title: Model
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
displayName:
|
||||||
|
type: string
|
||||||
|
isEnabled:
|
||||||
|
type: boolean
|
||||||
|
modelText:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
object.OidcDiscovery:
|
||||||
|
title: OidcDiscovery
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
authorization_endpoint:
|
||||||
|
type: string
|
||||||
|
claims_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
grant_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
id_token_signing_alg_values_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
introspection_endpoint:
|
||||||
|
type: string
|
||||||
|
issuer:
|
||||||
|
type: string
|
||||||
|
jwks_uri:
|
||||||
|
type: string
|
||||||
|
request_object_signing_alg_values_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
request_parameter_supported:
|
||||||
|
type: boolean
|
||||||
|
response_modes_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
response_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
scopes_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
subject_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
token_endpoint:
|
||||||
|
type: string
|
||||||
|
userinfo_endpoint:
|
||||||
|
type: string
|
||||||
object.Organization:
|
object.Organization:
|
||||||
title: Organization
|
title: Organization
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
accountItems:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.AccountItem'
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
@ -2051,6 +2368,8 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
favicon:
|
favicon:
|
||||||
type: string
|
type: string
|
||||||
|
isProfilePublic:
|
||||||
|
type: boolean
|
||||||
masterPassword:
|
masterPassword:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -2081,6 +2400,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
|
invoiceRemark:
|
||||||
|
type: string
|
||||||
|
invoiceTaxId:
|
||||||
|
type: string
|
||||||
|
invoiceTitle:
|
||||||
|
type: string
|
||||||
|
invoiceType:
|
||||||
|
type: string
|
||||||
|
invoiceUrl:
|
||||||
|
type: string
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -2091,6 +2420,14 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
payUrl:
|
payUrl:
|
||||||
type: string
|
type: string
|
||||||
|
personEmail:
|
||||||
|
type: string
|
||||||
|
personIdCard:
|
||||||
|
type: string
|
||||||
|
personName:
|
||||||
|
type: string
|
||||||
|
personPhone:
|
||||||
|
type: string
|
||||||
price:
|
price:
|
||||||
type: number
|
type: number
|
||||||
format: double
|
format: double
|
||||||
@ -2126,6 +2463,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
isEnabled:
|
isEnabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
model:
|
||||||
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
owner:
|
owner:
|
||||||
@ -2205,6 +2544,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
|
customAuthUrl:
|
||||||
|
type: string
|
||||||
|
customLogo:
|
||||||
|
type: string
|
||||||
|
customScope:
|
||||||
|
type: string
|
||||||
|
customTokenUrl:
|
||||||
|
type: string
|
||||||
|
customUserInfoUrl:
|
||||||
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
domain:
|
domain:
|
||||||
@ -2264,9 +2613,35 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
provider:
|
provider:
|
||||||
$ref: '#/definitions/object.Provider'
|
$ref: '#/definitions/object.Provider'
|
||||||
object.Records:
|
object.Record:
|
||||||
title: Records
|
title: Record
|
||||||
type: object
|
type: object
|
||||||
|
properties:
|
||||||
|
action:
|
||||||
|
type: string
|
||||||
|
clientIp:
|
||||||
|
type: string
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
extendedUser:
|
||||||
|
$ref: '#/definitions/object.User'
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
isTriggered:
|
||||||
|
type: boolean
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
organization:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
requestUri:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
object.Role:
|
object.Role:
|
||||||
title: Role
|
title: Role
|
||||||
type: object
|
type: object
|
||||||
@ -2442,6 +2817,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
baidu:
|
baidu:
|
||||||
type: string
|
type: string
|
||||||
|
bilibili:
|
||||||
|
type: string
|
||||||
bio:
|
bio:
|
||||||
type: string
|
type: string
|
||||||
birthday:
|
birthday:
|
||||||
@ -2452,10 +2829,14 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
|
custom:
|
||||||
|
type: string
|
||||||
dingtalk:
|
dingtalk:
|
||||||
type: string
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
|
douyin:
|
||||||
|
type: string
|
||||||
education:
|
education:
|
||||||
type: string
|
type: string
|
||||||
email:
|
email:
|
||||||
@ -2521,6 +2902,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
okta:
|
||||||
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
password:
|
password:
|
||||||
@ -2558,6 +2941,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
|
unionId:
|
||||||
|
type: string
|
||||||
updatedTime:
|
updatedTime:
|
||||||
type: string
|
type: string
|
||||||
wechat:
|
wechat:
|
||||||
@ -2618,9 +3003,6 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
smsForm:
|
|
||||||
title: smsForm
|
|
||||||
type: object
|
|
||||||
xorm.Engine:
|
xorm.Engine:
|
||||||
title: Engine
|
title: Engine
|
||||||
type: object
|
type: object
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<script>
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?5998fcd123c220efc0936edf4f250504";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
|
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Casdoor - An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="https://cdn.casdoor.com/static/favicon.png" />
|
<link rel="apple-touch-icon" href="https://cdn.casdoor.com/static/favicon.png" />
|
||||||
<!--
|
<!--
|
||||||
|
242
web/src/AccountTable.js
Normal file
242
web/src/AccountTable.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 AccountTable 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: Setting.getNewRowNameForTable(table, "Please select an account item"), visible: true};
|
||||||
|
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) => {
|
||||||
|
const items = [
|
||||||
|
{name: "Organization", displayName: i18next.t("general:Organization")},
|
||||||
|
{name: "ID", displayName: i18next.t("general:ID")},
|
||||||
|
{name: "Name", displayName: i18next.t("general:Name")},
|
||||||
|
{name: "Display name", displayName: i18next.t("general:Display name")},
|
||||||
|
{name: "Avatar", displayName: i18next.t("general:Avatar")},
|
||||||
|
{name: "User type", displayName: i18next.t("general:User type")},
|
||||||
|
{name: "Password", displayName: i18next.t("general:Password")},
|
||||||
|
{name: "Email", displayName: i18next.t("general:Email")},
|
||||||
|
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||||
|
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||||
|
{name: "Location", displayName: i18next.t("user:Location")},
|
||||||
|
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||||
|
{name: "Title", displayName: i18next.t("user:Title")},
|
||||||
|
{name: "Homepage", displayName: i18next.t("user:Homepage")},
|
||||||
|
{name: "Bio", displayName: i18next.t("user:Bio")},
|
||||||
|
{name: "Tag", displayName: i18next.t("user:Tag")},
|
||||||
|
{name: "Signup application", displayName: i18next.t("general:Signup application")},
|
||||||
|
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
|
||||||
|
{name: "Properties", displayName: i18next.t("user:Properties")},
|
||||||
|
{name: "Is admin", displayName: i18next.t("user:Is admin")},
|
||||||
|
{name: "Is global admin", displayName: i18next.t("user:Is global admin")},
|
||||||
|
{name: "Is forbidden", displayName: i18next.t("user:Is forbidden")},
|
||||||
|
{name: "Is deleted", displayName: i18next.t("user:Is deleted")},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getItemDisplayName = (text) => {
|
||||||
|
const item = items.filter(item => item.name === text);
|
||||||
|
if (item.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return item[0].displayName;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}}
|
||||||
|
value={getItemDisplayName(text)}
|
||||||
|
onChange={value => {
|
||||||
|
this.updateField(table, index, 'name', value);
|
||||||
|
}} >
|
||||||
|
{
|
||||||
|
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:visible"),
|
||||||
|
dataIndex: 'visible',
|
||||||
|
key: 'visible',
|
||||||
|
width: '120px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch checked={text} onChange={checked => {
|
||||||
|
this.updateField(table, index, 'visible', checked);
|
||||||
|
}} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("organization:viewRule"),
|
||||||
|
dataIndex: 'viewRule',
|
||||||
|
key: 'viewRule',
|
||||||
|
width: '155px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
if (!record.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = [
|
||||||
|
{id: 'Public', name: 'Public'},
|
||||||
|
{id: 'Self', name: 'Self'},
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||||
|
this.updateField(table, index, 'viewRule', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("organization:modifyRule"),
|
||||||
|
dataIndex: 'modifyRule',
|
||||||
|
key: 'modifyRule',
|
||||||
|
width: '155px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
if (!record.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options;
|
||||||
|
if (record.viewRule === "Admin") {
|
||||||
|
options = [
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
{id: 'Immutable', name: 'Immutable'},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
options = [
|
||||||
|
{id: 'Self', name: 'Self'},
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
{id: 'Immutable', name: 'Immutable'},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||||
|
this.updateField(table, index, 'modifyRule', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
key: 'action',
|
||||||
|
width: '100px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
|
||||||
|
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
|
||||||
|
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
|
||||||
|
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table scroll={{x: 'max-content'}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{this.props.title}
|
||||||
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col span={24}>
|
||||||
|
{
|
||||||
|
this.renderTable(this.props.table)
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountTable;
|
@ -69,6 +69,8 @@ import PromptPage from "./auth/PromptPage";
|
|||||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||||
import SamlCallback from './auth/SamlCallback';
|
import SamlCallback from './auth/SamlCallback';
|
||||||
import CasLogout from "./auth/CasLogout";
|
import CasLogout from "./auth/CasLogout";
|
||||||
|
import ModelListPage from "./ModelListPage";
|
||||||
|
import ModelEditPage from "./ModelEditPage";
|
||||||
|
|
||||||
const { Header, Footer } = Layout;
|
const { Header, Footer } = Layout;
|
||||||
|
|
||||||
@ -118,6 +120,8 @@ class App extends Component {
|
|||||||
this.setState({ selectedMenuKey: '/roles' });
|
this.setState({ selectedMenuKey: '/roles' });
|
||||||
} else if (uri.includes('/permissions')) {
|
} else if (uri.includes('/permissions')) {
|
||||||
this.setState({ selectedMenuKey: '/permissions' });
|
this.setState({ selectedMenuKey: '/permissions' });
|
||||||
|
} else if (uri.includes('/models')) {
|
||||||
|
this.setState({ selectedMenuKey: '/models' });
|
||||||
} else if (uri.includes('/providers')) {
|
} else if (uri.includes('/providers')) {
|
||||||
this.setState({ selectedMenuKey: '/providers' });
|
this.setState({ selectedMenuKey: '/providers' });
|
||||||
} else if (uri.includes('/applications')) {
|
} else if (uri.includes('/applications')) {
|
||||||
@ -382,6 +386,13 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="/models">
|
||||||
|
<Link to="/models">
|
||||||
|
{i18next.t("general:Models")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/providers">
|
<Menu.Item key="/providers">
|
||||||
<Link to="/providers">
|
<Link to="/providers">
|
||||||
@ -514,6 +525,8 @@ class App extends Component {
|
|||||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, Popover, Row, Select, Switch, Upload} from 'antd';
|
import {Button, Card, Col, Input, Popover, Row, Select, Switch, Upload} from 'antd';
|
||||||
import {LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import * as CertBackend from "./backend/CertBackend";
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
@ -28,14 +28,15 @@ import UrlTable from "./UrlTable";
|
|||||||
import ProviderTable from "./ProviderTable";
|
import ProviderTable from "./ProviderTable";
|
||||||
import SignupTable from "./SignupTable";
|
import SignupTable from "./SignupTable";
|
||||||
import PromptPage from "./auth/PromptPage";
|
import PromptPage from "./auth/PromptPage";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
|
||||||
import {Controlled as CodeMirror} from 'react-codemirror2';
|
import {Controlled as CodeMirror} from 'react-codemirror2';
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
require('codemirror/theme/material-darker.css');
|
require('codemirror/theme/material-darker.css');
|
||||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||||
|
require("codemirror/mode/xml/xml");
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
const { TextArea } = Input;
|
|
||||||
|
|
||||||
class ApplicationEditPage extends React.Component {
|
class ApplicationEditPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -180,7 +181,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: '100%'} :{}}>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
@ -402,7 +403,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}}/>
|
}}/>
|
||||||
<Upload maxCount={1} accept=".html" showUploadList={false}
|
<Upload maxCount={1} accept=".html" showUploadList={false}
|
||||||
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
||||||
<Button icon={<UploadOutlined />} loading={this.state.uploading}>Click to Upload</Button>
|
<Button icon={<UploadOutlined />} loading={this.state.uploading}>{i18next.t("general:Click to Upload")}</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -478,7 +479,11 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
|
{Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22}>
|
<Col span={22}>
|
||||||
<TextArea rows={8} value={this.state.samlMetadata} />
|
<CodeMirror
|
||||||
|
value={this.state.samlMetadata}
|
||||||
|
options={{mode: 'xml', theme: 'default'}}
|
||||||
|
onBeforeChange={(editor, data, value) => {}}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
@ -500,7 +505,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
{
|
{
|
||||||
this.renderPreview()
|
this.renderSignupSigninPreview()
|
||||||
}
|
}
|
||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
@ -524,29 +529,33 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
{
|
{
|
||||||
this.renderPreview2()
|
this.renderPromptPreview()
|
||||||
}
|
}
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPreview() {
|
renderSignupSigninPreview() {
|
||||||
let signUpUrl = `/signup/${this.state.application.name}`;
|
let signUpUrl = `/signup/${this.state.application.name}`;
|
||||||
let signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
let signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
||||||
|
let maskStyle = {position: 'absolute', top: '0px', left: '0px', zIndex: 10, height: '100%', width: '100%', background: 'rgba(0,0,0,0.4)'};
|
||||||
if (!this.state.application.enablePassword) {
|
if (!this.state.application.enablePassword) {
|
||||||
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||||
}
|
}
|
||||||
if (!Setting.isMobile()) {
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
<Col span={11}>
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
copy(`${window.location.origin}${signUpUrl}`);
|
||||||
</a>
|
Setting.showMessage("success", i18next.t("application:Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("application:Copy signup page URL")}
|
||||||
|
</Button>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
|
||||||
{
|
{
|
||||||
this.state.application.enablePassword ? (
|
this.state.application.enablePassword ? (
|
||||||
<SignupPage application={this.state.application} />
|
<SignupPage application={this.state.application} />
|
||||||
@ -554,64 +563,45 @@ class ApplicationEditPage extends React.Component {
|
|||||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
<Col span={11}>
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
copy(`${window.location.origin}${signInUrl}`);
|
||||||
</a>
|
Setting.showMessage("success", i18next.t("application:Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("application:Copy signin page URL")}
|
||||||
|
</Button>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
|
||||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
} else{
|
|
||||||
return(
|
|
||||||
<React.Fragment>
|
|
||||||
<Col span={24} style={{display:"flex", flexDirection: "column"}}>
|
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
|
||||||
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
|
||||||
</a>
|
|
||||||
<div style={{marginBottom:"10px", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
|
||||||
{
|
|
||||||
this.state.application.enablePassword ? (
|
|
||||||
<SignupPage application={this.state.application} />
|
|
||||||
) : (
|
|
||||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
|
||||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
|
||||||
</a>
|
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
|
||||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
renderPreview2() {
|
renderPromptPreview() {
|
||||||
let promptUrl = `/prompt/${this.state.application.name}`;
|
let promptUrl = `/prompt/${this.state.application.name}`;
|
||||||
|
let maskStyle = {position: 'absolute', top: '0px', left: '0px', zIndex: 10, height: '100%', width: '100%', background: 'rgba(0,0,0,0.4)'};
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<Col span={11}>
|
||||||
<Col span={(Setting.isMobile()) ? 24 : 11} style={{display:"flex", flexDirection: "column", flex: "auto"}} >
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<a style={{marginBottom: "10px"}} target="_blank" rel="noreferrer" href={promptUrl}>
|
copy(`${window.location.origin}${promptUrl}`);
|
||||||
<Button type="primary">{i18next.t("application:Test prompt page..")}</Button>
|
Setting.showMessage("success", i18next.t("application:Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
</a>
|
}}
|
||||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
>
|
||||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
{i18next.t("application:Copy prompt page URL")}
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
</Button>
|
||||||
<PromptPage application={this.state.application} account={this.props.account} />
|
<br/>
|
||||||
</div>
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
||||||
</Col>
|
<PromptPage application={this.state.application} account={this.props.account} />
|
||||||
</React.Fragment>
|
<div style={maskStyle}></div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ import i18next from "i18next";
|
|||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
class ApplicationListPage extends BaseListPage {
|
class ApplicationListPage extends BaseListPage {
|
||||||
|
|
||||||
newApplication() {
|
newApplication() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
@ -36,7 +35,9 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
enableSignUp: true,
|
enableSignUp: true,
|
||||||
enableSigninSession: false,
|
enableSigninSession: false,
|
||||||
enableCodeSignin: false,
|
enableCodeSignin: false,
|
||||||
providers: [],
|
providers: [
|
||||||
|
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, alertType: "None"},
|
||||||
|
],
|
||||||
signupItems: [
|
signupItems: [
|
||||||
{name: "ID", visible: false, required: true, rule: "Random"},
|
{name: "ID", visible: false, required: true, rule: "Random"},
|
||||||
{name: "Username", visible: true, required: true, rule: "None"},
|
{name: "Username", visible: true, required: true, rule: "None"},
|
||||||
|
@ -22,7 +22,6 @@ import i18next from "i18next";
|
|||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
class CertListPage extends BaseListPage {
|
class CertListPage extends BaseListPage {
|
||||||
|
|
||||||
newCert() {
|
newCert() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
|
208
web/src/ModelEditPage.js
Normal file
208
web/src/ModelEditPage.js
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// Copyright 2021 The Casdoor 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 {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
class ModelEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
modelName: props.match.params.modelName,
|
||||||
|
model: null,
|
||||||
|
organizations: [],
|
||||||
|
users: [],
|
||||||
|
models: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getModel();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getModel() {
|
||||||
|
ModelBackend.getModel(this.state.organizationName, this.state.modelName)
|
||||||
|
.then((model) => {
|
||||||
|
this.setState({
|
||||||
|
model: model,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getModels(model.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getModels(organizationName) {
|
||||||
|
ModelBackend.getModels(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
models: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseModelField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModelField(key, value) {
|
||||||
|
value = this.parseModelField(key, value);
|
||||||
|
|
||||||
|
let model = this.state.model;
|
||||||
|
model[key] = value;
|
||||||
|
this.setState({
|
||||||
|
model: model,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderModel() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("model:New Model") : i18next.t("model:Edit Model")}
|
||||||
|
<Button onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
||||||
|
<Row style={{marginTop: '10px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.model.owner} onChange={(value => {this.updateModelField('owner', value);})}>
|
||||||
|
{
|
||||||
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.model.name} onChange={e => {
|
||||||
|
this.updateModelField('name', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.model.displayName} onChange={e => {
|
||||||
|
this.updateModelField('displayName', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<TextArea rows={10} value={this.state.model.modelText} onChange={e => {
|
||||||
|
this.updateModelField('modelText', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.model.isEnabled} onChange={checked => {
|
||||||
|
this.updateModelField('isEnabled', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitModelEdit(willExist) {
|
||||||
|
let model = Setting.deepCopy(this.state.model);
|
||||||
|
ModelBackend.updateModel(this.state.organizationName, this.state.modelName, model)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully saved`);
|
||||||
|
this.setState({
|
||||||
|
modelName: this.state.model.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push(`/models`);
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/models/${this.state.model.owner}/${this.state.model.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
this.updateModelField('name', this.state.modelName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteModel() {
|
||||||
|
ModelBackend.deleteModel(this.state.model)
|
||||||
|
.then(() => {
|
||||||
|
this.props.history.push(`/models`);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.model !== null ? this.renderModel() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
|
<Button size="large" onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelEditPage;
|
201
web/src/ModelListPage.js
Normal file
201
web/src/ModelListPage.js
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
// Copyright 2021 The Casdoor 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 {Link} from "react-router-dom";
|
||||||
|
import {Button, Popconfirm, Switch, Table} from 'antd';
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class ModelListPage extends BaseListPage {
|
||||||
|
newModel() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "built-in",
|
||||||
|
name: `model_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
displayName: `New Model - ${randomName}`,
|
||||||
|
modelText: "",
|
||||||
|
isEnabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addModel() {
|
||||||
|
const newModel = this.newModel();
|
||||||
|
ModelBackend.addModel(newModel)
|
||||||
|
.then((res) => {
|
||||||
|
this.props.history.push({pathname: `/models/${newModel.owner}/${newModel.name}`, mode: "add"});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteModel(i) {
|
||||||
|
ModelBackend.deleteModel(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Model deleted successfully`);
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(models) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: 'owner',
|
||||||
|
key: 'owner',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('owner'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: '150px',
|
||||||
|
fixed: 'left',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('name'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/models/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: 'createdTime',
|
||||||
|
key: 'createdTime',
|
||||||
|
width: '160px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: 'displayName',
|
||||||
|
key: 'displayName',
|
||||||
|
width: '200px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('displayName'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Is enabled"),
|
||||||
|
dataIndex: 'isEnabled',
|
||||||
|
key: 'isEnabled',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: '',
|
||||||
|
key: 'op',
|
||||||
|
width: '170px',
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary"
|
||||||
|
onClick={() => this.props.history.push(`/models/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete model: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteModel(index)}
|
||||||
|
>
|
||||||
|
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={models} rowKey="name" size="middle" bordered
|
||||||
|
pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Models")}
|
||||||
|
<Button type="primary" size="small"
|
||||||
|
onClick={this.addModel.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
ModelBackend.getModels("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelListPage;
|
@ -20,6 +20,7 @@ import * as Setting from "./Setting";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
import LdapTable from "./LdapTable";
|
import LdapTable from "./LdapTable";
|
||||||
|
import AccountTable from "./AccountTable";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@ -127,7 +128,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{i18next.t("general:Preview")}:
|
{i18next.t("general:Preview")}:
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@ -187,7 +188,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@ -197,7 +198,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{i18next.t("general:Preview")}:
|
{i18next.t("general:Preview")}:
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@ -250,6 +251,18 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<AccountTable
|
||||||
|
title={i18next.t("organization:Account items")}
|
||||||
|
table={this.state.organization.accountItems}
|
||||||
|
onUpdateTable={(value) => { this.updateOrganizationField('accountItems', value)}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}}>
|
<Row style={{marginTop: '20px'}}>
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
||||||
|
@ -22,7 +22,6 @@ import i18next from "i18next";
|
|||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
class OrganizationListPage extends BaseListPage {
|
class OrganizationListPage extends BaseListPage {
|
||||||
|
|
||||||
newOrganization() {
|
newOrganization() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
@ -40,6 +39,31 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
masterPassword: "",
|
masterPassword: "",
|
||||||
enableSoftDeletion: false,
|
enableSoftDeletion: false,
|
||||||
isProfilePublic: true,
|
isProfilePublic: true,
|
||||||
|
accountItems: [
|
||||||
|
{name: "Organization", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||||
|
{name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
||||||
|
{name: "Name", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||||
|
{name: "Display name", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Avatar", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "User type", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||||
|
{name: "Password", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||||
|
{name: "Email", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Phone", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Country/Region", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Location", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Affiliation", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Title", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Homepage", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||||
|
{name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||||
|
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||||
|
{name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||||
|
{name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"},
|
||||||
|
{name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||||
|
{name: "Is global admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||||
|
{name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||||
|
{name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import * as UserBackend from "./backend/UserBackend";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as RoleBackend from "./backend/RoleBackend";
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
organizations: [],
|
organizations: [],
|
||||||
users: [],
|
users: [],
|
||||||
roles: [],
|
roles: [],
|
||||||
|
models: [],
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -52,6 +54,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
|
|
||||||
this.getUsers(permission.owner);
|
this.getUsers(permission.owner);
|
||||||
this.getRoles(permission.owner);
|
this.getRoles(permission.owner);
|
||||||
|
this.getModels(permission.owner);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +85,15 @@ class PermissionEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getModels(organizationName) {
|
||||||
|
ModelBackend.getModels(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
models: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parsePermissionField(key, value) {
|
parsePermissionField(key, value) {
|
||||||
if ([""].includes(key)) {
|
if ([""].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@ -146,6 +158,20 @@ class PermissionEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Model"), i18next.t("general:Model - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.model} onChange={(model => {
|
||||||
|
this.updatePermissionField('model', model);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
this.state.models.map((model, index) => <Option key={index} value={model.name}>{model.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
||||||
|
@ -206,7 +206,7 @@ class ProductBuyPage extends React.Component {
|
|||||||
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
|
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
|
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
|
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
|
||||||
<img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/>
|
<img src={product?.image} alt={product?.name} height={90} style={{marginBottom: '20px'}}/>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Price")}>
|
<Descriptions.Item label={i18next.t("product:Price")}>
|
||||||
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
|
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
|
||||||
|
@ -110,7 +110,7 @@ class ProductEditPage extends React.Component {
|
|||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
|
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: '100%'} :{}}>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
|
@ -19,7 +19,9 @@ import * as ProviderBackend from "./backend/ProviderBackend";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { authConfig } from "./auth/Auth";
|
import { authConfig } from "./auth/Auth";
|
||||||
|
import * as ProviderEditTestEmail from "./TestEmailWidget";
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { CaptchaPreview } from "./common/CaptchaPreview";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@ -32,6 +34,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
providerName: props.match.params.providerName,
|
providerName: props.match.params.providerName,
|
||||||
provider: null,
|
provider: null,
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
testEmail: this.props.account["email"] !== undefined ? this.props.account["email"] : "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,10 +73,15 @@ class ProviderEditPage extends React.Component {
|
|||||||
case "Email":
|
case "Email":
|
||||||
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
|
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
|
||||||
case "SMS":
|
case "SMS":
|
||||||
if (this.state.provider.type === "Volc Engine SMS")
|
if (this.state.provider.type === "Volc Engine SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
||||||
if (this.state.provider.type === "Huawei Cloud SMS")
|
} else if (this.state.provider.type === "Huawei Cloud SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||||
|
}
|
||||||
|
case "Captcha":
|
||||||
|
return Setting.getLabel(i18next.t("provider:Site key"), i18next.t("provider:Site key - Tooltip"));
|
||||||
default:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||||
}
|
}
|
||||||
@ -84,10 +92,15 @@ class ProviderEditPage extends React.Component {
|
|||||||
case "Email":
|
case "Email":
|
||||||
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
|
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
|
||||||
case "SMS":
|
case "SMS":
|
||||||
if (this.state.provider.type === "Volc Engine SMS")
|
if (this.state.provider.type === "Volc Engine SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
||||||
if (this.state.provider.type === "Huawei Cloud SMS")
|
} else if (this.state.provider.type === "Huawei Cloud SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
|
}
|
||||||
|
case "Captcha":
|
||||||
|
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||||
default:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
}
|
}
|
||||||
@ -187,6 +200,8 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField('domain', Setting.getFullServerUrl());
|
this.updateProviderField('domain', Setting.getFullServerUrl());
|
||||||
} else if (value === "SAML") {
|
} else if (value === "SAML") {
|
||||||
this.updateProviderField('type', 'Aliyun IDaaS');
|
this.updateProviderField('type', 'Aliyun IDaaS');
|
||||||
|
} else if (value === "Captcha") {
|
||||||
|
this.updateProviderField('type', 'Default');
|
||||||
}
|
}
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
@ -197,6 +212,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
{id: 'Storage', name: 'Storage'},
|
{id: 'Storage', name: 'Storage'},
|
||||||
{id: 'SAML', name: 'SAML'},
|
{id: 'SAML', name: 'SAML'},
|
||||||
{id: 'Payment', name: 'Payment'},
|
{id: 'Payment', name: 'Payment'},
|
||||||
|
{id: 'Captcha', name: 'Captcha'},
|
||||||
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
@ -243,7 +259,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "WeCom" ? null : (
|
this.state.provider.type !== "WeCom" ? null : (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={2}>
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||||
@ -311,7 +327,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@ -321,7 +337,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{i18next.t("general:Preview")}:
|
{i18next.t("general:Preview")}:
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@ -335,26 +351,32 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Row style={{marginTop: '20px'}} >
|
{
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
this.state.provider.category === "Captcha" && this.state.provider.type === "Default" ? null : (
|
||||||
{this.getClientIdLabel()}
|
<React.Fragment>
|
||||||
</Col>
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col span={22} >
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Input value={this.state.provider.clientId} onChange={e => {
|
{this.getClientIdLabel()}
|
||||||
this.updateProviderField('clientId', e.target.value);
|
</Col>
|
||||||
}} />
|
<Col span={22} >
|
||||||
</Col>
|
<Input value={this.state.provider.clientId} onChange={e => {
|
||||||
</Row>
|
this.updateProviderField('clientId', e.target.value);
|
||||||
<Row style={{marginTop: '20px'}} >
|
}} />
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
</Col>
|
||||||
{this.getClientSecretLabel()}
|
</Row>
|
||||||
</Col>
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col span={22} >
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Input value={this.state.provider.clientSecret} onChange={e => {
|
{this.getClientSecretLabel()}
|
||||||
this.updateProviderField('clientSecret', e.target.value);
|
</Col>
|
||||||
}} />
|
<Col span={22} >
|
||||||
</Col>
|
<Input value={this.state.provider.clientSecret} onChange={e => {
|
||||||
</Row>
|
this.updateProviderField('clientSecret', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "WeChat" ? null : (
|
this.state.provider.type !== "WeChat" ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -494,6 +516,27 @@ class ProviderEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={4} >
|
||||||
|
<Input value={this.state.testEmail}
|
||||||
|
placeHolder = {i18next.t("user:Input your email")}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({testEmail: e.target.value})
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
<Button style={{marginLeft: '10px', marginBottom: "5px"}} type="primary"
|
||||||
|
onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||||
|
{i18next.t("provider:Test Connection")}
|
||||||
|
</Button>
|
||||||
|
<Button style={{marginLeft: '10px', marginBottom: "5px"}} type="primary"
|
||||||
|
disabled={!Setting.isValidEmail(this.state.testEmail)}
|
||||||
|
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.testEmail)} >
|
||||||
|
{i18next.t("provider:Send Test Email")}
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : this.state.provider.category === "SMS" ? (
|
) : this.state.provider.category === "SMS" ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -631,6 +674,27 @@ class ProviderEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
{
|
||||||
|
this.state.provider.category !== "Captcha" ? null : (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<CaptchaPreview
|
||||||
|
provider={this.state.provider}
|
||||||
|
providerName={this.state.providerName}
|
||||||
|
clientSecret={this.state.provider.clientSecret}
|
||||||
|
captchaType={this.state.provider.type}
|
||||||
|
owner={this.state.provider.owner}
|
||||||
|
clientId={this.state.provider.clientId}
|
||||||
|
name={this.state.provider.name}
|
||||||
|
providerUrl={this.state.provider.providerUrl}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ import i18next from "i18next";
|
|||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
class ProviderListPage extends BaseListPage {
|
class ProviderListPage extends BaseListPage {
|
||||||
|
|
||||||
newProvider() {
|
newProvider() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
@ -134,6 +133,7 @@ class ProviderListPage extends BaseListPage {
|
|||||||
{text: 'SMS', value: 'SMS', children: Setting.getProviderTypeOptions('SMS').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'SMS', value: 'SMS', children: Setting.getProviderTypeOptions('SMS').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
{text: 'Storage', value: 'Storage', children: Setting.getProviderTypeOptions('Storage').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'Storage', value: 'Storage', children: Setting.getProviderTypeOptions('Storage').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
{text: 'SAML', value: 'SAML', children: Setting.getProviderTypeOptions('SAML').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'SAML', value: 'SAML', children: Setting.getProviderTypeOptions('SAML').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
|
{text: 'Captcha', value: 'Captcha', children: Setting.getProviderTypeOptions('Captcha').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
],
|
],
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
@ -22,7 +22,6 @@ import moment from "moment";
|
|||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
class RecordListPage extends BaseListPage {
|
class RecordListPage extends BaseListPage {
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.state.pagination.pageSize = 20;
|
this.state.pagination.pageSize = 20;
|
||||||
const { pagination } = this.state;
|
const { pagination } = this.state;
|
||||||
|
@ -206,7 +206,7 @@ class ResourceListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
if (record.fileType === "image") {
|
if (record.fileType === "image") {
|
||||||
return (
|
return (
|
||||||
<a target="_blank" href={record.url}>
|
<a target="_blank" rel="noreferrer" href={record.url}>
|
||||||
<img src={record.url} alt={record.name} width={100} />
|
<img src={record.url} alt={record.name} width={100} />
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,6 @@ import i18next from "i18next";
|
|||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import {authConfig} from "./auth/Auth";
|
import {authConfig} from "./auth/Auth";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import moment from "moment";
|
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
|
|
||||||
export let ServerUrl = "";
|
export let ServerUrl = "";
|
||||||
@ -107,6 +106,20 @@ export const OtherProviderInfo = {
|
|||||||
url: "https://gc.org"
|
url: "https://gc.org"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Captcha: {
|
||||||
|
"Default": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||||
|
url: "https://pkg.go.dev/github.com/dchest/captcha",
|
||||||
|
},
|
||||||
|
"reCAPTCHA": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
|
"hCaptcha": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
||||||
|
url: "https://www.hcaptcha.com",
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getCountryRegionData() {
|
export function getCountryRegionData() {
|
||||||
@ -225,7 +238,7 @@ export function isValidInvoiceTitle(invoiceTitle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://blog.css8.cn/post/14210975.html
|
// https://blog.css8.cn/post/14210975.html
|
||||||
const invoiceTitleRegex = /^[\(\)\(\)\u4e00-\u9fa5]{0,50}$/;
|
const invoiceTitleRegex = /^[()()\u4e00-\u9fa5]{0,50}$/;
|
||||||
return invoiceTitleRegex.test(invoiceTitle);
|
return invoiceTitleRegex.test(invoiceTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,27 +487,26 @@ export function changeLanguage(language) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function changeMomentLanguage(language) {
|
export function changeMomentLanguage(language) {
|
||||||
return;
|
// if (language === "zh") {
|
||||||
if (language === "zh") {
|
// moment.locale("zh", {
|
||||||
moment.locale("zh", {
|
// relativeTime: {
|
||||||
relativeTime: {
|
// future: "%s内",
|
||||||
future: "%s内",
|
// past: "%s前",
|
||||||
past: "%s前",
|
// s: "几秒",
|
||||||
s: "几秒",
|
// ss: "%d秒",
|
||||||
ss: "%d秒",
|
// m: "1分钟",
|
||||||
m: "1分钟",
|
// mm: "%d分钟",
|
||||||
mm: "%d分钟",
|
// h: "1小时",
|
||||||
h: "1小时",
|
// hh: "%d小时",
|
||||||
hh: "%d小时",
|
// d: "1天",
|
||||||
d: "1天",
|
// dd: "%d天",
|
||||||
dd: "%d天",
|
// M: "1个月",
|
||||||
M: "1个月",
|
// MM: "%d个月",
|
||||||
MM: "%d个月",
|
// y: "1年",
|
||||||
y: "1年",
|
// yy: "%d年",
|
||||||
yy: "%d年",
|
// },
|
||||||
},
|
// });
|
||||||
});
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClickable(text) {
|
export function getClickable(text) {
|
||||||
@ -556,6 +568,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'Steam', name: 'Steam'},
|
{id: 'Steam', name: 'Steam'},
|
||||||
{id: 'Bilibili', name: 'Bilibili'},
|
{id: 'Bilibili', name: 'Bilibili'},
|
||||||
{id: 'Okta', name: 'Okta'},
|
{id: 'Okta', name: 'Okta'},
|
||||||
|
{id: 'Douyin', name: 'Douyin'},
|
||||||
{id: 'Custom', name: 'Custom'},
|
{id: 'Custom', name: 'Custom'},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -596,6 +609,12 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'PayPal', name: 'PayPal'},
|
{id: 'PayPal', name: 'PayPal'},
|
||||||
{id: 'GC', name: 'GC'},
|
{id: 'GC', name: 'GC'},
|
||||||
]);
|
]);
|
||||||
|
} else if (category === "Captcha") {
|
||||||
|
return ([
|
||||||
|
{id: 'Default', name: 'Default'},
|
||||||
|
{id: 'reCAPTCHA', name: 'reCAPTCHA'},
|
||||||
|
{id: 'hCaptcha', name: 'hCaptcha'},
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -69,27 +69,35 @@ class SignupTable extends React.Component {
|
|||||||
key: 'name',
|
key: 'name',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
const items = [
|
const items = [
|
||||||
{id: 'Username', name: 'Username'},
|
{name: "Username", displayName: i18next.t("signup:Username")},
|
||||||
{id: 'ID', name: 'ID'},
|
{name: "ID", displayName: i18next.t("general:ID")},
|
||||||
{id: 'Display name', name: 'Display name'},
|
{name: "Display name", displayName: i18next.t("general:Display name")},
|
||||||
{id: 'Affiliation', name: 'Affiliation'},
|
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||||
{id: 'Country/Region', name: 'Country/Region'},
|
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||||
{id: 'ID card', name: 'ID card'},
|
{name: "ID card", displayName: i18next.t("user:ID card")},
|
||||||
{id: 'Email', name: 'Email'},
|
{name: "Email", displayName: i18next.t("general:Email")},
|
||||||
{id: 'Password', name: 'Password'},
|
{name: "Password", displayName: i18next.t("forget:Password")},
|
||||||
{id: 'Confirm password', name: 'Confirm password'},
|
{name: "Confirm password", displayName: i18next.t("forget:Confirm")},
|
||||||
{id: 'Phone', name: 'Phone'},
|
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||||
{id: 'Agreement', name: 'Agreement'},
|
{name: "Agreement", displayName: i18next.t("signup:Agreement")},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const getItemDisplayName = (text) => {
|
||||||
|
const item = items.filter(item => item.name === text);
|
||||||
|
if (item.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return item[0].displayName;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select virtual={false} style={{width: '100%'}}
|
<Select virtual={false} style={{width: '100%'}}
|
||||||
value={text}
|
value={getItemDisplayName(text)}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
this.updateField(table, index, 'name', value);
|
this.updateField(table, index, 'name', value);
|
||||||
}} >
|
}} >
|
||||||
{
|
{
|
||||||
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.name}</Option>)
|
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
)
|
)
|
||||||
@ -156,7 +164,7 @@ class SignupTable extends React.Component {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:rule"),
|
title: i18next.t("application:rule"),
|
||||||
dataIndex: 'rule',
|
dataIndex: 'rule',
|
||||||
key: 'rule',
|
key: 'rule',
|
||||||
width: '155px',
|
width: '155px',
|
||||||
|
@ -119,8 +119,12 @@ class SyncerEditPage extends React.Component {
|
|||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.type} onChange={(value => {
|
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.type} onChange={(value => {
|
||||||
this.updateSyncerField('type', value);
|
this.updateSyncerField('type', value);
|
||||||
this.state.syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer);
|
let syncer = this.state.syncer;
|
||||||
this.state.syncer.table = value === "Keycloak" ? "user_entity" : this.state.syncer.table;
|
syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer);
|
||||||
|
syncer.table = (value === "Keycloak") ? "user_entity" : this.state.syncer.table;
|
||||||
|
this.setState({
|
||||||
|
syncer: syncer,
|
||||||
|
});
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
['Database', 'LDAP', 'Keycloak']
|
['Database', 'LDAP', 'Keycloak']
|
||||||
|
@ -22,7 +22,6 @@ import i18next from "i18next";
|
|||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
class SyncerListPage extends BaseListPage {
|
class SyncerListPage extends BaseListPage {
|
||||||
|
|
||||||
newSyncer() {
|
newSyncer() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
|
59
web/src/TestEmailWidget.js
Normal file
59
web/src/TestEmailWidget.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2021 The Casdoor 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 * as Setting from "./Setting";
|
||||||
|
|
||||||
|
export function sendTestEmail(provider, email) {
|
||||||
|
testEmailProvider(provider, email)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully send email`);
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function connectSmtpServer(provider) {
|
||||||
|
testEmailProvider(provider)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully connecting smtp server`);
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testEmailProvider(provider, email = "") {
|
||||||
|
let emailForm = {
|
||||||
|
title: provider.title,
|
||||||
|
content: provider.content,
|
||||||
|
sender: provider.displayName,
|
||||||
|
receivers: email === "" ? ["TestSmtpServer"] : [email],
|
||||||
|
provider: provider.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/send-email`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(emailForm)
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
@ -22,7 +22,6 @@ import i18next from "i18next";
|
|||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
class TokenListPage extends BaseListPage {
|
class TokenListPage extends BaseListPage {
|
||||||
|
|
||||||
newToken() {
|
newToken() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
|
@ -124,46 +124,86 @@ class UserEditPage extends React.Component {
|
|||||||
return (this.state.user.id === this.props.account?.id) || Setting.isAdminUser(this.props.account);
|
return (this.state.user.id === this.props.account?.id) || Setting.isAdminUser(this.props.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUser() {
|
renderAccountItem(accountItem) {
|
||||||
return (
|
if (!accountItem.visible) {
|
||||||
<Card size="small" title={
|
return null;
|
||||||
<div>
|
}
|
||||||
{this.state.mode === "add" ? i18next.t("user:New User") : i18next.t("user:Edit User")}
|
|
||||||
<Button onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
|
const isSelf = this.state.user.id === this.props.account?.id;
|
||||||
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
const isAdmin = Setting.isAdminUser(this.props.account);
|
||||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
|
|
||||||
</div>
|
// return (
|
||||||
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
// <div>
|
||||||
|
// {
|
||||||
|
// JSON.stringify({accountItem: accountItem, isSelf: isSelf, isAdmin: isAdmin})
|
||||||
|
// }
|
||||||
|
// </div>
|
||||||
|
// )
|
||||||
|
|
||||||
|
if (accountItem.viewRule === "Self") {
|
||||||
|
if (!isSelf && !isAdmin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (accountItem.viewRule === "Admin") {
|
||||||
|
if (!isAdmin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let disabled = false;
|
||||||
|
if (accountItem.modifyRule === "Self") {
|
||||||
|
if (!isSelf && !isAdmin) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
} else if (accountItem.modifyRule === "Admin") {
|
||||||
|
if (!isAdmin) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
} else if (accountItem.modifyRule === "Immutable") {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountItem.name === "Organization") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '10px'}} >
|
<Row style={{marginTop: '10px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: '100%'}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.user.owner} onChange={(value => {this.updateUserField('owner', value);})}>
|
<Select virtual={false} style={{width: '100%'}} disabled={disabled} value={this.state.user.owner} onChange={(value => {this.updateUserField('owner', value);})}>
|
||||||
{
|
{
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "ID") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel("ID", i18next.t("general:ID - Tooltip"))} :
|
{Setting.getLabel("ID", i18next.t("general:ID - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.user.id} disabled={true} />
|
<Input value={this.state.user.id} disabled={disabled} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Name") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.user.name} disabled={!Setting.isAdminUser(this.props.account)} onChange={e => {
|
<Input value={this.state.user.name} disabled={disabled} onChange={e => {
|
||||||
this.updateUserField('name', e.target.value);
|
this.updateUserField('name', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Display name") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
@ -174,6 +214,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Avatar") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
||||||
@ -204,6 +247,9 @@ class UserEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "User type") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:User type"), i18next.t("general:User type - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:User type"), i18next.t("general:User type - Tooltip"))} :
|
||||||
@ -217,44 +263,56 @@ class UserEditPage extends React.Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Password") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<PasswordModal user={this.state.user} account={this.props.account} disabled={this.state.userName !== this.state.user.name} />
|
<PasswordModal user={this.state.user} account={this.props.account} disabled={disabled} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Email") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={{paddingRight: '20px'}} span={11} >
|
<Col style={{paddingRight: '20px'}} span={11} >
|
||||||
<Input value={this.state.user.email}
|
<Input value={this.state.user.email}
|
||||||
disabled={this.state.user.id === this.props.account?.id ? true : !Setting.isAdminUser(this.props.account)}
|
disabled={disabled}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
this.updateUserField('email', e.target.value);
|
this.updateUserField('email', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} >
|
<Col span={11} >
|
||||||
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
|
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Phone") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={{paddingRight: '20px'}} span={11} >
|
<Col style={{paddingRight: '20px'}} span={11} >
|
||||||
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
||||||
disabled={this.state.user.id === this.props.account?.id ? true : !Setting.isAdminUser(this.props.account)}
|
disabled={disabled}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
this.updateUserField('phone', e.target.value);
|
this.updateUserField('phone', e.target.value);
|
||||||
}}/>
|
}}/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} >
|
<Col span={11} >
|
||||||
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Country/Region") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
|
||||||
@ -265,6 +323,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Location") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} :
|
||||||
@ -275,11 +336,15 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
)
|
||||||
(this.state.application === null || this.state.user === null) ? null : (
|
} else if (accountItem.name === "Affiliation") {
|
||||||
<AffiliationSelect labelSpan={(Setting.isMobile()) ? 22 : 2} application={this.state.application} user={this.state.user} onUpdateUserField={(key, value) => { return this.updateUserField(key, value)}} />
|
return (
|
||||||
)
|
(this.state.application === null || this.state.user === null) ? null : (
|
||||||
}
|
<AffiliationSelect labelSpan={(Setting.isMobile()) ? 22 : 2} application={this.state.application} user={this.state.user} onUpdateUserField={(key, value) => { return this.updateUserField(key, value)}} />
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Title") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} :
|
||||||
@ -290,6 +355,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Homepage") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} :
|
||||||
@ -300,6 +368,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Bio") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} :
|
||||||
@ -310,6 +381,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Tag") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
||||||
@ -335,102 +409,138 @@ class UserEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Signup application") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Signup application"), i18next.t("general:Signup application - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Signup application"), i18next.t("general:Signup application - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: '100%'}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.user.signupApplication} onChange={(value => {this.updateUserField('signupApplication', value);})}>
|
<Select virtual={false} style={{width: '100%'}} disabled={disabled} value={this.state.user.signupApplication} onChange={(value => {this.updateUserField('signupApplication', value);})}>
|
||||||
{
|
{
|
||||||
this.state.applications.map((application, index) => <Option key={index} value={application.name}>{application.name}</Option>)
|
this.state.applications.map((application, index) => <Option key={index} value={application.name}>{application.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
)
|
||||||
!this.isSelfOrAdmin() ? null : (
|
} else if (accountItem.name === "3rd-party logins") {
|
||||||
<Row style={{marginTop: '20px'}} >
|
return (
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
!this.isSelfOrAdmin() ? null : (
|
||||||
{Setting.getLabel(i18next.t("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} :
|
<Row style={{marginTop: '20px'}} >
|
||||||
</Col>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Col span={22} >
|
{Setting.getLabel(i18next.t("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} :
|
||||||
<div style={{marginBottom: 20}}>
|
</Col>
|
||||||
{
|
<Col span={22} >
|
||||||
(this.state.application === null || this.state.user === null) ? null : (
|
<div style={{marginBottom: 20}}>
|
||||||
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
|
{
|
||||||
(providerItem.provider.category === "OAuth") ? (
|
(this.state.application === null || this.state.user === null) ? null : (
|
||||||
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} />
|
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
|
||||||
) : (
|
(providerItem.provider.category === "OAuth") ? (
|
||||||
<SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} />
|
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} />
|
||||||
)
|
) : (
|
||||||
|
<SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} />
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
</div>
|
}
|
||||||
</Col>
|
</div>
|
||||||
</Row>
|
</Col>
|
||||||
)
|
</Row>
|
||||||
}
|
)
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Properties") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{i18next.t("user:Properties")}:
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<CodeMirror
|
||||||
|
value={JSON.stringify(this.state.user.properties, null, 4)}
|
||||||
|
options={{mode: 'javascript', theme: "material-darker"}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Is admin") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
||||||
|
<Switch checked={this.state.user.isAdmin} onChange={checked => {
|
||||||
|
this.updateUserField('isAdmin', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Is global admin") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
||||||
|
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => {
|
||||||
|
this.updateUserField('isGlobalAdmin', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Is forbidden") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Is forbidden"), i18next.t("user:Is forbidden - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
||||||
|
<Switch checked={this.state.user.isForbidden} onChange={checked => {
|
||||||
|
this.updateUserField('isForbidden', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Is deleted") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Is deleted"), i18next.t("user:Is deleted - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
||||||
|
<Switch checked={this.state.user.isDeleted} onChange={checked => {
|
||||||
|
this.updateUserField('isDeleted', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUser() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("user:New User") : i18next.t("user:Edit User")}
|
||||||
|
<Button onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
||||||
{
|
{
|
||||||
!Setting.isAdminUser(this.props.account) ? null : (
|
this.state.application?.organizationObj.accountItems?.map(accountItem => {
|
||||||
<React.Fragment>
|
return (
|
||||||
{/*<Row style={{marginTop: '20px'}} >*/}
|
<React.Fragment key={accountItem.name}>
|
||||||
{/* <Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>*/}
|
{
|
||||||
{/* {i18next.t("user:Properties")}:*/}
|
this.renderAccountItem(accountItem)
|
||||||
{/* </Col>*/}
|
}
|
||||||
{/* <Col span={22} >*/}
|
</React.Fragment>
|
||||||
{/* <CodeMirror*/}
|
)
|
||||||
{/* value={JSON.stringify(this.state.user.properties, null, 4)}*/}
|
})
|
||||||
{/* options={{mode: 'javascript', theme: "material-darker"}}*/}
|
|
||||||
{/* />*/}
|
|
||||||
{/* </Col>*/}
|
|
||||||
{/*</Row>*/}
|
|
||||||
<Row style={{marginTop: '20px'}} >
|
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
|
||||||
<Switch checked={this.state.user.isAdmin} onChange={checked => {
|
|
||||||
this.updateUserField('isAdmin', checked);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: '20px'}} >
|
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
|
||||||
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => {
|
|
||||||
this.updateUserField('isGlobalAdmin', checked);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: '20px'}} >
|
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("user:Is forbidden"), i18next.t("user:Is forbidden - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
|
||||||
<Switch checked={this.state.user.isForbidden} onChange={checked => {
|
|
||||||
this.updateUserField('isForbidden', checked);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: '20px'}} >
|
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("user:Is deleted"), i18next.t("user:Is deleted - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
|
||||||
<Switch checked={this.state.user.isDeleted} onChange={checked => {
|
|
||||||
this.updateUserField('isDeleted', checked);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,7 +587,7 @@ class UserEditPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
this.state.loading ? <Spin loading={this.state.loading} size="large" /> : (
|
this.state.loading ? <Spin size="large" /> : (
|
||||||
this.state.user !== null ? this.renderUser() :
|
this.state.user !== null ? this.renderUser() :
|
||||||
<Result
|
<Result
|
||||||
status="404"
|
status="404"
|
||||||
|
32
web/src/auth/DouyinLoginButton.js
Normal file
32
web/src/auth/DouyinLoginButton.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 {createButton} from "react-social-login-buttons";
|
||||||
|
import {StaticBaseUrl} from "../Setting";
|
||||||
|
|
||||||
|
function Icon({width = 24, height = 24, color}) {
|
||||||
|
return <img src={`${StaticBaseUrl}/buttons/douyin.svg`} alt="Sign in with Douyin" style={{width: 24, height: 24}}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
text: "Sign in with Douyin",
|
||||||
|
icon: Icon,
|
||||||
|
iconFormat: name => `fa fa-${name}`,
|
||||||
|
style: {background: "#ffffff", color: "#000000"},
|
||||||
|
activeStyle: {background: "#ededee"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const DouyinLoginButton = createButton(config);
|
||||||
|
|
||||||
|
export default DouyinLoginButton;
|
@ -44,10 +44,13 @@ import AzureADLoginButton from "./AzureADLoginButton";
|
|||||||
import SlackLoginButton from "./SlackLoginButton";
|
import SlackLoginButton from "./SlackLoginButton";
|
||||||
import SteamLoginButton from "./SteamLoginButton";
|
import SteamLoginButton from "./SteamLoginButton";
|
||||||
import OktaLoginButton from "./OktaLoginButton";
|
import OktaLoginButton from "./OktaLoginButton";
|
||||||
|
import DouyinLoginButton from "./DouyinLoginButton";
|
||||||
import CustomGithubCorner from "../CustomGithubCorner";
|
import CustomGithubCorner from "../CustomGithubCorner";
|
||||||
import {CountDownInput} from "../common/CountDownInput";
|
import {CountDownInput} from "../common/CountDownInput";
|
||||||
import BilibiliLoginButton from "./BilibiliLoginButton";
|
import BilibiliLoginButton from "./BilibiliLoginButton";
|
||||||
|
|
||||||
|
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||||
|
|
||||||
class LoginPage extends React.Component {
|
class LoginPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -143,47 +146,48 @@ class LoginPage extends React.Component {
|
|||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
const ths = this;
|
const ths = this;
|
||||||
|
|
||||||
//here we are supposed to judge whether casdoor is working as a oauth server or CAS server
|
// here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
|
||||||
if (this.state.type === "cas") {
|
if (this.state.type === "cas") {
|
||||||
//cas
|
// CAS
|
||||||
const casParams = Util.getCasParameters()
|
const casParams = Util.getCasParameters();
|
||||||
values["type"] = this.state.type;
|
values["type"] = this.state.type;
|
||||||
AuthBackend.loginCas(values, casParams).then((res) => {
|
AuthBackend.loginCas(values, casParams).then((res) => {
|
||||||
if (res.status === 'ok') {
|
if (res.status === 'ok') {
|
||||||
let msg = "Logged in successfully. "
|
let msg = "Logged in successfully. ";
|
||||||
if (casParams.service === "") {
|
if (casParams.service === "") {
|
||||||
//If service was not specified, CAS MUST display a message notifying the client that it has successfully initiated a single sign-on session.
|
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
|
||||||
msg += "Now you can visit apps protected by casdoor."
|
msg += "Now you can visit apps protected by Casdoor.";
|
||||||
}
|
}
|
||||||
Util.showMessage("success", msg);
|
Util.showMessage("success", msg);
|
||||||
if (casParams.service !== "") {
|
|
||||||
let st = res.data
|
|
||||||
window.location.href = casParams.service + "?ticket=" + st
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (casParams.service !== "") {
|
||||||
|
let st = res.data;
|
||||||
|
let newUrl = new URL(casParams.service);
|
||||||
|
newUrl.searchParams.append("ticket", st);
|
||||||
|
window.location.href = newUrl.toString();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
//oauth
|
// OAuth
|
||||||
const oAuthParams = Util.getOAuthGetParameters();
|
const oAuthParams = Util.getOAuthGetParameters();
|
||||||
if (oAuthParams !== null && oAuthParams.responseType != null && oAuthParams.responseType !== "") {
|
if (oAuthParams !== null && oAuthParams.responseType != null && oAuthParams.responseType !== "") {
|
||||||
values["type"] = oAuthParams.responseType
|
values["type"] = oAuthParams.responseType;
|
||||||
}else{
|
} else {
|
||||||
values["type"] = this.state.type;
|
values["type"] = this.state.type;
|
||||||
}
|
}
|
||||||
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
|
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
|
||||||
|
|
||||||
if (oAuthParams !== null){
|
if (oAuthParams !== null) {
|
||||||
values["samlRequest"] = oAuthParams.samlRequest;
|
values["samlRequest"] = oAuthParams.samlRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (values["samlRequest"] != null && values["samlRequest"] !== "") {
|
if (values["samlRequest"] != null && values["samlRequest"] !== "") {
|
||||||
values["type"] = "saml";
|
values["type"] = "saml";
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthBackend.login(values, oAuthParams)
|
AuthBackend.login(values, oAuthParams)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 'ok') {
|
if (res.status === 'ok') {
|
||||||
@ -284,6 +288,8 @@ class LoginPage extends React.Component {
|
|||||||
return <BilibiliLoginButton text={text} align={"center"} />
|
return <BilibiliLoginButton text={text} align={"center"} />
|
||||||
} else if (type === "Okta") {
|
} else if (type === "Okta") {
|
||||||
return <OktaLoginButton text={text} align={"center"} />
|
return <OktaLoginButton text={text} align={"center"} />
|
||||||
|
} else if (type === "Douyin") {
|
||||||
|
return <DouyinLoginButton text={text} align={"center"} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
@ -111,6 +111,10 @@ const authInfo = {
|
|||||||
scope: "openid%20profile%20email",
|
scope: "openid%20profile%20email",
|
||||||
endpoint: "http://example.com",
|
endpoint: "http://example.com",
|
||||||
},
|
},
|
||||||
|
Douyin: {
|
||||||
|
scope: "user_info",
|
||||||
|
endpoint: "https://open.douyin.com/platform/oauth/connect",
|
||||||
|
},
|
||||||
Custom: {
|
Custom: {
|
||||||
endpoint: "https://example.com/",
|
endpoint: "https://example.com/",
|
||||||
},
|
},
|
||||||
@ -239,6 +243,8 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`;
|
return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`;
|
||||||
} else if (provider.type === "Okta") {
|
} else if (provider.type === "Okta") {
|
||||||
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
|
} else if (provider.type === "Douyin") {
|
||||||
|
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
} else if (provider.type === "Custom") {
|
} else if (provider.type === "Custom") {
|
||||||
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
|
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
|
||||||
} else if (provider.type === "Bilibili") {
|
} else if (provider.type === "Bilibili") {
|
||||||
|
@ -25,6 +25,8 @@ import {CountDownInput} from "../common/CountDownInput";
|
|||||||
import SelectRegionBox from "../SelectRegionBox";
|
import SelectRegionBox from "../SelectRegionBox";
|
||||||
import CustomGithubCorner from "../CustomGithubCorner";
|
import CustomGithubCorner from "../CustomGithubCorner";
|
||||||
|
|
||||||
|
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||||
|
|
||||||
const formItemLayout = {
|
const formItemLayout = {
|
||||||
labelCol: {
|
labelCol: {
|
||||||
xs: {
|
xs: {
|
||||||
|
56
web/src/backend/ModelBackend.js
Normal file
56
web/src/backend/ModelBackend.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2021 The Casdoor 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 * as Setting from "../Setting";
|
||||||
|
|
||||||
|
export function getModels(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-models?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include"
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModel(owner, name) {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-model?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include"
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateModel(owner, name, model) {
|
||||||
|
let newModel = Setting.deepCopy(model);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/update-model?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newModel),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addModel(model) {
|
||||||
|
let newModel = Setting.deepCopy(model);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/add-model`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newModel),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteModel(model) {
|
||||||
|
let newModel = Setting.deepCopy(model);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/delete-model`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newModel),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
@ -112,6 +112,30 @@ export function sendCode(checkType, checkId, checkKey, dest, type, orgId, checkU
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function verifyCaptcha(captchaType, captchaToken, clientSecret) {
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("captchaType", captchaType);
|
||||||
|
formData.append("captchaToken", captchaToken);
|
||||||
|
formData.append("clientSecret", clientSecret);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/verify-captcha`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: formData
|
||||||
|
}).then(res => res.json()).then(res => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
if (res.data) {
|
||||||
|
Setting.showMessage("success", i18next.t("user:Captcha Verify Success"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", i18next.t("user:Captcha Verify Failed"));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", i18next.t("user:" + res.msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function resetEmailOrPhone(dest, type, code) {
|
export function resetEmailOrPhone(dest, type, code) {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append("dest", dest);
|
formData.append("dest", dest);
|
||||||
@ -124,8 +148,8 @@ export function resetEmailOrPhone(dest, type, code) {
|
|||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHumanCheck() {
|
export function getCaptcha(owner, name, isCurrentProvider) {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-human-check`, {
|
return fetch(`${Setting.ServerUrl}/api/get-captcha?applicationId=${owner}/${encodeURIComponent(name)}&isCurrentProvider=${isCurrentProvider}`, {
|
||||||
method: "GET"
|
method: "GET"
|
||||||
}).then(res => res.json());
|
}).then(res => res.json()).then(res => res.data);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ class HomePage extends React.Component {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div style={{marginRight:'15px',marginLeft:'15px'}}>
|
<div style={{marginRight: "15px", marginLeft: "15px"}}>
|
||||||
<Row style={{marginLeft: "-20px", marginRight: "-20px", marginTop: "20px"}} gutter={24}>
|
<Row style={{marginLeft: "-20px", marginRight: "-20px", marginTop: "20px"}} gutter={24}>
|
||||||
{
|
{
|
||||||
items.map(item => {
|
items.map(item => {
|
||||||
|
139
web/src/common/CaptchaPreview.js
Normal file
139
web/src/common/CaptchaPreview.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 { Button, Col, Input, Modal, Row } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
|
import * as ProviderBackend from "../backend/ProviderBackend";
|
||||||
|
import { SafetyOutlined } from "@ant-design/icons";
|
||||||
|
import { CaptchaWidget } from "./CaptchaWidget";
|
||||||
|
|
||||||
|
export const CaptchaPreview = ({ provider, providerName, clientSecret, captchaType, owner, clientId, name, providerUrl }) => {
|
||||||
|
const [visible, setVisible] = React.useState(false);
|
||||||
|
const [captchaImg, setCaptchaImg] = React.useState("");
|
||||||
|
const [captchaToken, setCaptchaToken] = React.useState("");
|
||||||
|
const [secret, setSecret] = React.useState(clientSecret);
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
UserBackend.verifyCaptcha(
|
||||||
|
captchaType,
|
||||||
|
captchaToken,
|
||||||
|
secret
|
||||||
|
).then(() => {
|
||||||
|
setCaptchaToken("");
|
||||||
|
setVisible(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCaptchaFromBackend = () => {
|
||||||
|
UserBackend.getCaptcha(owner, name, true).then((res) => {
|
||||||
|
if (captchaType === "Default") {
|
||||||
|
setSecret(res.captchaId);
|
||||||
|
setCaptchaImg(res.captchaImage);
|
||||||
|
} else {
|
||||||
|
setSecret(res.clientSecret);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickPreview = () => {
|
||||||
|
setVisible(true);
|
||||||
|
provider.name = name;
|
||||||
|
provider.clientId = clientId;
|
||||||
|
provider.type = captchaType;
|
||||||
|
provider.providerUrl = providerUrl;
|
||||||
|
if (clientSecret !== "***") {
|
||||||
|
provider.clientSecret = clientSecret;
|
||||||
|
ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
|
||||||
|
getCaptchaFromBackend();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getCaptchaFromBackend();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDefaultCaptcha = () => {
|
||||||
|
return (
|
||||||
|
<Col>
|
||||||
|
<Row
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
height: "80px",
|
||||||
|
width: "200px",
|
||||||
|
borderRadius: "3px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Row>
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
value={captchaToken}
|
||||||
|
prefix={<SafetyOutlined />}
|
||||||
|
placeholder={i18next.t("general:Captcha")}
|
||||||
|
onPressEnter={handleOk}
|
||||||
|
onChange={(e) => setCaptchaToken(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (token) => {
|
||||||
|
setCaptchaToken(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const renderCheck = () => {
|
||||||
|
if (captchaType === "Default") {
|
||||||
|
return renderDefaultCaptcha();
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<CaptchaWidget
|
||||||
|
captchaType={captchaType}
|
||||||
|
siteKey={clientId}
|
||||||
|
onChange={onSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Button style={{ fontSize: 14 }} type={"primary"} onClick={clickPreview}>
|
||||||
|
{i18next.t("general:Preview")}
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
closable={false}
|
||||||
|
maskClosable={false}
|
||||||
|
destroyOnClose={true}
|
||||||
|
title={i18next.t("general:Captcha")}
|
||||||
|
visible={visible}
|
||||||
|
okText={i18next.t("user:OK")}
|
||||||
|
cancelText={i18next.t("user:Cancel")}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
width={348}
|
||||||
|
>
|
||||||
|
{renderCheck()}
|
||||||
|
</Modal>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
63
web/src/common/CaptchaWidget.js
Normal file
63
web/src/common/CaptchaWidget.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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, { useEffect } from "react";
|
||||||
|
|
||||||
|
export const CaptchaWidget = ({ captchaType, siteKey, onChange }) => {
|
||||||
|
const loadScript = (src) => {
|
||||||
|
var tag = document.createElement("script");
|
||||||
|
tag.async = false;
|
||||||
|
tag.src = src;
|
||||||
|
var body = document.getElementsByTagName("body")[0];
|
||||||
|
body.appendChild(tag);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
switch (captchaType) {
|
||||||
|
case "reCAPTCHA":
|
||||||
|
const reTimer = setInterval(() => {
|
||||||
|
if (!window.grecaptcha) {
|
||||||
|
loadScript("https://recaptcha.net/recaptcha/api.js");
|
||||||
|
}
|
||||||
|
if (window.grecaptcha && window.grecaptcha.render) {
|
||||||
|
window.grecaptcha.render("captcha", {
|
||||||
|
sitekey: siteKey,
|
||||||
|
callback: onChange,
|
||||||
|
});
|
||||||
|
clearInterval(reTimer);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
break;
|
||||||
|
case "hCaptcha":
|
||||||
|
const hTimer = setInterval(() => {
|
||||||
|
if (!window.hcaptcha) {
|
||||||
|
loadScript("https://js.hcaptcha.com/1/api.js");
|
||||||
|
}
|
||||||
|
if (window.hcaptcha && window.hcaptcha.render) {
|
||||||
|
window.hcaptcha.render("captcha", {
|
||||||
|
sitekey: siteKey,
|
||||||
|
callback: onChange,
|
||||||
|
});
|
||||||
|
clearInterval(hTimer);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [captchaType, siteKey]);
|
||||||
|
|
||||||
|
return <div id="captcha"></div>;
|
||||||
|
};
|
@ -18,6 +18,8 @@ import * as Setting from "../Setting";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as UserBackend from "../backend/UserBackend";
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
import {SafetyOutlined} from "@ant-design/icons";
|
import {SafetyOutlined} from "@ant-design/icons";
|
||||||
|
import {authConfig} from "../auth/Auth";
|
||||||
|
import { CaptchaWidget } from "./CaptchaWidget";
|
||||||
|
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
|
|
||||||
@ -30,6 +32,8 @@ export const CountDownInput = (props) => {
|
|||||||
const [checkId, setCheckId] = React.useState("");
|
const [checkId, setCheckId] = React.useState("");
|
||||||
const [buttonLeftTime, setButtonLeftTime] = React.useState(0);
|
const [buttonLeftTime, setButtonLeftTime] = React.useState(0);
|
||||||
const [buttonLoading, setButtonLoading] = React.useState(false);
|
const [buttonLoading, setButtonLoading] = React.useState(false);
|
||||||
|
const [buttonDisabled, setButtonDisabled] = React.useState(true);
|
||||||
|
const [clientId, setClientId] = React.useState("");
|
||||||
|
|
||||||
const handleCountDown = (leftTime = 60) => {
|
const handleCountDown = (leftTime = 60) => {
|
||||||
let leftTimeSecond = leftTime
|
let leftTimeSecond = leftTime
|
||||||
@ -62,14 +66,23 @@ export const CountDownInput = (props) => {
|
|||||||
setKey("");
|
setKey("");
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadHumanCheck = () => {
|
const loadCaptcha = () => {
|
||||||
UserBackend.getHumanCheck().then(res => {
|
UserBackend.getCaptcha("admin", authConfig.appName, false).then(res => {
|
||||||
if (res.type === "none") {
|
if (res.type === "none") {
|
||||||
UserBackend.sendCode("none", "", "", ...onButtonClickArgs);
|
UserBackend.sendCode("none", "", "", ...onButtonClickArgs).then(res => {
|
||||||
} else if (res.type === "captcha") {
|
if (res) {
|
||||||
|
handleCountDown(60);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (res.type === "Default") {
|
||||||
setCheckId(res.captchaId);
|
setCheckId(res.captchaId);
|
||||||
setCaptchaImg(res.captchaImage);
|
setCaptchaImg(res.captchaImage);
|
||||||
setCheckType("captcha");
|
setCheckType("Default");
|
||||||
|
setVisible(true);
|
||||||
|
} else if (res.type === "reCAPTCHA" || res.type === "hCaptcha") {
|
||||||
|
setCheckType(res.type);
|
||||||
|
setClientId(res.clientId);
|
||||||
|
setCheckId(res.clientSecret);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", i18next.t("signup:Unknown Check Type"));
|
Setting.showMessage("error", i18next.t("signup:Unknown Check Type"));
|
||||||
@ -98,9 +111,23 @@ export const CountDownInput = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSubmit = (token) => {
|
||||||
|
setButtonDisabled(false);
|
||||||
|
setKey(token);
|
||||||
|
}
|
||||||
|
|
||||||
const renderCheck = () => {
|
const renderCheck = () => {
|
||||||
if (checkType === "captcha") return renderCaptcha();
|
if (checkType === "Default") {
|
||||||
return null;
|
return renderCaptcha();
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<CaptchaWidget
|
||||||
|
captchaType={checkType}
|
||||||
|
siteKey={clientId}
|
||||||
|
onChange={onSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -116,7 +143,7 @@ export const CountDownInput = (props) => {
|
|||||||
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
|
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
onSearch={loadHumanCheck}
|
onSearch={loadCaptcha}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
closable={false}
|
closable={false}
|
||||||
@ -128,8 +155,8 @@ export const CountDownInput = (props) => {
|
|||||||
cancelText={i18next.t("user:Cancel")}
|
cancelText={i18next.t("user:Cancel")}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
okButtonProps={{disabled: key.length !== 5}}
|
okButtonProps={{disabled: key.length !== 5 && buttonDisabled}}
|
||||||
width={248}
|
width={348}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
renderCheck()
|
renderCheck()
|
||||||
|
@ -142,7 +142,7 @@ class OAuthWidget extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24 - this.props.labelSpan} >
|
<Col span={24 - this.props.labelSpan} >
|
||||||
<img style={{marginRight: '10px'}} width={30} height={30} src={avatarUrl} alt={name} />
|
<img style={{marginRight: '10px'}} width={30} height={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" />
|
||||||
<span style={{width: this.props.labelSpan === 3 ? '300px' : '130px', display: (Setting.isMobile()) ? 'inline' : "inline-block"}}>
|
<span style={{width: this.props.labelSpan === 3 ? '300px' : '130px', display: (Setting.isMobile()) ? 'inline' : "inline-block"}}>
|
||||||
{
|
{
|
||||||
linkedValue === "" ? (
|
linkedValue === "" ? (
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
"Sign Up": "Registrieren"
|
"Sign Up": "Registrieren"
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
|
"Copy prompt page URL": "Copy prompt page URL",
|
||||||
|
"Copy signin page URL": "Copy signin page URL",
|
||||||
|
"Copy signup page URL": "Copy signup page URL",
|
||||||
"Edit Application": "Anwendung bearbeiten",
|
"Edit Application": "Anwendung bearbeiten",
|
||||||
"Enable code signin": "Code-Anmeldung aktivieren",
|
"Enable code signin": "Code-Anmeldung aktivieren",
|
||||||
"Enable code signin - Tooltip": "Aktiviere Codeanmeldung - Tooltip",
|
"Enable code signin - Tooltip": "Aktiviere Codeanmeldung - Tooltip",
|
||||||
@ -19,21 +22,24 @@
|
|||||||
"Password ON": "Passwort AN",
|
"Password ON": "Passwort AN",
|
||||||
"Password ON - Tooltip": "Whether to allow password login",
|
"Password ON - Tooltip": "Whether to allow password login",
|
||||||
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei",
|
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei",
|
||||||
|
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Redirect URL": "Weiterleitungs-URL",
|
"Redirect URL": "Weiterleitungs-URL",
|
||||||
"Redirect URLs": "Umleitungs-URLs",
|
"Redirect URLs": "Umleitungs-URLs",
|
||||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||||
"Refresh token expire": "Aktualisierungs-Token läuft ab",
|
"Refresh token expire": "Aktualisierungs-Token läuft ab",
|
||||||
"Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip",
|
"Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip",
|
||||||
|
"SAML metadata": "SAML metadata",
|
||||||
|
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||||
|
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Signin session": "Anmeldesitzung",
|
"Signin session": "Anmeldesitzung",
|
||||||
"Signup items": "Artikel registrieren",
|
"Signup items": "Artikel registrieren",
|
||||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||||
"Test prompt page..": "Test-Nachfrageseite..",
|
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Test signin page..": "Anmeldeseite testen..",
|
|
||||||
"Test signup page..": "Anmeldeseite testen..",
|
|
||||||
"Token expire": "Token läuft ab",
|
"Token expire": "Token läuft ab",
|
||||||
"Token expire - Tooltip": "Token läuft ab - Tooltip",
|
"Token expire - Tooltip": "Token läuft ab - Tooltip",
|
||||||
"Token format": "Token-Format",
|
"Token format": "Token-Format",
|
||||||
"Token format - Tooltip": "Token-Format - Tooltip"
|
"Token format - Tooltip": "Token-Format - Tooltip",
|
||||||
|
"rule": "rule"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Bitgröße",
|
"Bit size": "Bitgröße",
|
||||||
@ -99,6 +105,7 @@
|
|||||||
"Cert": "Cert",
|
"Cert": "Cert",
|
||||||
"Cert - Tooltip": "Cert - Tooltip",
|
"Cert - Tooltip": "Cert - Tooltip",
|
||||||
"Certs": "Certs",
|
"Certs": "Certs",
|
||||||
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client-IP",
|
"Client IP": "Client-IP",
|
||||||
"Created time": "Erstellte Zeit",
|
"Created time": "Erstellte Zeit",
|
||||||
"Default avatar": "Standard Avatar",
|
"Default avatar": "Standard Avatar",
|
||||||
@ -131,6 +138,9 @@
|
|||||||
"Master password": "Master-Passwort",
|
"Master password": "Master-Passwort",
|
||||||
"Master password - Tooltip": "Masterpasswort - Tooltip",
|
"Master password - Tooltip": "Masterpasswort - Tooltip",
|
||||||
"Method": "Methode",
|
"Method": "Methode",
|
||||||
|
"Model": "Model",
|
||||||
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
|
"Models": "Models",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Name - Tooltip": "Unique string-style identifier",
|
"Name - Tooltip": "Unique string-style identifier",
|
||||||
"OAuth providers": "OAuth-Anbieter",
|
"OAuth providers": "OAuth-Anbieter",
|
||||||
@ -243,7 +253,15 @@
|
|||||||
"sign up now": "jetzt anmelden",
|
"sign up now": "jetzt anmelden",
|
||||||
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
|
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"Edit Model": "Edit Model",
|
||||||
|
"Model text": "Model text",
|
||||||
|
"Model text - Tooltip": "Model text - Tooltip",
|
||||||
|
"New Model": "New Model"
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
|
"Account items": "Account items",
|
||||||
|
"Account items - Tooltip": "Account items - Tooltip",
|
||||||
"Default avatar": "Standard Avatar",
|
"Default avatar": "Standard Avatar",
|
||||||
"Edit Organization": "Organisation bearbeiten",
|
"Edit Organization": "Organisation bearbeiten",
|
||||||
"Favicon": "Févicon",
|
"Favicon": "Févicon",
|
||||||
@ -255,7 +273,9 @@
|
|||||||
"Tags": "Tags",
|
"Tags": "Tags",
|
||||||
"Tags - Tooltip": "Tags - Tooltip",
|
"Tags - Tooltip": "Tags - Tooltip",
|
||||||
"Website URL": "Website-URL",
|
"Website URL": "Website-URL",
|
||||||
"Website URL - Tooltip": "Unique string-style identifier"
|
"Website URL - Tooltip": "Unique string-style identifier",
|
||||||
|
"modifyRule": "modifyRule",
|
||||||
|
"viewRule": "viewRule"
|
||||||
},
|
},
|
||||||
"payment": {
|
"payment": {
|
||||||
"Confirm your invoice information": "Confirm your invoice information",
|
"Confirm your invoice information": "Confirm your invoice information",
|
||||||
@ -387,7 +407,7 @@
|
|||||||
"Domain": "Domäne",
|
"Domain": "Domäne",
|
||||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||||
"Edit Provider": "Anbieter bearbeiten",
|
"Edit Provider": "Anbieter bearbeiten",
|
||||||
"Email Content": "Email Content",
|
"Email Content": "Email content",
|
||||||
"Email Content - Tooltip": "Unique string-style identifier",
|
"Email Content - Tooltip": "Unique string-style identifier",
|
||||||
"Email Title": "E-Mail-Titel",
|
"Email Title": "E-Mail-Titel",
|
||||||
"Email Title - Tooltip": "Unique string-style identifier",
|
"Email Title - Tooltip": "Unique string-style identifier",
|
||||||
@ -425,7 +445,10 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Geheimer Zugangsschlüssel",
|
"Secret access key": "Geheimer Zugangsschlüssel",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
||||||
|
"Send Test Email": "Send Test Email",
|
||||||
"Sign Name": "Schild Name",
|
"Sign Name": "Schild Name",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
"Sign request": "Signaturanfrage",
|
"Sign request": "Signaturanfrage",
|
||||||
@ -436,12 +459,17 @@
|
|||||||
"Signup HTML": "HTML registrieren",
|
"Signup HTML": "HTML registrieren",
|
||||||
"Signup HTML - Edit": "HTML registrieren - Bearbeiten",
|
"Signup HTML - Edit": "HTML registrieren - Bearbeiten",
|
||||||
"Signup HTML - Tooltip": "HTML registrieren - Tooltip",
|
"Signup HTML - Tooltip": "HTML registrieren - Tooltip",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Vorlagencode",
|
"Template Code": "Vorlagencode",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "Nutzungsbedingungen",
|
"Terms of Use": "Nutzungsbedingungen",
|
||||||
"Terms of Use - Tooltip": "Nutzungsbedingungen - Tooltip",
|
"Terms of Use - Tooltip": "Nutzungsbedingungen - Tooltip",
|
||||||
|
"Test Connection": "Test Smtp Connection",
|
||||||
|
"Test Email": "Test email config",
|
||||||
|
"Test Email - Tooltip": "Email Address",
|
||||||
"Token URL": "Token URL",
|
"Token URL": "Token URL",
|
||||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||||
"Type": "Typ",
|
"Type": "Typ",
|
||||||
@ -454,7 +482,6 @@
|
|||||||
"canUnlink": "canUnlink",
|
"canUnlink": "canUnlink",
|
||||||
"prompted": "gefragt",
|
"prompted": "gefragt",
|
||||||
"required": "benötigt",
|
"required": "benötigt",
|
||||||
"rule": "regel",
|
|
||||||
"visible": "sichtbar"
|
"visible": "sichtbar"
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
@ -483,6 +510,7 @@
|
|||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
"Accept": "Akzeptieren",
|
"Accept": "Akzeptieren",
|
||||||
|
"Agreement": "Agreement",
|
||||||
"Confirm": "Bestätigen",
|
"Confirm": "Bestätigen",
|
||||||
"Decline": "Ablehnen",
|
"Decline": "Ablehnen",
|
||||||
"Have account?": "Haben Sie Konto?",
|
"Have account?": "Haben Sie Konto?",
|
||||||
@ -558,6 +586,8 @@
|
|||||||
"Bio": "Bio",
|
"Bio": "Bio",
|
||||||
"Bio - Tooltip": "Bio - Tooltip",
|
"Bio - Tooltip": "Bio - Tooltip",
|
||||||
"Cancel": "Abbrechen",
|
"Cancel": "Abbrechen",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Code gesendet",
|
"Code Sent": "Code gesendet",
|
||||||
"Country/Region": "Land/Region",
|
"Country/Region": "Land/Region",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
"Sign Up": "Sign Up"
|
"Sign Up": "Sign Up"
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
|
"Copy prompt page URL": "Copy prompt page URL",
|
||||||
|
"Copy signin page URL": "Copy signin page URL",
|
||||||
|
"Copy signup page URL": "Copy signup page URL",
|
||||||
"Edit Application": "Edit Application",
|
"Edit Application": "Edit Application",
|
||||||
"Enable code signin": "Enable code signin",
|
"Enable code signin": "Enable code signin",
|
||||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||||
@ -19,21 +22,24 @@
|
|||||||
"Password ON": "Password ON",
|
"Password ON": "Password ON",
|
||||||
"Password ON - Tooltip": "Password ON - Tooltip",
|
"Password ON - Tooltip": "Password ON - Tooltip",
|
||||||
"Please select a HTML file": "Please select a HTML file",
|
"Please select a HTML file": "Please select a HTML file",
|
||||||
|
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Redirect URL": "Redirect URL",
|
"Redirect URL": "Redirect URL",
|
||||||
"Redirect URLs": "Redirect URLs",
|
"Redirect URLs": "Redirect URLs",
|
||||||
"Redirect URLs - Tooltip": "Redirect URLs - Tooltip",
|
"Redirect URLs - Tooltip": "Redirect URLs - Tooltip",
|
||||||
"Refresh token expire": "Refresh token expire",
|
"Refresh token expire": "Refresh token expire",
|
||||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||||
|
"SAML metadata": "SAML metadata",
|
||||||
|
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||||
|
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Signin session": "Signin session",
|
"Signin session": "Signin session",
|
||||||
"Signup items": "Signup items",
|
"Signup items": "Signup items",
|
||||||
"Signup items - Tooltip": "Signup items - Tooltip",
|
"Signup items - Tooltip": "Signup items - Tooltip",
|
||||||
"Test prompt page..": "Test prompt page..",
|
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Test signin page..": "Test signin page..",
|
|
||||||
"Test signup page..": "Test signup page..",
|
|
||||||
"Token expire": "Token expire",
|
"Token expire": "Token expire",
|
||||||
"Token expire - Tooltip": "Token expire - Tooltip",
|
"Token expire - Tooltip": "Token expire - Tooltip",
|
||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip"
|
"Token format - Tooltip": "Token format - Tooltip",
|
||||||
|
"rule": "rule"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Bit size",
|
"Bit size": "Bit size",
|
||||||
@ -99,6 +105,7 @@
|
|||||||
"Cert": "Cert",
|
"Cert": "Cert",
|
||||||
"Cert - Tooltip": "Cert - Tooltip",
|
"Cert - Tooltip": "Cert - Tooltip",
|
||||||
"Certs": "Certs",
|
"Certs": "Certs",
|
||||||
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -131,6 +138,9 @@
|
|||||||
"Master password": "Master password",
|
"Master password": "Master password",
|
||||||
"Master password - Tooltip": "Master password - Tooltip",
|
"Master password - Tooltip": "Master password - Tooltip",
|
||||||
"Method": "Method",
|
"Method": "Method",
|
||||||
|
"Model": "Model",
|
||||||
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
|
"Models": "Models",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Name - Tooltip": "Name - Tooltip",
|
"Name - Tooltip": "Name - Tooltip",
|
||||||
"OAuth providers": "OAuth providers",
|
"OAuth providers": "OAuth providers",
|
||||||
@ -243,7 +253,15 @@
|
|||||||
"sign up now": "sign up now",
|
"sign up now": "sign up now",
|
||||||
"username, Email or phone": "username, Email or phone"
|
"username, Email or phone": "username, Email or phone"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"Edit Model": "Edit Model",
|
||||||
|
"Model text": "Model text",
|
||||||
|
"Model text - Tooltip": "Model text - Tooltip",
|
||||||
|
"New Model": "New Model"
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
|
"Account items": "Account items",
|
||||||
|
"Account items - Tooltip": "Account items - Tooltip",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
"Edit Organization": "Edit Organization",
|
"Edit Organization": "Edit Organization",
|
||||||
"Favicon": "Favicon",
|
"Favicon": "Favicon",
|
||||||
@ -255,7 +273,9 @@
|
|||||||
"Tags": "Tags",
|
"Tags": "Tags",
|
||||||
"Tags - Tooltip": "Tags - Tooltip",
|
"Tags - Tooltip": "Tags - Tooltip",
|
||||||
"Website URL": "Website URL",
|
"Website URL": "Website URL",
|
||||||
"Website URL - Tooltip": "Website URL - Tooltip"
|
"Website URL - Tooltip": "Website URL - Tooltip",
|
||||||
|
"modifyRule": "modifyRule",
|
||||||
|
"viewRule": "viewRule"
|
||||||
},
|
},
|
||||||
"payment": {
|
"payment": {
|
||||||
"Confirm your invoice information": "Confirm your invoice information",
|
"Confirm your invoice information": "Confirm your invoice information",
|
||||||
@ -387,9 +407,9 @@
|
|||||||
"Domain": "Domain",
|
"Domain": "Domain",
|
||||||
"Domain - Tooltip": "Domain - Tooltip",
|
"Domain - Tooltip": "Domain - Tooltip",
|
||||||
"Edit Provider": "Edit Provider",
|
"Edit Provider": "Edit Provider",
|
||||||
"Email Content": "Email Content",
|
"Email Content": "Email content",
|
||||||
"Email Content - Tooltip": "Email Content - Tooltip",
|
"Email Content - Tooltip": "Email Content - Tooltip",
|
||||||
"Email Title": "Email Title",
|
"Email Title": "Email title",
|
||||||
"Email Title - Tooltip": "Email Title - Tooltip",
|
"Email Title - Tooltip": "Email Title - Tooltip",
|
||||||
"Endpoint": "Endpoint",
|
"Endpoint": "Endpoint",
|
||||||
"Endpoint (Intranet)": "Endpoint (Intranet)",
|
"Endpoint (Intranet)": "Endpoint (Intranet)",
|
||||||
@ -425,7 +445,10 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Secret access key",
|
"Secret access key": "Secret access key",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
||||||
|
"Send Test Email": "Send Test Email",
|
||||||
"Sign Name": "Sign Name",
|
"Sign Name": "Sign Name",
|
||||||
"Sign Name - Tooltip": "Sign Name - Tooltip",
|
"Sign Name - Tooltip": "Sign Name - Tooltip",
|
||||||
"Sign request": "Sign request",
|
"Sign request": "Sign request",
|
||||||
@ -436,12 +459,17 @@
|
|||||||
"Signup HTML": "Signup HTML",
|
"Signup HTML": "Signup HTML",
|
||||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||||
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Template Code",
|
"Template Code": "Template Code",
|
||||||
"Template Code - Tooltip": "Template Code - Tooltip",
|
"Template Code - Tooltip": "Template Code - Tooltip",
|
||||||
"Terms of Use": "Terms of Use",
|
"Terms of Use": "Terms of Use",
|
||||||
"Terms of Use - Tooltip": "Terms of Use - Tooltip",
|
"Terms of Use - Tooltip": "Terms of Use - Tooltip",
|
||||||
|
"Test Connection": "Test Smtp Connection",
|
||||||
|
"Test Email": "Test email config",
|
||||||
|
"Test Email - Tooltip": "Email Address",
|
||||||
"Token URL": "Token URL",
|
"Token URL": "Token URL",
|
||||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
@ -454,7 +482,6 @@
|
|||||||
"canUnlink": "canUnlink",
|
"canUnlink": "canUnlink",
|
||||||
"prompted": "prompted",
|
"prompted": "prompted",
|
||||||
"required": "required",
|
"required": "required",
|
||||||
"rule": "rule",
|
|
||||||
"visible": "visible"
|
"visible": "visible"
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
@ -483,6 +510,7 @@
|
|||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
"Accept": "Accept",
|
"Accept": "Accept",
|
||||||
|
"Agreement": "Agreement",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
"Decline": "Decline",
|
"Decline": "Decline",
|
||||||
"Have account?": "Have account?",
|
"Have account?": "Have account?",
|
||||||
@ -558,6 +586,8 @@
|
|||||||
"Bio": "Bio",
|
"Bio": "Bio",
|
||||||
"Bio - Tooltip": "Bio - Tooltip",
|
"Bio - Tooltip": "Bio - Tooltip",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Code Sent",
|
"Code Sent": "Code Sent",
|
||||||
"Country/Region": "Country/Region",
|
"Country/Region": "Country/Region",
|
||||||
"Country/Region - Tooltip": "Country/Region - Tooltip",
|
"Country/Region - Tooltip": "Country/Region - Tooltip",
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
"Sign Up": "S'inscrire"
|
"Sign Up": "S'inscrire"
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
|
"Copy prompt page URL": "Copy prompt page URL",
|
||||||
|
"Copy signin page URL": "Copy signin page URL",
|
||||||
|
"Copy signup page URL": "Copy signup page URL",
|
||||||
"Edit Application": "Modifier l'application",
|
"Edit Application": "Modifier l'application",
|
||||||
"Enable code signin": "Activer la connexion au code",
|
"Enable code signin": "Activer la connexion au code",
|
||||||
"Enable code signin - Tooltip": "Activer la connexion au code - infobulle",
|
"Enable code signin - Tooltip": "Activer la connexion au code - infobulle",
|
||||||
@ -19,21 +22,24 @@
|
|||||||
"Password ON": "Mot de passe activé",
|
"Password ON": "Mot de passe activé",
|
||||||
"Password ON - Tooltip": "Whether to allow password login",
|
"Password ON - Tooltip": "Whether to allow password login",
|
||||||
"Please select a HTML file": "Veuillez sélectionner un fichier HTML",
|
"Please select a HTML file": "Veuillez sélectionner un fichier HTML",
|
||||||
|
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Redirect URL": "URL de redirection",
|
"Redirect URL": "URL de redirection",
|
||||||
"Redirect URLs": "URL de redirection",
|
"Redirect URLs": "URL de redirection",
|
||||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||||
"Refresh token expire": "Expiration du jeton d'actualisation",
|
"Refresh token expire": "Expiration du jeton d'actualisation",
|
||||||
"Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle",
|
"Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle",
|
||||||
|
"SAML metadata": "SAML metadata",
|
||||||
|
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||||
|
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Signin session": "Connexion à la session",
|
"Signin session": "Connexion à la session",
|
||||||
"Signup items": "Inscrire des éléments",
|
"Signup items": "Inscrire des éléments",
|
||||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||||
"Test prompt page..": "Tester la vitesse d'exécution.",
|
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Test signin page..": "Tester la connexion en ligne.",
|
|
||||||
"Test signup page..": "Tester l'inscription.",
|
|
||||||
"Token expire": "Expiration du jeton",
|
"Token expire": "Expiration du jeton",
|
||||||
"Token expire - Tooltip": "Expiration du jeton - Info-bulle",
|
"Token expire - Tooltip": "Expiration du jeton - Info-bulle",
|
||||||
"Token format": "Format du jeton",
|
"Token format": "Format du jeton",
|
||||||
"Token format - Tooltip": "Format du jeton - infobulle"
|
"Token format - Tooltip": "Format du jeton - infobulle",
|
||||||
|
"rule": "rule"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Taille du bit",
|
"Bit size": "Taille du bit",
|
||||||
@ -99,6 +105,7 @@
|
|||||||
"Cert": "Cert",
|
"Cert": "Cert",
|
||||||
"Cert - Tooltip": "Cert - Tooltip",
|
"Cert - Tooltip": "Cert - Tooltip",
|
||||||
"Certs": "Certes",
|
"Certs": "Certes",
|
||||||
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "IP du client",
|
"Client IP": "IP du client",
|
||||||
"Created time": "Date de création",
|
"Created time": "Date de création",
|
||||||
"Default avatar": "Avatar par défaut",
|
"Default avatar": "Avatar par défaut",
|
||||||
@ -131,6 +138,9 @@
|
|||||||
"Master password": "Mot de passe maître",
|
"Master password": "Mot de passe maître",
|
||||||
"Master password - Tooltip": "Mot de passe maître - Infobulle",
|
"Master password - Tooltip": "Mot de passe maître - Infobulle",
|
||||||
"Method": "Méthode",
|
"Method": "Méthode",
|
||||||
|
"Model": "Model",
|
||||||
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
|
"Models": "Models",
|
||||||
"Name": "Nom",
|
"Name": "Nom",
|
||||||
"Name - Tooltip": "Unique string-style identifier",
|
"Name - Tooltip": "Unique string-style identifier",
|
||||||
"OAuth providers": "Fournisseurs OAuth",
|
"OAuth providers": "Fournisseurs OAuth",
|
||||||
@ -243,7 +253,15 @@
|
|||||||
"sign up now": "inscrivez-vous maintenant",
|
"sign up now": "inscrivez-vous maintenant",
|
||||||
"username, Email or phone": "nom d'utilisateur, e-mail ou téléphone"
|
"username, Email or phone": "nom d'utilisateur, e-mail ou téléphone"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"Edit Model": "Edit Model",
|
||||||
|
"Model text": "Model text",
|
||||||
|
"Model text - Tooltip": "Model text - Tooltip",
|
||||||
|
"New Model": "New Model"
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
|
"Account items": "Account items",
|
||||||
|
"Account items - Tooltip": "Account items - Tooltip",
|
||||||
"Default avatar": "Avatar par défaut",
|
"Default avatar": "Avatar par défaut",
|
||||||
"Edit Organization": "Modifier l'organisation",
|
"Edit Organization": "Modifier l'organisation",
|
||||||
"Favicon": "Favicon",
|
"Favicon": "Favicon",
|
||||||
@ -255,7 +273,9 @@
|
|||||||
"Tags": "Tags",
|
"Tags": "Tags",
|
||||||
"Tags - Tooltip": "Tags - Tooltip",
|
"Tags - Tooltip": "Tags - Tooltip",
|
||||||
"Website URL": "URL du site web",
|
"Website URL": "URL du site web",
|
||||||
"Website URL - Tooltip": "Unique string-style identifier"
|
"Website URL - Tooltip": "Unique string-style identifier",
|
||||||
|
"modifyRule": "modifyRule",
|
||||||
|
"viewRule": "viewRule"
|
||||||
},
|
},
|
||||||
"payment": {
|
"payment": {
|
||||||
"Confirm your invoice information": "Confirm your invoice information",
|
"Confirm your invoice information": "Confirm your invoice information",
|
||||||
@ -387,7 +407,7 @@
|
|||||||
"Domain": "Domaine",
|
"Domain": "Domaine",
|
||||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||||
"Edit Provider": "Modifier le fournisseur",
|
"Edit Provider": "Modifier le fournisseur",
|
||||||
"Email Content": "Email Content",
|
"Email Content": "Email content",
|
||||||
"Email Content - Tooltip": "Unique string-style identifier",
|
"Email Content - Tooltip": "Unique string-style identifier",
|
||||||
"Email Title": "Titre de l'e-mail",
|
"Email Title": "Titre de l'e-mail",
|
||||||
"Email Title - Tooltip": "Unique string-style identifier",
|
"Email Title - Tooltip": "Unique string-style identifier",
|
||||||
@ -425,7 +445,10 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Clé d'accès secrète",
|
"Secret access key": "Clé d'accès secrète",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Infobulle",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Infobulle",
|
||||||
|
"Send Test Email": "Send Test Email",
|
||||||
"Sign Name": "Nom du panneau",
|
"Sign Name": "Nom du panneau",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
"Sign request": "Demande de signature",
|
"Sign request": "Demande de signature",
|
||||||
@ -436,12 +459,17 @@
|
|||||||
"Signup HTML": "Inscription HTML",
|
"Signup HTML": "Inscription HTML",
|
||||||
"Signup HTML - Edit": "Inscription HTML - Modifier",
|
"Signup HTML - Edit": "Inscription HTML - Modifier",
|
||||||
"Signup HTML - Tooltip": "Inscription HTML - infobulle",
|
"Signup HTML - Tooltip": "Inscription HTML - infobulle",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Code du modèle",
|
"Template Code": "Code du modèle",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "Conditions d'utilisation",
|
"Terms of Use": "Conditions d'utilisation",
|
||||||
"Terms of Use - Tooltip": "Conditions d'utilisation - Info-bulle",
|
"Terms of Use - Tooltip": "Conditions d'utilisation - Info-bulle",
|
||||||
|
"Test Connection": "Test Smtp Connection",
|
||||||
|
"Test Email": "Test email config",
|
||||||
|
"Test Email - Tooltip": "Email Address",
|
||||||
"Token URL": "Token URL",
|
"Token URL": "Token URL",
|
||||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||||
"Type": "Type de texte",
|
"Type": "Type de texte",
|
||||||
@ -454,7 +482,6 @@
|
|||||||
"canUnlink": "canUnlink",
|
"canUnlink": "canUnlink",
|
||||||
"prompted": "invitée",
|
"prompted": "invitée",
|
||||||
"required": "Obligatoire",
|
"required": "Obligatoire",
|
||||||
"rule": "règle",
|
|
||||||
"visible": "Visible"
|
"visible": "Visible"
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
@ -483,6 +510,7 @@
|
|||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
"Accept": "Accepter",
|
"Accept": "Accepter",
|
||||||
|
"Agreement": "Agreement",
|
||||||
"Confirm": "Valider",
|
"Confirm": "Valider",
|
||||||
"Decline": "Refuser",
|
"Decline": "Refuser",
|
||||||
"Have account?": "Vous avez un compte ?",
|
"Have account?": "Vous avez un compte ?",
|
||||||
@ -558,6 +586,8 @@
|
|||||||
"Bio": "Bio",
|
"Bio": "Bio",
|
||||||
"Bio - Tooltip": "Bio - Infobulle",
|
"Bio - Tooltip": "Bio - Infobulle",
|
||||||
"Cancel": "Abandonner",
|
"Cancel": "Abandonner",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Code envoyé",
|
"Code Sent": "Code envoyé",
|
||||||
"Country/Region": "Pays/Région",
|
"Country/Region": "Pays/Région",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
"Sign Up": "新規登録"
|
"Sign Up": "新規登録"
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
|
"Copy prompt page URL": "Copy prompt page URL",
|
||||||
|
"Copy signin page URL": "Copy signin page URL",
|
||||||
|
"Copy signup page URL": "Copy signup page URL",
|
||||||
"Edit Application": "アプリケーションを編集",
|
"Edit Application": "アプリケーションを編集",
|
||||||
"Enable code signin": "コードサインインを有効にする",
|
"Enable code signin": "コードサインインを有効にする",
|
||||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||||
@ -19,21 +22,24 @@
|
|||||||
"Password ON": "パスワードON",
|
"Password ON": "パスワードON",
|
||||||
"Password ON - Tooltip": "Whether to allow password login",
|
"Password ON - Tooltip": "Whether to allow password login",
|
||||||
"Please select a HTML file": "HTMLファイルを選択してください",
|
"Please select a HTML file": "HTMLファイルを選択してください",
|
||||||
|
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Redirect URL": "リダイレクトURL",
|
"Redirect URL": "リダイレクトURL",
|
||||||
"Redirect URLs": "リダイレクトURL",
|
"Redirect URLs": "リダイレクトURL",
|
||||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||||
"Refresh token expire": "トークンの更新の期限が切れます",
|
"Refresh token expire": "トークンの更新の期限が切れます",
|
||||||
"Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ",
|
"Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ",
|
||||||
|
"SAML metadata": "SAML metadata",
|
||||||
|
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||||
|
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Signin session": "サインインセッション",
|
"Signin session": "サインインセッション",
|
||||||
"Signup items": "アイテムの登録",
|
"Signup items": "アイテムの登録",
|
||||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||||
"Test prompt page..": "テストプロンプトページ...",
|
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Test signin page..": "サインインテストページ...",
|
|
||||||
"Test signup page..": "登録ページのテスト",
|
|
||||||
"Token expire": "トークンの有効期限",
|
"Token expire": "トークンの有効期限",
|
||||||
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
|
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
|
||||||
"Token format": "トークンのフォーマット",
|
"Token format": "トークンのフォーマット",
|
||||||
"Token format - Tooltip": "トークンフォーマット - ツールチップ"
|
"Token format - Tooltip": "トークンフォーマット - ツールチップ",
|
||||||
|
"rule": "rule"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "ビットサイズ",
|
"Bit size": "ビットサイズ",
|
||||||
@ -99,6 +105,7 @@
|
|||||||
"Cert": "Cert",
|
"Cert": "Cert",
|
||||||
"Cert - Tooltip": "Cert - Tooltip",
|
"Cert - Tooltip": "Cert - Tooltip",
|
||||||
"Certs": "Certs",
|
"Certs": "Certs",
|
||||||
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "クライアント IP",
|
"Client IP": "クライアント IP",
|
||||||
"Created time": "作成日時",
|
"Created time": "作成日時",
|
||||||
"Default avatar": "デフォルトのアバター",
|
"Default avatar": "デフォルトのアバター",
|
||||||
@ -131,6 +138,9 @@
|
|||||||
"Master password": "マスターパスワード",
|
"Master password": "マスターパスワード",
|
||||||
"Master password - Tooltip": "マスターパスワード - ツールチップ",
|
"Master password - Tooltip": "マスターパスワード - ツールチップ",
|
||||||
"Method": "方法",
|
"Method": "方法",
|
||||||
|
"Model": "Model",
|
||||||
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
|
"Models": "Models",
|
||||||
"Name": "名前",
|
"Name": "名前",
|
||||||
"Name - Tooltip": "Unique string-style identifier",
|
"Name - Tooltip": "Unique string-style identifier",
|
||||||
"OAuth providers": "OAuthプロバイダー",
|
"OAuth providers": "OAuthプロバイダー",
|
||||||
@ -243,7 +253,15 @@
|
|||||||
"sign up now": "今すぐサインアップ",
|
"sign up now": "今すぐサインアップ",
|
||||||
"username, Email or phone": "ユーザー名、メールアドレスまたは電話番号"
|
"username, Email or phone": "ユーザー名、メールアドレスまたは電話番号"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"Edit Model": "Edit Model",
|
||||||
|
"Model text": "Model text",
|
||||||
|
"Model text - Tooltip": "Model text - Tooltip",
|
||||||
|
"New Model": "New Model"
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
|
"Account items": "Account items",
|
||||||
|
"Account items - Tooltip": "Account items - Tooltip",
|
||||||
"Default avatar": "デフォルトのアバター",
|
"Default avatar": "デフォルトのアバター",
|
||||||
"Edit Organization": "組織を編集",
|
"Edit Organization": "組織を編集",
|
||||||
"Favicon": "ファビコン",
|
"Favicon": "ファビコン",
|
||||||
@ -255,7 +273,9 @@
|
|||||||
"Tags": "Tags",
|
"Tags": "Tags",
|
||||||
"Tags - Tooltip": "Tags - Tooltip",
|
"Tags - Tooltip": "Tags - Tooltip",
|
||||||
"Website URL": "Website URL",
|
"Website URL": "Website URL",
|
||||||
"Website URL - Tooltip": "Unique string-style identifier"
|
"Website URL - Tooltip": "Unique string-style identifier",
|
||||||
|
"modifyRule": "modifyRule",
|
||||||
|
"viewRule": "viewRule"
|
||||||
},
|
},
|
||||||
"payment": {
|
"payment": {
|
||||||
"Confirm your invoice information": "Confirm your invoice information",
|
"Confirm your invoice information": "Confirm your invoice information",
|
||||||
@ -387,7 +407,7 @@
|
|||||||
"Domain": "ドメイン",
|
"Domain": "ドメイン",
|
||||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||||
"Edit Provider": "プロバイダーを編集",
|
"Edit Provider": "プロバイダーを編集",
|
||||||
"Email Content": "Email Content",
|
"Email Content": "Email content",
|
||||||
"Email Content - Tooltip": "Unique string-style identifier",
|
"Email Content - Tooltip": "Unique string-style identifier",
|
||||||
"Email Title": "メールタイトル",
|
"Email Title": "メールタイトル",
|
||||||
"Email Title - Tooltip": "Unique string-style identifier",
|
"Email Title - Tooltip": "Unique string-style identifier",
|
||||||
@ -425,7 +445,10 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "シークレットアクセスキー",
|
"Secret access key": "シークレットアクセスキー",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "シークレットアクセスキー - ツールチップ",
|
"SecretAccessKey - Tooltip": "シークレットアクセスキー - ツールチップ",
|
||||||
|
"Send Test Email": "Send Test Email",
|
||||||
"Sign Name": "署名名",
|
"Sign Name": "署名名",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
"Sign request": "サインリクエスト",
|
"Sign request": "サインリクエスト",
|
||||||
@ -436,12 +459,17 @@
|
|||||||
"Signup HTML": "HTMLの登録",
|
"Signup HTML": "HTMLの登録",
|
||||||
"Signup HTML - Edit": "HTMLの登録 - 編集",
|
"Signup HTML - Edit": "HTMLの登録 - 編集",
|
||||||
"Signup HTML - Tooltip": "サインアップ HTML - ツールチップ",
|
"Signup HTML - Tooltip": "サインアップ HTML - ツールチップ",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "テンプレートコード",
|
"Template Code": "テンプレートコード",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "利用規約",
|
"Terms of Use": "利用規約",
|
||||||
"Terms of Use - Tooltip": "利用規約 - ツールチップ",
|
"Terms of Use - Tooltip": "利用規約 - ツールチップ",
|
||||||
|
"Test Connection": "Test Smtp Connection",
|
||||||
|
"Test Email": "Test email config",
|
||||||
|
"Test Email - Tooltip": "Email Address",
|
||||||
"Token URL": "Token URL",
|
"Token URL": "Token URL",
|
||||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||||
"Type": "タイプ",
|
"Type": "タイプ",
|
||||||
@ -454,7 +482,6 @@
|
|||||||
"canUnlink": "canUnlink",
|
"canUnlink": "canUnlink",
|
||||||
"prompted": "プロンプトされた",
|
"prompted": "プロンプトされた",
|
||||||
"required": "必須",
|
"required": "必須",
|
||||||
"rule": "ルール",
|
|
||||||
"visible": "表示"
|
"visible": "表示"
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
@ -483,6 +510,7 @@
|
|||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
"Accept": "同意する",
|
"Accept": "同意する",
|
||||||
|
"Agreement": "Agreement",
|
||||||
"Confirm": "確認する",
|
"Confirm": "確認する",
|
||||||
"Decline": "同意しない",
|
"Decline": "同意しない",
|
||||||
"Have account?": "アカウントをお持ちですか?",
|
"Have account?": "アカウントをお持ちですか?",
|
||||||
@ -558,6 +586,8 @@
|
|||||||
"Bio": "略歴",
|
"Bio": "略歴",
|
||||||
"Bio - Tooltip": "バイオチップ(ツールチップ)",
|
"Bio - Tooltip": "バイオチップ(ツールチップ)",
|
||||||
"Cancel": "キャンセル",
|
"Cancel": "キャンセル",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "コードを送信しました",
|
"Code Sent": "コードを送信しました",
|
||||||
"Country/Region": "国/地域",
|
"Country/Region": "国/地域",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
"Sign Up": "Sign Up"
|
"Sign Up": "Sign Up"
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
|
"Copy prompt page URL": "Copy prompt page URL",
|
||||||
|
"Copy signin page URL": "Copy signin page URL",
|
||||||
|
"Copy signup page URL": "Copy signup page URL",
|
||||||
"Edit Application": "Edit Application",
|
"Edit Application": "Edit Application",
|
||||||
"Enable code signin": "Enable code signin",
|
"Enable code signin": "Enable code signin",
|
||||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||||
@ -19,21 +22,24 @@
|
|||||||
"Password ON": "Password ON",
|
"Password ON": "Password ON",
|
||||||
"Password ON - Tooltip": "Whether to allow password login",
|
"Password ON - Tooltip": "Whether to allow password login",
|
||||||
"Please select a HTML file": "Please select a HTML file",
|
"Please select a HTML file": "Please select a HTML file",
|
||||||
|
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Redirect URL": "Redirect URL",
|
"Redirect URL": "Redirect URL",
|
||||||
"Redirect URLs": "Redirect URLs",
|
"Redirect URLs": "Redirect URLs",
|
||||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||||
"Refresh token expire": "Refresh token expire",
|
"Refresh token expire": "Refresh token expire",
|
||||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||||
|
"SAML metadata": "SAML metadata",
|
||||||
|
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||||
|
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Signin session": "Signin session",
|
"Signin session": "Signin session",
|
||||||
"Signup items": "Signup items",
|
"Signup items": "Signup items",
|
||||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||||
"Test prompt page..": "Test prompt page..",
|
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Test signin page..": "Test signin page..",
|
|
||||||
"Test signup page..": "Test signup page..",
|
|
||||||
"Token expire": "Token expire",
|
"Token expire": "Token expire",
|
||||||
"Token expire - Tooltip": "Token expire - Tooltip",
|
"Token expire - Tooltip": "Token expire - Tooltip",
|
||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip"
|
"Token format - Tooltip": "Token format - Tooltip",
|
||||||
|
"rule": "rule"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Bit size",
|
"Bit size": "Bit size",
|
||||||
@ -99,6 +105,7 @@
|
|||||||
"Cert": "Cert",
|
"Cert": "Cert",
|
||||||
"Cert - Tooltip": "Cert - Tooltip",
|
"Cert - Tooltip": "Cert - Tooltip",
|
||||||
"Certs": "Certs",
|
"Certs": "Certs",
|
||||||
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -131,6 +138,9 @@
|
|||||||
"Master password": "Master password",
|
"Master password": "Master password",
|
||||||
"Master password - Tooltip": "Master password - Tooltip",
|
"Master password - Tooltip": "Master password - Tooltip",
|
||||||
"Method": "Method",
|
"Method": "Method",
|
||||||
|
"Model": "Model",
|
||||||
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
|
"Models": "Models",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Name - Tooltip": "Unique string-style identifier",
|
"Name - Tooltip": "Unique string-style identifier",
|
||||||
"OAuth providers": "OAuth providers",
|
"OAuth providers": "OAuth providers",
|
||||||
@ -243,7 +253,15 @@
|
|||||||
"sign up now": "sign up now",
|
"sign up now": "sign up now",
|
||||||
"username, Email or phone": "username, Email or phone"
|
"username, Email or phone": "username, Email or phone"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"Edit Model": "Edit Model",
|
||||||
|
"Model text": "Model text",
|
||||||
|
"Model text - Tooltip": "Model text - Tooltip",
|
||||||
|
"New Model": "New Model"
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
|
"Account items": "Account items",
|
||||||
|
"Account items - Tooltip": "Account items - Tooltip",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
"Edit Organization": "Edit Organization",
|
"Edit Organization": "Edit Organization",
|
||||||
"Favicon": "Favicon",
|
"Favicon": "Favicon",
|
||||||
@ -255,7 +273,9 @@
|
|||||||
"Tags": "Tags",
|
"Tags": "Tags",
|
||||||
"Tags - Tooltip": "Tags - Tooltip",
|
"Tags - Tooltip": "Tags - Tooltip",
|
||||||
"Website URL": "Website URL",
|
"Website URL": "Website URL",
|
||||||
"Website URL - Tooltip": "Unique string-style identifier"
|
"Website URL - Tooltip": "Unique string-style identifier",
|
||||||
|
"modifyRule": "modifyRule",
|
||||||
|
"viewRule": "viewRule"
|
||||||
},
|
},
|
||||||
"payment": {
|
"payment": {
|
||||||
"Confirm your invoice information": "Confirm your invoice information",
|
"Confirm your invoice information": "Confirm your invoice information",
|
||||||
@ -387,9 +407,9 @@
|
|||||||
"Domain": "Domain",
|
"Domain": "Domain",
|
||||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||||
"Edit Provider": "Edit Provider",
|
"Edit Provider": "Edit Provider",
|
||||||
"Email Content": "Email Content",
|
"Email Content": "Email content",
|
||||||
"Email Content - Tooltip": "Unique string-style identifier",
|
"Email Content - Tooltip": "Unique string-style identifier",
|
||||||
"Email Title": "Email Title",
|
"Email Title": "Email title",
|
||||||
"Email Title - Tooltip": "Unique string-style identifier",
|
"Email Title - Tooltip": "Unique string-style identifier",
|
||||||
"Endpoint": "Endpoint",
|
"Endpoint": "Endpoint",
|
||||||
"Endpoint (Intranet)": "Endpoint (Intranet)",
|
"Endpoint (Intranet)": "Endpoint (Intranet)",
|
||||||
@ -425,7 +445,10 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Secret access key",
|
"Secret access key": "Secret access key",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
||||||
|
"Send Test Email": "Send Test Email",
|
||||||
"Sign Name": "Sign Name",
|
"Sign Name": "Sign Name",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
"Sign request": "Sign request",
|
"Sign request": "Sign request",
|
||||||
@ -436,12 +459,17 @@
|
|||||||
"Signup HTML": "Signup HTML",
|
"Signup HTML": "Signup HTML",
|
||||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||||
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Template Code",
|
"Template Code": "Template Code",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "Terms of Use",
|
"Terms of Use": "Terms of Use",
|
||||||
"Terms of Use - Tooltip": "Terms of Use - Tooltip",
|
"Terms of Use - Tooltip": "Terms of Use - Tooltip",
|
||||||
|
"Test Connection": "Test Smtp Connection",
|
||||||
|
"Test Email": "Test email config",
|
||||||
|
"Test Email - Tooltip": "Email Address",
|
||||||
"Token URL": "Token URL",
|
"Token URL": "Token URL",
|
||||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
@ -454,7 +482,6 @@
|
|||||||
"canUnlink": "canUnlink",
|
"canUnlink": "canUnlink",
|
||||||
"prompted": "prompted",
|
"prompted": "prompted",
|
||||||
"required": "required",
|
"required": "required",
|
||||||
"rule": "rule",
|
|
||||||
"visible": "visible"
|
"visible": "visible"
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
@ -483,6 +510,7 @@
|
|||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
"Accept": "Accept",
|
"Accept": "Accept",
|
||||||
|
"Agreement": "Agreement",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
"Decline": "Decline",
|
"Decline": "Decline",
|
||||||
"Have account?": "Have account?",
|
"Have account?": "Have account?",
|
||||||
@ -558,6 +586,8 @@
|
|||||||
"Bio": "Bio",
|
"Bio": "Bio",
|
||||||
"Bio - Tooltip": "Bio - Tooltip",
|
"Bio - Tooltip": "Bio - Tooltip",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Code Sent",
|
"Code Sent": "Code Sent",
|
||||||
"Country/Region": "Country/Region",
|
"Country/Region": "Country/Region",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
"Sign Up": "Регистрация"
|
"Sign Up": "Регистрация"
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
|
"Copy prompt page URL": "Copy prompt page URL",
|
||||||
|
"Copy signin page URL": "Copy signin page URL",
|
||||||
|
"Copy signup page URL": "Copy signup page URL",
|
||||||
"Edit Application": "Изменить приложение",
|
"Edit Application": "Изменить приложение",
|
||||||
"Enable code signin": "Включить кодовый вход",
|
"Enable code signin": "Включить кодовый вход",
|
||||||
"Enable code signin - Tooltip": "Включить вход с кодом - Tooltip",
|
"Enable code signin - Tooltip": "Включить вход с кодом - Tooltip",
|
||||||
@ -19,21 +22,24 @@
|
|||||||
"Password ON": "Пароль ВКЛ",
|
"Password ON": "Пароль ВКЛ",
|
||||||
"Password ON - Tooltip": "Whether to allow password login",
|
"Password ON - Tooltip": "Whether to allow password login",
|
||||||
"Please select a HTML file": "Пожалуйста, выберите HTML-файл",
|
"Please select a HTML file": "Пожалуйста, выберите HTML-файл",
|
||||||
|
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Redirect URL": "URL перенаправления",
|
"Redirect URL": "URL перенаправления",
|
||||||
"Redirect URLs": "Перенаправление URL",
|
"Redirect URLs": "Перенаправление URL",
|
||||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||||
"Refresh token expire": "Срок действия обновления токена истекает",
|
"Refresh token expire": "Срок действия обновления токена истекает",
|
||||||
"Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка",
|
"Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка",
|
||||||
|
"SAML metadata": "SAML metadata",
|
||||||
|
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||||
|
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Signin session": "Сессия входа",
|
"Signin session": "Сессия входа",
|
||||||
"Signup items": "Элементы регистрации",
|
"Signup items": "Элементы регистрации",
|
||||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||||
"Test prompt page..": "Тестовая страница запроса..",
|
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||||
"Test signin page..": "Тестовая страница входа..",
|
|
||||||
"Test signup page..": "Тестовая страница регистрации..",
|
|
||||||
"Token expire": "Токен истекает",
|
"Token expire": "Токен истекает",
|
||||||
"Token expire - Tooltip": "Истек токен - Подсказка",
|
"Token expire - Tooltip": "Истек токен - Подсказка",
|
||||||
"Token format": "Формат токена",
|
"Token format": "Формат токена",
|
||||||
"Token format - Tooltip": "Формат токена - Подсказка"
|
"Token format - Tooltip": "Формат токена - Подсказка",
|
||||||
|
"rule": "rule"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "Размер бита",
|
"Bit size": "Размер бита",
|
||||||
@ -99,6 +105,7 @@
|
|||||||
"Cert": "Cert",
|
"Cert": "Cert",
|
||||||
"Cert - Tooltip": "Cert - Tooltip",
|
"Cert - Tooltip": "Cert - Tooltip",
|
||||||
"Certs": "Сертификаты",
|
"Certs": "Сертификаты",
|
||||||
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "IP клиента",
|
"Client IP": "IP клиента",
|
||||||
"Created time": "Время создания",
|
"Created time": "Время создания",
|
||||||
"Default avatar": "Аватар по умолчанию",
|
"Default avatar": "Аватар по умолчанию",
|
||||||
@ -131,6 +138,9 @@
|
|||||||
"Master password": "Мастер-пароль",
|
"Master password": "Мастер-пароль",
|
||||||
"Master password - Tooltip": "Мастер-пароль - Tooltip",
|
"Master password - Tooltip": "Мастер-пароль - Tooltip",
|
||||||
"Method": "Метод",
|
"Method": "Метод",
|
||||||
|
"Model": "Model",
|
||||||
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
|
"Models": "Models",
|
||||||
"Name": "Наименование",
|
"Name": "Наименование",
|
||||||
"Name - Tooltip": "Unique string-style identifier",
|
"Name - Tooltip": "Unique string-style identifier",
|
||||||
"OAuth providers": "Поставщики OAuth",
|
"OAuth providers": "Поставщики OAuth",
|
||||||
@ -243,7 +253,15 @@
|
|||||||
"sign up now": "зарегистрироваться",
|
"sign up now": "зарегистрироваться",
|
||||||
"username, Email or phone": "имя пользователя, адрес электронной почты или телефон"
|
"username, Email or phone": "имя пользователя, адрес электронной почты или телефон"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"Edit Model": "Edit Model",
|
||||||
|
"Model text": "Model text",
|
||||||
|
"Model text - Tooltip": "Model text - Tooltip",
|
||||||
|
"New Model": "New Model"
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
|
"Account items": "Account items",
|
||||||
|
"Account items - Tooltip": "Account items - Tooltip",
|
||||||
"Default avatar": "Аватар по умолчанию",
|
"Default avatar": "Аватар по умолчанию",
|
||||||
"Edit Organization": "Изменить организацию",
|
"Edit Organization": "Изменить организацию",
|
||||||
"Favicon": "Иконка",
|
"Favicon": "Иконка",
|
||||||
@ -255,7 +273,9 @@
|
|||||||
"Tags": "Tags",
|
"Tags": "Tags",
|
||||||
"Tags - Tooltip": "Tags - Tooltip",
|
"Tags - Tooltip": "Tags - Tooltip",
|
||||||
"Website URL": "URL сайта",
|
"Website URL": "URL сайта",
|
||||||
"Website URL - Tooltip": "Unique string-style identifier"
|
"Website URL - Tooltip": "Unique string-style identifier",
|
||||||
|
"modifyRule": "modifyRule",
|
||||||
|
"viewRule": "viewRule"
|
||||||
},
|
},
|
||||||
"payment": {
|
"payment": {
|
||||||
"Confirm your invoice information": "Confirm your invoice information",
|
"Confirm your invoice information": "Confirm your invoice information",
|
||||||
@ -387,7 +407,7 @@
|
|||||||
"Domain": "Домен",
|
"Domain": "Домен",
|
||||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||||
"Edit Provider": "Изменить провайдера",
|
"Edit Provider": "Изменить провайдера",
|
||||||
"Email Content": "Email Content",
|
"Email Content": "Email content",
|
||||||
"Email Content - Tooltip": "Unique string-style identifier",
|
"Email Content - Tooltip": "Unique string-style identifier",
|
||||||
"Email Title": "Заголовок письма",
|
"Email Title": "Заголовок письма",
|
||||||
"Email Title - Tooltip": "Unique string-style identifier",
|
"Email Title - Tooltip": "Unique string-style identifier",
|
||||||
@ -425,7 +445,10 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - Tooltip",
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
"Secret access key": "Секретный ключ доступа",
|
"Secret access key": "Секретный ключ доступа",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Подсказка",
|
"SecretAccessKey - Tooltip": "SecretAccessKey - Подсказка",
|
||||||
|
"Send Test Email": "Send Test Email",
|
||||||
"Sign Name": "Имя подписи",
|
"Sign Name": "Имя подписи",
|
||||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
"Sign request": "Запрос на подпись",
|
"Sign request": "Запрос на подпись",
|
||||||
@ -436,12 +459,17 @@
|
|||||||
"Signup HTML": "Регистрация HTML",
|
"Signup HTML": "Регистрация HTML",
|
||||||
"Signup HTML - Edit": "Регистрация HTML - Редактировать",
|
"Signup HTML - Edit": "Регистрация HTML - Редактировать",
|
||||||
"Signup HTML - Tooltip": "Регистрация HTML - Подсказка",
|
"Signup HTML - Tooltip": "Регистрация HTML - Подсказка",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "Site key - Tooltip",
|
||||||
"Sub type": "Sub type",
|
"Sub type": "Sub type",
|
||||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Код шаблона",
|
"Template Code": "Код шаблона",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "Условия использования",
|
"Terms of Use": "Условия использования",
|
||||||
"Terms of Use - Tooltip": "Условия использования - Tooltip",
|
"Terms of Use - Tooltip": "Условия использования - Tooltip",
|
||||||
|
"Test Connection": "Test Smtp Connection",
|
||||||
|
"Test Email": "Test email config",
|
||||||
|
"Test Email - Tooltip": "Email Address",
|
||||||
"Token URL": "Token URL",
|
"Token URL": "Token URL",
|
||||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||||
"Type": "Тип",
|
"Type": "Тип",
|
||||||
@ -454,7 +482,6 @@
|
|||||||
"canUnlink": "canUnlink",
|
"canUnlink": "canUnlink",
|
||||||
"prompted": "запрошено",
|
"prompted": "запрошено",
|
||||||
"required": "обязательный",
|
"required": "обязательный",
|
||||||
"rule": "правило",
|
|
||||||
"visible": "видимый"
|
"visible": "видимый"
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
@ -483,6 +510,7 @@
|
|||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
"Accept": "Принять",
|
"Accept": "Принять",
|
||||||
|
"Agreement": "Agreement",
|
||||||
"Confirm": "Подтвердить",
|
"Confirm": "Подтвердить",
|
||||||
"Decline": "Отклонить",
|
"Decline": "Отклонить",
|
||||||
"Have account?": "Есть аккаунт?",
|
"Have account?": "Есть аккаунт?",
|
||||||
@ -558,6 +586,8 @@
|
|||||||
"Bio": "Био",
|
"Bio": "Био",
|
||||||
"Bio - Tooltip": "Био - Подсказка",
|
"Bio - Tooltip": "Био - Подсказка",
|
||||||
"Cancel": "Отмена",
|
"Cancel": "Отмена",
|
||||||
|
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||||
|
"Captcha Verify Success": "Captcha Verify Success",
|
||||||
"Code Sent": "Код отправлен",
|
"Code Sent": "Код отправлен",
|
||||||
"Country/Region": "Страна/регион",
|
"Country/Region": "Страна/регион",
|
||||||
"Country/Region - Tooltip": "Country/Region",
|
"Country/Region - Tooltip": "Country/Region",
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
"Sign Up": "注册"
|
"Sign Up": "注册"
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
|
"Copy prompt page URL": "复制提醒页面URL",
|
||||||
|
"Copy signin page URL": "复制登录页面URL",
|
||||||
|
"Copy signup page URL": "复制注册页面URL",
|
||||||
"Edit Application": "编辑应用",
|
"Edit Application": "编辑应用",
|
||||||
"Enable code signin": "启用验证码登录",
|
"Enable code signin": "启用验证码登录",
|
||||||
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
|
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
|
||||||
@ -19,21 +22,24 @@
|
|||||||
"Password ON": "开启密码",
|
"Password ON": "开启密码",
|
||||||
"Password ON - Tooltip": "是否允许密码登录",
|
"Password ON - Tooltip": "是否允许密码登录",
|
||||||
"Please select a HTML file": "请选择一个HTML文件",
|
"Please select a HTML file": "请选择一个HTML文件",
|
||||||
|
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "提醒页面URL已成功复制到剪贴板,请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
|
||||||
"Redirect URL": "重定向 URL",
|
"Redirect URL": "重定向 URL",
|
||||||
"Redirect URLs": "重定向 URLs",
|
"Redirect URLs": "重定向 URLs",
|
||||||
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
|
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
|
||||||
"Refresh token expire": "Refresh Token过期时间",
|
"Refresh token expire": "Refresh Token过期",
|
||||||
"Refresh token expire - Tooltip": "Refresh Token过期时间",
|
"Refresh token expire - Tooltip": "Refresh Token过期时间",
|
||||||
|
"SAML metadata": "SAML元数据",
|
||||||
|
"SAML metadata - Tooltip": "SAML协议的元数据(Metadata)信息",
|
||||||
|
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "登录页面URL已成功复制到剪贴板,请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
|
||||||
"Signin session": "保持登录会话",
|
"Signin session": "保持登录会话",
|
||||||
"Signup items": "注册项",
|
"Signup items": "注册项",
|
||||||
"Signup items - Tooltip": "注册用户注册时需要填写的项目",
|
"Signup items - Tooltip": "注册用户注册时需要填写的项目",
|
||||||
"Test prompt page..": "测试提醒页面..",
|
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "注册页面URL已成功复制到剪贴板,请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
|
||||||
"Test signin page..": "测试登录页面..",
|
"Token expire": "Access Token过期",
|
||||||
"Test signup page..": "测试注册页面..",
|
|
||||||
"Token expire": "Access Token过期时间",
|
|
||||||
"Token expire - Tooltip": "Access Token过期时间",
|
"Token expire - Tooltip": "Access Token过期时间",
|
||||||
"Token format": "Access Token格式",
|
"Token format": "Access Token格式",
|
||||||
"Token format - Tooltip": "Access Token格式"
|
"Token format - Tooltip": "Access Token格式",
|
||||||
|
"rule": "规则"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
"Bit size": "位大小",
|
"Bit size": "位大小",
|
||||||
@ -99,6 +105,7 @@
|
|||||||
"Cert": "证书",
|
"Cert": "证书",
|
||||||
"Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书",
|
"Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书",
|
||||||
"Certs": "证书",
|
"Certs": "证书",
|
||||||
|
"Click to Upload": "点击上传",
|
||||||
"Client IP": "客户端IP",
|
"Client IP": "客户端IP",
|
||||||
"Created time": "创建时间",
|
"Created time": "创建时间",
|
||||||
"Default avatar": "默认头像",
|
"Default avatar": "默认头像",
|
||||||
@ -131,6 +138,9 @@
|
|||||||
"Master password": "万能密码",
|
"Master password": "万能密码",
|
||||||
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
|
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
|
||||||
"Method": "方法",
|
"Method": "方法",
|
||||||
|
"Model": "模型",
|
||||||
|
"Model - Tooltip": "Casbin模型",
|
||||||
|
"Models": "模型",
|
||||||
"Name": "名称",
|
"Name": "名称",
|
||||||
"Name - Tooltip": "唯一的、字符串式的ID",
|
"Name - Tooltip": "唯一的、字符串式的ID",
|
||||||
"OAuth providers": "OAuth提供方",
|
"OAuth providers": "OAuth提供方",
|
||||||
@ -243,11 +253,19 @@
|
|||||||
"sign up now": "立即注册",
|
"sign up now": "立即注册",
|
||||||
"username, Email or phone": "用户名、Email或手机号"
|
"username, Email or phone": "用户名、Email或手机号"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"Edit Model": "编辑模型",
|
||||||
|
"Model text": "模型文本",
|
||||||
|
"Model text - Tooltip": "Casbin访问控制模型",
|
||||||
|
"New Model": "添加模型"
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
|
"Account items": "个人页设置项",
|
||||||
|
"Account items - Tooltip": "个人设置页面中的项目",
|
||||||
"Default avatar": "默认头像",
|
"Default avatar": "默认头像",
|
||||||
"Edit Organization": "编辑组织",
|
"Edit Organization": "编辑组织",
|
||||||
"Favicon": "图标",
|
"Favicon": "图标",
|
||||||
"Is profile public": "公开用户主页",
|
"Is profile public": "用户个人页公开",
|
||||||
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
|
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
|
||||||
"New Organization": "添加组织",
|
"New Organization": "添加组织",
|
||||||
"Soft deletion": "软删除",
|
"Soft deletion": "软删除",
|
||||||
@ -255,7 +273,9 @@
|
|||||||
"Tags": "标签集合",
|
"Tags": "标签集合",
|
||||||
"Tags - Tooltip": "可供用户选择的标签的集合",
|
"Tags - Tooltip": "可供用户选择的标签的集合",
|
||||||
"Website URL": "网页地址",
|
"Website URL": "网页地址",
|
||||||
"Website URL - Tooltip": "网页地址"
|
"Website URL - Tooltip": "网页地址",
|
||||||
|
"modifyRule": "修改规则",
|
||||||
|
"viewRule": "查看规则"
|
||||||
},
|
},
|
||||||
"payment": {
|
"payment": {
|
||||||
"Confirm your invoice information": "确认您的发票信息",
|
"Confirm your invoice information": "确认您的发票信息",
|
||||||
@ -399,7 +419,7 @@
|
|||||||
"IdP public key": "IdP 公钥",
|
"IdP public key": "IdP 公钥",
|
||||||
"Issuer URL": "发行者网址",
|
"Issuer URL": "发行者网址",
|
||||||
"Issuer URL - Tooltip": "发行者URL - 工具提示",
|
"Issuer URL - Tooltip": "发行者URL - 工具提示",
|
||||||
"Link copied to clipboard successfully": "链接复制到剪贴板成功",
|
"Link copied to clipboard successfully": "链接已成功复制到剪贴板",
|
||||||
"Metadata": "元数据",
|
"Metadata": "元数据",
|
||||||
"Metadata - Tooltip": "元数据 - 工具提示",
|
"Metadata - Tooltip": "元数据 - 工具提示",
|
||||||
"Method": "方法",
|
"Method": "方法",
|
||||||
@ -425,23 +445,31 @@
|
|||||||
"Scope": "Scope",
|
"Scope": "Scope",
|
||||||
"Scope - Tooltip": "Scope - 工具提示",
|
"Scope - Tooltip": "Scope - 工具提示",
|
||||||
"Secret access key": "秘密访问密钥",
|
"Secret access key": "秘密访问密钥",
|
||||||
|
"Secret key": "Secret key",
|
||||||
|
"Secret key - Tooltip": "用于服务端调用验证码提供商API进行验证",
|
||||||
"SecretAccessKey - Tooltip": "访问密钥-工具提示",
|
"SecretAccessKey - Tooltip": "访问密钥-工具提示",
|
||||||
|
"Send Test Email": "发送测试邮件",
|
||||||
"Sign Name": "签名名称",
|
"Sign Name": "签名名称",
|
||||||
"Sign Name - Tooltip": "签名名称",
|
"Sign Name - Tooltip": "签名名称",
|
||||||
"Sign request": "签名请求",
|
"Sign request": "签名请求",
|
||||||
"Sign request - Tooltip": "签名请求 - 工具提示",
|
"Sign request - Tooltip": "签名请求 - 工具提示",
|
||||||
"Signin HTML": "登录 HTML",
|
"Signin HTML": "登录页面HTML",
|
||||||
"Signin HTML - Edit": "登录 HTML - 编辑",
|
"Signin HTML - Edit": "登录页面 - 编辑",
|
||||||
"Signin HTML - Tooltip": "登录 HTML - 工具提示",
|
"Signin HTML - Tooltip": "自定义HTML,用于替换默认的登录页面样式",
|
||||||
"Signup HTML": "注册 HTML",
|
"Signup HTML": "注册页面HTML",
|
||||||
"Signup HTML - Edit": "注册 HTML - 编辑",
|
"Signup HTML - Edit": "注册页面HTML - 编辑",
|
||||||
"Signup HTML - Tooltip": "注册 HTML - 工具提示",
|
"Signup HTML - Tooltip": "自定义HTML,用于替换默认的注册页面样式",
|
||||||
|
"Site key": "Site key",
|
||||||
|
"Site key - Tooltip": "用于前端嵌入页面",
|
||||||
"Sub type": "子类型",
|
"Sub type": "子类型",
|
||||||
"Sub type - Tooltip": "子类型",
|
"Sub type - Tooltip": "子类型",
|
||||||
"Template Code": "模板CODE",
|
"Template Code": "模板代码",
|
||||||
"Template Code - Tooltip": "模板CODE",
|
"Template Code - Tooltip": "模板代码",
|
||||||
"Terms of Use": "使用条款",
|
"Terms of Use": "使用条款",
|
||||||
"Terms of Use - Tooltip": "使用条款 - 工具提示",
|
"Terms of Use - Tooltip": "使用条款 - 工具提示",
|
||||||
|
"Test Connection": "测试Smtp连接",
|
||||||
|
"Test Email": "测试Email配置",
|
||||||
|
"Test Email - Tooltip": "邮箱地址",
|
||||||
"Token URL": "Token URL",
|
"Token URL": "Token URL",
|
||||||
"Token URL - Tooltip": "Token URL - 工具提示",
|
"Token URL - Tooltip": "Token URL - 工具提示",
|
||||||
"Type": "类型",
|
"Type": "类型",
|
||||||
@ -449,13 +477,12 @@
|
|||||||
"UserInfo URL": "UserInfo URL",
|
"UserInfo URL": "UserInfo URL",
|
||||||
"UserInfo URL - Tooltip": "UserInfo URL - 工具提示",
|
"UserInfo URL - Tooltip": "UserInfo URL - 工具提示",
|
||||||
"alertType": "警报类型",
|
"alertType": "警报类型",
|
||||||
"canSignIn": "canSignIn",
|
"canSignIn": "可用于登录",
|
||||||
"canSignUp": "canSignUp",
|
"canSignUp": "可用于注册",
|
||||||
"canUnlink": "canUnlink",
|
"canUnlink": "可解绑定",
|
||||||
"prompted": "提示",
|
"prompted": "注册后提醒绑定",
|
||||||
"required": "必需",
|
"required": "是否必填项",
|
||||||
"rule": "规则",
|
"visible": "是否可见"
|
||||||
"visible": "可见"
|
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
"Is Triggered": "已触发"
|
"Is Triggered": "已触发"
|
||||||
@ -483,6 +510,7 @@
|
|||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
"Accept": "阅读并接受",
|
"Accept": "阅读并接受",
|
||||||
|
"Agreement": "用户协议",
|
||||||
"Confirm": "确认密码",
|
"Confirm": "确认密码",
|
||||||
"Decline": "不接受",
|
"Decline": "不接受",
|
||||||
"Have account?": "已有账号?",
|
"Have account?": "已有账号?",
|
||||||
@ -558,6 +586,8 @@
|
|||||||
"Bio": "自我介绍",
|
"Bio": "自我介绍",
|
||||||
"Bio - Tooltip": "自我介绍",
|
"Bio - Tooltip": "自我介绍",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
|
"Captcha Verify Failed": "验证码校验失败",
|
||||||
|
"Captcha Verify Success": "验证码校验成功",
|
||||||
"Code Sent": "验证码已发送",
|
"Code Sent": "验证码已发送",
|
||||||
"Country/Region": "国家/地区",
|
"Country/Region": "国家/地区",
|
||||||
"Country/Region - Tooltip": "国家/地区",
|
"Country/Region - Tooltip": "国家/地区",
|
||||||
|
Reference in New Issue
Block a user