mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-30 08:10:29 +08:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8a66448365 | ||
![]() |
477d386f3c | ||
![]() |
339c6c2dd0 | ||
![]() |
7c9370ef90 | ||
![]() |
31b586e391 | ||
![]() |
249f83e764 | ||
![]() |
16f5569e50 | ||
![]() |
f99c1f44e8 | ||
![]() |
c8c4dfbfb8 | ||
![]() |
d9c6ff2507 | ||
![]() |
e1664f2f60 | ||
![]() |
460a4d4969 | ||
![]() |
376bac15dc | ||
![]() |
8d0e92edef | ||
![]() |
0075b7af52 | ||
![]() |
2c57bece39 | ||
![]() |
2e42511bc4 | ||
![]() |
ae4ab9902b | ||
![]() |
065b235dc5 |
@@ -95,7 +95,8 @@ p, *, *, GET, /api/get-providers, *, *
|
||||
p, *, *, POST, /api/unlink, *, *
|
||||
p, *, *, POST, /api/set-password, *, *
|
||||
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/upload-resource, *, *
|
||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||
|
29
captcha/default.go
Normal file
29
captcha/default.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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 "github.com/casdoor/casdoor/object"
|
||||
|
||||
type DefaultCaptchaProvider struct {
|
||||
}
|
||||
|
||||
func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
|
||||
captcha := &DefaultCaptchaProvider{}
|
||||
return captcha
|
||||
}
|
||||
|
||||
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
||||
return object.VerifyCaptcha(clientSecret, token), nil
|
||||
}
|
67
captcha/hcaptcha.go
Normal file
67
captcha/hcaptcha.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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"`
|
||||
ErrorCodes []string `json:"error-codes"`
|
||||
}
|
||||
captchaResp := &captchaResponse{}
|
||||
err = json.Unmarshal(body, captchaResp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(captchaResp.ErrorCodes) > 0 {
|
||||
return false, errors.New(strings.Join(captchaResp.ErrorCodes, ","))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
67
captcha/recaptcha.go
Normal file
67
captcha/recaptcha.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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"`
|
||||
ErrorCodes []string `json:"error-codes"`
|
||||
}
|
||||
captchaResp := &captchaResponse{}
|
||||
err = json.Unmarshal(body, captchaResp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(captchaResp.ErrorCodes) > 0 {
|
||||
return false, errors.New(strings.Join(captchaResp.ErrorCodes, ","))
|
||||
}
|
||||
|
||||
return captchaResp.Success, nil
|
||||
}
|
@@ -75,12 +75,14 @@ type Response struct {
|
||||
Data2 interface{} `json:"data2"`
|
||||
}
|
||||
|
||||
type HumanCheck struct {
|
||||
Type string `json:"type"`
|
||||
AppKey string `json:"appKey"`
|
||||
Scene string `json:"scene"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
CaptchaImage interface{} `json:"captchaImage"`
|
||||
type Captcha struct {
|
||||
Type string `json:"type"`
|
||||
AppKey string `json:"appKey"`
|
||||
Scene string `json:"scene"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
CaptchaImage []byte `json:"captchaImage"`
|
||||
ClientId string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
}
|
||||
|
||||
// Signup
|
||||
@@ -291,20 +293,30 @@ func (c *ApiController) GetUserinfo() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetHumanCheck ...
|
||||
// GetCaptcha ...
|
||||
// @Tag Login API
|
||||
// @Title GetHumancheck
|
||||
// @router /api/get-human-check [get]
|
||||
func (c *ApiController) GetHumanCheck() {
|
||||
c.Data["json"] = HumanCheck{Type: "none"}
|
||||
// @Title GetCaptcha
|
||||
// @router /api/get-captcha [get]
|
||||
func (c *ApiController) GetCaptcha() {
|
||||
applicationId := c.Input().Get("applicationId")
|
||||
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||
|
||||
provider := object.GetDefaultHumanCheckProvider()
|
||||
if provider == nil {
|
||||
id, img := object.GetCaptcha()
|
||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
||||
c.ServeJSON()
|
||||
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
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"})
|
||||
}
|
||||
|
@@ -133,7 +133,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
// @Param redirectUri query string true "redirect uri"
|
||||
// @Param scope query string true "scope"
|
||||
// @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]
|
||||
func (c *ApiController) GetApplicationLogin() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
@@ -163,9 +163,16 @@ func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
||||
// @Title Login
|
||||
// @Tag Login API
|
||||
// @Description login
|
||||
// @Param oAuthParams query string true "oAuth parameters"
|
||||
// @Param body body RequestForm true "Login information"
|
||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||
// @Param clientId query string true clientId
|
||||
// @Param responseType query string true responseType
|
||||
// @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]
|
||||
func (c *ApiController) Login() {
|
||||
resp := &Response{}
|
||||
|
@@ -18,6 +18,8 @@ import "github.com/casdoor/casdoor/object"
|
||||
|
||||
// @Title GetOidcDiscovery
|
||||
// @Tag OIDC API
|
||||
// @Description Get Oidc Discovery
|
||||
// @Success 200 {object} object.OidcDiscovery
|
||||
// @router /.well-known/openid-configuration [get]
|
||||
func (c *RootController) GetOidcDiscovery() {
|
||||
host := c.Ctx.Request.Host
|
||||
@@ -27,6 +29,7 @@ func (c *RootController) GetOidcDiscovery() {
|
||||
|
||||
// @Title GetJwks
|
||||
// @Tag OIDC API
|
||||
// @Success 200 {object} jose.JSONWebKey
|
||||
// @router /.well-known/jwks [get]
|
||||
func (c *RootController) GetJwks() {
|
||||
jwks, err := object.GetJsonWebKeySet()
|
||||
|
@@ -26,7 +26,7 @@ import (
|
||||
// @Description get all records
|
||||
// @Param pageSize query string true "The size of each 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]
|
||||
func (c *ApiController) GetRecords() {
|
||||
limit := c.Input().Get("pageSize")
|
||||
@@ -50,8 +50,8 @@ func (c *ApiController) GetRecords() {
|
||||
// @Tag Record API
|
||||
// @Title GetRecordsByFilter
|
||||
// @Description get records by filter
|
||||
// @Param body body object.Records true "filter Record message"
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @Param filter body string true "filter Record message"
|
||||
// @Success 200 {object} object.Record The Response object
|
||||
// @router /get-records-filter [post]
|
||||
func (c *ApiController) GetRecordsByFilter() {
|
||||
body := string(c.Ctx.Input.RequestBody)
|
||||
|
@@ -25,33 +25,61 @@ import (
|
||||
"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
|
||||
// @Title SendEmail
|
||||
// @Tag Service API
|
||||
// @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 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
|
||||
// @router /api/send-email [post]
|
||||
func (c *ApiController) SendEmail() {
|
||||
provider, _, ok := c.GetProviderFromContext("Email")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var emailForm EmailForm
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
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) {
|
||||
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
||||
return
|
||||
@@ -86,7 +114,7 @@ func (c *ApiController) SendEmail() {
|
||||
// @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 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
|
||||
// @router /api/send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
@@ -95,11 +123,7 @@ func (c *ApiController) SendSms() {
|
||||
return
|
||||
}
|
||||
|
||||
var smsForm struct {
|
||||
Content string `json:"content"`
|
||||
Receivers []string `json:"receivers"`
|
||||
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
||||
}
|
||||
var smsForm SmsForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@@ -48,20 +49,28 @@ func (c *ApiController) SendVerificationCode() {
|
||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||
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.")
|
||||
return
|
||||
}
|
||||
|
||||
isHuman := false
|
||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
||||
if captchaProvider == nil {
|
||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
||||
}
|
||||
captchaProvider := captcha.GetCaptchaProvider(checkType)
|
||||
|
||||
if !isHuman {
|
||||
c.ResponseError("Turing test failed.")
|
||||
return
|
||||
if captchaProvider != nil {
|
||||
if checkKey == "" {
|
||||
c.ResponseError("Missing parameter: checkKey.")
|
||||
return
|
||||
}
|
||||
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !isHuman {
|
||||
c.ResponseError("Turing test failed.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
@@ -173,3 +182,36 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
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(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(isValid)
|
||||
}
|
||||
|
@@ -38,8 +38,10 @@ func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getMd5HexDigest(password)
|
||||
res := getMd5HexDigest(hash + userSalt)
|
||||
res := getMd5HexDigest(password)
|
||||
if userSalt != "" {
|
||||
res = getMd5HexDigest(res + userSalt)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
|
@@ -38,8 +38,10 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getSha256HexDigest(password)
|
||||
res := getSha256HexDigest(hash + organizationSalt)
|
||||
res := getSha256HexDigest(password)
|
||||
if organizationSalt != "" {
|
||||
res = getSha256HexDigest(res + organizationSalt)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
|
@@ -25,3 +25,10 @@ func TestGetSaltedPassword(t *testing.T) {
|
||||
cm := NewSha256SaltCredManager()
|
||||
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, "", ""))
|
||||
}
|
||||
|
1
main.go
1
main.go
@@ -51,6 +51,7 @@ func main() {
|
||||
// https://studygolang.com/articles/2303
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||
|
||||
|
@@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@@ -45,6 +46,7 @@ type Application struct {
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableSigninSession bool `json:"enableSigninSession"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
@@ -319,3 +321,39 @@ func CheckRedirectUriValid(application *Application, redirectUri string) bool {
|
||||
}
|
||||
return validUri
|
||||
}
|
||||
|
||||
func IsAllowOrigin(origin string) bool {
|
||||
allowOrigin := false
|
||||
originUrl, err := url.Parse(origin)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
rows, err := adapter.Engine.Cols("redirect_uris").Rows(&Application{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
application := Application{}
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, tmpRedirectUri := range application.RedirectUris {
|
||||
u1, err := url.Parse(tmpRedirectUri)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if u1.Scheme == originUrl.Scheme && u1.Host == originUrl.Host {
|
||||
allowOrigin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if allowOrigin {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return allowOrigin
|
||||
}
|
||||
|
@@ -29,3 +29,16 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
||||
|
||||
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() {
|
||||
existed := initBuiltInOrganization()
|
||||
if !existed {
|
||||
initBuiltInProvider()
|
||||
initBuiltInUser()
|
||||
initBuiltInApplication()
|
||||
initBuiltInCert()
|
||||
@@ -47,6 +48,31 @@ func initBuiltInOrganization() bool {
|
||||
PhonePrefix: "86",
|
||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
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)
|
||||
return false
|
||||
@@ -102,7 +128,9 @@ func initBuiltInApplication() {
|
||||
Cert: "cert-built-in",
|
||||
EnablePassword: 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{
|
||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
@@ -176,3 +204,20 @@ func initBuiltInLdap() {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
@@ -20,6 +20,13 @@ import (
|
||||
"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 {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@@ -36,6 +43,8 @@ type Organization struct {
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
|
||||
AccountItems []*AccountItem `xorm:"varchar(2000)" json:"accountItems"`
|
||||
}
|
||||
|
||||
func GetOrganizationCount(owner, field, value string) int {
|
||||
|
@@ -32,10 +32,10 @@ func TestProduct(t *testing.T) {
|
||||
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)
|
||||
|
||||
paymentId := util.GenerateTimeId()
|
||||
paymentName := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -142,8 +142,8 @@ func GetProvider(id string) *Provider {
|
||||
return getProvider(owner, name)
|
||||
}
|
||||
|
||||
func GetDefaultHumanCheckProvider() *Provider {
|
||||
provider := Provider{Owner: "admin", Category: "HumanCheck"}
|
||||
func GetDefaultCaptchaProvider() *Provider {
|
||||
provider := Provider{Owner: "admin", Category: "Captcha"}
|
||||
existed, err := adapter.Engine.Get(&provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -225,3 +225,37 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
||||
func (p *Provider) GetId() string {
|
||||
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
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ import (
|
||||
)
|
||||
|
||||
//returns a saml2 response
|
||||
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, redirectUri []string) (*etree.Element, error) {
|
||||
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
|
||||
samlResponse := &etree.Element{
|
||||
Space: "samlp",
|
||||
Tag: "Response",
|
||||
@@ -51,7 +51,7 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
|
||||
samlResponse.CreateAttr("Version", "2.0")
|
||||
samlResponse.CreateAttr("IssueInstant", now)
|
||||
samlResponse.CreateAttr("Destination", destination)
|
||||
samlResponse.CreateAttr("InResponseTo", fmt.Sprintf("_%s", arId))
|
||||
samlResponse.CreateAttr("InResponseTo", requestId)
|
||||
samlResponse.CreateElement("saml:Issuer").SetText(host)
|
||||
|
||||
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
|
||||
@@ -68,7 +68,7 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
|
||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
||||
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
||||
subjectConfirmationData.CreateAttr("InResponseTo", fmt.Sprintf("_%s", arId))
|
||||
subjectConfirmationData.CreateAttr("InResponseTo", requestId)
|
||||
subjectConfirmationData.CreateAttr("Recipient", destination)
|
||||
subjectConfirmationData.CreateAttr("NotOnOrAfter", expireTime)
|
||||
condition := assertion.CreateElement("saml:Conditions")
|
||||
@@ -225,14 +225,15 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
//GenerateSamlResponse generates a SAML2.0 response
|
||||
//parameter samlRequest is saml request in base64 format
|
||||
// GetSamlResponse generates a SAML2.0 response
|
||||
// parameter samlRequest is saml request in base64 format
|
||||
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, error) {
|
||||
//decode samlRequest
|
||||
// base64 decode
|
||||
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
// decompress
|
||||
var buffer bytes.Buffer
|
||||
rdr := flate.NewReader(bytes.NewReader(defated))
|
||||
io.Copy(&buffer, rdr)
|
||||
@@ -241,20 +242,21 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
//verify samlRequest
|
||||
|
||||
// verify samlRequest
|
||||
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid {
|
||||
return "", "", fmt.Errorf("err: invalid issuer url")
|
||||
}
|
||||
|
||||
//get publickey string
|
||||
// get public key string
|
||||
cert := getCertByApplication(application)
|
||||
block, _ := pem.Decode([]byte(cert.PublicKey))
|
||||
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
|
||||
//build signedResponse
|
||||
samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, application.RedirectUris)
|
||||
// build signedResponse
|
||||
samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
||||
randomKeyStore := &X509Key{
|
||||
PrivateKey: cert.PrivateKey,
|
||||
X509Certificate: publicKey,
|
||||
@@ -270,15 +272,28 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(samlResponse)
|
||||
xmlStr, err := doc.WriteToString()
|
||||
xmlBytes, err := doc.WriteToBytes()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
res := base64.StdEncoding.EncodeToString([]byte(xmlStr))
|
||||
|
||||
// compress
|
||||
if application.EnableSamlCompress {
|
||||
flated := bytes.NewBuffer(nil)
|
||||
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
writer.Write(xmlBytes)
|
||||
writer.Close()
|
||||
xmlBytes = flated.Bytes()
|
||||
}
|
||||
// base64 encode
|
||||
res := base64.StdEncoding.EncodeToString(xmlBytes)
|
||||
return res, authnRequest.AssertionConsumerServiceURL, nil
|
||||
}
|
||||
|
||||
//return a saml1.1 response(not 2.0)
|
||||
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
||||
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
|
||||
samlResponse := &etree.Element{
|
||||
Space: "samlp",
|
||||
|
@@ -56,6 +56,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||
host = util.UrlJoin(provider.Domain, "/files")
|
||||
}
|
||||
if provider.Type == "Azure Blob" {
|
||||
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
|
||||
}
|
||||
|
||||
fileUrl := util.UrlJoin(host, objectKey)
|
||||
if hasTimestamp {
|
||||
|
31
routers/cors_filter.go
Normal file
31
routers/cors_filter.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
const (
|
||||
headerOrigin = "Origin"
|
||||
headerAllowOrigin = "Access-Control-Allow-Origin"
|
||||
headerAllowMethods = "Access-Control-Allow-Methods"
|
||||
headerAllowHeaders = "Access-Control-Allow-Headers"
|
||||
)
|
||||
|
||||
func CorsFilter(ctx *context.Context) {
|
||||
if ctx.Input.Method() == "OPTIONS" {
|
||||
origin := ctx.Input.Header(headerOrigin)
|
||||
|
||||
if object.IsAllowOrigin(origin) {
|
||||
ctx.Output.Header(headerAllowOrigin, origin)
|
||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS")
|
||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
@@ -94,8 +94,9 @@ func initAPI() {
|
||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||
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/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-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -12,11 +12,22 @@ paths:
|
||||
tags:
|
||||
- OIDC API
|
||||
operationId: RootController.GetJwks
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
schema:
|
||||
$ref: '#/definitions/jose.JSONWebKey'
|
||||
/.well-known/openid-configuration:
|
||||
get:
|
||||
tags:
|
||||
- OIDC API
|
||||
description: Get Oidc Discovery
|
||||
operationId: RootController.GetOidcDiscovery
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
schema:
|
||||
$ref: '#/definitions/object.OidcDiscovery'
|
||||
/api/add-application:
|
||||
post:
|
||||
tags:
|
||||
@@ -58,6 +69,24 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
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:
|
||||
post:
|
||||
tags:
|
||||
@@ -243,11 +272,11 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/api/get-human-check:
|
||||
/api/api/get-captcha:
|
||||
get:
|
||||
tags:
|
||||
- Login API
|
||||
operationId: ApiController.GetHumancheck
|
||||
operationId: ApiController.GetCaptcha
|
||||
/api/api/reset-email-or-phone:
|
||||
post:
|
||||
tags:
|
||||
@@ -271,11 +300,11 @@ paths:
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
name: from
|
||||
description: Details of the email request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/emailForm'
|
||||
$ref: '#/definitions/controllers.EmailForm'
|
||||
responses:
|
||||
"200":
|
||||
description: object
|
||||
@@ -299,11 +328,11 @@ paths:
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
name: from
|
||||
description: Details of the sms request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/smsForm'
|
||||
$ref: '#/definitions/controllers.SmsForm'
|
||||
responses:
|
||||
"200":
|
||||
description: object
|
||||
@@ -382,6 +411,24 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
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:
|
||||
post:
|
||||
tags:
|
||||
@@ -578,6 +625,43 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$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:
|
||||
get:
|
||||
tags:
|
||||
@@ -700,6 +784,42 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
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:
|
||||
get:
|
||||
tags:
|
||||
@@ -901,9 +1021,7 @@ paths:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Records'
|
||||
$ref: '#/definitions/object.Record'
|
||||
/api/get-records-filter:
|
||||
post:
|
||||
tags:
|
||||
@@ -912,18 +1030,17 @@ paths:
|
||||
operationId: ApiController.GetRecordsByFilter
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
name: filter
|
||||
description: filter Record message
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Records'
|
||||
type: string
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Records'
|
||||
$ref: '#/definitions/object.Record'
|
||||
/api/get-resource:
|
||||
get:
|
||||
tags:
|
||||
@@ -1216,6 +1333,23 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$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:
|
||||
post:
|
||||
tags:
|
||||
@@ -1224,21 +1358,51 @@ paths:
|
||||
operationId: ApiController.Login
|
||||
parameters:
|
||||
- in: query
|
||||
name: oAuthParams
|
||||
description: oAuth parameters
|
||||
name: clientId
|
||||
description: clientId
|
||||
required: true
|
||||
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
|
||||
name: body
|
||||
name: form
|
||||
description: Login information
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/RequestForm'
|
||||
$ref: '#/definitions/controllers.RequestForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.api_controller.Response'
|
||||
$ref: '#/definitions/Response'
|
||||
/api/login/oauth/access_token:
|
||||
post:
|
||||
tags:
|
||||
@@ -1424,6 +1588,24 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$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:
|
||||
post:
|
||||
tags:
|
||||
@@ -1493,42 +1675,6 @@ paths:
|
||||
tags:
|
||||
- Login API
|
||||
/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:
|
||||
tags:
|
||||
- Application API
|
||||
@@ -1579,6 +1725,29 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
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:
|
||||
post:
|
||||
tags:
|
||||
@@ -1830,27 +1999,97 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/verify-captcha:
|
||||
post:
|
||||
tags:
|
||||
- Verification API
|
||||
operationId: ApiController.VerifyCaptcha
|
||||
definitions:
|
||||
2127.0xc00036c600.false:
|
||||
2200.0xc0003c4b70.false:
|
||||
title: "false"
|
||||
type: object
|
||||
2161.0xc00036c630.false:
|
||||
2235.0xc0003c4ba0.false:
|
||||
title: "false"
|
||||
type: object
|
||||
RequestForm:
|
||||
title: RequestForm
|
||||
type: object
|
||||
Response:
|
||||
title: Response
|
||||
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:
|
||||
title: Response
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/2127.0xc00036c600.false'
|
||||
$ref: '#/definitions/2200.0xc0003c4b70.false'
|
||||
data2:
|
||||
$ref: '#/definitions/2161.0xc00036c630.false'
|
||||
$ref: '#/definitions/2235.0xc0003c4ba0.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
@@ -1859,25 +2098,33 @@ definitions:
|
||||
type: string
|
||||
sub:
|
||||
type: string
|
||||
controllers.api_controller.Response:
|
||||
title: Response
|
||||
controllers.SmsForm:
|
||||
title: SmsForm
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/2127.0xc00036c600.false'
|
||||
data2:
|
||||
$ref: '#/definitions/2161.0xc00036c630.false'
|
||||
msg:
|
||||
content:
|
||||
type: string
|
||||
organizationId:
|
||||
type: string
|
||||
receivers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
jose.JSONWebKey:
|
||||
title: JSONWebKey
|
||||
type: object
|
||||
object.AccountItem:
|
||||
title: AccountItem
|
||||
type: object
|
||||
properties:
|
||||
modifyRule:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
viewRule:
|
||||
type: string
|
||||
sub:
|
||||
type: string
|
||||
emailForm:
|
||||
title: emailForm
|
||||
type: object
|
||||
visible:
|
||||
type: boolean
|
||||
object.Adapter:
|
||||
title: Adapter
|
||||
type: object
|
||||
@@ -2037,10 +2284,80 @@ definitions:
|
||||
type: string
|
||||
username:
|
||||
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:
|
||||
title: Organization
|
||||
type: object
|
||||
properties:
|
||||
accountItems:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.AccountItem'
|
||||
createdTime:
|
||||
type: string
|
||||
defaultAvatar:
|
||||
@@ -2051,6 +2368,8 @@ definitions:
|
||||
type: boolean
|
||||
favicon:
|
||||
type: string
|
||||
isProfilePublic:
|
||||
type: boolean
|
||||
masterPassword:
|
||||
type: string
|
||||
name:
|
||||
@@ -2081,6 +2400,16 @@ definitions:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
invoiceRemark:
|
||||
type: string
|
||||
invoiceTaxId:
|
||||
type: string
|
||||
invoiceTitle:
|
||||
type: string
|
||||
invoiceType:
|
||||
type: string
|
||||
invoiceUrl:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
name:
|
||||
@@ -2091,6 +2420,14 @@ definitions:
|
||||
type: string
|
||||
payUrl:
|
||||
type: string
|
||||
personEmail:
|
||||
type: string
|
||||
personIdCard:
|
||||
type: string
|
||||
personName:
|
||||
type: string
|
||||
personPhone:
|
||||
type: string
|
||||
price:
|
||||
type: number
|
||||
format: double
|
||||
@@ -2126,6 +2463,8 @@ definitions:
|
||||
type: string
|
||||
isEnabled:
|
||||
type: boolean
|
||||
model:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
@@ -2205,6 +2544,16 @@ definitions:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
customAuthUrl:
|
||||
type: string
|
||||
customLogo:
|
||||
type: string
|
||||
customScope:
|
||||
type: string
|
||||
customTokenUrl:
|
||||
type: string
|
||||
customUserInfoUrl:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
domain:
|
||||
@@ -2264,9 +2613,35 @@ definitions:
|
||||
type: boolean
|
||||
provider:
|
||||
$ref: '#/definitions/object.Provider'
|
||||
object.Records:
|
||||
title: Records
|
||||
object.Record:
|
||||
title: Record
|
||||
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:
|
||||
title: Role
|
||||
type: object
|
||||
@@ -2442,6 +2817,8 @@ definitions:
|
||||
type: string
|
||||
baidu:
|
||||
type: string
|
||||
bilibili:
|
||||
type: string
|
||||
bio:
|
||||
type: string
|
||||
birthday:
|
||||
@@ -2452,10 +2829,14 @@ definitions:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
custom:
|
||||
type: string
|
||||
dingtalk:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
douyin:
|
||||
type: string
|
||||
education:
|
||||
type: string
|
||||
email:
|
||||
@@ -2521,6 +2902,8 @@ definitions:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
okta:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
password:
|
||||
@@ -2558,6 +2941,8 @@ definitions:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
unionId:
|
||||
type: string
|
||||
updatedTime:
|
||||
type: string
|
||||
wechat:
|
||||
@@ -2618,9 +3003,6 @@ definitions:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
smsForm:
|
||||
title: smsForm
|
||||
type: object
|
||||
xorm.Engine:
|
||||
title: Engine
|
||||
type: object
|
||||
|
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;
|
@@ -474,6 +474,16 @@ class ApplicationEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable SAML compress"), i18next.t("application:Enable SAML compress - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enableSamlCompress} onChange={checked => {
|
||||
this.updateApplicationField('enableSamlCompress', checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
|
||||
@@ -484,6 +494,14 @@ class ApplicationEditPage extends React.Component {
|
||||
options={{mode: 'xml', theme: 'default'}}
|
||||
onBeforeChange={(editor, data, value) => {}}
|
||||
/>
|
||||
<br/>
|
||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}`);
|
||||
Setting.showMessage("success", i18next.t("application:SAML metadata URL copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("application:Copy SAML metadata URL")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
|
@@ -23,7 +23,6 @@ import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class ApplicationListPage extends BaseListPage {
|
||||
|
||||
newApplication() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
@@ -36,7 +35,10 @@ class ApplicationListPage extends BaseListPage {
|
||||
enableSignUp: true,
|
||||
enableSigninSession: false,
|
||||
enableCodeSignin: false,
|
||||
providers: [],
|
||||
enableSamlCompress: false,
|
||||
providers: [
|
||||
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, alertType: "None"},
|
||||
],
|
||||
signupItems: [
|
||||
{name: "ID", visible: false, required: true, rule: "Random"},
|
||||
{name: "Username", visible: true, required: true, rule: "None"},
|
||||
|
@@ -22,7 +22,6 @@ import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class CertListPage extends BaseListPage {
|
||||
|
||||
newCert() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
|
@@ -20,6 +20,7 @@ import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import LdapTable from "./LdapTable";
|
||||
import AccountTable from "./AccountTable";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -250,6 +251,18 @@ class OrganizationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</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'}}>
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
||||
|
@@ -22,7 +22,6 @@ import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class OrganizationListPage extends BaseListPage {
|
||||
|
||||
newOrganization() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
@@ -40,6 +39,31 @@ class OrganizationListPage extends BaseListPage {
|
||||
masterPassword: "",
|
||||
enableSoftDeletion: false,
|
||||
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"},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,9 @@ import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import { authConfig } from "./auth/Auth";
|
||||
import * as ProviderEditTestEmail from "./TestEmailWidget";
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { CaptchaPreview } from "./common/CaptchaPreview";
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
@@ -32,6 +34,7 @@ class ProviderEditPage extends React.Component {
|
||||
providerName: props.match.params.providerName,
|
||||
provider: null,
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
testEmail: this.props.account["email"] !== undefined ? this.props.account["email"] : "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,6 +80,8 @@ class ProviderEditPage extends React.Component {
|
||||
} 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:
|
||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||
}
|
||||
@@ -94,6 +99,8 @@ class ProviderEditPage extends React.Component {
|
||||
} 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:
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
@@ -193,6 +200,8 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField('domain', Setting.getFullServerUrl());
|
||||
} else if (value === "SAML") {
|
||||
this.updateProviderField('type', 'Aliyun IDaaS');
|
||||
} else if (value === "Captcha") {
|
||||
this.updateProviderField('type', 'Default');
|
||||
}
|
||||
})}>
|
||||
{
|
||||
@@ -203,6 +212,7 @@ class ProviderEditPage extends React.Component {
|
||||
{id: 'Storage', name: 'Storage'},
|
||||
{id: 'SAML', name: 'SAML'},
|
||||
{id: 'Payment', name: 'Payment'},
|
||||
{id: 'Captcha', name: 'Captcha'},
|
||||
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
@@ -249,7 +259,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.provider.type !== "WeCom" ? null : (
|
||||
this.state.provider.type !== "WeCom" ? null : (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||
@@ -341,26 +351,32 @@ class ProviderEditPage extends React.Component {
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel()}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId} onChange={e => {
|
||||
this.updateProviderField('clientId', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientSecretLabel()}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientSecret} onChange={e => {
|
||||
this.updateProviderField('clientSecret', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.provider.category === "Captcha" && this.state.provider.type === "Default" ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel()}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId} onChange={e => {
|
||||
this.updateProviderField('clientId', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientSecretLabel()}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientSecret} onChange={e => {
|
||||
this.updateProviderField('clientSecret', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.provider.type !== "WeChat" ? null : (
|
||||
<React.Fragment>
|
||||
@@ -500,6 +516,27 @@ class ProviderEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</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>
|
||||
) : this.state.provider.category === "SMS" ? (
|
||||
<React.Fragment>
|
||||
@@ -637,6 +674,27 @@ class ProviderEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@ import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class ProviderListPage extends BaseListPage {
|
||||
|
||||
newProvider() {
|
||||
const randomName = Setting.getRandomName();
|
||||
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: '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: 'Captcha', value: 'Captcha', children: Setting.getProviderTypeOptions('Captcha').map((o) => {return {text:o.id, value:o.name}})},
|
||||
],
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
|
@@ -22,7 +22,6 @@ import moment from "moment";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class RecordListPage extends BaseListPage {
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.state.pagination.pageSize = 20;
|
||||
const { pagination } = this.state;
|
||||
|
@@ -106,6 +106,20 @@ export const OtherProviderInfo = {
|
||||
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() {
|
||||
@@ -595,6 +609,12 @@ export function getProviderTypeOptions(category) {
|
||||
{id: 'PayPal', name: 'PayPal'},
|
||||
{id: 'GC', name: 'GC'},
|
||||
]);
|
||||
} else if (category === "Captcha") {
|
||||
return ([
|
||||
{id: 'Default', name: 'Default'},
|
||||
{id: 'reCAPTCHA', name: 'reCAPTCHA'},
|
||||
{id: 'hCaptcha', name: 'hCaptcha'},
|
||||
]);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
@@ -69,27 +69,35 @@ class SignupTable extends React.Component {
|
||||
key: 'name',
|
||||
render: (text, record, index) => {
|
||||
const items = [
|
||||
{id: 'Username', name: 'Username'},
|
||||
{id: 'ID', name: 'ID'},
|
||||
{id: 'Display name', name: 'Display name'},
|
||||
{id: 'Affiliation', name: 'Affiliation'},
|
||||
{id: 'Country/Region', name: 'Country/Region'},
|
||||
{id: 'ID card', name: 'ID card'},
|
||||
{id: 'Email', name: 'Email'},
|
||||
{id: 'Password', name: 'Password'},
|
||||
{id: 'Confirm password', name: 'Confirm password'},
|
||||
{id: 'Phone', name: 'Phone'},
|
||||
{id: 'Agreement', name: 'Agreement'},
|
||||
{name: "Username", displayName: i18next.t("signup:Username")},
|
||||
{name: "ID", displayName: i18next.t("general:ID")},
|
||||
{name: "Display name", displayName: i18next.t("general:Display name")},
|
||||
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||
{name: "ID card", displayName: i18next.t("user:ID card")},
|
||||
{name: "Email", displayName: i18next.t("general:Email")},
|
||||
{name: "Password", displayName: i18next.t("forget:Password")},
|
||||
{name: "Confirm password", displayName: i18next.t("forget:Confirm")},
|
||||
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||
{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 (
|
||||
<Select virtual={false} style={{width: '100%'}}
|
||||
value={text}
|
||||
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.name}</Option>)
|
||||
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
|
||||
}
|
||||
</Select>
|
||||
)
|
||||
@@ -156,7 +164,7 @@ class SignupTable extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:rule"),
|
||||
title: i18next.t("application:rule"),
|
||||
dataIndex: 'rule',
|
||||
key: 'rule',
|
||||
width: '155px',
|
||||
|
@@ -22,7 +22,6 @@ import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class SyncerListPage extends BaseListPage {
|
||||
|
||||
newSyncer() {
|
||||
const randomName = Setting.getRandomName();
|
||||
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";
|
||||
|
||||
class TokenListPage extends BaseListPage {
|
||||
|
||||
newToken() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
|
@@ -28,10 +28,10 @@ import OAuthWidget from "./common/OAuthWidget";
|
||||
import SamlWidget from "./common/SamlWidget";
|
||||
import SelectRegionBox from "./SelectRegionBox";
|
||||
|
||||
// import {Controlled as CodeMirror} from 'react-codemirror2';
|
||||
// import "codemirror/lib/codemirror.css";
|
||||
// require('codemirror/theme/material-darker.css');
|
||||
// require("codemirror/mode/javascript/javascript");
|
||||
import {Controlled as CodeMirror} from 'react-codemirror2';
|
||||
import "codemirror/lib/codemirror.css";
|
||||
require('codemirror/theme/material-darker.css');
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -124,46 +124,86 @@ class UserEditPage extends React.Component {
|
||||
return (this.state.user.id === this.props.account?.id) || Setting.isAdminUser(this.props.account);
|
||||
}
|
||||
|
||||
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">
|
||||
renderAccountItem(accountItem) {
|
||||
if (!accountItem.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isSelf = this.state.user.id === this.props.account?.id;
|
||||
const isAdmin = Setting.isAdminUser(this.props.account);
|
||||
|
||||
// return (
|
||||
// <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'}} >
|
||||
<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%'}} 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>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "ID") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel("ID", i18next.t("general:ID - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.id} disabled={true} />
|
||||
<Input value={this.state.user.id} disabled={disabled} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Name") {
|
||||
return (
|
||||
<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.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);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Display name") {
|
||||
return (
|
||||
<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"))} :
|
||||
@@ -174,6 +214,9 @@ class UserEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Avatar") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
||||
@@ -204,6 +247,9 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "User type") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:User type"), i18next.t("general:User type - Tooltip"))} :
|
||||
@@ -217,44 +263,56 @@ class UserEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Password") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||
</Col>
|
||||
<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>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Email") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
|
||||
</Col>
|
||||
<Col style={{paddingRight: '20px'}} span={11} >
|
||||
<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 => {
|
||||
this.updateUserField('email', e.target.value);
|
||||
}} />
|
||||
this.updateUserField('email', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
<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}
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Phone") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
||||
</Col>
|
||||
<Col style={{paddingRight: '20px'}} span={11} >
|
||||
<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 => {
|
||||
this.updateUserField('phone', e.target.value);
|
||||
this.updateUserField('phone', e.target.value);
|
||||
}}/>
|
||||
</Col>
|
||||
<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}
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Country/Region") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
|
||||
@@ -265,6 +323,9 @@ class UserEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Location") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} :
|
||||
@@ -275,11 +336,15 @@ class UserEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
(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 === "Affiliation") {
|
||||
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'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} :
|
||||
@@ -290,6 +355,9 @@ class UserEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Homepage") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} :
|
||||
@@ -300,6 +368,9 @@ class UserEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Bio") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} :
|
||||
@@ -310,6 +381,9 @@ class UserEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Tag") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
||||
@@ -335,102 +409,138 @@ class UserEditPage extends React.Component {
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
} else if (accountItem.name === "Signup application") {
|
||||
return (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Signup application"), i18next.t("general:Signup application - Tooltip"))} :
|
||||
</Col>
|
||||
<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>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
!this.isSelfOrAdmin() ? null : (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<div style={{marginBottom: 20}}>
|
||||
{
|
||||
(this.state.application === null || this.state.user === null) ? null : (
|
||||
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
|
||||
(providerItem.provider.category === "OAuth") ? (
|
||||
<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()}} />
|
||||
)
|
||||
)
|
||||
} else if (accountItem.name === "3rd-party logins") {
|
||||
return (
|
||||
!this.isSelfOrAdmin() ? null : (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<div style={{marginBottom: 20}}>
|
||||
{
|
||||
(this.state.application === null || this.state.user === null) ? null : (
|
||||
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
|
||||
(providerItem.provider.category === "OAuth") ? (
|
||||
<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>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</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 : (
|
||||
<React.Fragment>
|
||||
{/*<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>*/}
|
||||
<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>
|
||||
)
|
||||
this.state.application?.organizationObj.accountItems?.map(accountItem => {
|
||||
return (
|
||||
<React.Fragment key={accountItem.name}>
|
||||
{
|
||||
this.renderAccountItem(accountItem)
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Card>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -477,7 +587,7 @@ class UserEditPage extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.state.loading ? <Spin loading={this.state.loading} size="large" /> : (
|
||||
this.state.loading ? <Spin size="large" /> : (
|
||||
this.state.user !== null ? this.renderUser() :
|
||||
<Result
|
||||
status="404"
|
||||
|
@@ -98,7 +98,10 @@ class SignupPage extends React.Component {
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
this.getTermsofuseContent(application.termsOfUse);
|
||||
|
||||
if (application !== null && application !== undefined) {
|
||||
this.getTermsofuseContent(application.termsOfUse);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -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) {
|
||||
let formData = new FormData();
|
||||
formData.append("dest", dest);
|
||||
@@ -124,8 +148,8 @@ export function resetEmailOrPhone(dest, type, code) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getHumanCheck() {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-human-check`, {
|
||||
export function getCaptcha(owner, name, isCurrentProvider) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-captcha?applicationId=${owner}/${encodeURIComponent(name)}&isCurrentProvider=${isCurrentProvider}`, {
|
||||
method: "GET"
|
||||
}).then(res => res.json());
|
||||
}).then(res => res.json()).then(res => res.data);
|
||||
}
|
||||
|
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} disabled={captchaType !== "Default" && (!clientId || !clientSecret)}>
|
||||
{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 * as UserBackend from "../backend/UserBackend";
|
||||
import {SafetyOutlined} from "@ant-design/icons";
|
||||
import {authConfig} from "../auth/Auth";
|
||||
import { CaptchaWidget } from "./CaptchaWidget";
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
@@ -30,6 +32,8 @@ export const CountDownInput = (props) => {
|
||||
const [checkId, setCheckId] = React.useState("");
|
||||
const [buttonLeftTime, setButtonLeftTime] = React.useState(0);
|
||||
const [buttonLoading, setButtonLoading] = React.useState(false);
|
||||
const [buttonDisabled, setButtonDisabled] = React.useState(true);
|
||||
const [clientId, setClientId] = React.useState("");
|
||||
|
||||
const handleCountDown = (leftTime = 60) => {
|
||||
let leftTimeSecond = leftTime
|
||||
@@ -62,14 +66,23 @@ export const CountDownInput = (props) => {
|
||||
setKey("");
|
||||
}
|
||||
|
||||
const loadHumanCheck = () => {
|
||||
UserBackend.getHumanCheck().then(res => {
|
||||
const loadCaptcha = () => {
|
||||
UserBackend.getCaptcha("admin", authConfig.appName, false).then(res => {
|
||||
if (res.type === "none") {
|
||||
UserBackend.sendCode("none", "", "", ...onButtonClickArgs);
|
||||
} else if (res.type === "captcha") {
|
||||
UserBackend.sendCode("none", "", "", ...onButtonClickArgs).then(res => {
|
||||
if (res) {
|
||||
handleCountDown(60);
|
||||
}
|
||||
});
|
||||
} else if (res.type === "Default") {
|
||||
setCheckId(res.captchaId);
|
||||
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);
|
||||
} else {
|
||||
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 = () => {
|
||||
if (checkType === "captcha") return renderCaptcha();
|
||||
return null;
|
||||
if (checkType === "Default") {
|
||||
return renderCaptcha();
|
||||
} else {
|
||||
return (
|
||||
<CaptchaWidget
|
||||
captchaType={checkType}
|
||||
siteKey={clientId}
|
||||
onChange={onSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -116,7 +143,7 @@ export const CountDownInput = (props) => {
|
||||
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
|
||||
</Button>
|
||||
}
|
||||
onSearch={loadHumanCheck}
|
||||
onSearch={loadCaptcha}
|
||||
/>
|
||||
<Modal
|
||||
closable={false}
|
||||
@@ -128,8 +155,8 @@ export const CountDownInput = (props) => {
|
||||
cancelText={i18next.t("user:Cancel")}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okButtonProps={{disabled: key.length !== 5}}
|
||||
width={248}
|
||||
okButtonProps={{disabled: key.length !== 5 && buttonDisabled}}
|
||||
width={348}
|
||||
>
|
||||
{
|
||||
renderCheck()
|
||||
|
@@ -38,7 +38,8 @@
|
||||
"Token expire": "Token läuft ab",
|
||||
"Token expire - Tooltip": "Token läuft ab - Tooltip",
|
||||
"Token format": "Token-Format",
|
||||
"Token format - Tooltip": "Token-Format - Tooltip"
|
||||
"Token format - Tooltip": "Token-Format - Tooltip",
|
||||
"rule": "rule"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Bitgröße",
|
||||
@@ -259,6 +260,8 @@
|
||||
"New Model": "New Model"
|
||||
},
|
||||
"organization": {
|
||||
"Account items": "Account items",
|
||||
"Account items - Tooltip": "Account items - Tooltip",
|
||||
"Default avatar": "Standard Avatar",
|
||||
"Edit Organization": "Organisation bearbeiten",
|
||||
"Favicon": "Févicon",
|
||||
@@ -270,7 +273,9 @@
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "Website-URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@@ -402,7 +407,7 @@
|
||||
"Domain": "Domäne",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Edit Provider": "Anbieter bearbeiten",
|
||||
"Email Content": "Email Content",
|
||||
"Email Content": "Email content",
|
||||
"Email Content - Tooltip": "Unique string-style identifier",
|
||||
"Email Title": "E-Mail-Titel",
|
||||
"Email Title - Tooltip": "Unique string-style identifier",
|
||||
@@ -440,7 +445,10 @@
|
||||
"Scope": "Scope",
|
||||
"Scope - Tooltip": "Scope - Tooltip",
|
||||
"Secret access key": "Geheimer Zugangsschlüssel",
|
||||
"Secret key": "Secret key",
|
||||
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
|
||||
"Send Test Email": "Send Test Email",
|
||||
"Sign Name": "Schild Name",
|
||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||
"Sign request": "Signaturanfrage",
|
||||
@@ -451,12 +459,17 @@
|
||||
"Signup HTML": "HTML registrieren",
|
||||
"Signup HTML - Edit": "HTML registrieren - Bearbeiten",
|
||||
"Signup HTML - Tooltip": "HTML registrieren - Tooltip",
|
||||
"Site key": "Site key",
|
||||
"Site key - Tooltip": "Site key - Tooltip",
|
||||
"Sub type": "Sub type",
|
||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||
"Template Code": "Vorlagencode",
|
||||
"Template Code - Tooltip": "Unique string-style identifier",
|
||||
"Terms of Use": "Nutzungsbedingungen",
|
||||
"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 - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Typ",
|
||||
@@ -469,7 +482,6 @@
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "gefragt",
|
||||
"required": "benötigt",
|
||||
"rule": "regel",
|
||||
"visible": "sichtbar"
|
||||
},
|
||||
"record": {
|
||||
@@ -498,6 +510,7 @@
|
||||
},
|
||||
"signup": {
|
||||
"Accept": "Akzeptieren",
|
||||
"Agreement": "Agreement",
|
||||
"Confirm": "Bestätigen",
|
||||
"Decline": "Ablehnen",
|
||||
"Have account?": "Haben Sie Konto?",
|
||||
@@ -573,6 +586,8 @@
|
||||
"Bio": "Bio",
|
||||
"Bio - Tooltip": "Bio - Tooltip",
|
||||
"Cancel": "Abbrechen",
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Code gesendet",
|
||||
"Country/Region": "Land/Region",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
|
@@ -38,7 +38,8 @@
|
||||
"Token expire": "Token expire",
|
||||
"Token expire - Tooltip": "Token expire - Tooltip",
|
||||
"Token format": "Token format",
|
||||
"Token format - Tooltip": "Token format - Tooltip"
|
||||
"Token format - Tooltip": "Token format - Tooltip",
|
||||
"rule": "rule"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Bit size",
|
||||
@@ -259,6 +260,8 @@
|
||||
"New Model": "New Model"
|
||||
},
|
||||
"organization": {
|
||||
"Account items": "Account items",
|
||||
"Account items - Tooltip": "Account items - Tooltip",
|
||||
"Default avatar": "Default avatar",
|
||||
"Edit Organization": "Edit Organization",
|
||||
"Favicon": "Favicon",
|
||||
@@ -270,7 +273,9 @@
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Website URL - Tooltip"
|
||||
"Website URL - Tooltip": "Website URL - Tooltip",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@@ -402,9 +407,9 @@
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Domain - Tooltip",
|
||||
"Edit Provider": "Edit Provider",
|
||||
"Email Content": "Email Content",
|
||||
"Email Content": "Email content",
|
||||
"Email Content - Tooltip": "Email Content - Tooltip",
|
||||
"Email Title": "Email Title",
|
||||
"Email Title": "Email title",
|
||||
"Email Title - Tooltip": "Email Title - Tooltip",
|
||||
"Endpoint": "Endpoint",
|
||||
"Endpoint (Intranet)": "Endpoint (Intranet)",
|
||||
@@ -440,7 +445,10 @@
|
||||
"Scope": "Scope",
|
||||
"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": "Sign Name - Tooltip",
|
||||
"Sign request": "Sign request",
|
||||
@@ -451,12 +459,17 @@
|
||||
"Signup HTML": "Signup HTML",
|
||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
||||
"Site key": "Site key",
|
||||
"Site key - Tooltip": "Site key - Tooltip",
|
||||
"Sub type": "Sub type",
|
||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||
"Template Code": "Template Code",
|
||||
"Template Code - Tooltip": "Template Code - Tooltip",
|
||||
"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 - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Type",
|
||||
@@ -469,7 +482,6 @@
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "prompted",
|
||||
"required": "required",
|
||||
"rule": "rule",
|
||||
"visible": "visible"
|
||||
},
|
||||
"record": {
|
||||
@@ -498,6 +510,7 @@
|
||||
},
|
||||
"signup": {
|
||||
"Accept": "Accept",
|
||||
"Agreement": "Agreement",
|
||||
"Confirm": "Confirm",
|
||||
"Decline": "Decline",
|
||||
"Have account?": "Have account?",
|
||||
@@ -573,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 - Tooltip",
|
||||
|
@@ -38,7 +38,8 @@
|
||||
"Token expire": "Expiration du jeton",
|
||||
"Token expire - Tooltip": "Expiration du jeton - Info-bulle",
|
||||
"Token format": "Format du jeton",
|
||||
"Token format - Tooltip": "Format du jeton - infobulle"
|
||||
"Token format - Tooltip": "Format du jeton - infobulle",
|
||||
"rule": "rule"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Taille du bit",
|
||||
@@ -259,6 +260,8 @@
|
||||
"New Model": "New Model"
|
||||
},
|
||||
"organization": {
|
||||
"Account items": "Account items",
|
||||
"Account items - Tooltip": "Account items - Tooltip",
|
||||
"Default avatar": "Avatar par défaut",
|
||||
"Edit Organization": "Modifier l'organisation",
|
||||
"Favicon": "Favicon",
|
||||
@@ -270,7 +273,9 @@
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"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": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@@ -402,7 +407,7 @@
|
||||
"Domain": "Domaine",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Edit Provider": "Modifier le fournisseur",
|
||||
"Email Content": "Email Content",
|
||||
"Email Content": "Email content",
|
||||
"Email Content - Tooltip": "Unique string-style identifier",
|
||||
"Email Title": "Titre de l'e-mail",
|
||||
"Email Title - Tooltip": "Unique string-style identifier",
|
||||
@@ -440,7 +445,10 @@
|
||||
"Scope": "Scope",
|
||||
"Scope - Tooltip": "Scope - Tooltip",
|
||||
"Secret access key": "Clé d'accès secrète",
|
||||
"Secret key": "Secret key",
|
||||
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Infobulle",
|
||||
"Send Test Email": "Send Test Email",
|
||||
"Sign Name": "Nom du panneau",
|
||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||
"Sign request": "Demande de signature",
|
||||
@@ -451,12 +459,17 @@
|
||||
"Signup HTML": "Inscription HTML",
|
||||
"Signup HTML - Edit": "Inscription HTML - Modifier",
|
||||
"Signup HTML - Tooltip": "Inscription HTML - infobulle",
|
||||
"Site key": "Site key",
|
||||
"Site key - Tooltip": "Site key - Tooltip",
|
||||
"Sub type": "Sub type",
|
||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||
"Template Code": "Code du modèle",
|
||||
"Template Code - Tooltip": "Unique string-style identifier",
|
||||
"Terms of Use": "Conditions d'utilisation",
|
||||
"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 - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Type de texte",
|
||||
@@ -469,7 +482,6 @@
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "invitée",
|
||||
"required": "Obligatoire",
|
||||
"rule": "règle",
|
||||
"visible": "Visible"
|
||||
},
|
||||
"record": {
|
||||
@@ -498,6 +510,7 @@
|
||||
},
|
||||
"signup": {
|
||||
"Accept": "Accepter",
|
||||
"Agreement": "Agreement",
|
||||
"Confirm": "Valider",
|
||||
"Decline": "Refuser",
|
||||
"Have account?": "Vous avez un compte ?",
|
||||
@@ -573,6 +586,8 @@
|
||||
"Bio": "Bio",
|
||||
"Bio - Tooltip": "Bio - Infobulle",
|
||||
"Cancel": "Abandonner",
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Code envoyé",
|
||||
"Country/Region": "Pays/Région",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
|
@@ -38,7 +38,8 @@
|
||||
"Token expire": "トークンの有効期限",
|
||||
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
|
||||
"Token format": "トークンのフォーマット",
|
||||
"Token format - Tooltip": "トークンフォーマット - ツールチップ"
|
||||
"Token format - Tooltip": "トークンフォーマット - ツールチップ",
|
||||
"rule": "rule"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "ビットサイズ",
|
||||
@@ -259,6 +260,8 @@
|
||||
"New Model": "New Model"
|
||||
},
|
||||
"organization": {
|
||||
"Account items": "Account items",
|
||||
"Account items - Tooltip": "Account items - Tooltip",
|
||||
"Default avatar": "デフォルトのアバター",
|
||||
"Edit Organization": "組織を編集",
|
||||
"Favicon": "ファビコン",
|
||||
@@ -270,7 +273,9 @@
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@@ -402,7 +407,7 @@
|
||||
"Domain": "ドメイン",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Edit Provider": "プロバイダーを編集",
|
||||
"Email Content": "Email Content",
|
||||
"Email Content": "Email content",
|
||||
"Email Content - Tooltip": "Unique string-style identifier",
|
||||
"Email Title": "メールタイトル",
|
||||
"Email Title - Tooltip": "Unique string-style identifier",
|
||||
@@ -440,7 +445,10 @@
|
||||
"Scope": "Scope",
|
||||
"Scope - Tooltip": "Scope - Tooltip",
|
||||
"Secret access key": "シークレットアクセスキー",
|
||||
"Secret key": "Secret key",
|
||||
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||
"SecretAccessKey - Tooltip": "シークレットアクセスキー - ツールチップ",
|
||||
"Send Test Email": "Send Test Email",
|
||||
"Sign Name": "署名名",
|
||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||
"Sign request": "サインリクエスト",
|
||||
@@ -451,12 +459,17 @@
|
||||
"Signup HTML": "HTMLの登録",
|
||||
"Signup HTML - Edit": "HTMLの登録 - 編集",
|
||||
"Signup HTML - Tooltip": "サインアップ HTML - ツールチップ",
|
||||
"Site key": "Site key",
|
||||
"Site key - Tooltip": "Site key - Tooltip",
|
||||
"Sub type": "Sub type",
|
||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||
"Template Code": "テンプレートコード",
|
||||
"Template Code - Tooltip": "Unique string-style identifier",
|
||||
"Terms of Use": "利用規約",
|
||||
"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 - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "タイプ",
|
||||
@@ -469,7 +482,6 @@
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "プロンプトされた",
|
||||
"required": "必須",
|
||||
"rule": "ルール",
|
||||
"visible": "表示"
|
||||
},
|
||||
"record": {
|
||||
@@ -498,6 +510,7 @@
|
||||
},
|
||||
"signup": {
|
||||
"Accept": "同意する",
|
||||
"Agreement": "Agreement",
|
||||
"Confirm": "確認する",
|
||||
"Decline": "同意しない",
|
||||
"Have account?": "アカウントをお持ちですか?",
|
||||
@@ -573,6 +586,8 @@
|
||||
"Bio": "略歴",
|
||||
"Bio - Tooltip": "バイオチップ(ツールチップ)",
|
||||
"Cancel": "キャンセル",
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "コードを送信しました",
|
||||
"Country/Region": "国/地域",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
|
@@ -38,7 +38,8 @@
|
||||
"Token expire": "Token expire",
|
||||
"Token expire - Tooltip": "Token expire - Tooltip",
|
||||
"Token format": "Token format",
|
||||
"Token format - Tooltip": "Token format - Tooltip"
|
||||
"Token format - Tooltip": "Token format - Tooltip",
|
||||
"rule": "rule"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Bit size",
|
||||
@@ -259,6 +260,8 @@
|
||||
"New Model": "New Model"
|
||||
},
|
||||
"organization": {
|
||||
"Account items": "Account items",
|
||||
"Account items - Tooltip": "Account items - Tooltip",
|
||||
"Default avatar": "Default avatar",
|
||||
"Edit Organization": "Edit Organization",
|
||||
"Favicon": "Favicon",
|
||||
@@ -270,7 +273,9 @@
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@@ -402,9 +407,9 @@
|
||||
"Domain": "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 Title": "Email Title",
|
||||
"Email Title": "Email title",
|
||||
"Email Title - Tooltip": "Unique string-style identifier",
|
||||
"Endpoint": "Endpoint",
|
||||
"Endpoint (Intranet)": "Endpoint (Intranet)",
|
||||
@@ -440,7 +445,10 @@
|
||||
"Scope": "Scope",
|
||||
"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 request": "Sign request",
|
||||
@@ -451,12 +459,17 @@
|
||||
"Signup HTML": "Signup HTML",
|
||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
||||
"Site key": "Site key",
|
||||
"Site key - Tooltip": "Site key - Tooltip",
|
||||
"Sub type": "Sub type",
|
||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||
"Template Code": "Template Code",
|
||||
"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 - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Type",
|
||||
@@ -469,7 +482,6 @@
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "prompted",
|
||||
"required": "required",
|
||||
"rule": "rule",
|
||||
"visible": "visible"
|
||||
},
|
||||
"record": {
|
||||
@@ -498,6 +510,7 @@
|
||||
},
|
||||
"signup": {
|
||||
"Accept": "Accept",
|
||||
"Agreement": "Agreement",
|
||||
"Confirm": "Confirm",
|
||||
"Decline": "Decline",
|
||||
"Have account?": "Have account?",
|
||||
@@ -573,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",
|
||||
|
@@ -38,7 +38,8 @@
|
||||
"Token expire": "Токен истекает",
|
||||
"Token expire - Tooltip": "Истек токен - Подсказка",
|
||||
"Token format": "Формат токена",
|
||||
"Token format - Tooltip": "Формат токена - Подсказка"
|
||||
"Token format - Tooltip": "Формат токена - Подсказка",
|
||||
"rule": "rule"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Размер бита",
|
||||
@@ -259,6 +260,8 @@
|
||||
"New Model": "New Model"
|
||||
},
|
||||
"organization": {
|
||||
"Account items": "Account items",
|
||||
"Account items - Tooltip": "Account items - Tooltip",
|
||||
"Default avatar": "Аватар по умолчанию",
|
||||
"Edit Organization": "Изменить организацию",
|
||||
"Favicon": "Иконка",
|
||||
@@ -270,7 +273,9 @@
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "URL сайта",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@@ -402,7 +407,7 @@
|
||||
"Domain": "Домен",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Edit Provider": "Изменить провайдера",
|
||||
"Email Content": "Email Content",
|
||||
"Email Content": "Email content",
|
||||
"Email Content - Tooltip": "Unique string-style identifier",
|
||||
"Email Title": "Заголовок письма",
|
||||
"Email Title - Tooltip": "Unique string-style identifier",
|
||||
@@ -440,7 +445,10 @@
|
||||
"Scope": "Scope",
|
||||
"Scope - Tooltip": "Scope - Tooltip",
|
||||
"Secret access key": "Секретный ключ доступа",
|
||||
"Secret key": "Secret key",
|
||||
"Secret key - Tooltip": "Secret key - Tooltip",
|
||||
"SecretAccessKey - Tooltip": "SecretAccessKey - Подсказка",
|
||||
"Send Test Email": "Send Test Email",
|
||||
"Sign Name": "Имя подписи",
|
||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||
"Sign request": "Запрос на подпись",
|
||||
@@ -451,12 +459,17 @@
|
||||
"Signup HTML": "Регистрация HTML",
|
||||
"Signup HTML - Edit": "Регистрация HTML - Редактировать",
|
||||
"Signup HTML - Tooltip": "Регистрация HTML - Подсказка",
|
||||
"Site key": "Site key",
|
||||
"Site key - Tooltip": "Site key - Tooltip",
|
||||
"Sub type": "Sub type",
|
||||
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||
"Template Code": "Код шаблона",
|
||||
"Template Code - Tooltip": "Unique string-style identifier",
|
||||
"Terms of Use": "Условия использования",
|
||||
"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 - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Тип",
|
||||
@@ -469,7 +482,6 @@
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "запрошено",
|
||||
"required": "обязательный",
|
||||
"rule": "правило",
|
||||
"visible": "видимый"
|
||||
},
|
||||
"record": {
|
||||
@@ -498,6 +510,7 @@
|
||||
},
|
||||
"signup": {
|
||||
"Accept": "Принять",
|
||||
"Agreement": "Agreement",
|
||||
"Confirm": "Подтвердить",
|
||||
"Decline": "Отклонить",
|
||||
"Have account?": "Есть аккаунт?",
|
||||
@@ -573,6 +586,8 @@
|
||||
"Bio": "Био",
|
||||
"Bio - Tooltip": "Био - Подсказка",
|
||||
"Cancel": "Отмена",
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Код отправлен",
|
||||
"Country/Region": "Страна/регион",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
|
@@ -7,11 +7,14 @@
|
||||
},
|
||||
"application": {
|
||||
"Copy prompt page URL": "复制提醒页面URL",
|
||||
"Copy SAML metadata URL": "复制SAML元数据URL",
|
||||
"Copy signin page URL": "复制登录页面URL",
|
||||
"Copy signup page URL": "复制注册页面URL",
|
||||
"Edit Application": "编辑应用",
|
||||
"Enable code signin": "启用验证码登录",
|
||||
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
|
||||
"Enable SAML compress": "压缩SAML响应",
|
||||
"Enable SAML compress - Tooltip": "Casdoor作为SAML idp时,是否压缩SAML响应信息",
|
||||
"Enable signin session - Tooltip": "从应用登录Casdoor后,Casdoor是否保持会话",
|
||||
"Enable signup": "启用注册",
|
||||
"Enable signup - Tooltip": "是否允许用户注册",
|
||||
@@ -30,6 +33,7 @@
|
||||
"Refresh token expire - Tooltip": "Refresh Token过期时间",
|
||||
"SAML metadata": "SAML元数据",
|
||||
"SAML metadata - Tooltip": "SAML协议的元数据(Metadata)信息",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML元数据URL已成功复制到剪贴板",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "登录页面URL已成功复制到剪贴板,请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
|
||||
"Signin session": "保持登录会话",
|
||||
"Signup items": "注册项",
|
||||
@@ -38,7 +42,8 @@
|
||||
"Token expire": "Access Token过期",
|
||||
"Token expire - Tooltip": "Access Token过期时间",
|
||||
"Token format": "Access Token格式",
|
||||
"Token format - Tooltip": "Access Token格式"
|
||||
"Token format - Tooltip": "Access Token格式",
|
||||
"rule": "规则"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "位大小",
|
||||
@@ -259,6 +264,8 @@
|
||||
"New Model": "添加模型"
|
||||
},
|
||||
"organization": {
|
||||
"Account items": "个人页设置项",
|
||||
"Account items - Tooltip": "个人设置页面中的项目",
|
||||
"Default avatar": "默认头像",
|
||||
"Edit Organization": "编辑组织",
|
||||
"Favicon": "图标",
|
||||
@@ -270,7 +277,9 @@
|
||||
"Tags": "标签集合",
|
||||
"Tags - Tooltip": "可供用户选择的标签的集合",
|
||||
"Website URL": "网页地址",
|
||||
"Website URL - Tooltip": "网页地址"
|
||||
"Website URL - Tooltip": "网页地址",
|
||||
"modifyRule": "修改规则",
|
||||
"viewRule": "查看规则"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "确认您的发票信息",
|
||||
@@ -440,7 +449,10 @@
|
||||
"Scope": "Scope",
|
||||
"Scope - Tooltip": "Scope - 工具提示",
|
||||
"Secret access key": "秘密访问密钥",
|
||||
"Secret key": "Secret key",
|
||||
"Secret key - Tooltip": "用于服务端调用验证码提供商API进行验证",
|
||||
"SecretAccessKey - Tooltip": "访问密钥-工具提示",
|
||||
"Send Test Email": "发送测试邮件",
|
||||
"Sign Name": "签名名称",
|
||||
"Sign Name - Tooltip": "签名名称",
|
||||
"Sign request": "签名请求",
|
||||
@@ -451,12 +463,17 @@
|
||||
"Signup HTML": "注册页面HTML",
|
||||
"Signup HTML - Edit": "注册页面HTML - 编辑",
|
||||
"Signup HTML - Tooltip": "自定义HTML,用于替换默认的注册页面样式",
|
||||
"Site key": "Site key",
|
||||
"Site key - Tooltip": "用于前端嵌入页面",
|
||||
"Sub type": "子类型",
|
||||
"Sub type - Tooltip": "子类型",
|
||||
"Template Code": "模板代码",
|
||||
"Template Code - Tooltip": "模板代码",
|
||||
"Terms of Use": "使用条款",
|
||||
"Terms of Use - Tooltip": "使用条款 - 工具提示",
|
||||
"Test Connection": "测试Smtp连接",
|
||||
"Test Email": "测试Email配置",
|
||||
"Test Email - Tooltip": "邮箱地址",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL - 工具提示",
|
||||
"Type": "类型",
|
||||
@@ -469,7 +486,6 @@
|
||||
"canUnlink": "可解绑定",
|
||||
"prompted": "注册后提醒绑定",
|
||||
"required": "是否必填项",
|
||||
"rule": "规则",
|
||||
"visible": "是否可见"
|
||||
},
|
||||
"record": {
|
||||
@@ -498,6 +514,7 @@
|
||||
},
|
||||
"signup": {
|
||||
"Accept": "阅读并接受",
|
||||
"Agreement": "用户协议",
|
||||
"Confirm": "确认密码",
|
||||
"Decline": "不接受",
|
||||
"Have account?": "已有账号?",
|
||||
@@ -573,6 +590,8 @@
|
||||
"Bio": "自我介绍",
|
||||
"Bio - Tooltip": "自我介绍",
|
||||
"Cancel": "取消",
|
||||
"Captcha Verify Failed": "验证码校验失败",
|
||||
"Captcha Verify Success": "验证码校验成功",
|
||||
"Code Sent": "验证码已发送",
|
||||
"Country/Region": "国家/地区",
|
||||
"Country/Region - Tooltip": "国家/地区",
|
||||
|
Reference in New Issue
Block a user