Compare commits

...

19 Commits

Author SHA1 Message Date
6d6cbc7e6f feat: add dynamic mode for provider to enable verification code when the login password is wrong (#1753)
* fix: update webAuthnBufferDecode to support Base64URL for WebAuthn updates

* feat: enable verification code when the login password is wrong

* fix: only enable captcha when login in password

* fix: disable login error limits when captcha on

* fix: pass "enableCaptcha" as an optional param

* fix: change enbleCapctah to optional bool param
2023-04-22 16:16:25 +08:00
ee8c2650c3 Remove useless "/api/login/oauth/code" API and update Swagger 2023-04-22 09:47:52 +08:00
f3ea39d20c Fix result page button link 2023-04-21 23:56:33 +08:00
e78d9e5d2b Fix local file system storage provider path error 2023-04-21 10:12:09 +08:00
19209718ea feat: fix wrong CAS login mode (#1762) 2023-04-20 22:18:02 +08:00
e75d26260a Fix table name in getEnforcer() 2023-04-20 01:33:47 +08:00
6572ab69ce fix: fix pemContent decode error bug for WeChat Pay provider (#1751) 2023-04-19 22:13:13 +08:00
8db87a7559 fix: function comments (#1757)
Modify the function annotation so that the swagger can parse correctly
2023-04-19 21:19:48 +08:00
0dcccfc19c feat: rollback anted to v5.2.3 (#1755) 2023-04-19 11:30:49 +08:00
96219442f5 feat: fix Tencent Cloud OSS storage connect incorrect issue (#1752)
* fix: fix Tencent Cloud OSS storage connect incorrect

* Update provider.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-04-18 21:30:46 +08:00
903745c540 fix: improve LDAP page UI (#1749)
* refactor: improve LDAP sync page

* refactor: update anted version

* chore: i18
2023-04-17 22:03:05 +08:00
df741805cd Fix chat send 2023-04-17 20:50:03 +08:00
ee5c3f3f39 feat: fix display name null error during 3rd-party binding (#1747) 2023-04-17 15:39:33 +08:00
714f69be7b Use HTTP for IP host in getOriginFromHost() 2023-04-17 00:55:40 +08:00
0d12972e92 Fix "auto single OAuth signin doesn't work" bug 2023-04-17 00:38:48 +08:00
78b62c28ab Fix the wrong order of g policy in enforce() API 2023-04-16 22:26:22 +08:00
wht
5c26335fd6 feat: add rule option for phone in application's signup page (#1745) 2023-04-16 20:34:06 +08:00
7edaeafea5 Call refreshAvatar() in addUser() 2023-04-16 01:00:02 +08:00
336f3f7a7b Add user.refreshAvatar() 2023-04-16 01:00:02 +08:00
62 changed files with 1715 additions and 319 deletions

View File

@ -90,6 +90,7 @@ p, *, *, GET, /api/userinfo, *, *
p, *, *, GET, /api/user, *, *
p, *, *, POST, /api/webhook, *, *
p, *, *, GET, /api/get-webhook-event, *, *
p, *, *, GET, /api/get-captcha-status, *, *
p, *, *, *, /api/login/oauth, *, *
p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-organization-applications, *, *

View File

@ -137,7 +137,7 @@ func (c *ApiController) Signup() {
}
var checkPhone string
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && form.Phone != "" {
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
if checkResult.Code != object.VerificationSuccess {

View File

@ -281,8 +281,8 @@ func (c *ApiController) Login() {
c.ResponseError(c.T("auth:The login method: login with password is not enabled for the application"))
return
}
if object.CheckToEnableCaptcha(application) {
var enableCaptcha bool
if enableCaptcha = object.CheckToEnableCaptcha(application, form.Organization, form.Username); enableCaptcha {
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)
if err != nil {
c.ResponseError(err.Error())
@ -296,7 +296,8 @@ func (c *ApiController) Login() {
}
password := form.Password
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage())
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage(), enableCaptcha)
}
if msg != "" {
@ -610,3 +611,21 @@ func (c *ApiController) GetWebhookEventType() {
wechatScanType = ""
c.ServeJSON()
}
// GetCaptchaStatus
// @Title GetCaptchaStatus
// @Tag Token API
// @Description Get Login Error Counts
// @Param id query string true "The id ( owner/name ) of user"
// @Success 200 {object} controllers.Response The Response object
// @router /api/get-captcha-status [get]
func (c *ApiController) GetCaptchaStatus() {
organization := c.Input().Get("organization")
userId := c.Input().Get("user_id")
user := object.GetUserByFields(organization, userId)
var captchaEnabled bool
if user != nil && user.SigninWrongTimes >= object.SigninWrongTimesLimit {
captchaEnabled = true
}
c.ResponseOk(captchaEnabled)
}

View File

@ -89,7 +89,7 @@ func (c *ApiController) GetLdapUsers() {
uuids = append(uuids, user.Uuid)
}
existUuids := object.CheckLdapUuidExist(ldapServer.Owner, uuids)
existUuids := object.GetExistUuids(ldapServer.Owner, uuids)
c.ResponseOk(resp, existUuids)
}

View File

@ -124,40 +124,6 @@ func (c *ApiController) DeleteToken() {
c.ServeJSON()
}
// GetOAuthCode
// @Title GetOAuthCode
// @Tag Token API
// @Description get OAuth code
// @Param id query string true "The id ( owner/name ) of user"
// @Param client_id query string true "OAuth client id"
// @Param response_type query string true "OAuth response type"
// @Param redirect_uri query string true "OAuth redirect URI"
// @Param scope query string true "OAuth scope"
// @Param state query string true "OAuth state"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/code [post]
func (c *ApiController) GetOAuthCode() {
userId := c.Input().Get("user_id")
clientId := c.Input().Get("client_id")
responseType := c.Input().Get("response_type")
redirectUri := c.Input().Get("redirect_uri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
nonce := c.Input().Get("nonce")
challengeMethod := c.Input().Get("code_challenge_method")
codeChallenge := c.Input().Get("code_challenge")
if challengeMethod != "S256" && challengeMethod != "null" && challengeMethod != "" {
c.ResponseError(c.T("auth:Challenge method should be S256"))
return
}
host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, host, c.GetAcceptLanguage())
c.ServeJSON()
}
// GetOAuthToken
// @Title GetOAuthToken
// @Tag Token API

2
go.mod
View File

@ -17,6 +17,7 @@ require (
github.com/casdoor/xorm-adapter/v3 v3.0.4
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0
github.com/fogleman/gg v1.3.0
github.com/forestmgy/ldapserver v1.1.0
github.com/go-git/go-git/v5 v5.6.0
github.com/go-ldap/ldap/v3 v3.3.0
@ -25,6 +26,7 @@ require (
github.com/go-sql-driver/mysql v1.6.0
github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect

5
go.sum
View File

@ -173,6 +173,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/forestmgy/ldapserver v1.1.0 h1:gvil4nuLhqPEL8SugCkFhRyA0/lIvRdwZSqlrw63ll4=
github.com/forestmgy/ldapserver v1.1.0/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
@ -239,6 +241,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -677,6 +681,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "Benutzername muss mindestens 2 Zeichen lang sein",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Sie haben zu oft das falsche Passwort oder den falschen Code eingegeben. Bitte warten Sie %d Minuten und versuchen Sie es erneut",
"Your region is not allow to signup by phone": "Ihre Region ist nicht berechtigt, sich telefonisch anzumelden",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "Das Passwort oder der Code ist falsch. Du hast noch %d Versuche übrig",
"unsupported password type: %s": "Nicht unterstützter Passworttyp: %s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "Nombre de usuario debe tener al menos 2 caracteres",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Has ingresado la contraseña o código incorrecto demasiadas veces, por favor espera %d minutos e intenta de nuevo",
"Your region is not allow to signup by phone": "Tu región no está permitida para registrarse por teléfono",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "Contraseña o código incorrecto, tienes %d intentos restantes",
"unsupported password type: %s": "Tipo de contraseña no compatible: %s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "Nama pengguna harus memiliki setidaknya 2 karakter",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Anda telah memasukkan kata sandi atau kode yang salah terlalu banyak kali, mohon tunggu selama %d menit dan coba lagi",
"Your region is not allow to signup by phone": "Wilayah Anda tidak diizinkan untuk mendaftar melalui telepon",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "Kata sandi atau kode salah, Anda memiliki %d kesempatan tersisa",
"unsupported password type: %s": "jenis sandi tidak didukung: %s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "ユーザー名は少なくとも2文字必要です",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "あなたは間違ったパスワードまたはコードを何度も入力しました。%d 分間待ってから再度お試しください",
"Your region is not allow to signup by phone": "あなたの地域は電話でサインアップすることができません",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "パスワードまたはコードが間違っています。あと%d回の試行機会があります",
"unsupported password type: %s": "サポートされていないパスワードタイプ:%s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "사용자 이름은 적어도 2개의 문자가 있어야 합니다",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "올바르지 않은 비밀번호나 코드를 여러 번 입력했습니다. %d분 동안 기다리신 후 다시 시도해주세요",
"Your region is not allow to signup by phone": "당신의 지역은 전화로 가입할 수 없습니다",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "암호 또는 코드가 올바르지 않습니다. %d번의 기회가 남아 있습니다",
"unsupported password type: %s": "지원되지 않는 암호 유형: %s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "Имя пользователя должно содержать не менее 2 символов",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Вы ввели неправильный пароль или код слишком много раз, пожалуйста, подождите %d минут и попробуйте снова",
"Your region is not allow to signup by phone": "Ваш регион не разрешает регистрацию по телефону",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "Неправильный пароль или код, у вас осталось %d попыток",
"unsupported password type: %s": "неподдерживаемый тип пароля: %s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "Tên đăng nhập phải có ít nhất 2 ký tự",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Bạn đã nhập sai mật khẩu hoặc mã quá nhiều lần, vui lòng đợi %d phút và thử lại",
"Your region is not allow to signup by phone": "Vùng của bạn không được phép đăng ký bằng điện thoại",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "Mật khẩu hoặc mã không chính xác, bạn còn %d lần cơ hội",
"unsupported password type: %s": "Loại mật khẩu không được hỗ trợ: %s"
},

View File

@ -52,6 +52,7 @@
"Username must have at least 2 characters": "用户名至少要有2个字符",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "密码错误次数已达上限,请在 %d 分后重试",
"Your region is not allow to signup by phone": "所在地区不支持手机号注册",
"password or code is incorrect": "密码错误",
"password or code is incorrect, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会",
"unsupported password type: %s": "不支持的密码类型: %s"
},

View File

@ -80,3 +80,21 @@ func DownloadAndUpload(url string, fullFilePath string, lang string) {
panic(err)
}
}
func getPermanentAvatarUrlFromBuffer(organization string, username string, fileBuffer *bytes.Buffer, ext string, upload bool) string {
if defaultStorageProvider == nil {
return ""
}
fullFilePath := fmt.Sprintf("/avatar/%s/%s%s", organization, username, ext)
uploadedFileUrl, _ := GetUploadFileUrl(defaultStorageProvider, fullFilePath, false)
if upload {
_, _, err := UploadFileSafe(defaultStorageProvider, fullFilePath, fileBuffer, "en")
if err != nil {
panic(err)
}
}
return uploadedFileUrl
}

View File

@ -16,6 +16,7 @@ package object
import (
"fmt"
"strings"
"testing"
"github.com/casdoor/casdoor/proxy"
@ -37,3 +38,22 @@ func TestSyncPermanentAvatars(t *testing.T) {
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
}
}
func TestUpdateAvatars(t *testing.T) {
InitConfig()
InitDefaultStorageProvider()
proxy.InitHttpClient()
users := GetUsers("casdoor")
for _, user := range users {
if strings.HasPrefix(user.Avatar, "http") {
continue
}
updated := user.refreshAvatar()
if updated {
user.PermanentAvatar = "*"
UpdateUser(user.GetId(), user, []string{"avatar"}, true)
}
}
}

167
object/avatar_util.go Normal file
View File

@ -0,0 +1,167 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"crypto/md5"
"fmt"
"image"
"image/color"
"image/png"
"io"
"net/http"
"strings"
"github.com/fogleman/gg"
)
func hasGravatar(client *http.Client, email string) (bool, error) {
// Clean and lowercase the email
email = strings.TrimSpace(strings.ToLower(email))
// Generate MD5 hash of the email
hash := md5.New()
io.WriteString(hash, email)
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
// Create Gravatar URL with d=404 parameter
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s?d=404", hashedEmail)
// Send a request to Gravatar
req, err := http.NewRequest("GET", gravatarURL, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
// Check if the user has a custom Gravatar image
if resp.StatusCode == http.StatusOK {
return true, nil
} else if resp.StatusCode == http.StatusNotFound {
return false, nil
} else {
return false, fmt.Errorf("failed to fetch gravatar image: %s", resp.Status)
}
}
func getGravatarFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
// Clean and lowercase the email
email = strings.TrimSpace(strings.ToLower(email))
// Generate MD5 hash of the email
hash := md5.New()
io.WriteString(hash, email)
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
// Create Gravatar URL
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s", hashedEmail)
// Download the image
req, err := http.NewRequest("GET", gravatarURL, nil)
if err != nil {
return nil, "", err
}
resp, err := client.Do(req)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, "", fmt.Errorf("failed to download gravatar image: %s", resp.Status)
}
// Get the content type and determine the file extension
contentType := resp.Header.Get("Content-Type")
fileExtension := ""
switch contentType {
case "image/jpeg":
fileExtension = ".jpg"
case "image/png":
fileExtension = ".png"
case "image/gif":
fileExtension = ".gif"
default:
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
}
// Save the image to a bytes.Buffer
buffer := &bytes.Buffer{}
_, err = io.Copy(buffer, resp.Body)
if err != nil {
return nil, "", err
}
return buffer, fileExtension, nil
}
func getColor(data []byte) color.RGBA {
r := int(data[0]) % 256
g := int(data[1]) % 256
b := int(data[2]) % 256
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
}
func getIdenticonFileBuffer(username string) (*bytes.Buffer, string, error) {
username = strings.TrimSpace(strings.ToLower(username))
hash := md5.New()
io.WriteString(hash, username)
hashedUsername := hash.Sum(nil)
// Define the size of the image
const imageSize = 420
const cellSize = imageSize / 7
// Create a new image
img := image.NewRGBA(image.Rect(0, 0, imageSize, imageSize))
// Create a context
dc := gg.NewContextForRGBA(img)
// Set a background color
dc.SetColor(color.RGBA{240, 240, 240, 255})
dc.Clear()
// Get avatar color
avatarColor := getColor(hashedUsername)
// Draw cells
for i := 0; i < 7; i++ {
for j := 0; j < 7; j++ {
if (hashedUsername[i] >> uint(j) & 1) == 1 {
dc.SetColor(avatarColor)
dc.DrawRectangle(float64(j*cellSize), float64(i*cellSize), float64(cellSize), float64(cellSize))
dc.Fill()
}
}
}
// Save image to a bytes.Buffer
buffer := &bytes.Buffer{}
err := png.Encode(buffer, img)
if err != nil {
return nil, "", fmt.Errorf("failed to save image: %w", err)
}
return buffer, ".png", nil
}

View File

@ -157,10 +157,16 @@ func checkSigninErrorTimes(user *User, lang string) string {
return ""
}
func CheckPassword(user *User, password string, lang string) string {
func CheckPassword(user *User, password string, lang string, options ...bool) string {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
// check the login error times
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
if !enableCaptcha {
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
}
}
organization := GetOrganizationByUser(user)
@ -182,7 +188,7 @@ func CheckPassword(user *User, password string, lang string) string {
return ""
}
return recordSigninErrorInfo(user, lang)
return recordSigninErrorInfo(user, lang, enableCaptcha)
} else {
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
}
@ -231,7 +237,11 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
return ""
}
func CheckUserPassword(organization string, username string, password string, lang string) (*User, string) {
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
user := GetUserByFields(organization, username)
if user == nil || user.IsDeleted == true {
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
@ -250,7 +260,7 @@ func CheckUserPassword(organization string, username string, password string, la
return nil, msg
}
} else {
if msg := CheckPassword(user, password, lang); msg != "" {
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" {
return nil, msg
}
}
@ -380,7 +390,7 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
return ""
}
func CheckToEnableCaptcha(application *Application) bool {
func CheckToEnableCaptcha(application *Application, organization, username string) bool {
if len(application.Providers) == 0 {
return false
}
@ -390,6 +400,10 @@ func CheckToEnableCaptcha(application *Application) bool {
continue
}
if providerItem.Provider.Category == "Captcha" {
if providerItem.Rule == "Dynamic" {
user := GetUserByFields(organization, username)
return user != nil && user.SigninWrongTimes >= SigninWrongTimesLimit
}
return providerItem.Rule == "Always"
}
}

View File

@ -45,9 +45,15 @@ func resetUserSigninErrorTimes(user *User) {
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
}
func recordSigninErrorInfo(user *User, lang string) string {
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
// increase failed login count
user.SigninWrongTimes++
if user.SigninWrongTimes < SigninWrongTimesLimit {
user.SigninWrongTimes++
}
if user.SigninWrongTimes >= SigninWrongTimesLimit {
// record the latest failed login time
@ -57,10 +63,11 @@ func recordSigninErrorInfo(user *User, lang string) string {
// update user
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
if leftChances > 0 {
if leftChances == 0 && enableCaptcha {
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))
} else if leftChances >= 0 {
return fmt.Sprintf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
}
// don't show the chance error message if the user has no chance left
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
}

View File

@ -268,7 +268,7 @@ func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]Ld
uuids = append(uuids, user.Uuid)
}
existUuids := CheckLdapUuidExist(owner, uuids)
existUuids := GetExistUuids(owner, uuids)
organization := getOrganization("admin", owner)
ldap := GetLdap(ldapId)
@ -327,18 +327,18 @@ func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]Ld
return &existUsers, &failedUsers
}
func CheckLdapUuidExist(owner string, uuids []string) []string {
var results []User
func GetExistUuids(owner string, uuids []string) []string {
var users []User
var existUuids []string
existUuidSet := make(map[string]struct{})
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&users)
if err != nil {
panic(err)
}
if len(results) > 0 {
for _, result := range results {
if len(users) > 0 {
for _, result := range users {
existUuidSet[result.Ldap] = struct{}{}
}
}

View File

@ -69,7 +69,7 @@ func GetMessages(owner string) []*Message {
func GetChatMessages(chat string) []*Message {
messages := []*Message{}
err := adapter.Engine.Desc("created_time").Find(&messages, &Message{Chat: chat})
err := adapter.Engine.Asc("created_time").Find(&messages, &Message{Chat: chat})
if err != nil {
panic(err)
}

View File

@ -18,6 +18,7 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"net"
"strings"
"github.com/casdoor/casdoor/conf"
@ -43,6 +44,26 @@ type OidcDiscovery struct {
EndSessionEndpoint string `json:"end_session_endpoint"`
}
func isIpAddress(host string) bool {
// Attempt to split the host and port, ignoring the error
hostWithoutPort, _, err := net.SplitHostPort(host)
if err != nil {
// If an error occurs, it might be because there's no port
// In that case, use the original host string
hostWithoutPort = host
}
// Attempt to parse the host as an IP address (both IPv4 and IPv6)
ip := net.ParseIP(hostWithoutPort)
if ip != nil {
// The host is an IP address
return true
}
// The host is not an IP address
return false
}
func getOriginFromHost(host string) (string, string) {
origin := conf.GetConfigString("origin")
if origin != "" {
@ -52,6 +73,8 @@ func getOriginFromHost(host string) (string, string) {
protocol := "https://"
if strings.HasPrefix(host, "localhost") {
protocol = "http://"
} else if isIpAddress(host) {
protocol = "http://"
}
if host == "localhost:8000" {

View File

@ -29,7 +29,10 @@ import (
func getEnforcer(permission *Permission) *casbin.Enforcer {
tableName := "permission_rule"
if len(permission.Adapter) != 0 {
tableName = permission.Adapter
adapterObj := getCasbinAdapter(permission.Owner, permission.Adapter)
if adapterObj != nil && adapterObj.Table != "" {
tableName = adapterObj.Table
}
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
driverName := conf.GetConfigString("driverName")
@ -130,7 +133,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
for _, subUser := range roleObj.Users {
if domainExist {
for _, domain := range permission.Domains {
groupingPolicies = append(groupingPolicies, []string{subUser, domain, role, "", "", permissionId})
groupingPolicies = append(groupingPolicies, []string{subUser, role, domain, "", "", permissionId})
}
} else {
groupingPolicies = append(groupingPolicies, []string{subUser, role, "", "", "", permissionId})
@ -140,7 +143,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
for _, subRole := range roleObj.Roles {
if domainExist {
for _, domain := range permission.Domains {
groupingPolicies = append(groupingPolicies, []string{subRole, domain, role, "", "", permissionId})
groupingPolicies = append(groupingPolicies, []string{subRole, role, domain, "", "", permissionId})
}
} else {
groupingPolicies = append(groupingPolicies, []string{subRole, role, "", "", "", permissionId})

View File

@ -30,7 +30,10 @@ func TestProduct(t *testing.T) {
product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-pay-alipay")
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
if err != nil {
panic(err)
}
paymentName := util.GenerateTimeId()
returnUrl := ""

View File

@ -221,6 +221,10 @@ func UpdateProvider(id string, provider *Provider) bool {
if provider.ClientSecret2 == "***" {
session = session.Omit("client_secret2")
}
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
affected, err := session.Update(provider)
if err != nil {
panic(err)
@ -230,6 +234,9 @@ func UpdateProvider(id string, provider *Provider) bool {
}
func AddProvider(provider *Provider) bool {
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
affected, err := adapter.Engine.Insert(provider)
if err != nil {
panic(err)

View File

@ -15,10 +15,12 @@
package object
import (
"bytes"
"fmt"
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/xorm-io/core"
@ -513,7 +515,10 @@ func AddUser(user *User) bool {
user.UpdateUserHash()
user.PreHash = user.Hash
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
updated := user.refreshAvatar()
if updated && user.PermanentAvatar != "*" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
}
user.Ranking = GetUserCount(user.Owner, "", "") + 1
@ -693,3 +698,40 @@ func userChangeTrigger(oldName string, newName string) error {
return session.Commit()
}
func (user *User) refreshAvatar() bool {
var err error
var fileBuffer *bytes.Buffer
var ext string
// Gravatar + Identicon
if strings.Contains(user.Avatar, "Gravatar") && user.Email != "" {
client := proxy.ProxyHttpClient
has, err := hasGravatar(client, user.Email)
if err != nil {
panic(err)
}
if has {
fileBuffer, ext, err = getGravatarFileBuffer(client, user.Email)
if err != nil {
panic(err)
}
}
}
if fileBuffer == nil && strings.Contains(user.Avatar, "Identicon") {
fileBuffer, ext, err = getIdenticonFileBuffer(user.Name)
if err != nil {
panic(err)
}
}
if fileBuffer != nil {
avatarUrl := getPermanentAvatarUrlFromBuffer(user.Owner, user.Name, fileBuffer, ext, true)
user.Avatar = avatarUrl
return true
}
return false
}

View File

@ -131,6 +131,12 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
if user.DisplayName == "" {
user.DisplayName = userInfo.DisplayName
}
} else if user.DisplayName == "" {
if userInfo.Username != "" {
user.DisplayName = userInfo.Username
} else {
user.DisplayName = userInfo.Id
}
}
if userInfo.Email != "" {
propertyName := fmt.Sprintf("oauth_%s_email", providerType)

View File

@ -33,7 +33,7 @@ func GetPaymentProvider(typ string, appId string, clientSecret string, host stri
return NewGcPaymentProvider(appId, clientSecret, host), nil
} else if typ == "WeChat Pay" {
// appId, mchId, mchCertSerialNumber, apiV3Key, privateKey
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, authorityPublicKey, clientSecret, appPrivateKey)
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, appCertificate, clientSecret, appPrivateKey)
if err != nil {
return nil, err
}

View File

@ -57,6 +57,7 @@ func initAPI() {
beego.Router("/api/saml/metadata", &controllers.ApiController{}, "GET:GetSamlMeta")
beego.Router("/api/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent")
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
@ -155,7 +156,6 @@ func initAPI() {
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
beego.Router("/api/login/oauth/code", &controllers.ApiController{}, "POST:GetOAuthCode")
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")

View File

@ -51,6 +51,12 @@ func StaticFilter(ctx *context.Context) {
path += urlPath
}
path2 := strings.TrimLeft(path, "web/build/images/")
if util.FileExist(path2) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, path2)
return
}
if !util.FileExist(path) {
path = "web/build/index.html"
}

View File

@ -99,6 +99,34 @@
}
}
},
"/api/add-chat": {
"post": {
"tags": [
"Chat API"
],
"description": "add chat",
"operationId": "ApiController.AddChat",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the chat",
"required": true,
"schema": {
"$ref": "#/definitions/object.Chat"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/add-ldap": {
"post": {
"tags": [
@ -107,6 +135,34 @@
"operationId": "ApiController.AddLdap"
}
},
"/api/add-message": {
"post": {
"tags": [
"Message API"
],
"description": "add message",
"operationId": "ApiController.AddMessage",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the message",
"required": true,
"schema": {
"$ref": "#/definitions/object.Message"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/add-model": {
"post": {
"tags": [
@ -496,6 +552,14 @@
}
},
"/api/api/get-webhook-event": {
"get": {
"tags": [
"GetCaptchaStatus API"
],
"operationId": "ApiController.GetCaptchaStatus"
}
},
"/api/api/get-captcha-status": {
"get": {
"tags": [
"GetWebhookEventType API"
@ -595,6 +659,14 @@
}
}
},
"/api/api/verify-code": {
"post": {
"tags": [
"Account API"
],
"operationId": "ApiController.VerifyCode"
}
},
"/api/api/webhook": {
"post": {
"tags": [
@ -700,6 +772,34 @@
}
}
},
"/api/delete-chat": {
"post": {
"tags": [
"Chat API"
],
"description": "delete chat",
"operationId": "ApiController.DeleteChat",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the chat",
"required": true,
"schema": {
"$ref": "#/definitions/object.Chat"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/delete-ldap": {
"post": {
"tags": [
@ -708,6 +808,34 @@
"operationId": "ApiController.DeleteLdap"
}
},
"/api/delete-message": {
"post": {
"tags": [
"Message API"
],
"description": "delete message",
"operationId": "ApiController.DeleteMessage",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the message",
"required": true,
"schema": {
"$ref": "#/definitions/object.Message"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/delete-model": {
"post": {
"tags": [
@ -1234,6 +1362,61 @@
}
}
},
"/api/get-chat": {
"get": {
"tags": [
"Chat API"
],
"description": "get chat",
"operationId": "ApiController.GetChat",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the chat",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.Chat"
}
}
}
}
},
"/api/get-chats": {
"get": {
"tags": [
"Chat API"
],
"description": "get chats",
"operationId": "ApiController.GetChats",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of chats",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Chat"
}
}
}
}
}
},
"/api/get-default-application": {
"get": {
"tags": [
@ -1357,6 +1540,61 @@
"operationId": "ApiController.GetLdaps"
}
},
"/api/get-message": {
"get": {
"tags": [
"Message API"
],
"description": "get message",
"operationId": "ApiController.GetMessage",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the message",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.Message"
}
}
}
}
},
"/api/get-messages": {
"get": {
"tags": [
"Message API"
],
"description": "get messages",
"operationId": "ApiController.GetMessages",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of messages",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Message"
}
}
}
}
}
},
"/api/get-model": {
"get": {
"tags": [
@ -2587,67 +2825,6 @@
}
}
},
"/api/login/oauth/code": {
"post": {
"tags": [
"Token API"
],
"description": "get OAuth code",
"operationId": "ApiController.GetOAuthCode",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of user",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "client_id",
"description": "OAuth client id",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "response_type",
"description": "OAuth response type",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "redirect_uri",
"description": "OAuth redirect URI",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "scope",
"description": "OAuth scope",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "state",
"description": "OAuth state",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenWrapper"
}
}
}
}
},
"/api/login/oauth/introspect": {
"post": {
"description": "The introspection endpoint is an OAuth 2.0 endpoint that takes a",
@ -3056,6 +3233,41 @@
}
}
},
"/api/update-chat": {
"post": {
"tags": [
"Chat API"
],
"description": "update chat",
"operationId": "ApiController.UpdateChat",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the chat",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "body",
"description": "The details of the chat",
"required": true,
"schema": {
"$ref": "#/definitions/object.Chat"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/update-ldap": {
"post": {
"tags": [
@ -3064,6 +3276,41 @@
"operationId": "ApiController.UpdateLdap"
}
},
"/api/update-message": {
"post": {
"tags": [
"Message API"
],
"description": "update message",
"operationId": "ApiController.UpdateMessage",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the message",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "body",
"description": "The details of the message",
"required": true,
"schema": {
"$ref": "#/definitions/object.Message"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/update-model": {
"post": {
"tags": [
@ -3644,11 +3891,11 @@
}
},
"definitions": {
"2306.0xc0003a4480.false": {
"2306.0xc0004a1410.false": {
"title": "false",
"type": "object"
},
"2340.0xc0003a44b0.false": {
"2340.0xc0004a1440.false": {
"title": "false",
"type": "object"
},
@ -3782,10 +4029,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/2306.0xc0003a4480.false"
"$ref": "#/definitions/2306.0xc0004a1410.false"
},
"data2": {
"$ref": "#/definitions/2340.0xc0003a44b0.false"
"$ref": "#/definitions/2340.0xc0004a1440.false"
},
"msg": {
"type": "string"
@ -4047,6 +4294,52 @@
}
}
},
"object.Chat": {
"title": "Chat",
"type": "object",
"properties": {
"category": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"displayName": {
"type": "string"
},
"messageCount": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"organization": {
"type": "string"
},
"owner": {
"type": "string"
},
"type": {
"type": "string"
},
"updatedTime": {
"type": "string"
},
"user1": {
"type": "string"
},
"user2": {
"type": "string"
},
"users": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"object.Header": {
"title": "Header",
"type": "object",
@ -4125,6 +4418,33 @@
}
}
},
"object.Message": {
"title": "Message",
"type": "object",
"properties": {
"author": {
"type": "string"
},
"chat": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"name": {
"type": "string"
},
"organization": {
"type": "string"
},
"owner": {
"type": "string"
},
"text": {
"type": "string"
}
}
},
"object.Model": {
"title": "Model",
"type": "object",

View File

@ -64,11 +64,47 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-chat:
post:
tags:
- Chat API
description: add chat
operationId: ApiController.AddChat
parameters:
- in: body
name: body
description: The details of the chat
required: true
schema:
$ref: '#/definitions/object.Chat'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-ldap:
post:
tags:
- Account API
operationId: ApiController.AddLdap
/api/add-message:
post:
tags:
- Message API
description: add message
operationId: ApiController.AddMessage
parameters:
- in: body
name: body
description: The details of the message
required: true
schema:
$ref: '#/definitions/object.Message'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-model:
post:
tags:
@ -324,6 +360,11 @@ paths:
tags:
- GetWebhookEventType API
operationId: ApiController.GetWebhookEventType
/api/api/get-captcha-status:
get:
tags:
- GetCaptchaStatus API
operationId: ApiController.GetCaptchaStatus
/api/api/reset-email-or-phone:
post:
tags:
@ -385,6 +426,11 @@ paths:
description: object
schema:
$ref: '#/definitions/Response'
/api/api/verify-code:
post:
tags:
- Account API
operationId: ApiController.VerifyCode
/api/api/webhook:
post:
tags:
@ -453,11 +499,47 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-chat:
post:
tags:
- Chat API
description: delete chat
operationId: ApiController.DeleteChat
parameters:
- in: body
name: body
description: The details of the chat
required: true
schema:
$ref: '#/definitions/object.Chat'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-ldap:
post:
tags:
- Account API
operationId: ApiController.DeleteLdap
/api/delete-message:
post:
tags:
- Message API
description: delete message
operationId: ApiController.DeleteMessage
parameters:
- in: body
name: body
description: The details of the message
required: true
schema:
$ref: '#/definitions/object.Message'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-model:
post:
tags:
@ -800,6 +882,42 @@ paths:
type: array
items:
$ref: '#/definitions/object.Cert'
/api/get-chat:
get:
tags:
- Chat API
description: get chat
operationId: ApiController.GetChat
parameters:
- in: query
name: id
description: The id ( owner/name ) of the chat
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Chat'
/api/get-chats:
get:
tags:
- Chat API
description: get chats
operationId: ApiController.GetChats
parameters:
- in: query
name: owner
description: The owner of chats
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Chat'
/api/get-default-application:
get:
tags:
@ -880,6 +998,42 @@ paths:
tags:
- Account API
operationId: ApiController.GetLdaps
/api/get-message:
get:
tags:
- Message API
description: get message
operationId: ApiController.GetMessage
parameters:
- in: query
name: id
description: The id ( owner/name ) of the message
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Message'
/api/get-messages:
get:
tags:
- Message API
description: get messages
operationId: ApiController.GetMessages
parameters:
- in: query
name: owner
description: The owner of messages
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Message'
/api/get-model:
get:
tags:
@ -1690,48 +1844,6 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
/api/login/oauth/code:
post:
tags:
- Token API
description: get OAuth code
operationId: ApiController.GetOAuthCode
parameters:
- in: query
name: id
description: The id ( owner/name ) of user
required: true
type: string
- in: query
name: client_id
description: OAuth client id
required: true
type: string
- in: query
name: response_type
description: OAuth response type
required: true
type: string
- in: query
name: redirect_uri
description: OAuth redirect URI
required: true
type: string
- in: query
name: scope
description: OAuth scope
required: true
type: string
- in: query
name: state
description: OAuth state
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.TokenWrapper'
/api/login/oauth/introspect:
post:
description: The introspection endpoint is an OAuth 2.0 endpoint that takes a
@ -2001,11 +2113,57 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-chat:
post:
tags:
- Chat API
description: update chat
operationId: ApiController.UpdateChat
parameters:
- in: query
name: id
description: The id ( owner/name ) of the chat
required: true
type: string
- in: body
name: body
description: The details of the chat
required: true
schema:
$ref: '#/definitions/object.Chat'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-ldap:
post:
tags:
- Account API
operationId: ApiController.UpdateLdap
/api/update-message:
post:
tags:
- Message API
description: update message
operationId: ApiController.UpdateMessage
parameters:
- in: query
name: id
description: The id ( owner/name ) of the message
required: true
type: string
- in: body
name: body
description: The details of the message
required: true
schema:
$ref: '#/definitions/object.Message'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-model:
post:
tags:
@ -2385,10 +2543,10 @@ paths:
schema:
$ref: '#/definitions/Response'
definitions:
2306.0xc0003a4480.false:
2306.0xc0004a1410.false:
title: "false"
type: object
2340.0xc0003a44b0.false:
2340.0xc0004a1440.false:
title: "false"
type: object
LaravelResponse:
@ -2480,9 +2638,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/2306.0xc0003a4480.false'
$ref: '#/definitions/2306.0xc0004a1410.false'
data2:
$ref: '#/definitions/2340.0xc0003a44b0.false'
$ref: '#/definitions/2340.0xc0004a1440.false'
msg:
type: string
name:
@ -2657,6 +2815,37 @@ definitions:
type: string
type:
type: string
object.Chat:
title: Chat
type: object
properties:
category:
type: string
createdTime:
type: string
displayName:
type: string
messageCount:
type: integer
format: int64
name:
type: string
organization:
type: string
owner:
type: string
type:
type: string
updatedTime:
type: string
user1:
type: string
user2:
type: string
users:
type: array
items:
type: string
object.Header:
title: Header
type: object
@ -2710,6 +2899,24 @@ definitions:
type: string
username:
type: string
object.Message:
title: Message
type: object
properties:
author:
type: string
chat:
type: string
createdTime:
type: string
name:
type: string
organization:
type: string
owner:
type: string
text:
type: string
object.Model:
title: Model
type: object

View File

@ -259,3 +259,11 @@ func maskString(str string) string {
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
}
}
// GetEndPoint remove scheme from url
func GetEndPoint(endpoint string) string {
for _, prefix := range []string{"https://", "http://"} {
endpoint = strings.TrimPrefix(endpoint, prefix)
}
return endpoint
}

View File

@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/cssinjs": "^1.5.6",
"@ant-design/cssinjs": "^1.8.1",
"@ant-design/icons": "^4.7.0",
"@craco/craco": "^6.4.5",
"@crowdin/cli": "^3.7.10",
@ -12,7 +12,7 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"antd": "5.1.6",
"antd": "5.2.3",
"antd-token-previewer": "^1.1.0-22",
"codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1",
@ -76,7 +76,6 @@
"eslint-plugin-react": "^7.31.1",
"husky": "^4.3.8",
"lint-staged": "^13.0.3",
"path-browserify": "^1.0.1",
"stylelint": "^14.11.0",
"stylelint-config-recommended-less": "^1.0.4",
"stylelint-config-standard": "^28.0.0"

View File

@ -15,7 +15,6 @@
import React from "react";
import {Avatar, Input, List} from "antd";
import {CopyOutlined, DislikeOutlined, LikeOutlined, SendOutlined} from "@ant-design/icons";
import * as Setting from "./Setting";
const {TextArea} = Input;
@ -25,6 +24,14 @@ class ChatBox extends React.Component {
this.state = {
inputValue: "",
};
this.listContainerRef = React.createRef();
}
componentDidUpdate(prevProps) {
if (prevProps.messages !== this.props.messages) {
this.scrollToListItem(this.props.messages.length);
}
}
handleKeyDown = (e) => {
@ -38,21 +45,43 @@ class ChatBox extends React.Component {
}
};
scrollToListItem(index) {
const listContainerElement = this.listContainerRef.current;
if (!listContainerElement) {
return;
}
const targetItem = listContainerElement.querySelector(
`#chatbox-list-item-${index}`
);
if (!targetItem) {
return;
}
const scrollDistance = targetItem.offsetTop - listContainerElement.offsetTop;
listContainerElement.scrollTo({
top: scrollDistance,
behavior: "smooth",
});
}
send = (text) => {
Setting.showMessage("success", text);
this.props.sendMessage(text);
this.setState({inputValue: ""});
};
renderList() {
return (
<div style={{position: "relative"}}>
<div ref={this.listContainerRef} style={{position: "relative", maxHeight: "calc(100vh - 140px)", overflowY: "auto"}}>
<List
style={{maxHeight: "calc(100vh - 140px)", overflowY: "auto"}}
itemLayout="horizontal"
dataSource={this.props.messages === undefined ? undefined : [...this.props.messages, {}]}
renderItem={(item, index) => {
if (Object.keys(item).length === 0 && item.constructor === Object) {
return <List.Item style={{
return <List.Item id={`chatbox-list-item-${index}`} style={{
height: "160px",
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
borderBottom: "1px solid rgb(229, 229, 229)",
@ -61,7 +90,7 @@ class ChatBox extends React.Component {
}
return (
<List.Item style={{
<List.Item id={`chatbox-list-item-${index}`} style={{
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
borderBottom: "1px solid rgb(229, 229, 229)",
position: "relative",

View File

@ -47,7 +47,7 @@ class ChatMenu extends React.Component {
const globalChatIndex = chats.indexOf(chat);
return {
key: `${index}-${chatIndex}`,
dataIndex: globalChatIndex,
index: globalChatIndex,
label: chat.displayName,
};
}),
@ -61,7 +61,7 @@ class ChatMenu extends React.Component {
this.setState({selectedKeys: [`${categoryIndex}-${chatIndex}`]});
if (this.props.onSelect) {
this.props.onSelect(selectedItem.dataIndex);
this.props.onSelect(selectedItem.index);
}
};

View File

@ -42,13 +42,59 @@ class ChatPage extends BaseListPage {
};
}
addChat() {
const newChat = this.newChat();
ChatBackend.addChat(newChat)
// addChat() {
// const newChat = this.newChat();
// ChatBackend.addChat(newChat)
// .then((res) => {
// if (res.status === "ok") {
// this.props.history.push({pathname: `/chats/${newChat.name}`, mode: "add"});
// Setting.showMessage("success", i18next.t("general:Successfully added"));
// } else {
// Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
// }
// })
// .catch(error => {
// Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
// });
// }
//
// deleteChat(i) {
// ChatBackend.deleteChat(this.state.data[i])
// .then((res) => {
// if (res.status === "ok") {
// Setting.showMessage("success", i18next.t("general:Successfully deleted"));
// this.setState({
// data: Setting.deleteRow(this.state.data, i),
// pagination: {total: this.state.pagination.total - 1},
// });
// } else {
// Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
// }
// })
// .catch(error => {
// Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
// });
// }
newMessage(text) {
const randomName = Setting.getRandomName();
return {
owner: "admin", // this.props.account.messagename,
name: `message_${randomName}`,
createdTime: moment().format(),
organization: this.props.account.owner,
chat: this.state.chatName,
author: `${this.props.account.owner}/${this.props.account.name}`,
text: text,
};
}
sendMessage(text) {
const newMessage = this.newMessage(text);
MessageBackend.addMessage(newMessage)
.then((res) => {
if (res.status === "ok") {
this.props.history.push({pathname: `/chats/${newChat.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
this.getMessages(this.state.chatName);
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
@ -58,21 +104,14 @@ class ChatPage extends BaseListPage {
});
}
deleteChat(i) {
ChatBackend.deleteChat(this.state.data[i])
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
getMessages(chatName) {
MessageBackend.getChatMessages(chatName)
.then((messages) => {
this.setState({
messages: messages,
});
Setting.scrollToDiv(`chatbox-list-item-${messages.length}`);
});
}
@ -84,6 +123,9 @@ class ChatPage extends BaseListPage {
<ChatMenu chats={chats} onSelect={(i) => {
const chat = chats[i];
this.getMessages(chat.name);
this.setState({
chatName: chat.name,
});
}} />
</div>
<div style={{flex: 1, height: "100%", backgroundColor: "white", position: "relative"}}>
@ -102,7 +144,7 @@ class ChatPage extends BaseListPage {
opacity: 0.5,
}}>
</div>
<ChatBox messages={this.state.messages} account={this.props.account} />
<ChatBox messages={this.state.messages} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
</div>
</div>
)
@ -133,6 +175,15 @@ class ChatPage extends BaseListPage {
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
const chats = res.data;
if (this.state.chatName === undefined && chats.length > 0) {
const chat = chats[0];
this.getMessages(chat.name);
this.setState({
chatName: chat.name,
});
}
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
@ -143,15 +194,6 @@ class ChatPage extends BaseListPage {
}
});
};
getMessages(chatName) {
MessageBackend.getChatMessages(chatName)
.then((messages) => {
this.setState({
messages: messages,
});
});
}
}
export default ChatPage;

View File

@ -69,7 +69,7 @@ class EntryPage extends React.Component {
return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
<Spin size="large" spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
<Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
@ -84,7 +84,7 @@ class EntryPage extends React.Component {
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
</Switch>
</div>
);

View File

@ -13,10 +13,11 @@
// limitations under the License.
import React from "react";
import {Button, Col, Popconfirm, Row, Table} from "antd";
import {Button, Popconfirm, Table} from "antd";
import * as Setting from "./Setting";
import * as LdapBackend from "./backend/LdapBackend";
import i18next from "i18next";
import {Link} from "react-router-dom";
class LdapSyncPage extends React.Component {
constructor(props) {
@ -77,9 +78,8 @@ class LdapSyncPage extends React.Component {
LdapBackend.getLdap(this.state.organizationName, this.state.ldapId)
.then((res) => {
if (res.status === "ok") {
this.setState((prevState) => {
prevState.ldap = res.data;
return prevState;
this.setState({
ldap: res.data,
});
this.getLdapUser();
} else {
@ -139,22 +139,46 @@ class LdapSyncPage extends React.Component {
dataIndex: "cn",
key: "cn",
sorter: (a, b) => a.cn.localeCompare(b.cn),
render: (text, record, index) => {
return (<div style={{display: "flex", justifyContent: "space-between"}}>
<div>
{text}
</div>
{this.state.existUuids.includes(record.uuid) ?
Setting.getTag("green", i18next.t("ldap:synced")) :
Setting.getTag("red", i18next.t("ldap:unsynced"))
}
</div>);
},
},
{
title: i18next.t("ldap:UidNumber / Uid"),
title: "Uid",
dataIndex: "uid",
key: "uid",
sorter: (a, b) => a.uid.localeCompare(b.uid),
render: (text, record, index) => {
return (
this.state.existUuids.includes(record.uuid) ?
<Link to={`/users/${this.state.organizationName}/${text}`}>
{text}
</Link> :
text
);
},
},
{
title: "UidNumber",
dataIndex: "uidNumber",
key: "uidNumber",
width: "200px",
sorter: (a, b) => a.uidNumber.localeCompare(b.uidNumber),
render: (text, record, index) => {
return `${text} / ${record.uid}`;
return text;
},
},
{
title: i18next.t("ldap:Group ID"),
dataIndex: "groupId",
key: "groupId",
width: "140px",
sorter: (a, b) => a.groupId.localeCompare(b.groupId),
filters: this.buildFilter(this.state.users, "groupId"),
onFilter: (value, record) => record.groupId.indexOf(value) === 0,
@ -163,14 +187,12 @@ class LdapSyncPage extends React.Component {
title: i18next.t("general:Email"),
dataIndex: "email",
key: "email",
width: "240px",
sorter: (a, b) => a.email.localeCompare(b.email),
},
{
title: i18next.t("general:Phone"),
dataIndex: "phone",
key: "phone",
width: "160px",
sorter: (a, b) => a.phone.localeCompare(b.phone),
},
{
@ -183,9 +205,8 @@ class LdapSyncPage extends React.Component {
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState(prevState => {
prevState.selectedUsers = selectedRows;
return prevState;
this.setState({
selectedUsers: selectedRows,
});
},
getCheckboxProps: record => ({
@ -194,42 +215,36 @@ class LdapSyncPage extends React.Component {
};
return (
<div>
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
title={() => (
<div>
<span>{this.state.ldap?.serverName}</span>
<Popconfirm placement={"right"}
title={"Please confirm to sync selected users"}
onConfirm={() => this.syncUsers()}
>
<Button type="primary" style={{marginLeft: "10px"}}>
{i18next.t("general:Sync")}
</Button>
</Popconfirm>
<Button style={{marginLeft: "20px"}}
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
{i18next.t("general:Edit")} LDAP
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered size="small"
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
title={() => (
<div>
{this.state.ldap?.serverName}
<Popconfirm placement={"right"} disabled={this.state.selectedUsers.length === 0}
title={"Please confirm to sync selected users"}
onConfirm={() => this.syncUsers()}
>
<Button type="primary" style={{marginLeft: "10px"}} disabled={this.state.selectedUsers.length === 0}>
{i18next.t("general:Sync")}
</Button>
</div>
)}
loading={users === null}
/>
</div>
</Popconfirm>
<Button style={{marginLeft: "20px"}}
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
{i18next.t("general:Edit")} LDAP
</Button>
</div>
)}
loading={users === null}
/>
);
}
render() {
return (
<div>
<Row style={{width: "100%", justifyContent: "center"}}>
<Col span={22}>
{
this.renderTable(this.state.users)
}
</Col>
</Row>
{
this.renderTable(this.state.users)
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => {
this.props.history.push(`/organizations/${this.state.organizationName}`);

View File

@ -821,6 +821,20 @@ class ProviderEditPage extends React.Component {
</React.Fragment>
) : null
}
{
this.state.provider.type === "WeChat Pay" ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel("cert", "cert")} :
</Col>
<Col span={22} >
<Input value={this.state.provider.cert} onChange={e => {
this.updateProviderField("cert", e.target.value);
}} />
</Col>
</Row>
) : null
}
{this.getAppIdRow(this.state.provider)}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>

View File

@ -24,7 +24,6 @@ import {authConfig} from "./auth/Auth";
import {Helmet} from "react-helmet";
import * as Conf from "./Conf";
import * as phoneNumber from "libphonenumber-js";
import * as path from "path-browserify";
const {Option} = Select;
@ -888,7 +887,7 @@ export function getLoginLink(application) {
} else if (authConfig.appName === application.name) {
url = "/login";
} else if (application.signinUrl === "") {
url = path.join(application.homepageUrl, "/login");
url = trim(application.homepageUrl, "/") + "/login";
} else {
url = application.signinUrl;
}
@ -902,10 +901,11 @@ export function renderLoginLink(application, text) {
export function redirectToLoginPage(application, history) {
const loginLink = getLoginLink(application);
if (loginLink.indexOf("http") === 0 || loginLink.indexOf("https") === 0) {
window.location.replace(loginLink);
if (loginLink.startsWith("http://") || loginLink.startsWith("https://")) {
goToLink(loginLink);
} else {
history.push(loginLink);
}
history.push(loginLink);
}
function renderLink(url, text, onClick) {

View File

@ -139,3 +139,13 @@ export function getWechatMessageEvent() {
},
}).then(res => res.json());
}
export function getCaptchaStatus(values) {
return fetch(`${Setting.ServerUrl}/api/get-captcha-status?organization=${values["organization"]}&user_id=${values["username"]}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}

View File

@ -31,6 +31,7 @@ import CustomGithubCorner from "../common/CustomGithubCorner";
import {SendCodeInput} from "../common/SendCodeInput";
import LanguageSelect from "../common/select/LanguageSelect";
import {CaptchaModal} from "../common/modal/CaptchaModal";
import {CaptchaRule} from "../common/modal/CaptchaModal";
import RedirectForm from "../common/RedirectForm";
class LoginPage extends React.Component {
@ -47,7 +48,7 @@ class LoginPage extends React.Component {
validEmailOrPhone: false,
validEmail: false,
loginMethod: "password",
enableCaptchaModal: false,
enableCaptchaModal: CaptchaRule.Never,
openCaptchaModal: false,
verifyCaptcha: undefined,
samlResponse: "",
@ -81,7 +82,13 @@ class LoginPage extends React.Component {
if (prevProps.application !== this.props.application) {
const captchaProviderItems = this.getCaptchaProviderItems(this.props.application);
if (captchaProviderItems) {
this.setState({enableCaptchaModal: captchaProviderItems.some(providerItem => providerItem.rule === "Always")});
if (captchaProviderItems.some(providerItem => providerItem.rule === "Always")) {
this.setState({enableCaptchaModal: CaptchaRule.Always});
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Dynamic")) {
this.setState({enableCaptchaModal: CaptchaRule.Dynamic});
} else {
this.setState({enableCaptchaModal: CaptchaRule.Never});
}
}
if (this.props.account && this.props.account.owner === this.props.application?.organization) {
@ -110,6 +117,22 @@ class LoginPage extends React.Component {
}
}
checkCaptchaStatus(values) {
AuthBackend.getCaptchaStatus(values)
.then((res) => {
if (res.status === "ok") {
if (res.data) {
this.setState({
openCaptchaModal: true,
values: values,
});
return null;
}
}
this.login(values);
});
}
getApplicationLogin() {
const oAuthParams = Util.getOAuthGetParameters();
AuthBackend.getApplicationLogin(oAuthParams)
@ -255,15 +278,19 @@ class LoginPage extends React.Component {
this.signInWithWebAuthn(username, values);
return;
}
if (this.state.loginMethod === "password" && this.state.enableCaptchaModal) {
this.setState({
openCaptchaModal: true,
values: values,
});
} else {
this.login(values);
if (this.state.loginMethod === "password") {
if (this.state.enableCaptchaModal === CaptchaRule.Always) {
this.setState({
openCaptchaModal: true,
values: values,
});
return;
} else if (this.state.enableCaptchaModal === CaptchaRule.Dynamic) {
this.checkCaptchaStatus(values);
return;
}
}
this.login(values);
}
login(values) {
@ -544,13 +571,15 @@ class LoginPage extends React.Component {
}
renderCaptchaModal(application) {
if (!this.state.enableCaptchaModal) {
if (this.state.enableCaptchaModal === CaptchaRule.Never) {
return null;
}
const provider = this.getCaptchaProviderItems(application)
.filter(providerItem => providerItem.rule === "Always")
.map(providerItem => providerItem.provider)[0];
const captchaProviderItems = this.getCaptchaProviderItems(application);
const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
const provider = alwaysProviderItems.length > 0
? alwaysProviderItems[0].provider
: dynamicProviderItems[0].provider;
return <CaptchaModal
owner={provider.owner}
@ -787,11 +816,11 @@ class LoginPage extends React.Component {
}
const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem));
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
<div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}>
<Spin size="large" tip={i18next.t("login:Signing in...")} />
</div>
);
}

View File

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Result} from "antd";
import {Button, Result, Spin} from "antd";
import i18next from "i18next";
import {authConfig} from "./Auth";
import * as ApplicationBackend from "../backend/ApplicationBackend";
@ -53,6 +53,14 @@ class ResultPage extends React.Component {
render() {
const application = this.state.application;
if (application === null) {
return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Loading")} style={{paddingTop: "10%"}} />
</div>
);
}
return (
<div>
{
@ -68,7 +76,7 @@ class ResultPage extends React.Component {
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage);
} else {
Setting.redirectToLoginPage(application);
Setting.redirectToLoginPage(application, this.props.history);
}
}}>
{i18next.t("login:Sign In")}

View File

@ -408,24 +408,27 @@ class SignupPage extends React.Component {
</Form.Item>
</Input.Group>
</Form.Item>
<Form.Item
name="phoneCode"
label={i18next.t("code:Phone code")}
rules={[
{
required: required,
message: i18next.t("code:Please input your phone verification code!"),
},
]}
>
<SendCodeInput
disabled={!this.state.validPhone}
method={"signup"}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
application={application}
countryCode={this.form.current?.getFieldValue("countryCode")}
/>
</Form.Item>
{
signupItem.rule !== "No verification" &&
<Form.Item
name="phoneCode"
label={i18next.t("code:Phone code")}
rules={[
{
required: required,
message: i18next.t("code:Please input your phone verification code!"),
},
]}
>
<SendCodeInput
disabled={!this.state.validPhone}
method={"signup"}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
application={application}
countryCode={this.form.current?.getFieldValue("countryCode")}
/>
</Form.Item>
}
</React.Fragment>
);
} else if (signupItem.name === "Password") {

View File

@ -170,3 +170,9 @@ export const CaptchaModal = (props) => {
</Modal>
);
};
export const CaptchaRule = {
Always: "Always",
Never: "Never",
Dynamic: "Dynamic",
};

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "URL der Prompt-Seite kopieren",
"Copy signin page URL": "URL der Anmeldeseite kopieren",
"Copy signup page URL": "URL der Anmeldeseite kopieren",
"Dynamic": "Dynamic",
"Edit Application": "Anwendung bearbeiten",
"Enable Email linking": "E-Mail-Verknüpfung aktivieren",
"Enable Email linking - Tooltip": "Bei der Verwendung von Drittanbietern zur Anmeldung wird, wenn es in der Organisation einen Benutzer mit der gleichen E-Mail gibt, automatisch die Drittanbieter-Anmelde-Methode mit diesem Benutzer verbunden",
@ -324,7 +325,8 @@
"Server port": "Server-Port",
"Server port - Tooltip": "LDAP-Server-Port",
"The Auto Sync option will sync all users to specify organization": "Die Option \"Auto Sync\" synchronisiert alle Benutzer mit der angegebenen Organisation",
"UidNumber / Uid": "UidNumber / Uid"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "Automatische Anmeldung",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "Arbeitgeber, wie Firmenname oder Organisationsname",
"Bio": "Bio",
"Bio - Tooltip": "Selbstvorstellung des Nutzers",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "Captcha-Überprüfung fehlgeschlagen",
"Captcha Verify Success": "Captcha-Verifizierung Erfolgreich",
"Country code": "Ländercode",
"Country/Region": "Land/Region",
"Country/Region - Tooltip": "Land oder Region",
"Edit User": "Benutzer bearbeiten",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "E-Mail darf nicht leer sein",
"Email/phone reset successfully": "E-Mail-/Telefon-Zurücksetzung erfolgreich durchgeführt",
"Empty input!": "Leere Eingabe!",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Startseite des Benutzers",
"Homepage - Tooltip": "Homepage-URL des Benutzers",
"ID card": "Ausweis",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "Geben Sie Ihre E-Mail-Adresse ein",
"Input your phone number": "Geben Sie Ihre Telefonnummer ein",
"Is admin": "Ist Admin",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "Verbotene Benutzer können sich nicht mehr einloggen",
"Is global admin": "Ist globaler Administrator",
"Is global admin - Tooltip": "Ist ein Administrator von Casdoor",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "Keys",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "Link",
"Location": "Ort",
"Location - Tooltip": "Stadt des Wohnsitzes",
@ -778,9 +794,13 @@
"Please select avatar from resources": "Bitte wählen Sie einen Avatar aus den Ressourcen aus",
"Properties": "Eigenschaften",
"Properties - Tooltip": "Eigenschaften des Benutzers",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "Neueingabe wiederholen",
"Reset Email...": "E-Mail zurücksetzen...",
"Reset Phone...": "Telefon zurücksetzen...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "Wählen Sie ein Foto aus...",
"Set Password": "Passwort festlegen",
"Set new profile picture": "Neues Profilbild festlegen",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL",
"Dynamic": "Dynamic",
"Edit Application": "Edit Application",
"Enable Email linking": "Enable Email linking",
"Enable Email linking - Tooltip": "When using 3rd-party providers to log in, if there is a user in the organization with the same Email, the 3rd-party login method will be automatically associated with that user",
@ -324,7 +325,8 @@
"Server port": "Server port",
"Server port - Tooltip": "LDAP server port",
"The Auto Sync option will sync all users to specify organization": "The Auto Sync option will sync all users to specify organization",
"UidNumber / Uid": "UidNumber / Uid"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "Auto sign in",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",
"Bio - Tooltip": "Self introduction of the user",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "Captcha Verify Failed",
"Captcha Verify Success": "Captcha Verify Success",
"Country code": "Country code",
"Country/Region": "Country/Region",
"Country/Region - Tooltip": "Country or region",
"Edit User": "Edit User",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",
"Homepage - Tooltip": "Homepage URL of the user",
"ID card": "ID card",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "Input your email",
"Input your phone number": "Input your phone number",
"Is admin": "Is admin",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "Forbidden users cannot log in any more",
"Is global admin": "Is global admin",
"Is global admin - Tooltip": "Is an administrator of Casdoor",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "Keys",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "Link",
"Location": "Location",
"Location - Tooltip": "City of residence",
@ -778,9 +794,13 @@
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties",
"Properties - Tooltip": "Properties of the user",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...",
"Reset Phone...": "Reset Phone...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "Select a photo...",
"Set Password": "Set Password",
"Set new profile picture": "Set new profile picture",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "Copiar URL de la página del prompt",
"Copy signin page URL": "Copiar la URL de la página de inicio de sesión",
"Copy signup page URL": "Copiar URL de la página de registro",
"Dynamic": "Dynamic",
"Edit Application": "Editar solicitud",
"Enable Email linking": "Habilitar enlace de correo electrónico",
"Enable Email linking - Tooltip": "Cuando se utilizan proveedores externos de inicio de sesión, si hay un usuario en la organización con el mismo correo electrónico, el método de inicio de sesión externo se asociará automáticamente con ese usuario",
@ -324,7 +325,8 @@
"Server port": "Puerto del servidor",
"Server port - Tooltip": "Puerto del servidor LDAP",
"The Auto Sync option will sync all users to specify organization": "La opción Auto Sync sincronizará a todos los usuarios con la organización especificada",
"UidNumber / Uid": "UidNumber / Uid"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "Inicio de sesión automático",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "Empleador, como el nombre de una empresa u organización",
"Bio": "Bio - Biografía",
"Bio - Tooltip": "Introducción personal del usuario",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "Validación de Captcha fallida",
"Captcha Verify Success": "Verificación de Captcha Exitosa",
"Country code": "Código de país",
"Country/Region": "País/Región",
"Country/Region - Tooltip": "País o región",
"Edit User": "Editar usuario",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "El correo electrónico no puede estar vacío",
"Email/phone reset successfully": "Restablecimiento de correo electrónico/teléfono exitoso",
"Empty input!": "¡Entrada vacía!",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Página de inicio del usuario",
"Homepage - Tooltip": "URL de la página de inicio del usuario",
"ID card": "Tarjeta de identificación",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "Introduce tu correo electrónico",
"Input your phone number": "Ingrese su número de teléfono",
"Is admin": "Es el administrador",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "Los usuarios bloqueados ya no pueden iniciar sesión",
"Is global admin": "¿Es administrador global?",
"Is global admin - Tooltip": "Es un administrador de Casdoor",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "Claves",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "Enlace",
"Location": "Ubicación",
"Location - Tooltip": "Ciudad de residencia",
@ -778,9 +794,13 @@
"Please select avatar from resources": "Por favor, selecciona un avatar de los recursos disponibles",
"Properties": "Propiedades",
"Properties - Tooltip": "Propiedades del usuario",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "Volver a ingresar Nueva",
"Reset Email...": "Restablecer Correo Electrónico...",
"Reset Phone...": "Reiniciar teléfono...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "Selecciona una foto...",
"Set Password": "Establecer contraseña",
"Set new profile picture": "Establecer nueva foto de perfil",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "Copier l'URL de la page de l'invite",
"Copy signin page URL": "Copier l'URL de la page de connexion",
"Copy signup page URL": "Copiez l'URL de la page d'inscription",
"Dynamic": "Dynamic",
"Edit Application": "Modifier l'application",
"Enable Email linking": "Autoriser la liaison de courrier électronique",
"Enable Email linking - Tooltip": "Lorsque l'on utilise des fournisseurs tiers pour se connecter, s'il y a un utilisateur dans l'organisation avec la même adresse e-mail, la méthode de connexion tierce sera automatiquement associée à cet utilisateur",
@ -324,7 +325,8 @@
"Server port": "Port du serveur",
"Server port - Tooltip": "Port du serveur LDAP",
"The Auto Sync option will sync all users to specify organization": "L'option de synchronisation automatique synchronisera tous les utilisateurs vers l'organisation spécifiée",
"UidNumber / Uid": "NuméroUID / UID"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "Connexion automatique",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "Employeur, tel que le nom de l'entreprise ou de l'organisation",
"Bio": "Bio",
"Bio - Tooltip": "Présentation de l'utilisateur",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "La vérification Captcha a échoué",
"Captcha Verify Success": "Succès de vérification de Captcha",
"Country code": "Code pays",
"Country/Region": "Pays/Région",
"Country/Region - Tooltip": "Pays ou région",
"Edit User": "Modifier l'utilisateur",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "L'e-mail ne peut pas être vide",
"Email/phone reset successfully": "Réinitialisation de l'email/du téléphone réussie",
"Empty input!": "Entrée vide !",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Page d'accueil de l'utilisateur",
"Homepage - Tooltip": "Adresse URL de la page d'accueil de l'utilisateur",
"ID card": "carte d'identité",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "Entrez votre adresse e-mail",
"Input your phone number": "Saisissez votre numéro de téléphone",
"Is admin": "Est l'administrateur",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "Les utilisateurs interdits ne peuvent plus se connecter",
"Is global admin": "Est l'administrateur global",
"Is global admin - Tooltip": "Est un administrateur de Casdoor",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "Clés",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "Lien",
"Location": "Location",
"Location - Tooltip": "Ville de résidence",
@ -778,9 +794,13 @@
"Please select avatar from resources": "Veuillez sélectionner un avatar à partir des ressources",
"Properties": "Propriétés",
"Properties - Tooltip": "Propriétés de l'utilisateur",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "Entrer de nouveau dans le nouveau",
"Reset Email...": "Réinitialisation de l'e-mail...",
"Reset Phone...": "Réinitialiser le téléphone...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "Sélectionnez une photo...",
"Set Password": "Définir un mot de passe",
"Set new profile picture": "Changer la photo de profil",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "Salin URL halaman prompt",
"Copy signin page URL": "Salin URL halaman masuk",
"Copy signup page URL": "Salin URL halaman pendaftaran",
"Dynamic": "Dynamic",
"Edit Application": "Mengedit aplikasi",
"Enable Email linking": "Aktifkan pengaitan email",
"Enable Email linking - Tooltip": "Ketika menggunakan penyedia layanan pihak ketiga untuk masuk, jika ada pengguna di organisasi dengan email yang sama, metode login pihak ketiga akan secara otomatis terhubung dengan pengguna tersebut",
@ -324,7 +325,8 @@
"Server port": "Port server",
"Server port - Tooltip": "Port server LDAP",
"The Auto Sync option will sync all users to specify organization": "Opsi Auto Sync akan menyinkronkan semua pengguna ke organisasi tertentu",
"UidNumber / Uid": "NomorUID / UID"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "Masuk otomatis",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "Pemberi Kerja, seperti nama perusahaan atau nama organisasi",
"Bio": "Bio: Biografi",
"Bio - Tooltip": "Pengenalan diri dari pengguna",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "Gagal memverifikasi Captcha",
"Captcha Verify Success": "Captcha Verifikasi Berhasil",
"Country code": "Kode negara",
"Country/Region": "Negara/daerah",
"Country/Region - Tooltip": "Negara atau wilayah",
"Edit User": "Edit Pengguna",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "Email tidak boleh kosong",
"Email/phone reset successfully": "Email/telepon berhasil diatur ulang",
"Empty input!": "Masukan kosong!",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",
"Homepage - Tooltip": "URL halaman depan pengguna",
"ID card": "Kartu identitas",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "Masukkan alamat email Anda",
"Input your phone number": "Masukkan nomor telepon Anda",
"Is admin": "Apakah admin?",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "User yang dilarang tidak dapat masuk lagi",
"Is global admin": "Apakah global admin",
"Is global admin - Tooltip": "Adalah seorang administrator Casdoor",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "Kunci",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "Tautan",
"Location": "Lokasi",
"Location - Tooltip": "Kota tempat tinggal",
@ -778,9 +794,13 @@
"Please select avatar from resources": "Silakan pilih avatar dari sumber daya",
"Properties": "Properti",
"Properties - Tooltip": "Properti dari pengguna",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "Masukkan kembali baru",
"Reset Email...": "Atur Ulang Email...",
"Reset Phone...": "Atur Ulang Telepon...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "Pilih foto...",
"Set Password": "Atur Kata Sandi",
"Set new profile picture": "Mengatur gambar profil baru",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "プロンプトページのURLをコピーしてください",
"Copy signin page URL": "サインインページのURLをコピーしてください",
"Copy signup page URL": "サインアップページのURLをコピーしてください",
"Dynamic": "Dynamic",
"Edit Application": "アプリケーションを編集する",
"Enable Email linking": "イーメールリンクの有効化",
"Enable Email linking - Tooltip": "組織内に同じメールアドレスを持つユーザーがいる場合、サードパーティのログイン方法は自動的にそのユーザーに関連付けられます",
@ -324,7 +325,8 @@
"Server port": "サーバーポート",
"Server port - Tooltip": "LDAPサーバーポート",
"The Auto Sync option will sync all users to specify organization": "オート同期オプションは、特定の組織に全ユーザーを同期します",
"UidNumber / Uid": "Uid番号 / Uid"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "自動サインイン",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "企業名や団体名などの雇用主",
"Bio": "バイオ技術",
"Bio - Tooltip": "ユーザーの自己紹介\n\n私は○○です。私は○○国、都市、職業など出身で、現在は○○国、都市、職業などに住んでいます。私は○○趣味、特技、興味などが好きで、空き時間にはよくそれをしています。よろしくお願いします",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "キャプチャ検証に失敗しました",
"Captcha Verify Success": "キャプチャを確認しました。成功しました",
"Country code": "国番号",
"Country/Region": "国/地域",
"Country/Region - Tooltip": "国または地域",
"Edit User": "ユーザーの編集",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "電子メールは空にできません",
"Email/phone reset successfully": "メール/電話のリセットが成功しました",
"Empty input!": "空の入力!",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "ユーザーのホームページ",
"Homepage - Tooltip": "ユーザーのホームページのURL",
"ID card": "IDカード",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "あなたのメールアドレスを入力してください",
"Input your phone number": "電話番号を入力してください",
"Is admin": "管理者ですか?",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "禁止されたユーザーはこれ以上ログインできません",
"Is global admin": "グローバル管理者です",
"Is global admin - Tooltip": "Casdoorの管理者です",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "鍵",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "リンク",
"Location": "場所",
"Location - Tooltip": "居住都市",
@ -778,9 +794,13 @@
"Please select avatar from resources": "リソースからアバターを選択してください",
"Properties": "特性",
"Properties - Tooltip": "ユーザーのプロパティー",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "新しく入り直す",
"Reset Email...": "リセットメール...",
"Reset Phone...": "リセットします...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "写真を選択してください...",
"Set Password": "パスワードを設定する",
"Set new profile picture": "新しいプロフィール写真を設定する",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "프롬프트 페이지 URL을 복사하세요",
"Copy signin page URL": "사인인 페이지 URL 복사",
"Copy signup page URL": "가입 페이지 URL을 복사하세요",
"Dynamic": "Dynamic",
"Edit Application": "앱 편집하기",
"Enable Email linking": "이메일 링크 사용 가능하도록 설정하기",
"Enable Email linking - Tooltip": "3rd-party 로그인 공급자를 사용할 때, 만약 조직 내에 동일한 이메일을 사용하는 사용자가 있다면, 3rd-party 로그인 방법은 자동으로 해당 사용자와 연동됩니다",
@ -324,7 +325,8 @@
"Server port": "서버 포트",
"Server port - Tooltip": "LDAP 서버 포트",
"The Auto Sync option will sync all users to specify organization": "오토 동기화 옵션은 모든 사용자를 지정된 조직에 동기화합니다",
"UidNumber / Uid": "식별자 번호 / 식별자"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "자동 로그인",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "고용주, 회사명 또는 조직명",
"Bio": "바이오",
"Bio - Tooltip": "사용자의 자기소개\n\n안녕하세요, 저는 [이름]입니다. 한국을 포함한 여러 나라에서 살아본 적이 있습니다. 저는 [직업/전공]을 공부하고 있으며 [취미/관심사]에 대해 깊게 알고 있습니다. 이 채팅 서비스를 사용하여 새로운 사람들과 함께 대화를 나누기를 원합니다. 감사합니다",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "캡차 검증 실패",
"Captcha Verify Success": "캡차 검증 성공",
"Country code": "국가 코드",
"Country/Region": "국가 / 지역",
"Country/Region - Tooltip": "국가 또는 지역",
"Edit User": "사용자 편집",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "이메일은 비어 있을 수 없습니다",
"Email/phone reset successfully": "이메일/전화 초기화가 성공적으로 완료되었습니다",
"Empty input!": "빈 입력!",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "사용자의 홈페이지",
"Homepage - Tooltip": "사용자의 홈페이지 URL",
"ID card": "ID 카드",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "이메일을 입력하세요",
"Input your phone number": "전화번호를 입력하세요",
"Is admin": "어드민인가요?",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "금지된 사용자는 더 이상 로그인할 수 없습니다",
"Is global admin": "전세계 관리자입니까?",
"Is global admin - Tooltip": "캐스도어의 관리자입니다",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "열쇠",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "링크",
"Location": "장소",
"Location - Tooltip": "거주 도시",
@ -778,9 +794,13 @@
"Please select avatar from resources": "자원에서 아바타를 선택해주세요",
"Properties": "특성",
"Properties - Tooltip": "사용자의 속성",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "재진입 새로운",
"Reset Email...": "이메일 리셋...",
"Reset Phone...": "폰 초기화...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "사진을 선택하세요.",
"Set Password": "비밀번호 설정",
"Set new profile picture": "새로운 프로필 사진을 설정하세요",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "Скопируйте URL страницы предложения",
"Copy signin page URL": "Скопируйте URL-адрес страницы входа",
"Copy signup page URL": "Скопируйте URL страницы регистрации",
"Dynamic": "Dynamic",
"Edit Application": "Изменить приложение",
"Enable Email linking": "Включить связывание электронной почты",
"Enable Email linking - Tooltip": "При использовании сторонних провайдеров для входа, если в организации есть пользователь с такой же электронной почтой, то способ входа через стороннего провайдера автоматически будет связан с этим пользователем",
@ -324,7 +325,8 @@
"Server port": "Порт сервера",
"Server port - Tooltip": "Port сервера LDAP",
"The Auto Sync option will sync all users to specify organization": "Опция \"Авто-синхронизация\" синхронизирует всех пользователей с указанной организацией",
"UidNumber / Uid": "НомерUid / Uid"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "Автоматическая авторизация",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "Работодатель, такой как название компании или организации",
"Bio": "Био",
"Bio - Tooltip": "Само представление пользователя",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "Ошибка верификации Captcha",
"Captcha Verify Success": "Успешно прошли проверку Captcha",
"Country code": "Код страны",
"Country/Region": "Страна/регион",
"Country/Region - Tooltip": "Страна или регион",
"Edit User": "Редактировать пользователь",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "Email не может быть пустым",
"Email/phone reset successfully": "Электронная почта / номер телефона успешно сброшены",
"Empty input!": "Пустой ввод!",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Главная страница пользователя",
"Homepage - Tooltip": "URL домашней страницы пользователя",
"ID card": "ID-карта",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "Введите свой адрес электронной почты",
"Input your phone number": "Введите ваш номер телефона",
"Is admin": "Это администратор",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "Запрещенные пользователи больше не могут выполнять вход в систему",
"Is global admin": "Является глобальным администратором",
"Is global admin - Tooltip": "Является администратором Casdoor",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "Ключи",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "Ссылка",
"Location": "Местоположение",
"Location - Tooltip": "Город проживания",
@ -778,9 +794,13 @@
"Please select avatar from resources": "Пожалуйста, выберите аватар из ресурсов",
"Properties": "Свойства",
"Properties - Tooltip": "Свойства пользователя",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "Войдите снова Новый",
"Reset Email...": "Сбросить электронное письмо...",
"Reset Phone...": "Сбросить телефон...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "Выберите фотографию...",
"Set Password": "Установить пароль",
"Set new profile picture": "Установить новое фото профиля",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "Sao chép URL của trang nhắc nhở",
"Copy signin page URL": "Sao chép URL trang đăng nhập",
"Copy signup page URL": "Sao chép URL trang đăng ký",
"Dynamic": "Dynamic",
"Edit Application": "Chỉnh sửa ứng dụng",
"Enable Email linking": "Cho phép liên kết Email",
"Enable Email linking - Tooltip": "Khi sử dụng nhà cung cấp bên thứ ba để đăng nhập, nếu có người dùng trong tổ chức có cùng địa chỉ Email, phương pháp đăng nhập bên thứ ba sẽ tự động được liên kết với người dùng đó",
@ -324,7 +325,8 @@
"Server port": "Cổng máy chủ",
"Server port - Tooltip": "Cổng máy chủ LDAP",
"The Auto Sync option will sync all users to specify organization": "Tùy chọn Auto Sync sẽ đồng bộ tất cả người dùng vào tổ chức cụ thể",
"UidNumber / Uid": "Số UID / UID"
"synced": "synced",
"unsynced": "unsynced"
},
"login": {
"Auto sign in": "Tự động đăng nhập",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "Nhà tuyển dụng, chẳng hạn như tên công ty hoặc tổ chức",
"Bio": "bản vẻ đời sống",
"Bio - Tooltip": "Tự giới thiệu của người dùng",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "Xác thực Captcha không thành công",
"Captcha Verify Success": "Xác thực Captcha Thành công",
"Country code": "Mã quốc gia",
"Country/Region": "Quốc gia / Vùng miền",
"Country/Region - Tooltip": "Quốc gia hoặc khu vực",
"Edit User": "Chỉnh sửa người dùng",
"Education": "Education",
"Education - Tooltip": "Education - Tooltip",
"Email cannot be empty": "Email không được để trống",
"Email/phone reset successfully": "Đặt lại email/điện thoại thành công",
"Empty input!": "Đầu vào trống!",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Trang chủ của người dùng",
"Homepage - Tooltip": "Địa chỉ URL của trang chủ của người dùng",
"ID card": "Thẻ căn cước dân sự",
"ID card - Tooltip": "ID card - Tooltip",
"ID card type": "ID card type",
"ID card type - Tooltip": "ID card type - Tooltip",
"Input your email": "Nhập địa chỉ email của bạn",
"Input your phone number": "Nhập số điện thoại của bạn",
"Is admin": "Là quản trị viên",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "Người dùng bị cấm không thể đăng nhập nữa",
"Is global admin": "Là quản trị viên toàn cầu",
"Is global admin - Tooltip": "Là một quản trị viên của Casdoor",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "Chìa khóa",
"Language": "Language",
"Language - Tooltip": "Language - Tooltip",
"Link": "Liên kết",
"Location": "Vị trí",
"Location - Tooltip": "Thành phố cư trú",
@ -778,9 +794,13 @@
"Please select avatar from resources": "Vui lòng chọn avatar từ tài nguyên",
"Properties": "Đặc tính",
"Properties - Tooltip": "Các thuộc tính của người dùng",
"Ranking": "Ranking",
"Ranking - Tooltip": "Ranking - Tooltip",
"Re-enter New": "Nhập lại New",
"Reset Email...": "Thiết lập lại Email...",
"Reset Phone...": "Đặt lại điện thoại...",
"Score": "Score",
"Score - Tooltip": "Score - Tooltip",
"Select a photo...": "Chọn một bức ảnh...",
"Set Password": "Đặt mật khẩu",
"Set new profile picture": "Đặt hình đại diện mới",

View File

@ -25,6 +25,7 @@
"Copy prompt page URL": "复制提醒页面URL",
"Copy signin page URL": "复制登录页面URL",
"Copy signup page URL": "复制注册页面URL",
"Dynamic": "动态开启",
"Edit Application": "编辑应用",
"Enable Email linking": "自动关联邮箱相同的账号",
"Enable Email linking - Tooltip": "使用第三方授权登录时,如果组织中存在与授权用户邮箱相同的用户,会自动关联该第三方登录方式到该用户",
@ -324,7 +325,8 @@
"Server port": "端口",
"Server port - Tooltip": "LDAP服务器端口号",
"The Auto Sync option will sync all users to specify organization": "自动同步选项将同步所有用户以指定组织",
"UidNumber / Uid": "Uid号码 / Uid"
"synced": "已同步",
"unsynced": "未同步"
},
"login": {
"Auto sign in": "下次自动登录",
@ -740,18 +742,27 @@
"Affiliation - Tooltip": "工作单位,如公司、组织名称",
"Bio": "自我介绍",
"Bio - Tooltip": "用户的自我介绍",
"Birthday": "Birthday",
"Birthday - Tooltip": "Birthday - Tooltip",
"Captcha Verify Failed": "验证码校验失败",
"Captcha Verify Success": "验证码校验成功",
"Country code": "国家代码",
"Country/Region": "国家/地区",
"Country/Region - Tooltip": "国家或地区",
"Edit User": "编辑用户",
"Education": "教育",
"Education - Tooltip": "教育 - Tooltip",
"Email cannot be empty": "邮箱不能为空",
"Email/phone reset successfully": "邮箱或手机号重置成功",
"Empty input!": "输入为空!",
"Gender": "性别",
"Gender - Tooltip": "性别 - Tooltip",
"Homepage": "个人主页",
"Homepage - Tooltip": "个人主页链接",
"ID card": "身份证号",
"ID card - Tooltip": "身份证号 - Tooltip",
"ID card type": "身份证类型",
"ID card type - Tooltip": "身份证类型 - Tooltip",
"Input your email": "请输入邮箱",
"Input your phone number": "输入手机号",
"Is admin": "是组织管理员",
@ -762,7 +773,12 @@
"Is forbidden - Tooltip": "被禁用的用户无法再登录",
"Is global admin": "是全局管理员",
"Is global admin - Tooltip": "是Casdoor平台的管理员",
"Is online": "Is online",
"Karma": "Karma",
"Karma - Tooltip": "Karma - Tooltip",
"Keys": "键",
"Language": "语言",
"Language - Tooltip": "语言 - Tooltip",
"Link": "绑定",
"Location": "城市",
"Location - Tooltip": "居住地址所在的城市",
@ -778,9 +794,13 @@
"Please select avatar from resources": "从资源中选择...",
"Properties": "属性",
"Properties - Tooltip": "用户的属性",
"Ranking": "排名",
"Ranking - Tooltip": "排名 - Tooltip",
"Re-enter New": "重复新密码",
"Reset Email...": "重置邮箱...",
"Reset Phone...": "重置手机号...",
"Score": "积分",
"Score - Tooltip": "积分 - Tooltip",
"Select a photo...": "选择图片...",
"Set Password": "设置密码",
"Set new profile picture": "设置新头像",

View File

@ -189,6 +189,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "rule", value);
}} >
<Option key="None" value="None">{i18next.t("application:None")}</Option>
<Option key="Dynamic" value="Dynamic">{i18next.t("application:Dynamic")}</Option>
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
</Select>
);

View File

@ -186,6 +186,11 @@ class SignupTable extends React.Component {
{id: "Normal", name: i18next.t("application:Normal")},
{id: "No verification", name: i18next.t("application:No verification")},
];
} else if (record.name === "Phone") {
options = [
{id: "Normal", name: i18next.t("application:Normal")},
{id: "No verification", name: i18next.t("application:No verification")},
];
} else if (record.name === "Agreement") {
options = [
{id: "None", name: i18next.t("application:Only signup")},

View File

@ -37,6 +37,19 @@
rc-util "^5.27.0"
stylis "^4.0.13"
"@ant-design/cssinjs@^1.8.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.8.1.tgz#326682e779f5cd074668391a6698b50342a07d92"
integrity sha512-pOQJV9H9viB6qB9u7hkpKEOIQGx4dd8zjpwzF1v8YNwjffbZTlyUNQYln56gwpFF7SFskpYpnSfgoqTK4sFE/Q==
dependencies:
"@babel/runtime" "^7.11.1"
"@emotion/hash" "^0.8.0"
"@emotion/unitless" "^0.7.5"
classnames "^2.3.1"
csstype "^3.0.10"
rc-util "^5.27.0"
stylis "^4.0.13"
"@ant-design/icons-svg@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a"
@ -1807,6 +1820,11 @@
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.5.0.tgz#6e52b3d1c38d13130101771821e09cdd414a16bc"
integrity sha512-tlJpwF40DEQcfR/QF+wNMVyGMaO9FQp6Z1Wahj4Gk3CJQYHwA2xVG7iKDFdW6zuxZY9XWOpGcfNCTsX4McOsOg==
"@ctrl/tinycolor@^3.6.0":
version "3.6.0"
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz#53fa5fe9c34faee89469e48f91d51a3766108bc8"
integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==
"@cypress/request@^2.88.10":
version "2.88.11"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.11.tgz#5a4c7399bc2d7e7ed56e92ce5acb620c8b187047"
@ -2383,6 +2401,17 @@
rc-trigger "^5.3.4"
rc-util "^5.24.4"
"@rc-component/tour@~1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@rc-component/tour/-/tour-1.6.0.tgz#ef3888c8ccc5d1a2e465d7d2615abd1d2dd51e27"
integrity sha512-b/s7LCb7bW4wxpWfZyNpl7khHUzSyObSlsLaIScRGd+W/v1wFVk8F7gRytl/z8ik9ZSXbLWx9EvexIuHoO/RcQ==
dependencies:
"@babel/runtime" "^7.18.0"
"@rc-component/portal" "^1.0.0-9"
classnames "^2.3.2"
rc-trigger "^5.3.4"
rc-util "^5.24.4"
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@ -3445,7 +3474,60 @@ antd-token-previewer@^1.1.0-22:
tinycolor2 "^1.4.2"
use-debouncy "^4.3.0"
antd@5.1.6, antd@^5:
antd@5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.2.3.tgz#049bdd5f654f2fd42584c9c0eb71152df9da6e3c"
integrity sha512-mUSVH4ZhzC8h3eLNyL7PWquKUmvDcWAVRZYi060MOw6uiKl0pqsB/FC8OpfwntpBlVYgGMk+y3GB0loqTMSmJA==
dependencies:
"@ant-design/colors" "^7.0.0"
"@ant-design/cssinjs" "^1.5.6"
"@ant-design/icons" "^5.0.0"
"@ant-design/react-slick" "~1.0.0"
"@babel/runtime" "^7.18.3"
"@ctrl/tinycolor" "^3.6.0"
"@rc-component/mutate-observer" "^1.0.0"
"@rc-component/tour" "~1.6.0"
classnames "^2.2.6"
copy-to-clipboard "^3.2.0"
dayjs "^1.11.1"
qrcode.react "^3.1.0"
rc-cascader "~3.8.0"
rc-checkbox "~2.3.0"
rc-collapse "~3.5.2"
rc-dialog "~9.0.2"
rc-drawer "~6.1.1"
rc-dropdown "~4.0.0"
rc-field-form "~1.27.0"
rc-image "~5.13.0"
rc-input "~0.2.1"
rc-input-number "~7.4.0"
rc-mentions "~2.0.0"
rc-menu "~9.8.2"
rc-motion "^2.6.1"
rc-notification "~5.0.0"
rc-pagination "~3.2.0"
rc-picker "~3.1.1"
rc-progress "~3.4.1"
rc-rate "~2.9.0"
rc-resize-observer "^1.2.0"
rc-segmented "~2.1.2"
rc-select "~14.2.0"
rc-slider "~10.1.0"
rc-steps "~6.0.0"
rc-switch "~4.0.0"
rc-table "~7.30.2"
rc-tabs "~12.5.6"
rc-textarea "~1.0.0"
rc-tooltip "~5.3.1"
rc-tree "~5.7.0"
rc-tree-select "~5.6.0"
rc-trigger "^5.3.4"
rc-upload "~4.3.0"
rc-util "^5.27.0"
scroll-into-view-if-needed "^3.0.3"
throttle-debounce "^5.0.0"
antd@^5:
version "5.1.6"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.1.6.tgz#9ac912279f9f8e571674b3220b668897b4ffb0b3"
integrity sha512-9bn2B4rZ1c7IXtn5U95aNGJXmLOZyL1V5TiGq3pk3S3bGD13BA2eynSI//e3c2FAslZlqQlQEGV+NHms9ygAVA==
@ -8713,11 +8795,6 @@ pascal-case@^3.1.2:
no-case "^3.0.4"
tslib "^2.0.3"
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
@ -9650,6 +9727,16 @@ rc-collapse@~3.4.2:
rc-util "^5.2.1"
shallowequal "^1.1.0"
rc-collapse@~3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.5.2.tgz#abb7d144ad55bd9cbd201fa95bc5b271da2aa7c3"
integrity sha512-/TNiT3DW1t3sUCiVD/DPUYooJZ3BLA93/2rZsB3eM2bGJCCla2X9D2E4tgm7LGMQGy5Atb2lMUn2FQuvQNvavQ==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
rc-motion "^2.3.4"
rc-util "^5.27.0"
rc-dialog@~9.0.0, rc-dialog@~9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-9.0.2.tgz#aadfebdeba145f256c1fac9b9f509f893cdbb5b8"
@ -9713,6 +9800,15 @@ rc-input-number@~7.4.0:
classnames "^2.2.5"
rc-util "^5.23.0"
rc-input@^0.2.1, rc-input@^0.2.2, rc-input@~0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-0.2.2.tgz#de1c3c0e090d15777a6b1a3fd4a9566e25b3fbee"
integrity sha512-xgkVcFgtRO0Hl9hmvslZhObNyxbSpTmy3nR1Tk4XrjjZ9lFJ7GcJBy6ss30Pdb0oX36cHzLN8I7VCjBGeRNB9A==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-util "^5.18.1"
rc-input@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-0.1.4.tgz#45cb4ba209ae6cc835a2acb8629d4f8f0cb347e0"
@ -9722,6 +9818,15 @@ rc-input@~0.1.4:
classnames "^2.2.1"
rc-util "^5.18.1"
rc-input@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-1.0.4.tgz#2f2c73c884f41e80685bb2eb7b9d5533e8540a77"
integrity sha512-clY4oneVHRtKHYf/HCxT/MO+4BGzCIywSNLosXWOm7fcQAS0jQW7n0an8Raa8JMB8kpxc8m28p7SNwFZmlMj6g==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-util "^5.18.1"
rc-mentions@~1.13.1:
version "1.13.1"
resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-1.13.1.tgz#c884b70e1505a197f1b32a7c6b39090db6992a72"
@ -9734,6 +9839,19 @@ rc-mentions@~1.13.1:
rc-trigger "^5.0.4"
rc-util "^5.22.5"
rc-mentions@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-2.0.0.tgz#688846d698943340334657d96c1448d8b57e5666"
integrity sha512-58NSeM6R5MrgYAhR2TH27JgAN7ivp3iBTmty3q6gvrrGHelPMdGxpJ5aH7AIlodCrPWLAm1lT4XoiuI4s9snXA==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.6"
rc-input "^0.2.2"
rc-menu "~9.8.0"
rc-textarea "^1.0.0"
rc-trigger "^5.0.4"
rc-util "^5.22.5"
rc-menu@~9.8.0:
version "9.8.0"
resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.8.0.tgz#d3d820f52ec37e27c5aec42b3cc68bbcfcaba1f7"
@ -9864,6 +9982,16 @@ rc-segmented@~2.1.0:
rc-motion "^2.4.4"
rc-util "^5.17.0"
rc-segmented@~2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/rc-segmented/-/rc-segmented-2.1.2.tgz#14c9077a1dae9c2ccb2ef5fbc5662c1c48c7ce8e"
integrity sha512-qGo1bCr83ESXpXVOCXjFe1QJlCAQXyi9KCiy8eX3rIMYlTeJr/ftySIaTnYsitL18SvWf5ZEHsfqIWoX0EMfFQ==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-motion "^2.4.4"
rc-util "^5.17.0"
rc-select@~14.2.0:
version "14.2.0"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.2.0.tgz#60e83a7d5a5dae7fd9e3918d9b209074ea2c92d4"
@ -9888,6 +10016,15 @@ rc-slider@~10.0.0:
rc-util "^5.18.1"
shallowequal "^1.1.0"
rc-slider@~10.1.0:
version "10.1.1"
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.1.1.tgz#5e82036e60b61021aba3ea0e353744dd7c74e104"
integrity sha512-gn8oXazZISEhnmRinI89Z/JD/joAaM35jp+gDtIVSTD/JJMCCBqThqLk1SVJmvtfeiEF/kKaFY0+qt4SDHFUDw==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.5"
rc-util "^5.27.0"
rc-steps@~6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-6.0.0.tgz#f7148f8097d5d135f19b96c1b4f4b50ad6093753"
@ -9930,6 +10067,19 @@ rc-tabs@~12.5.1:
rc-resize-observer "^1.0.0"
rc-util "^5.16.0"
rc-tabs@~12.5.6:
version "12.5.10"
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.5.10.tgz#0e41c723fac66c4f0bcad3271429fff6653b0721"
integrity sha512-Ay0l0jtd4eXepFH9vWBvinBjqOpqzcsJTerBGwJy435P2S90Uu38q8U/mvc1sxUEVOXX5ZCFbxcWPnfG3dH+tQ==
dependencies:
"@babel/runtime" "^7.11.2"
classnames "2.x"
rc-dropdown "~4.0.0"
rc-menu "~9.8.0"
rc-motion "^2.6.2"
rc-resize-observer "^1.0.0"
rc-util "^5.16.0"
rc-textarea@^0.4.0, rc-textarea@~0.4.5:
version "0.4.7"
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-0.4.7.tgz#627f662d46f99e0059d1c1ebc8db40c65339fe90"
@ -9941,6 +10091,28 @@ rc-textarea@^0.4.0, rc-textarea@~0.4.5:
rc-util "^5.24.4"
shallowequal "^1.1.0"
rc-textarea@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.2.2.tgz#111fa90fcedba6d244bc94615b7971b8d8f68815"
integrity sha512-S9fkiek5VezfwJe2McEs/NH63xgnnZ4iDh6a8n01mIfzyNJj0HkS0Uz6boyR3/eONYjmKaqhrpuJJuEClRDEBw==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.1"
rc-input "~1.0.4"
rc-resize-observer "^1.0.0"
rc-util "^5.27.0"
rc-textarea@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.0.1.tgz#44b1e09eda37a7af19cf884251e530cc7b03050b"
integrity sha512-dtIm96apjJpCUcCeTtbnLGJaVlqbOqVgN0P9z+bqMSi7rcV5QVeUtBnG+jQTGk/uD183Z7jbhc8Dx7G3luDCwg==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.1"
rc-input "^0.2.1"
rc-resize-observer "^1.0.0"
rc-util "^5.27.0"
rc-tooltip@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.1.1.tgz#94178ed162d0252bc4993b725f5dc2ac0fccf154"
@ -9958,6 +10130,15 @@ rc-tooltip@~5.2.0:
classnames "^2.3.1"
rc-trigger "^5.0.0"
rc-tooltip@~5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.3.1.tgz#3dde4e1865f79cd23f202bba4e585c2a1173024b"
integrity sha512-e6H0dMD38EPaSPD2XC8dRfct27VvT2TkPdoBSuNl3RRZ5tspiY/c5xYEmGC0IrABvMBgque4Mr2SMZuliCvoiQ==
dependencies:
"@babel/runtime" "^7.11.2"
classnames "^2.3.1"
rc-trigger "^5.3.1"
rc-tree-select@~5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-5.6.0.tgz#f34147f4c14341430bcece481804496d0abd3371"