mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-14 08:03:23 +08:00
Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
e21087aa50 | |||
b38f2218a3 | |||
afd3c4ed25 | |||
5caceb4ae2 | |||
f5672357e6 | |||
181e7c8c7d | |||
36c5a9d09b | |||
9acb3c499e | |||
0e9a3b0f30 | |||
d104a292e7 | |||
8fbd5b1a74 | |||
f5a05ac534 | |||
05fade1d05 | |||
8aefa02036 | |||
3b6ec3e7c4 | |||
910816c7a3 | |||
412a8b5da7 | |||
8ebd16a14e | |||
44ec854465 | |||
26e87b0d98 | |||
7e0ea0b8d9 | |||
ace8e9da06 | |||
aac8714d72 | |||
e71e41b343 | |||
6131286cbd | |||
3bda8fb9dc | |||
11f55a474c | |||
4806e76cf6 | |||
edbd3d4018 | |||
3f0a741e6c | |||
d273fdd670 | |||
3ae81716b9 | |||
3a70f4e788 | |||
842d4865b2 | |||
19fb7273bb | |||
943bd82731 | |||
f2f962b893 | |||
eb72c9f273 | |||
4605938f8e | |||
14fa914e6f | |||
e877045671 | |||
29f1ec08a2 | |||
389744a27d | |||
dc7b66822d | |||
efacf8226c |
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@ -97,21 +97,20 @@ jobs:
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
- name: back start
|
||||
run: nohup go run ./main.go &
|
||||
working-directory: ./
|
||||
- name: front install
|
||||
run: yarn install
|
||||
working-directory: ./web
|
||||
- name: front start
|
||||
run: nohup yarn start &
|
||||
working-directory: ./web
|
||||
- name: back start
|
||||
run: nohup go run ./main.go &
|
||||
working-directory: ./
|
||||
- name: Sleep for starting
|
||||
run: sleep 90s
|
||||
shell: bash
|
||||
- name: e2e
|
||||
run: npx cypress run --spec "**/e2e/**.cy.js"
|
||||
working-directory: ./web
|
||||
- uses: cypress-io/github-action@v4
|
||||
with:
|
||||
working-directory: ./web
|
||||
wait-on: 'http://localhost:7001'
|
||||
wait-on-timeout: 180
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
|
@ -80,7 +80,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
|
||||
p, built-in, *, *, *, *, *
|
||||
p, app, *, *, *, *, *
|
||||
p, *, *, POST, /api/signup, *, *
|
||||
p, *, *, POST, /api/get-email-and-phone, *, *
|
||||
p, *, *, GET, /api/get-email-and-phone, *, *
|
||||
p, *, *, POST, /api/login, *, *
|
||||
p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
|
@ -21,19 +21,21 @@ type CaptchaProvider interface {
|
||||
}
|
||||
|
||||
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
||||
if captchaType == "Default" {
|
||||
switch captchaType {
|
||||
case "Default":
|
||||
return NewDefaultCaptchaProvider()
|
||||
} else if captchaType == "reCAPTCHA" {
|
||||
case "reCAPTCHA":
|
||||
return NewReCaptchaProvider()
|
||||
} else if captchaType == "hCaptcha" {
|
||||
return NewHCaptchaProvider()
|
||||
} else if captchaType == "Aliyun Captcha" {
|
||||
case "Aliyun Captcha":
|
||||
return NewAliyunCaptchaProvider()
|
||||
} else if captchaType == "GEETEST" {
|
||||
case "hCaptcha":
|
||||
return NewHCaptchaProvider()
|
||||
case "GEETEST":
|
||||
return NewGEETESTCaptchaProvider()
|
||||
} else if captchaType == "Cloudflare Turnstile" {
|
||||
case "Cloudflare Turnstile":
|
||||
return NewCloudflareTurnstileProvider()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -20,5 +20,5 @@ staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
ldapServerPort = 389
|
||||
languages = en,zh,es,fr,de,ja,ko,ru
|
||||
languages = en,zh,es,fr,de,ja,ko,ru,vi
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
|
17
conf/conf.go
17
conf/conf.go
@ -105,6 +105,23 @@ func GetConfigDataSourceName() string {
|
||||
return dataSourceName
|
||||
}
|
||||
|
||||
func GetLanguage(language string) string {
|
||||
if language == "" || language == "*" {
|
||||
return "en"
|
||||
}
|
||||
|
||||
if len(language) < 2 {
|
||||
return "en"
|
||||
}
|
||||
|
||||
language = language[0:2]
|
||||
if strings.Contains(GetConfigString("languages"), language) {
|
||||
return language
|
||||
} else {
|
||||
return "en"
|
||||
}
|
||||
}
|
||||
|
||||
func IsDemoMode() bool {
|
||||
return strings.ToLower(GetConfigString("isDemoMode")) == "true"
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ type RequestForm struct {
|
||||
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
PhonePrefix string `json:"phonePrefix"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
|
||||
@ -121,7 +121,7 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.Affiliation, c.GetAcceptLanguage())
|
||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.CountryCode, form.Affiliation, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
@ -137,7 +137,7 @@ func (c *ApiController) Signup() {
|
||||
|
||||
var checkPhone string
|
||||
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
|
||||
checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone)
|
||||
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(c.T("account:Phone: %s"), checkResult)
|
||||
@ -179,6 +179,7 @@ func (c *ApiController) Signup() {
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: form.Email,
|
||||
Phone: form.Phone,
|
||||
CountryCode: form.CountryCode,
|
||||
Address: []string{},
|
||||
Affiliation: form.Affiliation,
|
||||
IdCard: form.IdCard,
|
||||
@ -255,7 +256,9 @@ func (c *ApiController) Logout() {
|
||||
if accessToken == "" && redirectUri == "" {
|
||||
c.ClearUserSession()
|
||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||
object.DeleteSessionId(util.GetSessionId(object.CasdoorOrganization, object.CasdoorApplication, user), c.Ctx.Input.CruSession.SessionID())
|
||||
owner, username := util.GetOwnerAndNameFromId(user)
|
||||
|
||||
object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
application := c.GetSessionApplication()
|
||||
|
@ -263,34 +263,32 @@ func (c *ApiController) Login() {
|
||||
checkDest = form.Username
|
||||
} else {
|
||||
verificationCodeType = "phone"
|
||||
if len(form.PhonePrefix) == 0 {
|
||||
responseText := fmt.Sprintf(c.T("auth:%s No phone prefix"), verificationCodeType)
|
||||
c.ResponseError(responseText)
|
||||
return
|
||||
}
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||
form.Username = user.Phone
|
||||
}
|
||||
checkDest = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
|
||||
}
|
||||
user = object.GetUserByFields(form.Organization, form.Username)
|
||||
if user == nil {
|
||||
|
||||
if user = object.GetUserByFields(form.Organization, form.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
|
||||
return
|
||||
}
|
||||
if verificationCodeType == "phone" {
|
||||
form.CountryCode = user.GetCountryCode(form.CountryCode)
|
||||
var ok bool
|
||||
if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), form.CountryCode))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
checkResult = object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
|
||||
if len(checkResult) != 0 {
|
||||
responseText := fmt.Sprintf("%s - %s", verificationCodeType, checkResult)
|
||||
c.ResponseError(responseText)
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
||||
return
|
||||
}
|
||||
|
||||
// disable the verification code
|
||||
if strings.Contains(form.Username, "@") {
|
||||
object.DisableVerificationCode(form.Username)
|
||||
} else {
|
||||
object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username))
|
||||
}
|
||||
object.DisableVerificationCode(checkDest)
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
if application == nil {
|
||||
|
@ -51,7 +51,7 @@ type LdapSyncResp struct {
|
||||
func (c *ApiController) GetLdapUser() {
|
||||
ldapServer := LdapServer{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
|
||||
if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
||||
if err != nil || util.IsStringsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
@ -104,7 +104,7 @@ func (c *ApiController) GetLdapUser() {
|
||||
// GetLdaps
|
||||
// @Tag Account API
|
||||
// @Title GetLdaps
|
||||
// @router /get-ldaps [post]
|
||||
// @router /get-ldaps [get]
|
||||
func (c *ApiController) GetLdaps() {
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
@ -115,11 +115,11 @@ func (c *ApiController) GetLdaps() {
|
||||
// GetLdap
|
||||
// @Tag Account API
|
||||
// @Title GetLdap
|
||||
// @router /get-ldap [post]
|
||||
// @router /get-ldap [get]
|
||||
func (c *ApiController) GetLdap() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
if util.IsStrsEmpty(id) {
|
||||
if util.IsStringsEmpty(id) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
@ -140,7 +140,7 @@ func (c *ApiController) AddLdap() {
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
@ -170,7 +170,7 @@ func (c *ApiController) AddLdap() {
|
||||
func (c *ApiController) UpdateLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ func (c *ApiController) SendEmail() {
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
||||
if util.IsStringsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
||||
c.ResponseError(fmt.Sprintf(c.T("service:Empty parameters for emailForm: %v"), emailForm))
|
||||
return
|
||||
}
|
||||
@ -130,13 +130,13 @@ func (c *ApiController) SendSms() {
|
||||
return
|
||||
}
|
||||
|
||||
org := object.GetOrganization(smsForm.OrgId)
|
||||
var invalidReceivers []string
|
||||
for idx, receiver := range smsForm.Receivers {
|
||||
if !util.IsPhoneCnValid(receiver) {
|
||||
// The receiver phone format: E164 like +8613854673829 +441932567890
|
||||
if !util.IsPhoneValid(receiver, "") {
|
||||
invalidReceivers = append(invalidReceivers, receiver)
|
||||
} else {
|
||||
smsForm.Receivers[idx] = fmt.Sprintf("+%s%s", org.PhonePrefix, receiver)
|
||||
smsForm.Receivers[idx] = receiver
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,24 +231,20 @@ func (c *ApiController) DeleteUser() {
|
||||
// @Param username formData string true "The username of the user"
|
||||
// @Param organization formData string true "The organization of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-email-and-phone [post]
|
||||
// @router /get-email-and-phone [get]
|
||||
func (c *ApiController) GetEmailAndPhone() {
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
organization := c.Ctx.Request.Form.Get("organization")
|
||||
username := c.Ctx.Request.Form.Get("username")
|
||||
|
||||
user := object.GetUserByFields(form.Organization, form.Username)
|
||||
user := object.GetUserByFields(organization, username)
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(organization, username)))
|
||||
return
|
||||
}
|
||||
|
||||
respUser := object.User{Name: user.Name}
|
||||
var contentType string
|
||||
switch form.Username {
|
||||
switch username {
|
||||
case user.Email:
|
||||
contentType = "email"
|
||||
respUser.Email = user.Email
|
||||
@ -281,7 +277,7 @@ func (c *ApiController) SetPassword() {
|
||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||
|
||||
requestUserId := c.GetSessionUsername()
|
||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||
userId := util.GetId(userOwner, userName)
|
||||
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
@ -311,8 +307,7 @@ func (c *ApiController) SetPassword() {
|
||||
|
||||
targetUser.Password = newPassword
|
||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ServeJSON()
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// CheckUserPassword
|
||||
|
@ -62,6 +62,10 @@ func checkPermissionForUpdateUser(userId string, newUser object.User, c *ApiCont
|
||||
item := object.GetAccountItemByName("Phone", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.CountryCode != newUser.CountryCode {
|
||||
item := object.GetAccountItemByName("Country code", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Region != newUser.Region {
|
||||
item := object.GetAccountItemByName("Country/Region", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
|
@ -17,7 +17,6 @@ package controllers
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
@ -56,11 +55,8 @@ func (c *ApiController) T(error string) string {
|
||||
|
||||
// GetAcceptLanguage ...
|
||||
func (c *ApiController) GetAcceptLanguage() string {
|
||||
lang := c.Ctx.Request.Header.Get("Accept-Language")
|
||||
if lang == "" || !strings.Contains(conf.GetConfigString("languages"), lang[0:2]) {
|
||||
lang = "en"
|
||||
}
|
||||
return lang[0:2]
|
||||
language := c.Ctx.Request.Header.Get("Accept-Language")
|
||||
return conf.GetLanguage(language)
|
||||
}
|
||||
|
||||
// SetTokenErrorHttpStatus ...
|
||||
|
@ -24,6 +24,13 @@ import (
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
SignupVerification = "signup"
|
||||
ResetVerification = "reset"
|
||||
LoginVerification = "login"
|
||||
ForgetVerification = "forget"
|
||||
)
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUsername()
|
||||
@ -42,18 +49,15 @@ func (c *ApiController) getCurrentUser() *object.User {
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
countryCode := c.Ctx.Request.Form.Get("countryCode")
|
||||
checkType := c.Ctx.Request.Form.Get("checkType")
|
||||
checkId := c.Ctx.Request.Form.Get("checkId")
|
||||
checkKey := c.Ctx.Request.Form.Get("checkKey")
|
||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||
applicationId := c.Ctx.Request.Form.Get("applicationId")
|
||||
method := c.Ctx.Request.Form.Get("method")
|
||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||
|
||||
if destType == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": type.")
|
||||
return
|
||||
}
|
||||
if dest == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": dest.")
|
||||
return
|
||||
@ -62,98 +66,104 @@ func (c *ApiController) SendVerificationCode() {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": applicationId.")
|
||||
return
|
||||
}
|
||||
if !strings.Contains(applicationId, "/") {
|
||||
c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.")
|
||||
return
|
||||
}
|
||||
if checkType == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": checkType.")
|
||||
return
|
||||
}
|
||||
if !strings.Contains(applicationId, "/") {
|
||||
c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.")
|
||||
return
|
||||
}
|
||||
|
||||
captchaProvider := captcha.GetCaptchaProvider(checkType)
|
||||
|
||||
if captchaProvider != nil {
|
||||
if checkType != "none" {
|
||||
if checkKey == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": checkKey.")
|
||||
return
|
||||
}
|
||||
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
|
||||
if err != nil {
|
||||
|
||||
if captchaProvider := captcha.GetCaptchaProvider(checkType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + checkType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !isHuman {
|
||||
} else if !isHuman {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
application := object.GetApplication(applicationId)
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", application.Owner, application.Organization))
|
||||
organization := object.GetOrganization(util.GetId(application.Owner, application.Organization))
|
||||
if organization == nil {
|
||||
c.ResponseError(c.T("verification:Organization does not exist"))
|
||||
return
|
||||
}
|
||||
|
||||
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
var user *object.User
|
||||
// checkUser != "", means method is ForgetVerification
|
||||
if checkUser != "" {
|
||||
owner := application.Organization
|
||||
user = object.GetUser(util.GetId(owner, checkUser))
|
||||
}
|
||||
|
||||
sendResp := errors.New("invalid dest type")
|
||||
|
||||
if user == nil && checkUser != "" && checkUser != "true" {
|
||||
name := application.Organization
|
||||
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
|
||||
}
|
||||
switch destType {
|
||||
case "email":
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
||||
dest = user.Email
|
||||
}
|
||||
if !util.IsEmailValid(dest) {
|
||||
c.ResponseError(c.T("verification:Email is invalid"))
|
||||
return
|
||||
}
|
||||
|
||||
userByEmail := object.GetUserByEmail(organization.Name, dest)
|
||||
if userByEmail == nil && method != "signup" && method != "reset" {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
if method == LoginVerification || method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
||||
dest = user.Email
|
||||
}
|
||||
|
||||
user = object.GetUserByEmail(organization.Name, dest)
|
||||
if user == nil {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
} else if method == ResetVerification {
|
||||
user = c.getCurrentUser()
|
||||
}
|
||||
|
||||
provider := application.GetEmailProvider()
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||
dest = user.Phone
|
||||
}
|
||||
if !util.IsPhoneCnValid(dest) {
|
||||
c.ResponseError(c.T("verification:Phone number is invalid"))
|
||||
return
|
||||
if method == LoginVerification || method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||
dest = user.Phone
|
||||
}
|
||||
|
||||
if user = object.GetUserByPhone(organization.Name, dest); user == nil {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
|
||||
countryCode = user.GetCountryCode(countryCode)
|
||||
} else if method == ResetVerification {
|
||||
if user = c.getCurrentUser(); user != nil {
|
||||
countryCode = user.GetCountryCode(countryCode)
|
||||
}
|
||||
}
|
||||
|
||||
userByPhone := object.GetUserByPhone(organization.Name, dest)
|
||||
if userByPhone == nil && method != "signup" && method != "reset" {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
|
||||
dest = fmt.Sprintf("+%s%s", organization.PhonePrefix, dest)
|
||||
provider := application.GetSmsProvider()
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
|
||||
if phone, ok := util.GetE164Number(dest, countryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), countryCode))
|
||||
return
|
||||
} else {
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, phone)
|
||||
}
|
||||
}
|
||||
|
||||
if sendResp != nil {
|
||||
c.Data["json"] = Response{Status: "error", Msg: sendResp.Error()}
|
||||
c.ResponseError(sendResp.Error())
|
||||
} else {
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ResetEmailOrPhone ...
|
||||
@ -169,7 +179,8 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
code := c.Ctx.Request.Form.Get("code")
|
||||
if len(dest) == 0 || len(code) == 0 || len(destType) == 0 {
|
||||
|
||||
if util.IsStringsEmpty(destType, dest, code) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
@ -177,7 +188,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
checkDest := dest
|
||||
organization := object.GetOrganizationByUser(user)
|
||||
if destType == "phone" {
|
||||
if object.HasUserByField(user.Owner, "phone", user.Phone) {
|
||||
if object.HasUserByField(user.Owner, "phone", dest) {
|
||||
c.ResponseError(c.T("check:Phone already exists"))
|
||||
return
|
||||
}
|
||||
@ -192,14 +203,12 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
c.ResponseError(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
phonePrefix := "86"
|
||||
if organization != nil && organization.PhonePrefix != "" {
|
||||
phonePrefix = organization.PhonePrefix
|
||||
if checkDest, ok = util.GetE164Number(dest, user.GetCountryCode("")); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
|
||||
return
|
||||
}
|
||||
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
|
||||
} else if destType == "email" {
|
||||
if object.HasUserByField(user.Owner, "email", user.Email) {
|
||||
if object.HasUserByField(user.Owner, "email", dest) {
|
||||
c.ResponseError(c.T("check:Email already exists"))
|
||||
return
|
||||
}
|
||||
@ -215,8 +224,8 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ret := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); len(ret) != 0 {
|
||||
c.ResponseError(ret)
|
||||
if msg := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); len(msg) != 0 {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
@ -233,8 +242,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
}
|
||||
|
||||
object.DisableVerificationCode(checkDest)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ServeJSON()
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// VerifyCaptcha ...
|
||||
|
11
go.mod
11
go.mod
@ -3,13 +3,15 @@ module github.com/casdoor/casdoor
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/squirrel v1.5.3
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.4
|
||||
github.com/beego/beego v1.12.11
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casdoor/go-sms-sender v0.5.1
|
||||
github.com/casdoor/go-sms-sender v0.5.2
|
||||
github.com/casdoor/gomail/v2 v2.0.1
|
||||
github.com/casdoor/oss v1.2.0
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||
@ -18,24 +20,27 @@ require (
|
||||
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
|
||||
github.com/forestmgy/ldapserver v1.1.0
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-mysql-org/go-mysql v1.7.0
|
||||
github.com/go-pay/gopay v1.5.72
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/lestrrat-go/jwx v1.2.21
|
||||
github.com/lib/pq v1.8.0
|
||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||
github.com/markbates/goth v1.75.2
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/gosaml2 v0.6.0
|
||||
github.com/russellhaering/goxmldsig v1.1.1
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
|
87
go.sum
87
go.sum
@ -58,6 +58,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
||||
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc=
|
||||
github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b h1:EgJ6N2S0h1WfFIjU5/VVHWbMSVYXAluop97Qxpr/lfQ=
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b/go.mod h1:3SAoF0F5EbcOuBD5WT9nYkbIJieBS84cUQXADbXeBsU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@ -68,8 +70,9 @@ github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90v
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
|
||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188 h1:8lIVcOlHW+fKCGMEf6nuGTTEFOt/EZJPZ8oq0kTlhGk=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
@ -83,6 +86,8 @@ github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkY
|
||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@ -94,8 +99,8 @@ github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n
|
||||
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeKF0=
|
||||
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casdoor/go-sms-sender v0.5.1 h1:1/Wp1OLkVAVY4lEGQhekSNetSAWhnPcxYPV7xpCZgC0=
|
||||
github.com/casdoor/go-sms-sender v0.5.1/go.mod h1:kBykbqwgRDXbXdMAIxmZKinVM1WjdqEbej5LAbUbcfI=
|
||||
github.com/casdoor/go-sms-sender v0.5.2 h1:LRjqoXBAOsH4yxXeJ7kINMesyoyEx5pEoiJz9d7lGJA=
|
||||
github.com/casdoor/go-sms-sender v0.5.2/go.mod h1:kBykbqwgRDXbXdMAIxmZKinVM1WjdqEbej5LAbUbcfI=
|
||||
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
|
||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
|
||||
@ -118,6 +123,9 @@ github.com/couchbase/gomemcached v0.1.2-0.20201224031647-c432ccf49f32/go.mod h1:
|
||||
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||
github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=
|
||||
github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -159,6 +167,8 @@ github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ
|
||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-mysql-org/go-mysql v1.7.0 h1:qE5FTRb3ZeTQmlk3pjE+/m2ravGxxRDrVDTyDe9tvqI=
|
||||
github.com/go-mysql-org/go-mysql v1.7.0/go.mod h1:9cRWLtuXNKhamUPMkrDVzBhaomGvqLRLtBiyjvjc4pk=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
|
||||
@ -171,8 +181,9 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
|
||||
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
|
||||
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
@ -247,8 +258,9 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@ -276,6 +288,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
@ -304,6 +317,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
||||
@ -319,6 +336,7 @@ github.com/lestrrat-go/jwx v1.2.21/go.mod h1:9cfxnOH7G1gN75CaJP2hKGcxFEx5sPh1abR
|
||||
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
@ -354,6 +372,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/nyaruka/phonenumbers v1.1.5 h1:vYy2DI+z5hdaemqVzXYJ4CVyK92IG484CirEY+40GTo=
|
||||
github.com/nyaruka/phonenumbers v1.1.5/go.mod h1:yShPJHDSH3aTKzCbXyVxNpbl2kA+F+Ne5Pun/MvFRos=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
||||
@ -361,8 +381,19 @@ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||
github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 h1:USx2/E1bX46VG32FIw034Au6seQ2fY9NEILmNh/UlQg=
|
||||
github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ=
|
||||
github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 h1:+FZIDR/D97YOPik4N4lPDaUcLDF/EQPogxtlHB2ZZRM=
|
||||
github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
|
||||
github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7 h1:k2BbABz9+TNpYRwsCCFS8pEEnFVOdbgEjL/kTlLuzZQ=
|
||||
github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7/go.mod h1:8AanEdAHATuRurdGxZXBz0At+9avep+ub7U1AGYLIMM=
|
||||
github.com/pingcap/tidb/parser v0.0.0-20221126021158-6b02a5d8ba7d h1:1DyyRrgYeNjqPkgjrdEsaIbX+kHpuTTk5ZOCtrcRFcQ=
|
||||
github.com/pingcap/tidb/parser v0.0.0-20221126021158-6b02a5d8ba7d/go.mod h1:ElJiub4lRy6UZDb+0JHDkGEdr6aOli+ykhyej7VCLoI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -410,7 +441,14 @@ github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
|
||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||
github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4=
|
||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed h1:KMgQoLJGCq1IoZpLZE3AIffh9veYWoVlsvA4ib55TMM=
|
||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4=
|
||||
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
|
||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
@ -447,6 +485,10 @@ github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/twilio/twilio-go v0.26.0 h1:wFW4oTe3/LKt6bvByP7eio8JsjtaLHjMQKOUEzQry7U=
|
||||
github.com/twilio/twilio-go v0.26.0/go.mod h1:lz62Hopu4vicpQ056H5TJ0JE4AP0rS3sQ35/ejmgOwE=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
|
||||
@ -473,6 +515,19 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4=
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -491,6 +546,7 @@ golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -512,6 +568,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
@ -663,6 +720,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@ -691,6 +750,7 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -792,9 +852,12 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
@ -821,26 +884,38 @@ modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9J
|
||||
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
|
||||
modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA=
|
||||
modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY=
|
||||
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
||||
modernc.org/golex v1.0.1/go.mod h1:QCA53QtsT1NdGkaZZkF5ezFwk4IXh4BGNafAARTC254=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/lex v1.0.0/go.mod h1:G6rxMTy3cH2iA0iXL/HRRv4Znu8MK4higxph/lE7ypk=
|
||||
modernc.org/lexer v1.0.0/go.mod h1:F/Dld0YKYdZCLQ7bD0USbWL4YKCyTDRDHiDTOs0q0vk=
|
||||
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8=
|
||||
modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/parser v1.0.0/go.mod h1:H20AntYJ2cHHL6MHthJ8LZzXCdDCHMWt1KZXtIMjejA=
|
||||
modernc.org/parser v1.0.2/go.mod h1:TXNq3HABP3HMaqLK7brD1fLA/LfN0KS6JxZn71QdDqs=
|
||||
modernc.org/scanner v1.0.1/go.mod h1:OIzD2ZtjYk6yTuyqZr57FmifbM9fIH74SumloSsajuE=
|
||||
modernc.org/sortutil v1.0.0/go.mod h1:1QO0q8IlIlmjBIwm6t/7sof874+xCfZouyqZMLIAtxM=
|
||||
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY=
|
||||
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU=
|
||||
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc=
|
||||
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs=
|
||||
modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/y v1.0.1/go.mod h1:Ho86I+LVHEI+LYXoUKlmOMAM1JTXOCfj8qi1T8PsClE=
|
||||
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||
modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc=
|
||||
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||
|
@ -30,24 +30,26 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
||||
enData := parseEnData("frontend")
|
||||
writeI18nFile("frontend", "en", enData)
|
||||
|
||||
applyToOtherLanguage("frontend", "de", enData)
|
||||
applyToOtherLanguage("frontend", "zh", enData)
|
||||
applyToOtherLanguage("frontend", "es", enData)
|
||||
applyToOtherLanguage("frontend", "fr", enData)
|
||||
applyToOtherLanguage("frontend", "de", enData)
|
||||
applyToOtherLanguage("frontend", "ja", enData)
|
||||
applyToOtherLanguage("frontend", "ko", enData)
|
||||
applyToOtherLanguage("frontend", "ru", enData)
|
||||
applyToOtherLanguage("frontend", "zh", enData)
|
||||
applyToOtherLanguage("frontend", "vi", enData)
|
||||
}
|
||||
|
||||
func TestGenerateI18nBackend(t *testing.T) {
|
||||
enData := parseEnData("backend")
|
||||
writeI18nFile("backend", "en", enData)
|
||||
|
||||
applyToOtherLanguage("backend", "de", enData)
|
||||
applyToOtherLanguage("backend", "zh", enData)
|
||||
applyToOtherLanguage("backend", "es", enData)
|
||||
applyToOtherLanguage("backend", "fr", enData)
|
||||
applyToOtherLanguage("backend", "de", enData)
|
||||
applyToOtherLanguage("backend", "ja", enData)
|
||||
applyToOtherLanguage("backend", "ko", enData)
|
||||
applyToOtherLanguage("backend", "ru", enData)
|
||||
applyToOtherLanguage("backend", "zh", enData)
|
||||
applyToOtherLanguage("backend", "vi", enData)
|
||||
}
|
||||
|
@ -9,7 +9,6 @@
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"%s No phone prefix": "%s No phone prefix",
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
@ -55,15 +54,17 @@
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"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": "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, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist"
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
@ -130,7 +131,7 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
|
@ -9,7 +9,6 @@
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"%s No phone prefix": "%s No phone prefix",
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
@ -57,13 +56,15 @@
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"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, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist"
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
@ -130,7 +131,7 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
|
@ -9,7 +9,6 @@
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"%s No phone prefix": "%s No phone prefix",
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
@ -57,13 +56,15 @@
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"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, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist"
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
@ -130,7 +131,7 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
|
@ -9,7 +9,6 @@
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"%s No phone prefix": "%s No phone prefix",
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
@ -57,13 +56,15 @@
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"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, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist"
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
@ -130,7 +131,7 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
|
@ -9,7 +9,6 @@
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"%s No phone prefix": "%s No phone prefix",
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
@ -57,13 +56,15 @@
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"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, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist"
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
@ -130,7 +131,7 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
|
@ -9,7 +9,6 @@
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"%s No phone prefix": "%s No phone prefix",
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
@ -57,13 +56,15 @@
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"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, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist"
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
@ -130,7 +131,7 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
|
@ -9,7 +9,6 @@
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"%s No phone prefix": "%s No phone prefix",
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
@ -57,13 +56,15 @@
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"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, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist"
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
@ -130,7 +131,7 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
|
147
i18n/locales/vi/data.json
Normal file
147
i18n/locales/vi/data.json
Normal file
@ -0,0 +1,147 @@
|
||||
{
|
||||
"account": {
|
||||
"Email: %s": "Email: %s",
|
||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||
"Invalid information": "Invalid information",
|
||||
"Phone: %s": "Phone: %s",
|
||||
"Please sign out first before signing in": "Please sign out first before signing in",
|
||||
"Please sign out first before signing up": "Please sign out first before signing up",
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
"Invalid token": "Invalid token",
|
||||
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
"Email cannot be empty": "Email cannot be empty",
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Ldap user name or password incorrect": "Ldap user name or password incorrect",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"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, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
},
|
||||
"link": {
|
||||
"Please link first": "Please link first",
|
||||
"This application has no providers": "This application has no providers",
|
||||
"This application has no providers of type": "This application has no providers of type",
|
||||
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||
"The %s is immutable.": "The %s is immutable.",
|
||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Invalid application id",
|
||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||
},
|
||||
"saml": {
|
||||
"Application %s not found": "Application %s not found"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||
"Invalid client_id": "Invalid client_id",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space.",
|
||||
"New password must have at least 6 characters": "New password must have at least 6 characters"
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"verification": {
|
||||
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
"Unknown type": "Unknown type",
|
||||
"Wrong parameter": "Wrong parameter",
|
||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||
},
|
||||
"webauthn": {
|
||||
"Found no credentials for this user": "Found no credentials for this user",
|
||||
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
"The application does not allow to sign up new account": "该应用不允许注册新用户"
|
||||
},
|
||||
"auth": {
|
||||
"%s No phone prefix": "%s 无此手机号前缀",
|
||||
"Challenge method should be S256": "Challenge 方法应该为 S256",
|
||||
"Failed to create user, user information is invalid: %s": "创建用户失败,用户信息无效: %s",
|
||||
"Failed to login in: %s": "登录失败: %s",
|
||||
@ -57,13 +56,15 @@
|
||||
"Username is too long (maximum is 39 characters).": "用户名过长(最大允许长度为39个字符)",
|
||||
"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, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会",
|
||||
"unsupported password type: %s": "不支持的密码类型: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "缺少参数",
|
||||
"Please login first": "请先登录",
|
||||
"The user: %s doesn't exist": "用户: %s 不存在"
|
||||
"The user: %s doesn't exist": "用户: %s 不存在",
|
||||
"don't support captchaProvider: ": "不支持验证码提供商: "
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "LDAP服务器已存在"
|
||||
@ -130,7 +131,7 @@
|
||||
"Email is invalid": "非法的邮箱",
|
||||
"Invalid captcha provider.": "非法的验证码提供商",
|
||||
"Organization does not exist": "组织不存在",
|
||||
"Phone number is invalid": "非法的手机号码",
|
||||
"Phone number is invalid in your region %s": "您所在地区的电话号码无效 %s",
|
||||
"Turing test failed.": "验证码还未发送",
|
||||
"Unable to get the email modify rule.": "无法获取邮箱修改规则",
|
||||
"Unable to get the phone modify rule.": "无法获取手机号修改规则",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"websiteUrl": "",
|
||||
"favicon": "",
|
||||
"passwordType": "",
|
||||
"phonePrefix": "",
|
||||
"countryCodes": [""],
|
||||
"defaultAvatar": "",
|
||||
"tags": [""]
|
||||
}
|
||||
@ -107,6 +107,7 @@
|
||||
"avatar": "",
|
||||
"email": "",
|
||||
"phone": "",
|
||||
"countryCode": "",
|
||||
"address": [],
|
||||
"affiliation": "",
|
||||
"tag": "",
|
||||
|
@ -42,7 +42,7 @@ func init() {
|
||||
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||
}
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, affiliation string, lang string) string {
|
||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, countryCode string, affiliation string, lang string) string {
|
||||
if organization == nil {
|
||||
return i18n.Translate(lang, "check:Organization does not exist")
|
||||
}
|
||||
@ -107,7 +107,9 @@ func CheckUserSignup(application *Application, organization *Organization, usern
|
||||
|
||||
if HasUserByField(organization.Name, "phone", phone) {
|
||||
return i18n.Translate(lang, "check:Phone already exists")
|
||||
} else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) {
|
||||
} else if !util.IsPhoneAllowInRegin(countryCode, organization.CountryCodes) {
|
||||
return i18n.Translate(lang, "check:Your region is not allow to signup by phone")
|
||||
} else if !util.IsPhoneValid(phone, countryCode) {
|
||||
return i18n.Translate(lang, "check:Phone number is invalid")
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,39 @@ func InitDb() {
|
||||
initWebAuthn()
|
||||
}
|
||||
|
||||
func getBuiltInAccountItems() []*AccountItem {
|
||||
return []*AccountItem{
|
||||
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Country code", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
}
|
||||
}
|
||||
|
||||
func initBuiltInOrganization() bool {
|
||||
organization := getOrganization("admin", "built-in")
|
||||
if organization != nil {
|
||||
@ -53,40 +86,12 @@ func initBuiltInOrganization() bool {
|
||||
WebsiteUrl: "https://example.com",
|
||||
Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")),
|
||||
PasswordType: "plain",
|
||||
PhonePrefix: "86",
|
||||
CountryCodes: []string{"CN"},
|
||||
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
||||
Tags: []string{},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru", "vi"},
|
||||
InitScore: 2000,
|
||||
AccountItems: []*AccountItem{
|
||||
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
},
|
||||
AccountItems: getBuiltInAccountItems(),
|
||||
}
|
||||
AddOrganization(organization)
|
||||
return false
|
||||
@ -109,6 +114,7 @@ func initBuiltInUser() {
|
||||
Avatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
||||
Email: "admin@example.com",
|
||||
Phone: "12345678910",
|
||||
CountryCode: "CN",
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
|
@ -179,33 +179,7 @@ func initDefinedOrganization(organization *Organization) {
|
||||
return
|
||||
}
|
||||
organization.CreatedTime = util.GetCurrentTime()
|
||||
organization.AccountItems = []*AccountItem{
|
||||
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
}
|
||||
organization.AccountItems = getBuiltInAccountItems()
|
||||
|
||||
AddOrganization(organization)
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ func GetLdaps(owner string) []*Ldap {
|
||||
}
|
||||
|
||||
func GetLdap(id string) *Ldap {
|
||||
if util.IsStrsEmpty(id) {
|
||||
if util.IsStringsEmpty(id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ type Organization struct {
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
@ -211,6 +211,10 @@ func GetAccountItemByName(name string, organization *Organization) *AccountItem
|
||||
}
|
||||
|
||||
func CheckAccountItemModifyRule(accountItem *AccountItem, user *User, lang string) (bool, string) {
|
||||
if accountItem == nil {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
switch accountItem.ModifyRule {
|
||||
case "Admin":
|
||||
if user == nil || !user.IsAdmin && !user.IsGlobalAdmin {
|
||||
|
@ -43,7 +43,7 @@ type Payment struct {
|
||||
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
Message string `xorm:"varchar(1000)" json:"message"`
|
||||
Message string `xorm:"varchar(2000)" json:"message"`
|
||||
|
||||
PersonName string `xorm:"varchar(100)" json:"personName"`
|
||||
PersonIdCard string `xorm:"varchar(100)" json:"personIdCard"`
|
||||
|
@ -64,7 +64,7 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
|
||||
assertion.CreateAttr("IssueInstant", now)
|
||||
assertion.CreateElement("saml:Issuer").SetText(host)
|
||||
subject := assertion.CreateElement("saml:Subject")
|
||||
subject.CreateElement("saml:NameID").SetText(user.Email)
|
||||
subject.CreateElement("saml:NameID").SetText(user.Name)
|
||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
||||
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
||||
@ -230,16 +230,20 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
// base64 decode
|
||||
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
||||
if err != nil {
|
||||
return "", "", method, fmt.Errorf("err: %s", err.Error())
|
||||
return "", "", method, fmt.Errorf("err: Failed to decode SAML request , %s", err.Error())
|
||||
}
|
||||
|
||||
// decompress
|
||||
var buffer bytes.Buffer
|
||||
rdr := flate.NewReader(bytes.NewReader(defated))
|
||||
io.Copy(&buffer, rdr)
|
||||
_, err = io.Copy(&buffer, rdr)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
var authnRequest saml.AuthnRequest
|
||||
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
||||
if err != nil {
|
||||
return "", "", method, fmt.Errorf("err: %s", err.Error())
|
||||
return "", "", method, fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request. %s", err.Error())
|
||||
}
|
||||
|
||||
// verify samlRequest
|
||||
@ -252,14 +256,15 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
block, _ := pem.Decode([]byte(cert.Certificate))
|
||||
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
|
||||
// redirect Url (Assertion Consumer Url)
|
||||
if application.SamlReplyUrl != "" {
|
||||
method = "POST"
|
||||
authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl
|
||||
} else if authnRequest.AssertionConsumerServiceURL == "" {
|
||||
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
|
||||
}
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
// build signedResponse
|
||||
samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
||||
randomKeyStore := &X509Key{
|
||||
@ -279,7 +284,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
doc.SetRoot(samlResponse)
|
||||
xmlBytes, err := doc.WriteToBytes()
|
||||
if err != nil {
|
||||
return "", "", method, fmt.Errorf("err: %s", err.Error())
|
||||
return "", "", method, fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
|
||||
}
|
||||
|
||||
// compress
|
||||
@ -287,15 +292,21 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
flated := bytes.NewBuffer(nil)
|
||||
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
|
||||
if err != nil {
|
||||
return "", "", method, fmt.Errorf("err: %s", err.Error())
|
||||
return "", "", method, err
|
||||
}
|
||||
_, err = writer.Write(xmlBytes)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
writer.Write(xmlBytes)
|
||||
writer.Close()
|
||||
xmlBytes = flated.Bytes()
|
||||
}
|
||||
// base64 encode
|
||||
res := base64.StdEncoding.EncodeToString(xmlBytes)
|
||||
return res, authnRequest.AssertionConsumerServiceURL, method, nil
|
||||
return res, authnRequest.AssertionConsumerServiceURL, method, err
|
||||
}
|
||||
|
||||
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
||||
@ -359,7 +370,10 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
|
||||
|
||||
data, _ := json.Marshal(user)
|
||||
tmp := map[string]string{}
|
||||
json.Unmarshal(data, &tmp)
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for k, v := range tmp {
|
||||
if v != "" {
|
||||
|
@ -14,7 +14,11 @@
|
||||
|
||||
package object
|
||||
|
||||
import "github.com/casdoor/go-sms-sender"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/go-sms-sender"
|
||||
)
|
||||
|
||||
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||
@ -25,6 +29,12 @@ func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if provider.Type == go_sms_sender.Aliyun {
|
||||
for i, number := range phoneNumbers {
|
||||
phoneNumbers[i] = strings.TrimPrefix(number, "+")
|
||||
}
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
if provider.Type == go_sms_sender.TencentCloud {
|
||||
params["0"] = content
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
hourMinutes = 60
|
||||
hourSeconds = int(time.Hour / time.Second)
|
||||
InvalidRequest = "invalid_request"
|
||||
InvalidClient = "invalid_client"
|
||||
InvalidGrant = "invalid_grant"
|
||||
@ -306,7 +306,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: application.ExpireInHours * hourMinutes,
|
||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
CodeChallenge: challenge,
|
||||
@ -442,7 +442,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: newAccessToken,
|
||||
RefreshToken: newRefreshToken,
|
||||
ExpiresIn: application.ExpireInHours * hourMinutes,
|
||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
}
|
||||
@ -592,7 +592,7 @@ func GetPasswordToken(application *Application, username string, password string
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: application.ExpireInHours * hourMinutes,
|
||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
CodeIsUsed: true,
|
||||
@ -632,7 +632,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
||||
User: nullUser.Name,
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
ExpiresIn: application.ExpireInHours * hourMinutes,
|
||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
CodeIsUsed: true,
|
||||
@ -659,7 +659,7 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: application.ExpireInHours * hourMinutes,
|
||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
CodeIsUsed: true,
|
||||
|
@ -265,8 +265,8 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
||||
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
|
||||
|
||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
|
||||
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||
claims.TokenType = "refresh-token"
|
||||
claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||
claimsWithoutThirdIdp.TokenType = "refresh-token"
|
||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,8 @@ type User struct {
|
||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
@ -448,13 +449,13 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
||||
if len(columns) == 0 {
|
||||
columns = []string{
|
||||
"owner", "display_name", "avatar",
|
||||
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
|
||||
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time",
|
||||
}
|
||||
}
|
||||
if isGlobalAdmin {
|
||||
columns = append(columns, "name", "email", "phone")
|
||||
columns = append(columns, "name", "email", "phone", "country_code")
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
|
||||
|
@ -53,9 +53,11 @@ func GetUserByFields(organization string, field string) *User {
|
||||
}
|
||||
|
||||
// check email
|
||||
user = GetUserByField(organization, "email", field)
|
||||
if user != nil {
|
||||
return user
|
||||
if strings.Contains(field, "@") {
|
||||
user = GetUserByField(organization, "email", field)
|
||||
if user != nil {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
// check phone
|
||||
@ -170,3 +172,18 @@ func ClearUserOAuthProperties(user *User, providerType string) bool {
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (user *User) GetCountryCode(countryCode string) string {
|
||||
if countryCode != "" {
|
||||
return countryCode
|
||||
}
|
||||
|
||||
if user != nil && user.CountryCode != "" {
|
||||
return user.CountryCode
|
||||
}
|
||||
|
||||
if org := GetOrganizationByUser(user); org != nil && len(org.CountryCodes) > 0 {
|
||||
return org.CountryCodes[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -122,11 +122,8 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
|
||||
record.Owner = provider.Owner
|
||||
record.Name = util.GenerateId()
|
||||
record.CreatedTime = util.GetCurrentTime()
|
||||
if user != nil {
|
||||
record.User = user.GetId()
|
||||
}
|
||||
record.Provider = provider.Name
|
||||
|
||||
record.Provider = provider.Name
|
||||
record.Receiver = dest
|
||||
record.Code = code
|
||||
record.Time = time.Now().Unix()
|
||||
|
@ -57,7 +57,7 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// "/page?username=abc&password=123"
|
||||
// "/page?username=built-in/admin&password=123"
|
||||
userId = ctx.Input.Query("username")
|
||||
password := ctx.Input.Query("password")
|
||||
if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
|
||||
|
@ -19,6 +19,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@ -46,8 +48,17 @@ func responseError(ctx *context.Context, error string, data ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func getAcceptLanguage(ctx *context.Context) string {
|
||||
language := ctx.Request.Header.Get("Accept-Language")
|
||||
return conf.GetLanguage(language)
|
||||
}
|
||||
|
||||
func T(ctx *context.Context, error string) string {
|
||||
return i18n.Translate(getAcceptLanguage(ctx), error)
|
||||
}
|
||||
|
||||
func denyRequest(ctx *context.Context) {
|
||||
responseError(ctx, "Unauthorized operation")
|
||||
responseError(ctx, T(ctx, "auth:Unauthorized operation"))
|
||||
}
|
||||
|
||||
func getUsernameByClientIdSecret(ctx *context.Context) string {
|
||||
|
@ -112,15 +112,15 @@ func initAPI() {
|
||||
|
||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")
|
||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||
|
||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||
beego.Router("/api/get-ldap", &controllers.ApiController{}, "POST:GetLdap")
|
||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
|
||||
beego.Router("/api/get-ldap", &controllers.ApiController{}, "GET:GetLdap")
|
||||
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
|
||||
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
|
||||
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
|
||||
|
@ -339,6 +339,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-session": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Session API"
|
||||
],
|
||||
"description": "Add session for one user in one application. If there are other existing sessions, join the session into the list.",
|
||||
"operationId": "ApiController.AddSession",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id(organization/application/user) of session",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "sessionId",
|
||||
"description": "sessionId to be added",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-syncer": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -889,13 +925,13 @@
|
||||
"tags": [
|
||||
"Session API"
|
||||
],
|
||||
"description": "Delete session by userId",
|
||||
"description": "Delete session for one user in one application.",
|
||||
"operationId": "ApiController.DeleteSession",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name )(owner/name) of user.",
|
||||
"description": "The id(organization/application/user) of session",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
@ -1233,7 +1269,7 @@
|
||||
}
|
||||
},
|
||||
"/api/get-email-and-phone": {
|
||||
"post": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"User API"
|
||||
],
|
||||
@ -1306,7 +1342,7 @@
|
||||
}
|
||||
},
|
||||
"/api/get-ldap": {
|
||||
"post": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Account API"
|
||||
],
|
||||
@ -1322,7 +1358,7 @@
|
||||
}
|
||||
},
|
||||
"/api/get-ldaps": {
|
||||
"post": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Account API"
|
||||
],
|
||||
@ -1884,12 +1920,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-session": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Session API"
|
||||
],
|
||||
"description": "Get session for one user in one application.",
|
||||
"operationId": "ApiController.GetSingleSession",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id(organization/application/user) of session",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-sessions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Session API"
|
||||
],
|
||||
"description": "Get organization user sessions",
|
||||
"description": "Get organization user sessions.",
|
||||
"operationId": "ApiController.GetSessions",
|
||||
"parameters": [
|
||||
{
|
||||
@ -2356,6 +2421,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/is-session-duplicated": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Session API"
|
||||
],
|
||||
"description": "Check if there are other different sessions for one user in one application.",
|
||||
"operationId": "ApiController.IsSessionDuplicated",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id(organization/application/user) of session",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "sessionId",
|
||||
"description": "sessionId to be checked",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -3224,6 +3325,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-session": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Session API"
|
||||
],
|
||||
"description": "Update session for one user in one application.",
|
||||
"operationId": "ApiController.UpdateSession",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id(organization/application/user) of session",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-syncer": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -3505,11 +3635,11 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"2346.0xc0001ce990.false": {
|
||||
"2346.0xc000278ab0.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
"2381.0xc0001ce9c0.false": {
|
||||
"2381.0xc000278ae0.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
@ -3566,6 +3696,9 @@
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -3599,9 +3732,6 @@
|
||||
"phoneCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"phonePrefix": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -3636,10 +3766,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/2346.0xc0001ce990.false"
|
||||
"$ref": "#/definitions/2346.0xc000278ab0.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/2381.0xc0001ce9c0.false"
|
||||
"$ref": "#/definitions/2381.0xc000278ae0.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
@ -4091,6 +4221,12 @@
|
||||
"$ref": "#/definitions/object.AccountItem"
|
||||
}
|
||||
},
|
||||
"countryCodes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -4137,9 +4273,6 @@
|
||||
"passwordType": {
|
||||
"type": "string"
|
||||
},
|
||||
"phonePrefix": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -4906,6 +5039,9 @@
|
||||
"cloudfoundry": {
|
||||
"type": "string"
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdIp": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -218,6 +218,30 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-session:
|
||||
post:
|
||||
tags:
|
||||
- Session API
|
||||
description: Add session for one user in one application. If there are other existing sessions, join the session into the list.
|
||||
operationId: ApiController.AddSession
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id(organization/application/user) of session
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: sessionId
|
||||
description: sessionId to be added
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
/api/add-syncer:
|
||||
post:
|
||||
tags:
|
||||
@ -574,12 +598,12 @@ paths:
|
||||
post:
|
||||
tags:
|
||||
- Session API
|
||||
description: Delete session by userId
|
||||
description: Delete session for one user in one application.
|
||||
operationId: ApiController.DeleteSession
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name )(owner/name) of user.
|
||||
description: The id(organization/application/user) of session
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
@ -799,7 +823,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
/api/get-email-and-phone:
|
||||
post:
|
||||
get:
|
||||
tags:
|
||||
- User API
|
||||
description: get email and phone by username
|
||||
@ -847,7 +871,7 @@ paths:
|
||||
items:
|
||||
$ref: '#/definitions/object.User'
|
||||
/api/get-ldap:
|
||||
post:
|
||||
get:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.GetLdap
|
||||
@ -857,7 +881,7 @@ paths:
|
||||
- Account API
|
||||
operationId: ApiController.GetLdapser
|
||||
/api/get-ldaps:
|
||||
post:
|
||||
get:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.GetLdaps
|
||||
@ -1224,11 +1248,30 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Role'
|
||||
/api/get-session:
|
||||
get:
|
||||
tags:
|
||||
- Session API
|
||||
description: Get session for one user in one application.
|
||||
operationId: ApiController.GetSingleSession
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id(organization/application/user) of session
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
/api/get-sessions:
|
||||
get:
|
||||
tags:
|
||||
- Session API
|
||||
description: Get organization user sessions
|
||||
description: Get organization user sessions.
|
||||
operationId: ApiController.GetSessions
|
||||
parameters:
|
||||
- in: query
|
||||
@ -1535,6 +1578,30 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/is-session-duplicated:
|
||||
get:
|
||||
tags:
|
||||
- Session API
|
||||
description: Check if there are other different sessions for one user in one application.
|
||||
operationId: ApiController.IsSessionDuplicated
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id(organization/application/user) of session
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: sessionId
|
||||
description: sessionId to be checked
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
/api/login:
|
||||
post:
|
||||
tags:
|
||||
@ -2110,6 +2177,25 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-session:
|
||||
post:
|
||||
tags:
|
||||
- Session API
|
||||
description: Update session for one user in one application.
|
||||
operationId: ApiController.UpdateSession
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id(organization/application/user) of session
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
/api/update-syncer:
|
||||
post:
|
||||
tags:
|
||||
@ -2293,10 +2379,10 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
definitions:
|
||||
2346.0xc0001ce990.false:
|
||||
2346.0xc000278ab0.false:
|
||||
title: "false"
|
||||
type: object
|
||||
2381.0xc0001ce9c0.false:
|
||||
2381.0xc000278ae0.false:
|
||||
title: "false"
|
||||
type: object
|
||||
Response:
|
||||
@ -2336,6 +2422,8 @@ definitions:
|
||||
type: string
|
||||
code:
|
||||
type: string
|
||||
countryCode:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
emailCode:
|
||||
@ -2358,8 +2446,6 @@ definitions:
|
||||
type: string
|
||||
phoneCode:
|
||||
type: string
|
||||
phonePrefix:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
redirectUri:
|
||||
@ -2383,9 +2469,9 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/2346.0xc0001ce990.false'
|
||||
$ref: '#/definitions/2346.0xc000278ab0.false'
|
||||
data2:
|
||||
$ref: '#/definitions/2381.0xc0001ce9c0.false'
|
||||
$ref: '#/definitions/2381.0xc000278ae0.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
@ -2689,6 +2775,10 @@ definitions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.AccountItem'
|
||||
countryCodes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
defaultApplication:
|
||||
@ -2720,8 +2810,6 @@ definitions:
|
||||
type: string
|
||||
passwordType:
|
||||
type: string
|
||||
phonePrefix:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
@ -3237,6 +3325,8 @@ definitions:
|
||||
type: string
|
||||
cloudfoundry:
|
||||
type: string
|
||||
countryCode:
|
||||
type: string
|
||||
createdIp:
|
||||
type: string
|
||||
createdTime:
|
||||
|
187
sync/bi_sync_mysql.go
Normal file
187
sync/bi_sync_mysql.go
Normal file
@ -0,0 +1,187 @@
|
||||
// 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 sync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-mysql-org/go-mysql/canal"
|
||||
"github.com/go-mysql-org/go-mysql/mysql"
|
||||
"github.com/go-mysql-org/go-mysql/replication"
|
||||
"github.com/siddontang/go-log/log"
|
||||
"github.com/xorm-io/xorm"
|
||||
)
|
||||
|
||||
var (
|
||||
dataSourceName1 string
|
||||
dataSourceName2 string
|
||||
engin1 *xorm.Engine
|
||||
engin2 *xorm.Engine
|
||||
)
|
||||
|
||||
func InitConfig() *canal.Config {
|
||||
// init dataSource
|
||||
dataSourceName1 = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username1, password1, host1, port1, database1)
|
||||
dataSourceName2 = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username2, password2, host2, port2, database2)
|
||||
|
||||
// create engine
|
||||
engin1, _ = CreateEngine(dataSourceName1)
|
||||
engin2, _ = CreateEngine(dataSourceName2)
|
||||
log.Info("init engine success…")
|
||||
|
||||
// config canal
|
||||
cfg := canal.NewDefaultConfig()
|
||||
cfg.Addr = fmt.Sprintf("%s:%d", host1, port1)
|
||||
cfg.Password = password1
|
||||
cfg.User = username1
|
||||
// We only care table in database1
|
||||
cfg.Dump.TableDB = database1
|
||||
// cfg.Dump.Tables = []string{"user"}
|
||||
log.Info("config canal success…")
|
||||
return cfg
|
||||
}
|
||||
|
||||
func StartBinlogSync() error {
|
||||
// init config
|
||||
config := InitConfig()
|
||||
|
||||
c, err := canal.NewCanal(config)
|
||||
pos, err := c.GetMasterPos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Register a handler to handle RowsEvent
|
||||
c.SetEventHandler(&MyEventHandler{})
|
||||
|
||||
// Start canal
|
||||
c.RunFrom(pos)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MyEventHandler struct {
|
||||
canal.DummyEventHandler
|
||||
}
|
||||
|
||||
func OnTableChanged(header *replication.EventHeader, schema string, table string) error {
|
||||
log.Info("table changed event")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MyEventHandler) onDDL(header *replication.EventHeader, nextPos mysql.Position, queryEvent *replication.QueryEvent) error {
|
||||
log.Info("into DDL event")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
||||
length := len(e.Table.Columns)
|
||||
columnNames := make([]string, length)
|
||||
oldColumnValue := make([]interface{}, length)
|
||||
newColumnValue := make([]interface{}, length)
|
||||
isChar := make([]bool, len(e.Table.Columns))
|
||||
|
||||
for i, col := range e.Table.Columns {
|
||||
columnNames[i] = col.Name
|
||||
if col.Type <= 2 {
|
||||
isChar[i] = false
|
||||
} else {
|
||||
isChar[i] = true
|
||||
}
|
||||
}
|
||||
|
||||
switch e.Action {
|
||||
case canal.UpdateAction:
|
||||
for i, row := range e.Rows {
|
||||
for j, item := range row {
|
||||
if i%2 == 0 {
|
||||
if isChar[j] == true {
|
||||
oldColumnValue[j] = fmt.Sprintf("%s", item)
|
||||
} else {
|
||||
oldColumnValue[j] = fmt.Sprintf("%d", item)
|
||||
}
|
||||
} else {
|
||||
if isChar[j] == true {
|
||||
newColumnValue[j] = fmt.Sprintf("%s", item)
|
||||
} else {
|
||||
newColumnValue[j] = fmt.Sprintf("%d", item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i%2 == 1 {
|
||||
updateSql, args, err := GetUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, oldColumnValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := engin2.DB().Exec(updateSql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(updateSql, args, res)
|
||||
}
|
||||
}
|
||||
case canal.DeleteAction:
|
||||
for _, row := range e.Rows {
|
||||
for j, item := range row {
|
||||
if isChar[j] == true {
|
||||
oldColumnValue[j] = fmt.Sprintf("%s", item)
|
||||
} else {
|
||||
oldColumnValue[j] = fmt.Sprintf("%d", item)
|
||||
}
|
||||
}
|
||||
|
||||
deleteSql, args, err := GetDeleteSql(e.Table.Schema, e.Table.Name, columnNames, oldColumnValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := engin2.DB().Exec(deleteSql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(deleteSql, args, res)
|
||||
}
|
||||
case canal.InsertAction:
|
||||
for _, row := range e.Rows {
|
||||
for j, item := range row {
|
||||
if isChar[j] == true {
|
||||
newColumnValue[j] = fmt.Sprintf("%s", item)
|
||||
} else {
|
||||
newColumnValue[j] = fmt.Sprintf("%d", item)
|
||||
}
|
||||
}
|
||||
|
||||
insertSql, args, err := GetInsertSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := engin2.DB().Exec(insertSql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(insertSql, args, res)
|
||||
}
|
||||
default:
|
||||
log.Infof("%v", e.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MyEventHandler) String() string {
|
||||
return "MyEventHandler"
|
||||
}
|
17
sync/conf.go
Normal file
17
sync/conf.go
Normal file
@ -0,0 +1,17 @@
|
||||
package sync
|
||||
|
||||
var (
|
||||
host1 = "127.0.0.1"
|
||||
port1 = 3306
|
||||
username1 = "root"
|
||||
password1 = "123456"
|
||||
database1 = "db"
|
||||
)
|
||||
|
||||
var (
|
||||
host2 = "127.0.0.1"
|
||||
port2 = 3306
|
||||
username2 = "root"
|
||||
password2 = "123456"
|
||||
database2 = "db"
|
||||
)
|
72
sync/utils.go
Normal file
72
sync/utils.go
Normal file
@ -0,0 +1,72 @@
|
||||
// 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 sync
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/xorm-io/xorm"
|
||||
)
|
||||
|
||||
func GetUpdateSql(schemaName string, tableName string, columnNames []string, newColumnVal []interface{}, oldColumnVal []interface{}) (string, []interface{}, error) {
|
||||
updateSql := squirrel.Update(schemaName + "." + tableName)
|
||||
for i, columnName := range columnNames {
|
||||
updateSql = updateSql.Set(columnName, newColumnVal[i])
|
||||
}
|
||||
|
||||
for i, columnName := range columnNames {
|
||||
updateSql = updateSql.Where(squirrel.Eq{columnName: oldColumnVal[i]})
|
||||
}
|
||||
|
||||
sql, args, err := updateSql.ToSql()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return sql, args, nil
|
||||
}
|
||||
|
||||
func GetInsertSql(schemaName string, tableName string, columnNames []string, columnValue []interface{}) (string, []interface{}, error) {
|
||||
insertSql := squirrel.Insert(schemaName + "." + tableName).Columns(columnNames...).Values(columnValue...)
|
||||
|
||||
return insertSql.ToSql()
|
||||
}
|
||||
|
||||
func GetDeleteSql(schemaName string, tableName string, columnNames []string, columnValue []interface{}) (string, []interface{}, error) {
|
||||
deleteSql := squirrel.Delete(schemaName + "." + tableName)
|
||||
|
||||
for i, columnName := range columnNames {
|
||||
deleteSql = deleteSql.Where(squirrel.Eq{columnName: columnValue[i]})
|
||||
}
|
||||
|
||||
return deleteSql.ToSql()
|
||||
}
|
||||
|
||||
func CreateEngine(dataSourceName string) (*xorm.Engine, error) {
|
||||
engine, err := xorm.NewEngine("mysql", dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ping mysql
|
||||
err = engine.Ping()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("mysql connection success……")
|
||||
return engine, nil
|
||||
}
|
@ -14,6 +14,8 @@
|
||||
|
||||
package util
|
||||
|
||||
import "sort"
|
||||
|
||||
func DeleteVal(values []string, val string) []string {
|
||||
newValues := []string{}
|
||||
for _, v := range values {
|
||||
@ -23,3 +25,8 @@ func DeleteVal(values []string, val string) []string {
|
||||
}
|
||||
return newValues
|
||||
}
|
||||
|
||||
func ContainsString(values []string, val string) bool {
|
||||
sort.Strings(values)
|
||||
return sort.SearchStrings(values, val) != len(values)
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ func GetMd5Hash(text string) string {
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func IsStrsEmpty(strs ...string) bool {
|
||||
func IsStringsEmpty(strs ...string) bool {
|
||||
for _, str := range strs {
|
||||
if len(str) == 0 {
|
||||
return true
|
||||
@ -227,7 +227,7 @@ func IsChinese(str string) bool {
|
||||
}
|
||||
|
||||
func GetMaskedPhone(phone string) string {
|
||||
return getMaskedPhone(phone)
|
||||
return rePhone.ReplaceAllString(phone, "$1****$2")
|
||||
}
|
||||
|
||||
func GetMaskedEmail(email string) string {
|
||||
|
@ -183,7 +183,7 @@ func TestIsStrsEmpty(t *testing.T) {
|
||||
}
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual := IsStrsEmpty(scenery.input...)
|
||||
actual := IsStringsEmpty(scenery.input...)
|
||||
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
|
||||
})
|
||||
}
|
||||
|
@ -31,6 +31,6 @@ func GetCurrentUnixTime() string {
|
||||
|
||||
func IsTokenExpired(createdTime string, expiresIn int) bool {
|
||||
createdTimeObj, _ := time.Parse(time.RFC3339, createdTime)
|
||||
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Minute)
|
||||
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Second)
|
||||
return time.Now().After(expiresAtObj)
|
||||
}
|
||||
|
@ -56,15 +56,15 @@ func Test_IsTokenExpired(t *testing.T) {
|
||||
description: "Token emitted now is valid for 60 minutes",
|
||||
input: input{
|
||||
createdTime: time.Now().Format(time.RFC3339),
|
||||
expiresIn: 60,
|
||||
expiresIn: 3600,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
description: "Token emitted 60 minutes before now is valid for 60 minutes",
|
||||
description: "Token emitted 60 minutes before now is valid for 61 minutes",
|
||||
input: input{
|
||||
createdTime: time.Now().Add(-time.Minute * 60).Format(time.RFC3339),
|
||||
expiresIn: 61,
|
||||
expiresIn: 3660,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
@ -72,7 +72,7 @@ func Test_IsTokenExpired(t *testing.T) {
|
||||
description: "Token emitted 2 hours before now is Expired after 60 minutes",
|
||||
input: input{
|
||||
createdTime: time.Now().Add(-time.Hour * 2).Format(time.RFC3339),
|
||||
expiresIn: 60,
|
||||
expiresIn: 3600,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
@ -80,23 +80,23 @@ func Test_IsTokenExpired(t *testing.T) {
|
||||
description: "Token emitted 61 minutes before now is Expired after 60 minutes",
|
||||
input: input{
|
||||
createdTime: time.Now().Add(-time.Minute * 61).Format(time.RFC3339),
|
||||
expiresIn: 60,
|
||||
expiresIn: 3600,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
description: "Token emitted 2 hours before now is valid for 120 minutes",
|
||||
description: "Token emitted 2 hours before now is valid for 121 minutes",
|
||||
input: input{
|
||||
createdTime: time.Now().Add(-time.Hour * 2).Format(time.RFC3339),
|
||||
expiresIn: 121,
|
||||
expiresIn: 7260,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
description: "Token emitted 159 minutes before now is Expired after 60 minutes",
|
||||
description: "Token emitted 159 minutes before now is Expired after 120 minutes",
|
||||
input: input{
|
||||
createdTime: time.Now().Add(-time.Minute * 159).Format(time.RFC3339),
|
||||
expiresIn: 120,
|
||||
expiresIn: 7200,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
|
@ -17,16 +17,13 @@ package util
|
||||
import (
|
||||
"net/mail"
|
||||
"regexp"
|
||||
|
||||
"github.com/nyaruka/phonenumbers"
|
||||
)
|
||||
|
||||
var (
|
||||
rePhoneCn *regexp.Regexp
|
||||
rePhone *regexp.Regexp
|
||||
)
|
||||
var rePhone *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
// https://learnku.com/articles/31543
|
||||
rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
|
||||
rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})")
|
||||
}
|
||||
|
||||
@ -35,10 +32,19 @@ func IsEmailValid(email string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func IsPhoneCnValid(phone string) bool {
|
||||
return rePhoneCn.MatchString(phone)
|
||||
func IsPhoneValid(phone string, countryCode string) bool {
|
||||
phoneNumber, err := phonenumbers.Parse(phone, countryCode)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return phonenumbers.IsValidNumber(phoneNumber)
|
||||
}
|
||||
|
||||
func getMaskedPhone(phone string) string {
|
||||
return rePhone.ReplaceAllString(phone, "$1****$2")
|
||||
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
|
||||
return ContainsString(allowRegions, countryCode)
|
||||
}
|
||||
|
||||
func GetE164Number(phone string, countryCode string) (string, bool) {
|
||||
phoneNumber, _ := phonenumbers.Parse(phone, countryCode)
|
||||
return phonenumbers.Format(phoneNumber, phonenumbers.E164), phonenumbers.IsValidNumber(phoneNumber)
|
||||
}
|
@ -15,7 +15,6 @@ describe("Login test", () => {
|
||||
"password": "123",
|
||||
"autoSignin": true,
|
||||
"type": "login",
|
||||
"phonePrefix": "86",
|
||||
},
|
||||
}).then((Response) => {
|
||||
expect(Response).property("body").property("status").to.equal("ok");
|
||||
@ -40,7 +39,6 @@ describe("Login test", () => {
|
||||
"password": "1234",
|
||||
"autoSignin": true,
|
||||
"type": "login",
|
||||
"phonePrefix": "86",
|
||||
},
|
||||
}).then((Response) => {
|
||||
expect(Response).property("body").property("status").to.equal("error");
|
||||
|
@ -34,7 +34,6 @@ Cypress.Commands.add('login', ()=>{
|
||||
"password": "123",
|
||||
"autoSignin": true,
|
||||
"type": "login",
|
||||
"phonePrefix": "86",
|
||||
},
|
||||
}).then((Response) => {
|
||||
expect(Response).property("body").property("status").to.equal("ok");
|
||||
|
@ -3,6 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/cssinjs": "^1.5.6",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@craco/craco": "^6.4.5",
|
||||
"@crowdin/cli": "^3.7.10",
|
||||
@ -21,6 +22,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"i18n-iso-countries": "^7.0.0",
|
||||
"i18next": "^19.8.9",
|
||||
"libphonenumber-js": "^1.10.19",
|
||||
"moment": "^2.29.1",
|
||||
"qs": "^6.10.2",
|
||||
"react": "^18.2.0",
|
||||
|
@ -78,6 +78,7 @@ class AccountTable extends React.Component {
|
||||
{name: "Password", displayName: i18next.t("general:Password")},
|
||||
{name: "Email", displayName: i18next.t("general:Email")},
|
||||
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||
{name: "Country code", displayName: i18next.t("user:Country code")},
|
||||
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||
{name: "Location", displayName: i18next.t("user:Location")},
|
||||
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||
|
@ -261,7 +261,7 @@ class AdapterListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -16,8 +16,9 @@ import React, {Component} from "react";
|
||||
import "./App.less";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Setting from "./Setting";
|
||||
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
||||
import {StyleProvider} from "@ant-design/cssinjs";
|
||||
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import OrganizationListPage from "./OrganizationListPage";
|
||||
import OrganizationEditPage from "./OrganizationEditPage";
|
||||
@ -671,6 +672,9 @@ class App extends Component {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
this.renderBanner()
|
||||
}
|
||||
<FloatButton.BackTop />
|
||||
<CustomGithubCorner />
|
||||
{
|
||||
@ -680,6 +684,32 @@ class App extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderBanner() {
|
||||
if (!Conf.IsDemoMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const language = Setting.getLanguage();
|
||||
if (language === "en" || language === "zh") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert type="info" banner showIcon={false} closable message={
|
||||
<div style={{textAlign: "center"}}>
|
||||
<InfoCircleFilled style={{color: "rgb(87,52,211)"}} />
|
||||
|
||||
{i18next.t("general:Found some texts still not translated? Please help us translate at")}
|
||||
|
||||
<a target="_blank" rel="noreferrer" href={"https://crowdin.com/project/casdoor-site"}>
|
||||
Crowdin
|
||||
</a>
|
||||
! 🙏
|
||||
</div>
|
||||
} />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
@ -700,9 +730,11 @@ class App extends Component {
|
||||
},
|
||||
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||
}}>
|
||||
{
|
||||
this.renderPage()
|
||||
}
|
||||
<StyleProvider hashPriority="high">
|
||||
{
|
||||
this.renderPage()
|
||||
}
|
||||
</StyleProvider>
|
||||
</ConfigProvider>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -287,7 +287,7 @@ class ApplicationListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -228,7 +228,7 @@ class CertListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
export const ShowGithubCorner = false;
|
||||
export const GithubRepo = "https://github.com/casdoor/casdoor";
|
||||
export const IsDemoMode = false;
|
||||
|
||||
export const ForceLanguage = "";
|
||||
export const DefaultLanguage = "en";
|
||||
|
@ -83,8 +83,8 @@ class EntryPage extends React.Component {
|
||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<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} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} {...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} />);}} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
|
@ -201,7 +201,7 @@ class ModelListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -184,12 +184,19 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Phone prefix"), i18next.t("general:Phone prefix - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input addonBefore={"+"} value={this.state.organization.phonePrefix} onChange={e => {
|
||||
this.updateOrganizationField("phonePrefix", e.target.value);
|
||||
}} />
|
||||
<Select virtual={false} mode={"multiple"} style={{width: "100%"}} value={this.state.organization.countryCodes ?? []}
|
||||
onChange={value => {
|
||||
this.updateOrganizationField("countryCodes", value);
|
||||
}}
|
||||
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||
>
|
||||
{
|
||||
Setting.getCountryCodeData().map((country) => Setting.getCountryCodeOption(country))
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -257,22 +264,13 @@ class OrganizationEditPage extends React.Component {
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="tags" style={{width: "100%"}}
|
||||
value={this.state.organization.languages}
|
||||
options={Setting.Countries.map((item) => {
|
||||
return Setting.getOption(item.label, item.key);
|
||||
})}
|
||||
value={this.state.organization.languages ?? []}
|
||||
onChange={(value => {
|
||||
this.updateOrganizationField("languages", value);
|
||||
})} >
|
||||
{
|
||||
[
|
||||
{value: "en", label: "English"},
|
||||
{value: "zh", label: "简体中文"},
|
||||
{value: "es", label: "Español"},
|
||||
{value: "fr", label: "Français"},
|
||||
{value: "de", label: "Deutsch"},
|
||||
{value: "ja", label: "日本語"},
|
||||
{value: "ko", label: "한국어"},
|
||||
{value: "ru", label: "Русский"},
|
||||
].map((item, index) => <Option key={index} value={item.value}>{item.label}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -33,11 +33,11 @@ class OrganizationListPage extends BaseListPage {
|
||||
favicon: `${Setting.StaticBaseUrl}/img/favicon.png`,
|
||||
passwordType: "plain",
|
||||
PasswordSalt: "",
|
||||
phonePrefix: "86",
|
||||
countryCodes: ["CN"],
|
||||
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
||||
defaultApplication: "",
|
||||
tags: [],
|
||||
languages: ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
|
||||
languages: Setting.Countries.map(item => item.key),
|
||||
masterPassword: "",
|
||||
enableSoftDeletion: false,
|
||||
isProfilePublic: true,
|
||||
@ -284,7 +284,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -279,7 +279,7 @@ class PaymentListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -359,7 +359,7 @@ class PermissionListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -296,7 +296,7 @@ class ProductListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -82,20 +82,20 @@ class ProviderEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getClientIdLabel() {
|
||||
switch (this.state.provider.category) {
|
||||
getClientIdLabel(provider) {
|
||||
switch (provider.category) {
|
||||
case "Email":
|
||||
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
|
||||
case "SMS":
|
||||
if (this.state.provider.type === "Volc Engine SMS") {
|
||||
if (provider.type === "Volc Engine SMS") {
|
||||
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
||||
} else if (this.state.provider.type === "Huawei Cloud SMS") {
|
||||
} else if (provider.type === "Huawei Cloud SMS") {
|
||||
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||
}
|
||||
case "Captcha":
|
||||
if (this.state.provider.type === "Aliyun Captcha") {
|
||||
if (provider.type === "Aliyun Captcha") {
|
||||
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Site key"), i18next.t("provider:Site key - Tooltip"));
|
||||
@ -105,20 +105,20 @@ class ProviderEditPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getClientSecretLabel() {
|
||||
switch (this.state.provider.category) {
|
||||
getClientSecretLabel(provider) {
|
||||
switch (provider.category) {
|
||||
case "Email":
|
||||
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
|
||||
case "SMS":
|
||||
if (this.state.provider.type === "Volc Engine SMS") {
|
||||
if (provider.type === "Volc Engine SMS") {
|
||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
||||
} else if (this.state.provider.type === "Huawei Cloud SMS") {
|
||||
} else if (provider.type === "Huawei Cloud SMS") {
|
||||
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
case "Captcha":
|
||||
if (this.state.provider.type === "Aliyun Captcha") {
|
||||
if (provider.type === "Aliyun Captcha") {
|
||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||
@ -128,40 +128,52 @@ class ProviderEditPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getAppIdRow() {
|
||||
let text, tooltip;
|
||||
if (this.state.provider.category === "SMS" && this.state.provider.type === "Tencent Cloud SMS") {
|
||||
text = i18next.t("provider:App ID");
|
||||
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||
} else if (this.state.provider.type === "WeCom" && this.state.provider.subType === "Internal") {
|
||||
text = i18next.t("provider:Agent ID");
|
||||
tooltip = i18next.t("provider:Agent ID - Tooltip");
|
||||
} else if (this.state.provider.type === "Infoflow") {
|
||||
text = i18next.t("provider:Agent ID");
|
||||
tooltip = i18next.t("provider:Agent ID - Tooltip");
|
||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
|
||||
text = i18next.t("provider:SMS account");
|
||||
tooltip = i18next.t("provider:SMS account - Tooltip");
|
||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Huawei Cloud SMS") {
|
||||
text = i18next.t("provider:Channel No.");
|
||||
tooltip = i18next.t("provider:Channel No. - Tooltip");
|
||||
} else if (this.state.provider.category === "Email" && this.state.provider.type === "SUBMAIL") {
|
||||
text = i18next.t("provider:App ID");
|
||||
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||
} else {
|
||||
return null;
|
||||
getAppIdRow(provider) {
|
||||
let text = "";
|
||||
let tooltip = "";
|
||||
|
||||
if (provider.category === "OAuth") {
|
||||
if (provider.type === "WeCom" && provider.subType === "Internal") {
|
||||
text = i18next.t("provider:Agent ID");
|
||||
tooltip = i18next.t("provider:Agent ID - Tooltip");
|
||||
} else if (provider.type === "Infoflow") {
|
||||
text = i18next.t("provider:Agent ID");
|
||||
tooltip = i18next.t("provider:Agent ID - Tooltip");
|
||||
}
|
||||
} else if (provider.category === "SMS") {
|
||||
if (provider.type === "Tencent Cloud SMS") {
|
||||
text = i18next.t("provider:App ID");
|
||||
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||
} else if (provider.type === "Volc Engine SMS") {
|
||||
text = i18next.t("provider:SMS account");
|
||||
tooltip = i18next.t("provider:SMS account - Tooltip");
|
||||
} else if (provider.type === "Huawei Cloud SMS") {
|
||||
text = i18next.t("provider:Channel No.");
|
||||
tooltip = i18next.t("provider:Channel No. - Tooltip");
|
||||
}
|
||||
} else if (provider.category === "Email") {
|
||||
if (provider.type === "SUBMAIL") {
|
||||
text = i18next.t("provider:App ID");
|
||||
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||
}
|
||||
}
|
||||
|
||||
return <Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(text, tooltip)} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.appId} onChange={e => {
|
||||
this.updateProviderField("appId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>;
|
||||
if (text === "" && tooltip === "") {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(text, tooltip)} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.appId} onChange={e => {
|
||||
this.updateProviderField("appId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
loadSamlConfiguration() {
|
||||
@ -404,7 +416,7 @@ class ProviderEditPage extends React.Component {
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel()}
|
||||
{this.getClientIdLabel(this.state.provider)}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId} onChange={e => {
|
||||
@ -414,7 +426,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientSecretLabel()}
|
||||
{this.getClientSecretLabel(this.state.provider)}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientSecret} onChange={e => {
|
||||
@ -750,7 +762,7 @@ class ProviderEditPage extends React.Component {
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
{this.getAppIdRow()}
|
||||
{this.getAppIdRow(this.state.provider)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
|
||||
|
@ -267,7 +267,7 @@ class ProviderListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -25,7 +25,7 @@ export const ResetModal = (props) => {
|
||||
const [confirmLoading, setConfirmLoading] = React.useState(false);
|
||||
const [dest, setDest] = React.useState("");
|
||||
const [code, setCode] = React.useState("");
|
||||
const {buttonText, destType, application} = props;
|
||||
const {buttonText, destType, application, account} = props;
|
||||
|
||||
const showModal = () => {
|
||||
setVisible(true);
|
||||
@ -87,7 +87,7 @@ export const ResetModal = (props) => {
|
||||
<Row style={{width: "100%", marginBottom: "20px"}}>
|
||||
<Input
|
||||
addonBefore={destType === "email" ? i18next.t("user:New Email") : i18next.t("user:New phone")}
|
||||
prefix={destType === "email" ? <MailOutlined /> : <PhoneOutlined />}
|
||||
prefix={destType === "email" ? <React.Fragment><MailOutlined /> </React.Fragment> : (<React.Fragment><PhoneOutlined /> {`+${Setting.getCountryCode(account.countryCode)}`} </React.Fragment>)}
|
||||
placeholder={placeholder}
|
||||
onChange={e => setDest(e.target.value)}
|
||||
/>
|
||||
|
@ -82,7 +82,7 @@ class RoleListPage extends BaseListPage {
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/roles/${text}`}>
|
||||
<Link to={`/roles/${record.owner}/${record.name}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
@ -232,7 +232,7 @@ class RoleListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -18,16 +18,6 @@ import {Dropdown} from "antd";
|
||||
import "./App.less";
|
||||
import {GlobalOutlined} from "@ant-design/icons";
|
||||
|
||||
export const Countries = [{label: "English", key: "en", country: "US", alt: "English"},
|
||||
{label: "简体中文", key: "zh", country: "CN", alt: "简体中文"},
|
||||
{label: "Español", key: "es", country: "ES", alt: "Español"},
|
||||
{label: "Français", key: "fr", country: "FR", alt: "Français"},
|
||||
{label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"},
|
||||
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
];
|
||||
|
||||
function flagIcon(country, alt) {
|
||||
return (
|
||||
<img width={24} alt={alt} src={`${Setting.StaticBaseUrl}/flag-icons/${country}.svg`} />
|
||||
@ -39,15 +29,15 @@ class SelectLanguageBox extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
|
||||
languages: props.languages ?? Setting.Countries.map(item => item.key),
|
||||
};
|
||||
|
||||
Countries.forEach((country) => {
|
||||
Setting.Countries.forEach((country) => {
|
||||
new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`;
|
||||
});
|
||||
}
|
||||
|
||||
items = Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
|
||||
items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
|
||||
|
||||
getOrganizationLanguages(languages) {
|
||||
const select = [];
|
||||
|
@ -41,17 +41,15 @@ class SelectRegionBox extends React.Component {
|
||||
defaultValue={this.props.defaultValue || undefined}
|
||||
placeholder="Please select country/region"
|
||||
onChange={(value => {this.onChange(value);})}
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||
filterSort={(optionA, optionB) =>
|
||||
(optionA?.label ?? "").toLowerCase().localeCompare((optionB?.label ?? "").toLowerCase())
|
||||
}
|
||||
>
|
||||
{
|
||||
Setting.getCountryNames().map((item) => (
|
||||
Setting.getCountryCodeData().map((item) => (
|
||||
<Option key={item.code} value={item.code} label={`${item.name} (${item.code})`} >
|
||||
<img src={`${Setting.StaticBaseUrl}/flag-icons/${item.code}.svg`} alt={item.name} height={20} style={{marginRight: 10}} />
|
||||
{Setting.getCountryImage(item)}
|
||||
{`${item.name} (${item.code})`}
|
||||
</Option>
|
||||
))
|
||||
|
@ -148,7 +148,7 @@ class SessionListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Checkbox, Form, Modal, Tag, Tooltip, message, theme} from "antd";
|
||||
import {Checkbox, Form, Modal, Select, Tag, Tooltip, message, theme} from "antd";
|
||||
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||
import "./i18n";
|
||||
@ -23,13 +23,27 @@ import copy from "copy-to-clipboard";
|
||||
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;
|
||||
|
||||
export const ServerUrl = "";
|
||||
|
||||
// export const StaticBaseUrl = "https://cdn.jsdelivr.net/gh/casbin/static";
|
||||
export const StaticBaseUrl = "https://cdn.casbin.org";
|
||||
|
||||
export const Countries = [{label: "English", key: "en", country: "US", alt: "English"},
|
||||
{label: "中文", key: "zh", country: "CN", alt: "中文"},
|
||||
{label: "Español", key: "es", country: "ES", alt: "Español"},
|
||||
{label: "Français", key: "fr", country: "FR", alt: "Français"},
|
||||
{label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"},
|
||||
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
{label: "TiếngViệt", key: "vi", country: "VI", alt: "TiếngViệt"},
|
||||
];
|
||||
|
||||
export function getThemeData(organization, application) {
|
||||
if (application?.themeData?.isEnabled) {
|
||||
return application.themeData;
|
||||
@ -80,7 +94,7 @@ export const OtherProviderInfo = {
|
||||
url: "https://www.huaweicloud.com/product/msgsms.html",
|
||||
},
|
||||
"Twilio SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_twilio.png`,
|
||||
logo: `${StaticBaseUrl}/img/social_twilio.svg`,
|
||||
url: "https://www.twilio.com/messaging",
|
||||
},
|
||||
"SmsBao SMS": {
|
||||
@ -188,18 +202,46 @@ export const OtherProviderInfo = {
|
||||
},
|
||||
};
|
||||
|
||||
export function getCountriesData() {
|
||||
export function initCountries() {
|
||||
const countries = require("i18n-iso-countries");
|
||||
countries.registerLocale(require("i18n-iso-countries/langs/" + getLanguage() + ".json"));
|
||||
return countries;
|
||||
}
|
||||
|
||||
export function getCountryNames() {
|
||||
const data = getCountriesData().getNames(getLanguage(), {select: "official"});
|
||||
export function getCountryCode(country) {
|
||||
return phoneNumber.getCountryCallingCode(country);
|
||||
}
|
||||
|
||||
return Object.entries(data).map(items => {
|
||||
return {code: items[0], name: items[1]};
|
||||
});
|
||||
export function getCountryCodeData(countryCodes = phoneNumber.getCountries()) {
|
||||
return countryCodes?.map((countryCode) => {
|
||||
if (phoneNumber.isSupportedCountry(countryCode)) {
|
||||
const name = initCountries().getName(countryCode, getLanguage());
|
||||
return {
|
||||
code: countryCode,
|
||||
name: name || "",
|
||||
phone: phoneNumber.getCountryCallingCode(countryCode),
|
||||
};
|
||||
}
|
||||
}).filter(item => item.name !== "")
|
||||
.sort((a, b) => a.phone - b.phone);
|
||||
}
|
||||
|
||||
export function getCountryCodeOption(country) {
|
||||
return (
|
||||
<Option key={country.code} value={country.code} label={`+${country.phone}`} text={`${country.name}, ${country.code}, ${country.phone}`} >
|
||||
<div style={{display: "flex", justifyContent: "space-between", marginRight: "10px"}}>
|
||||
<div>
|
||||
{getCountryImage(country)}
|
||||
{`${country.name}`}
|
||||
</div>
|
||||
{`+${country.phone}`}
|
||||
</div>
|
||||
</Option>
|
||||
);
|
||||
}
|
||||
|
||||
export function getCountryImage(country) {
|
||||
return <img src={`${StaticBaseUrl}/flag-icons/${country.code}.svg`} alt={country.name} height={20} style={{marginRight: 10}} />;
|
||||
}
|
||||
|
||||
export function initServerUrl() {
|
||||
@ -238,6 +280,13 @@ export function isProviderVisible(providerItem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isResponseDenied(data) {
|
||||
if (data.msg === "Unauthorized operation" || data.msg === "未授权的操作") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isProviderVisibleForSignUp(providerItem) {
|
||||
if (providerItem.canSignUp === false) {
|
||||
return false;
|
||||
@ -299,16 +348,16 @@ export function isValidEmail(email) {
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
export function isValidPhone(phone) {
|
||||
return phone !== "";
|
||||
export function isValidPhone(phone, countryCode = "") {
|
||||
if (countryCode !== "" && countryCode !== "CN") {
|
||||
return phoneNumber.isValidPhoneNumber(phone, countryCode);
|
||||
}
|
||||
|
||||
// if (phone === "") {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // https://learnku.com/articles/31543, `^s*$` filter empty email individually.
|
||||
// const phoneRegex = /^\s*$|^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
|
||||
// return phoneRegex.test(phone);
|
||||
// https://learnku.com/articles/31543, `^s*$` filter empty email individually.
|
||||
const phoneCnRegex = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
|
||||
const phoneRegex = /[0-9]{4,15}$/;
|
||||
|
||||
return countryCode === "CN" ? phoneCnRegex.test(phone) : phoneRegex.test(phone);
|
||||
}
|
||||
|
||||
export function isValidInvoiceTitle(invoiceTitle) {
|
||||
|
@ -289,7 +289,7 @@ class SyncerListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -254,7 +254,7 @@ class TokenListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -29,6 +29,7 @@ import SelectRegionBox from "./SelectRegionBox";
|
||||
import WebAuthnCredentialTable from "./WebauthnCredentialTable";
|
||||
import ManagedAccountTable from "./ManagedAccountTable";
|
||||
import PropertyTable from "./propertyTable";
|
||||
import {PhoneNumberInput} from "./common/PhoneNumberInput";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -284,48 +285,48 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
|
||||
</Col>
|
||||
<Col style={{paddingRight: "20px"}} span={11} >
|
||||
{Setting.isLocalAdminUser(this.props.account) ?
|
||||
(<Input value={this.state.user.email}
|
||||
disabled={disabled}
|
||||
onChange={e => {
|
||||
this.updateUserField("email", e.target.value);
|
||||
}} />) :
|
||||
(<Select virtual={false} value={this.state.user.email}
|
||||
options={[Setting.getItem(this.state.user.email, this.state.user.email)]}
|
||||
disabled={disabled}
|
||||
onChange={e => {
|
||||
this.updateUserField("email", e.target.value);
|
||||
}} />)
|
||||
}
|
||||
<Input
|
||||
value={this.state.user.email}
|
||||
style={{width: "280Px"}}
|
||||
disabled={!Setting.isLocalAdminUser(this.props.account) ? true : disabled}
|
||||
onChange={e => {
|
||||
this.updateUserField("email", e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={11} >
|
||||
<Col span={Setting.isMobile() ? 22 : 11} >
|
||||
{/* backend auto get the current user, so admin can not edit. Just self can reset*/}
|
||||
{this.isSelf() ? <ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} /> : null}
|
||||
{this.isSelf() ? <ResetModal application={this.state.application} account={this.props.account} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} /> : null}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Phone") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Col style={{marginTop: "5px"}} span={Setting.isMobile() ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
||||
</Col>
|
||||
<Col style={{paddingRight: "20px"}} span={11} >
|
||||
{Setting.isLocalAdminUser(this.props.account) ?
|
||||
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
||||
disabled={disabled}
|
||||
<Input.Group compact style={{width: "280Px"}}>
|
||||
<PhoneNumberInput
|
||||
style={{width: "30%"}}
|
||||
// disabled={!Setting.isLocalAdminUser(this.props.account) ? true : disabled}
|
||||
value={this.state.user.countryCode}
|
||||
onChange={(value) => {
|
||||
this.updateUserField("countryCode", value);
|
||||
}}
|
||||
countryCodes={this.state.application?.organizationObj.countryCodes}
|
||||
/>
|
||||
<Input value={this.state.user.phone}
|
||||
style={{width: "70%"}}
|
||||
disabled={!Setting.isLocalAdminUser(this.props.account) ? true : disabled}
|
||||
onChange={e => {
|
||||
this.updateUserField("phone", e.target.value);
|
||||
}} /> :
|
||||
(<Select virtual={false} value={`+${this.state.application?.organizationObj.phonePrefix} ${this.state.user.phone}`}
|
||||
options={[Setting.getItem(`+${this.state.application?.organizationObj.phonePrefix} ${this.state.user.phone}`, this.state.user.phone)]}
|
||||
disabled={disabled}
|
||||
onChange={e => {
|
||||
this.updateUserField("phone", e.target.value);
|
||||
}} />)}
|
||||
}} />
|
||||
</Input.Group>
|
||||
</Col>
|
||||
<Col span={11} >
|
||||
{this.isSelf() ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
||||
<Col span={Setting.isMobile() ? 24 : 11} >
|
||||
{this.isSelf() ? (<ResetModal application={this.state.application} account={this.props.account} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
@ -49,6 +49,7 @@ class UserListPage extends BaseListPage {
|
||||
avatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
||||
email: `${randomName}@example.com`,
|
||||
phone: Setting.getRandomNumber(),
|
||||
countryCode: this.state.organization.countryCodes?.length > 0 ? this.state.organization.countryCodes[0] : "",
|
||||
address: [],
|
||||
affiliation: "Example Inc.",
|
||||
tag: "staff",
|
||||
@ -261,7 +262,7 @@ class UserListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("region"),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getCountriesData().getName(record.region, Setting.getLanguage(), {select: "official"});
|
||||
return Setting.initCountries().getName(record.region, Setting.getLanguage(), {select: "official"});
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -403,7 +404,7 @@ class UserListPage extends BaseListPage {
|
||||
this.getOrganization(users[0].owner);
|
||||
}
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
@ -431,7 +432,7 @@ class UserListPage extends BaseListPage {
|
||||
this.getOrganization(users[0].owner);
|
||||
}
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -254,7 +254,7 @@ class WebhookListPage extends BaseListPage {
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (res.msg.includes("Unauthorized")) {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
|
@ -36,11 +36,10 @@ export function signup(values) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getEmailAndPhone(values) {
|
||||
return fetch(`${authConfig.serverUrl}/api/get-email-and-phone`, {
|
||||
method: "POST",
|
||||
export function getEmailAndPhone(organization, username) {
|
||||
return fetch(`${authConfig.serverUrl}/api/get-email-and-phone?organization=${organization}&username=${username}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(values),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
|
@ -40,7 +40,6 @@ class CasLogout extends React.Component {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", "Logged out successfully");
|
||||
this.props.onUpdateAccount(null);
|
||||
this.onUpdateApplication(null);
|
||||
const redirectUri = res.data2;
|
||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||
Setting.goToLink(redirectUri);
|
||||
@ -50,7 +49,6 @@ class CasLogout extends React.Component {
|
||||
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
|
||||
}
|
||||
} else {
|
||||
this.onUpdateApplication(null);
|
||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
|
@ -24,8 +24,6 @@ import * as UserBackend from "../backend/UserBackend";
|
||||
import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
|
||||
import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import {withRouter} from "react-router-dom";
|
||||
|
||||
const {Step} = Steps;
|
||||
const {Option} = Select;
|
||||
|
||||
class ForgetPage extends React.Component {
|
||||
@ -33,23 +31,21 @@ class ForgetPage extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
account: props.account,
|
||||
applicationName: props.applicationName ?? props.match.params?.applicationName,
|
||||
application: null,
|
||||
msg: null,
|
||||
userId: "",
|
||||
username: "",
|
||||
name: "",
|
||||
email: "",
|
||||
isFixed: false,
|
||||
fixedContent: "",
|
||||
token: "",
|
||||
phone: "",
|
||||
emailCode: "",
|
||||
phoneCode: "",
|
||||
verifyType: null, // "email" or "phone"
|
||||
email: "",
|
||||
dest: "",
|
||||
isVerifyTypeFixed: false,
|
||||
verifyType: "", // "email", "phone"
|
||||
current: 0,
|
||||
};
|
||||
|
||||
this.form = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -88,73 +84,67 @@ class ForgetPage extends React.Component {
|
||||
switch (name) {
|
||||
case "step1":
|
||||
const username = forms.step1.getFieldValue("username");
|
||||
AuthBackend.getEmailAndPhone({
|
||||
application: forms.step1.getFieldValue("application"),
|
||||
organization: forms.step1.getFieldValue("organization"),
|
||||
username: username,
|
||||
}).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const phone = res.data.phone;
|
||||
const email = res.data.email;
|
||||
const saveFields = () => {
|
||||
if (this.state.isFixed) {
|
||||
forms.step2.setFieldsValue({email: this.state.fixedContent});
|
||||
this.setState({username: this.state.fixedContent});
|
||||
AuthBackend.getEmailAndPhone(forms.step1.getFieldValue("organization"), username)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const phone = res.data.phone;
|
||||
const email = res.data.email;
|
||||
|
||||
if (phone === "" && email === "") {
|
||||
Setting.showMessage("error", "no verification method!");
|
||||
} else {
|
||||
this.setState({
|
||||
name: res.data.name,
|
||||
phone: phone,
|
||||
email: email,
|
||||
});
|
||||
|
||||
const saveFields = (type, dest, fixed) => {
|
||||
this.setState({
|
||||
verifyType: type,
|
||||
isVerifyTypeFixed: fixed,
|
||||
dest: dest,
|
||||
});
|
||||
};
|
||||
|
||||
switch (res.data2) {
|
||||
case "email":
|
||||
saveFields("email", email, true);
|
||||
break;
|
||||
case "phone":
|
||||
saveFields("phone", phone, true);
|
||||
break;
|
||||
case "username":
|
||||
phone !== "" ? saveFields("phone", phone, false) : saveFields("email", email, false);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
current: 1,
|
||||
});
|
||||
}
|
||||
this.setState({current: 1});
|
||||
};
|
||||
this.setState({phone: phone, email: email, username: res.data.name, name: res.data.name});
|
||||
|
||||
if (phone !== "" && email === "") {
|
||||
this.setState({
|
||||
verifyType: "phone",
|
||||
});
|
||||
} else if (phone === "" && email !== "") {
|
||||
this.setState({
|
||||
verifyType: "email",
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
|
||||
switch (res.data2) {
|
||||
case "email":
|
||||
this.setState({isFixed: true, fixedContent: email, verifyType: "email"}, () => {saveFields();});
|
||||
break;
|
||||
case "phone":
|
||||
this.setState({isFixed: true, fixedContent: phone, verifyType: "phone"}, () => {saveFields();});
|
||||
break;
|
||||
default:
|
||||
saveFields();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
case "step2":
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
const login = () => {
|
||||
AuthBackend.login({
|
||||
application: forms.step2.getFieldValue("application"),
|
||||
organization: forms.step2.getFieldValue("organization"),
|
||||
username: this.state.username,
|
||||
name: this.state.name,
|
||||
code: forms.step2.getFieldValue("emailCode"),
|
||||
phonePrefix: this.getApplicationObj()?.organizationObj.phonePrefix,
|
||||
type: "login",
|
||||
}, oAuthParams).then(res => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({current: 2, userId: res.data, username: res.data.split("/")[1]});
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
}
|
||||
});
|
||||
};
|
||||
if (this.state.verifyType === "email") {
|
||||
this.setState({username: this.state.email}, () => {login();});
|
||||
} else if (this.state.verifyType === "phone") {
|
||||
this.setState({username: this.state.phone}, () => {login();});
|
||||
}
|
||||
|
||||
AuthBackend.login({
|
||||
application: forms.step2.getFieldValue("application"),
|
||||
organization: forms.step2.getFieldValue("organization"),
|
||||
username: forms.step2.getFieldValue("dest"),
|
||||
name: this.state.name,
|
||||
code: forms.step2.getFieldValue("code"),
|
||||
type: "login",
|
||||
}, oAuthParams).then(res => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({current: 2, userId: res.data});
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -162,13 +152,13 @@ class ForgetPage extends React.Component {
|
||||
}
|
||||
|
||||
onFinish(values) {
|
||||
values.username = this.state.username;
|
||||
values.username = this.state.name;
|
||||
values.userOwner = this.getApplicationObj()?.organizationObj.name;
|
||||
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
|
||||
if (res.status === "ok") {
|
||||
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -180,7 +170,7 @@ class ForgetPage extends React.Component {
|
||||
|
||||
if (this.state.phone !== "") {
|
||||
options.push(
|
||||
<Option key={"phone"} value={"phone"}>
|
||||
<Option key={"phone"} value={this.state.phone} >
|
||||
{this.state.phone}
|
||||
</Option>
|
||||
);
|
||||
@ -188,7 +178,7 @@ class ForgetPage extends React.Component {
|
||||
|
||||
if (this.state.email !== "") {
|
||||
options.push(
|
||||
<Option key={"email"} value={"email"}>
|
||||
<Option key={"email"} value={this.state.email} >
|
||||
{this.state.email}
|
||||
</Option>
|
||||
);
|
||||
@ -203,76 +193,70 @@ class ForgetPage extends React.Component {
|
||||
this.onFormFinish(name, info, forms);
|
||||
}}>
|
||||
{/* STEP 1: input username -> get email & phone */}
|
||||
<Form
|
||||
hidden={this.state.current !== 0}
|
||||
ref={this.form}
|
||||
name="step1"
|
||||
// eslint-disable-next-line no-console
|
||||
onFinishFailed={(errorInfo) => console.log(errorInfo)}
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
}}
|
||||
style={{width: "300px"}}
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
style={{height: 0, visibility: "hidden"}}
|
||||
name="application"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your application!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
style={{height: 0, visibility: "hidden"}}
|
||||
name="organization"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your organization!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your username!"
|
||||
),
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
{this.state.current === 0 ?
|
||||
<Form
|
||||
ref={this.form}
|
||||
name="step1"
|
||||
// eslint-disable-next-line no-console
|
||||
onFinishFailed={(errorInfo) => console.log(errorInfo)}
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
}}
|
||||
style={{width: "300px"}}
|
||||
size="large"
|
||||
>
|
||||
<Input
|
||||
onChange={(e) => {
|
||||
this.setState({
|
||||
username: e.target.value,
|
||||
});
|
||||
}}
|
||||
prefix={<UserOutlined />}
|
||||
placeholder={i18next.t("login:username, Email or phone")}
|
||||
<Form.Item
|
||||
hidden
|
||||
name="application"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your application!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item>
|
||||
<Button block type="primary" htmlType="submit">
|
||||
{i18next.t("forget:Next Step")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Form.Item
|
||||
hidden
|
||||
name="organization"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your organization!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your username!"
|
||||
),
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
placeholder={i18next.t("login:username, Email or phone")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item>
|
||||
<Button block type="primary" htmlType="submit">
|
||||
{i18next.t("forget:Next Step")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form> : null}
|
||||
|
||||
{/* STEP 2: verify email or phone */}
|
||||
<Form
|
||||
hidden={this.state.current !== 1}
|
||||
{this.state.current === 1 ? <Form
|
||||
ref={this.form}
|
||||
name="step2"
|
||||
onFinishFailed={(errorInfo) =>
|
||||
@ -282,9 +266,17 @@ class ForgetPage extends React.Component {
|
||||
errorInfo.outOfDate
|
||||
)
|
||||
}
|
||||
onValuesChange={(changedValues, allValues) => {
|
||||
const verifyType = changedValues.dest?.indexOf("@") === -1 ? "phone" : "email";
|
||||
this.setState({
|
||||
dest: changedValues.dest,
|
||||
verifyType: verifyType,
|
||||
});
|
||||
}}
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
dest: this.state.dest,
|
||||
}}
|
||||
style={{width: "300px"}}
|
||||
size="large"
|
||||
@ -302,7 +294,7 @@ class ForgetPage extends React.Component {
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
style={{height: 0, visibility: "hidden"}}
|
||||
hidden
|
||||
name="organization"
|
||||
rules={[
|
||||
{
|
||||
@ -314,32 +306,24 @@ class ForgetPage extends React.Component {
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="email" // use email instead of email/phone to adapt to RequestForm in account.go
|
||||
name="dest"
|
||||
validateFirst
|
||||
hasFeedback
|
||||
>
|
||||
{
|
||||
this.state.isFixed ? <Input disabled /> :
|
||||
<Select virtual={false}
|
||||
key={this.state.verifyType}
|
||||
style={{textAlign: "left"}}
|
||||
defaultValue={this.state.verifyType}
|
||||
disabled={this.state.username === ""}
|
||||
placeholder={i18next.t("forget:Choose email or phone")}
|
||||
onChange={(value) => {
|
||||
this.setState({
|
||||
verifyType: value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{
|
||||
this.renderOptions()
|
||||
}
|
||||
</Select>
|
||||
<Select virtual={false}
|
||||
disabled={this.state.isVerifyTypeFixed}
|
||||
style={{textAlign: "left"}}
|
||||
placeholder={i18next.t("forget:Choose email or phone")}
|
||||
>
|
||||
{
|
||||
this.renderOptions()
|
||||
}
|
||||
</Select>
|
||||
}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="emailCode" // use emailCode instead of email/phoneCode to adapt to RequestForm in account.go
|
||||
name="code"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@ -349,21 +333,11 @@ class ForgetPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
{this.state.verifyType === "email" ? (
|
||||
<SendCodeInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
method={"forget"}
|
||||
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
|
||||
application={application}
|
||||
/>
|
||||
) : (
|
||||
<SendCodeInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
method={"forget"}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
|
||||
application={application}
|
||||
/>
|
||||
)}
|
||||
<SendCodeInput disabled={this.state.dest === ""}
|
||||
method={"forget"}
|
||||
onButtonClickArgs={[this.state.dest, this.state.verifyType, Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
|
||||
application={application}
|
||||
/>
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item>
|
||||
@ -375,109 +349,109 @@ class ForgetPage extends React.Component {
|
||||
{i18next.t("forget:Next Step")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Form> : null}
|
||||
|
||||
{/* STEP 3 */}
|
||||
<Form
|
||||
hidden={this.state.current !== 2}
|
||||
ref={this.form}
|
||||
name="step3"
|
||||
onFinish={(values) => this.onFinish(values)}
|
||||
onFinishFailed={(errorInfo) =>
|
||||
this.onFinishFailed(
|
||||
errorInfo.values,
|
||||
errorInfo.errorFields,
|
||||
errorInfo.outOfDate
|
||||
)
|
||||
}
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
}}
|
||||
style={{width: "300px"}}
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
style={{height: 0, visibility: "hidden"}}
|
||||
name="application"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your application!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
style={{height: 0, visibility: "hidden"}}
|
||||
name="organization"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your organization!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="newPassword"
|
||||
hidden={this.state.current !== 2}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your password!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
{this.state.current === 2 ?
|
||||
<Form
|
||||
ref={this.form}
|
||||
name="step3"
|
||||
onFinish={(values) => this.onFinish(values)}
|
||||
onFinishFailed={(errorInfo) =>
|
||||
this.onFinishFailed(
|
||||
errorInfo.values,
|
||||
errorInfo.errorFields,
|
||||
errorInfo.outOfDate
|
||||
)
|
||||
}
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
}}
|
||||
style={{width: "300px"}}
|
||||
size="large"
|
||||
>
|
||||
<Input.Password
|
||||
disabled={this.state.userId === ""}
|
||||
prefix={<LockOutlined />}
|
||||
placeholder={i18next.t("forget:Password")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="confirm"
|
||||
dependencies={["newPassword"]}
|
||||
hasFeedback
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please confirm your password!"
|
||||
),
|
||||
},
|
||||
({getFieldValue}) => ({
|
||||
validator(rule, value) {
|
||||
if (!value || getFieldValue("newPassword") === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
i18next.t(
|
||||
"forget:Your confirmed password is inconsistent with the password!"
|
||||
)
|
||||
);
|
||||
<Form.Item
|
||||
hidden
|
||||
name="application"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your application!"
|
||||
),
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
disabled={this.state.userId === ""}
|
||||
prefix={<CheckCircleOutlined />}
|
||||
placeholder={i18next.t("forget:Confirm")}
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item hidden={this.state.current !== 2}>
|
||||
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
|
||||
{i18next.t("forget:Change Password")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Form.Item
|
||||
hidden
|
||||
name="organization"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your organization!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="newPassword"
|
||||
hidden={this.state.current !== 2}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your password!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password
|
||||
disabled={this.state.userId === ""}
|
||||
prefix={<LockOutlined />}
|
||||
placeholder={i18next.t("forget:Password")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="confirm"
|
||||
dependencies={["newPassword"]}
|
||||
hasFeedback
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please confirm your password!"
|
||||
),
|
||||
},
|
||||
({getFieldValue}) => ({
|
||||
validator(rule, value) {
|
||||
if (!value || getFieldValue("newPassword") === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
i18next.t(
|
||||
"forget:Your confirmed password is inconsistent with the password!"
|
||||
)
|
||||
);
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
disabled={this.state.userId === ""}
|
||||
prefix={<CheckCircleOutlined />}
|
||||
placeholder={i18next.t("forget:Confirm")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item hidden={this.state.current !== 2}>
|
||||
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
|
||||
{i18next.t("forget:Change Password")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form> : null}
|
||||
</Form.Provider>
|
||||
);
|
||||
}
|
||||
@ -517,6 +491,20 @@ class ForgetPage extends React.Component {
|
||||
<Col span={24}>
|
||||
<Steps
|
||||
current={this.state.current}
|
||||
items={[
|
||||
{
|
||||
title: i18next.t("forget:Account"),
|
||||
icon: <UserOutlined />,
|
||||
},
|
||||
{
|
||||
title: i18next.t("forget:Account"),
|
||||
icon: <SolutionOutlined />,
|
||||
},
|
||||
{
|
||||
title: i18next.t("forget:Account"),
|
||||
icon: <KeyOutlined />,
|
||||
},
|
||||
]}
|
||||
style={{
|
||||
width: "90%",
|
||||
maxWidth: "500px",
|
||||
@ -524,24 +512,12 @@ class ForgetPage extends React.Component {
|
||||
marginTop: "80px",
|
||||
}}
|
||||
>
|
||||
<Step
|
||||
title={i18next.t("forget:Account")}
|
||||
icon={<UserOutlined />}
|
||||
/>
|
||||
<Step
|
||||
title={i18next.t("forget:Verify")}
|
||||
icon={<SolutionOutlined />}
|
||||
/>
|
||||
<Step
|
||||
title={i18next.t("forget:Reset")}
|
||||
icon={<KeyOutlined />}
|
||||
/>
|
||||
</Steps>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
||||
<div style={{marginTop: "10px", textAlign: "center"}}>
|
||||
<div style={{marginTop: "40px", textAlign: "center"}}>
|
||||
{this.renderForm(application)}
|
||||
</div>
|
||||
</Col>
|
||||
|
@ -16,6 +16,7 @@ import React from "react";
|
||||
import {Button, Checkbox, Col, Form, Input, Result, Row, Spin, Tabs} from "antd";
|
||||
import {LockOutlined, UserOutlined} from "@ant-design/icons";
|
||||
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
|
||||
import * as Conf from "../Conf";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import * as OrganizationBackend from "../backend/OrganizationBackend";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
@ -189,7 +190,6 @@ class LoginPage extends React.Component {
|
||||
} else {
|
||||
values["type"] = this.state.type;
|
||||
}
|
||||
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
|
||||
|
||||
if (oAuthParams !== null) {
|
||||
values["samlRequest"] = oAuthParams.samlRequest;
|
||||
@ -204,6 +204,7 @@ class LoginPage extends React.Component {
|
||||
values["organization"] = this.getApplicationObj().organization;
|
||||
}
|
||||
}
|
||||
|
||||
postCodeLoginAction(res) {
|
||||
const application = this.getApplicationObj();
|
||||
const ths = this;
|
||||
@ -364,7 +365,8 @@ class LoginPage extends React.Component {
|
||||
title={i18next.t("application:Sign Up Error")}
|
||||
subTitle={i18next.t("application:The application does not allow to sign up new account")}
|
||||
extra={[
|
||||
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
|
||||
<Button type="primary" key="signin"
|
||||
onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
|
||||
{
|
||||
i18next.t("login:Sign In")
|
||||
}
|
||||
@ -383,8 +385,12 @@ class LoginPage extends React.Component {
|
||||
organization: application.organization,
|
||||
application: application.name,
|
||||
autoSignin: true,
|
||||
username: Conf.ShowGithubCorner ? "admin" : "",
|
||||
password: Conf.ShowGithubCorner ? "123" : "",
|
||||
}}
|
||||
onFinish={(values) => {
|
||||
this.onFinish(values);
|
||||
}}
|
||||
onFinish={(values) => {this.onFinish(values);}}
|
||||
style={{width: "300px"}}
|
||||
size="large"
|
||||
ref={this.form}
|
||||
@ -424,7 +430,7 @@ class LoginPage extends React.Component {
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (this.state.loginMethod === "verificationCode") {
|
||||
if (this.state.email !== "" && !Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
|
||||
if (!Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
|
||||
}
|
||||
@ -444,7 +450,7 @@ class LoginPage extends React.Component {
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
id = "input"
|
||||
id="input"
|
||||
prefix={<UserOutlined className="site-form-item-icon" />}
|
||||
placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
|
||||
disabled={!application.enablePassword}
|
||||
@ -774,13 +780,18 @@ class LoginPage extends React.Component {
|
||||
const items = [
|
||||
{label: i18next.t("login:Password"), key: "password"},
|
||||
];
|
||||
application.enableCodeSignin ? items.push({label: i18next.t("login:Verification Code"), key: "verificationCode"}) : null;
|
||||
application.enableCodeSignin ? items.push({
|
||||
label: i18next.t("login:Verification Code"),
|
||||
key: "verificationCode",
|
||||
}) : null;
|
||||
application.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null;
|
||||
|
||||
if (application.enableCodeSignin || application.enableWebAuthn) {
|
||||
return (
|
||||
<div>
|
||||
<Tabs items={items} size={"small"} defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
|
||||
<Tabs items={items} size={"small"} defaultActiveKey="password" onChange={(key) => {
|
||||
this.setState({loginMethod: key});
|
||||
}} centered>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
@ -823,7 +834,7 @@ class LoginPage extends React.Component {
|
||||
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
|
||||
</div>
|
||||
<div className="login-form">
|
||||
<div >
|
||||
<div>
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(application)
|
||||
|
@ -26,6 +26,7 @@ import SelectRegionBox from "../SelectRegionBox";
|
||||
import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import SelectLanguageBox from "../SelectLanguageBox";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import {PhoneNumberInput} from "../common/PhoneNumberInput";
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
@ -68,6 +69,7 @@ class SignupPage extends React.Component {
|
||||
application: null,
|
||||
email: "",
|
||||
phone: "",
|
||||
countryCode: "",
|
||||
emailCode: "",
|
||||
phoneCode: "",
|
||||
validEmail: false,
|
||||
@ -80,7 +82,7 @@ class SignupPage extends React.Component {
|
||||
this.form = React.createRef();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
componentDidMount() {
|
||||
let applicationName = this.state.applicationName;
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
if (oAuthParams !== null) {
|
||||
@ -157,7 +159,6 @@ class SignupPage extends React.Component {
|
||||
|
||||
onFinish(values) {
|
||||
const application = this.getApplicationObj();
|
||||
values.phonePrefix = application.organizationObj.phonePrefix;
|
||||
AuthBackend.signup(values)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
@ -378,35 +379,53 @@ class SignupPage extends React.Component {
|
||||
} else if (signupItem.name === "Phone") {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
key="phone"
|
||||
label={i18next.t("general:Phone")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("signup:Please input your phone number!"),
|
||||
},
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone)) {
|
||||
this.setState({validPhone: false});
|
||||
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
|
||||
}
|
||||
<Form.Item label={i18next.t("general:Phone")} required>
|
||||
<Input.Group compact>
|
||||
<Form.Item
|
||||
name="countryCode"
|
||||
key="countryCode"
|
||||
noStyle
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("signup:Please select your country code!"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<PhoneNumberInput
|
||||
style={{width: "35%"}}
|
||||
countryCodes={this.getApplicationObj().organizationObj.countryCodes}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
key="phone"
|
||||
dependencies={["countryCode"]}
|
||||
noStyle
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("signup:Please input your phone number!"),
|
||||
},
|
||||
({getFieldValue}) => ({
|
||||
validator: (_, value) => {
|
||||
if (value !== "" && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
|
||||
this.setState({validPhone: false});
|
||||
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
|
||||
}
|
||||
|
||||
this.setState({validPhone: true});
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
addonBefore={`+${this.getApplicationObj()?.organizationObj.phonePrefix}`}
|
||||
onChange={e => this.setState({phone: e.target.value})}
|
||||
/>
|
||||
this.setState({validPhone: true});
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
style={{width: "65%"}}
|
||||
onChange={e => this.setState({phone: e.target.value})}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
@ -424,6 +443,7 @@ class SignupPage extends React.Component {
|
||||
method={"signup"}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
countryCode={this.state.countryCode}
|
||||
/>
|
||||
</Form.Item>
|
||||
</React.Fragment>
|
||||
@ -528,6 +548,7 @@ class SignupPage extends React.Component {
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
countryCode: application.organizationObj.countryCodes?.[0],
|
||||
}}
|
||||
size="large"
|
||||
layout={Setting.isMobile() ? "vertical" : "horizontal"}
|
||||
|
60
web/src/backend/FetchFilter.js
Normal file
60
web/src/backend/FetchFilter.js
Normal file
@ -0,0 +1,60 @@
|
||||
// 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.
|
||||
|
||||
import {Modal} from "antd";
|
||||
import {ExclamationCircleFilled} from "@ant-design/icons";
|
||||
import i18next from "i18next";
|
||||
import * as Conf from "../Conf";
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
const {confirm} = Modal;
|
||||
const {fetch: originalFetch} = window;
|
||||
|
||||
const demoModeCallback = (res) => {
|
||||
res.json().then(data => {
|
||||
if (Setting.isResponseDenied(data)) {
|
||||
confirm({
|
||||
title: i18next.t("general:This is a read-only demo site!"),
|
||||
icon: <ExclamationCircleFilled />,
|
||||
content: i18next.t("general:Go to writable demo site?"),
|
||||
okText: i18next.t("user:OK"),
|
||||
cancelText: i18next.t("general:Cancel"),
|
||||
onOk() {
|
||||
Setting.openLink(`https://demo.casdoor.com${location.pathname}${location.search}?username=built-in/admin&password=123`);
|
||||
},
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const requestFilters = [];
|
||||
const responseFilters = [];
|
||||
|
||||
if (Conf.IsDemoMode) {
|
||||
responseFilters.push(demoModeCallback);
|
||||
}
|
||||
|
||||
window.fetch = async(url, option = {}) => {
|
||||
requestFilters.forEach(filter => filter(url, option));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
originalFetch(url, option).then(res => {
|
||||
if (!url.startsWith("/api/get-organizations")) {
|
||||
responseFilters.forEach(filter => filter(res.clone()));
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
};
|
@ -16,7 +16,7 @@ import * as Setting from "../Setting";
|
||||
|
||||
export function getLdaps(owner) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-ldaps?owner=${owner}`, {
|
||||
method: "POST",
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
@ -26,7 +26,7 @@ export function getLdaps(owner) {
|
||||
|
||||
export function getLdap(id) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-ldap?id=${id}`, {
|
||||
method: "POST",
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
|
@ -109,12 +109,13 @@ export function setPassword(userOwner, userName, oldPassword, newPassword) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function sendCode(checkType, checkId, checkKey, method, dest, type, applicationId, checkUser = "") {
|
||||
export function sendCode(checkType, checkId, checkKey, method, countryCode = "", dest, type, applicationId, checkUser = "") {
|
||||
const formData = new FormData();
|
||||
formData.append("checkType", checkType);
|
||||
formData.append("checkId", checkId);
|
||||
formData.append("checkKey", checkKey);
|
||||
formData.append("method", method);
|
||||
formData.append("countryCode", countryCode);
|
||||
formData.append("dest", dest);
|
||||
formData.append("type", type);
|
||||
formData.append("applicationId", applicationId);
|
||||
|
@ -61,8 +61,13 @@ class HomePage extends React.Component {
|
||||
}
|
||||
} else {
|
||||
this.state.applications.forEach(application => {
|
||||
let homepageUrl = application.homepageUrl;
|
||||
if (homepageUrl === "<custom-url>") {
|
||||
homepageUrl = this.props.account.homepage;
|
||||
}
|
||||
|
||||
items.push({
|
||||
link: application.homepageUrl, name: application.displayName, organizer: application.description, logo: application.logo, createdTime: "",
|
||||
link: homepageUrl, name: application.displayName, organizer: application.description, logo: application.logo, createdTime: "",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -16,12 +16,10 @@ import {Button} from "antd";
|
||||
import React from "react";
|
||||
import i18next from "i18next";
|
||||
import {CaptchaModal} from "./CaptchaModal";
|
||||
import * as ProviderBackend from "../backend/ProviderBackend";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
|
||||
export const CaptchaPreview = ({
|
||||
provider,
|
||||
providerName,
|
||||
clientSecret,
|
||||
captchaType,
|
||||
subType,
|
||||
@ -41,9 +39,10 @@ export const CaptchaPreview = ({
|
||||
provider.providerUrl = providerUrl;
|
||||
if (clientSecret !== "***") {
|
||||
provider.clientSecret = clientSecret;
|
||||
ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
|
||||
setOpen(true);
|
||||
});
|
||||
// ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
|
||||
// setOpen(true);
|
||||
// });
|
||||
setOpen(true);
|
||||
} else {
|
||||
setOpen(true);
|
||||
}
|
||||
|
44
web/src/common/PhoneNumberInput.js
Normal file
44
web/src/common/PhoneNumberInput.js
Normal file
@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
|
||||
import {Select} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import React from "react";
|
||||
|
||||
export const PhoneNumberInput = (props) => {
|
||||
const {onChange, style, disabled, value} = props;
|
||||
const countryCodes = props.countryCodes ?? [];
|
||||
|
||||
const handleOnChange = (value) => {
|
||||
onChange?.(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
virtual={false}
|
||||
showSearch
|
||||
style={style}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
dropdownMatchSelectWidth={false}
|
||||
optionLabelProp={"label"}
|
||||
onChange={handleOnChange}
|
||||
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||
>
|
||||
{
|
||||
Setting.getCountryCodeData(countryCodes).map((country) => Setting.getCountryCodeOption(country))
|
||||
}
|
||||
</Select>
|
||||
);
|
||||
};
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Col, Row} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
|
@ -22,7 +22,7 @@ import {CaptchaWidget} from "./CaptchaWidget";
|
||||
const {Search} = Input;
|
||||
|
||||
export const SendCodeInput = (props) => {
|
||||
const {disabled, textBefore, onChange, onButtonClickArgs, application, method} = props;
|
||||
const {disabled, textBefore, onChange, onButtonClickArgs, application, method, countryCode} = props;
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [key, setKey] = React.useState("");
|
||||
const [captchaImg, setCaptchaImg] = React.useState("");
|
||||
@ -53,7 +53,7 @@ export const SendCodeInput = (props) => {
|
||||
const handleOk = () => {
|
||||
setVisible(false);
|
||||
setButtonLoading(true);
|
||||
UserBackend.sendCode(checkType, checkId, key, method, ...onButtonClickArgs).then(res => {
|
||||
UserBackend.sendCode(checkType, checkId, key, method, countryCode, ...onButtonClickArgs).then(res => {
|
||||
setKey("");
|
||||
setButtonLoading(false);
|
||||
if (res) {
|
||||
@ -70,7 +70,7 @@ export const SendCodeInput = (props) => {
|
||||
const loadCaptcha = () => {
|
||||
UserBackend.getCaptcha(application.owner, application.name, false).then(res => {
|
||||
if (res.type === "none") {
|
||||
UserBackend.sendCode("none", "", "", method, ...onButtonClickArgs).then(res => {
|
||||
UserBackend.sendCode("none", "", "", method, countryCode, ...onButtonClickArgs).then(res => {
|
||||
if (res) {
|
||||
handleCountDown(60);
|
||||
}
|
||||
|
@ -13,26 +13,28 @@
|
||||
// limitations under the License.
|
||||
|
||||
import i18n from "i18next";
|
||||
import zh from "./locales/zh/data.json";
|
||||
import en from "./locales/en/data.json";
|
||||
import zh from "./locales/zh/data.json";
|
||||
import es from "./locales/es/data.json";
|
||||
import fr from "./locales/fr/data.json";
|
||||
import de from "./locales/de/data.json";
|
||||
import ja from "./locales/ja/data.json";
|
||||
import ko from "./locales/ko/data.json";
|
||||
import ru from "./locales/ru/data.json";
|
||||
import ja from "./locales/ja/data.json";
|
||||
import es from "./locales/es/data.json";
|
||||
import vi from "./locales/vi/data.json";
|
||||
import * as Conf from "./Conf";
|
||||
import {initReactI18next} from "react-i18next";
|
||||
|
||||
const resources = {
|
||||
en: en,
|
||||
es: es,
|
||||
zh: zh,
|
||||
es: es,
|
||||
fr: fr,
|
||||
de: de,
|
||||
ja: ja,
|
||||
ko: ko,
|
||||
ru: ru,
|
||||
ja: ja,
|
||||
vi: vi,
|
||||
};
|
||||
|
||||
function initLanguage() {
|
||||
@ -43,17 +45,20 @@ function initLanguage() {
|
||||
} else {
|
||||
const userLanguage = navigator.language;
|
||||
switch (userLanguage) {
|
||||
case "en":
|
||||
language = "en";
|
||||
break;
|
||||
case "en-US":
|
||||
language = "en";
|
||||
break;
|
||||
case "zh-CN":
|
||||
language = "zh";
|
||||
break;
|
||||
case "zh":
|
||||
language = "zh";
|
||||
break;
|
||||
case "en":
|
||||
language = "en";
|
||||
break;
|
||||
case "en-US":
|
||||
language = "en";
|
||||
case "es":
|
||||
language = "es";
|
||||
break;
|
||||
case "fr":
|
||||
language = "fr";
|
||||
@ -61,14 +66,17 @@ function initLanguage() {
|
||||
case "de":
|
||||
language = "de";
|
||||
break;
|
||||
case "ja":
|
||||
language = "ja";
|
||||
break;
|
||||
case "ko":
|
||||
language = "ko";
|
||||
break;
|
||||
case "ru":
|
||||
language = "ru";
|
||||
break;
|
||||
case "ja":
|
||||
language = "ja";
|
||||
case "vi":
|
||||
language = "vi";
|
||||
break;
|
||||
default:
|
||||
language = Conf.DefaultLanguage;
|
||||
|
@ -22,6 +22,7 @@ import "./App.less";
|
||||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import {BrowserRouter} from "react-router-dom";
|
||||
import "./backend/FetchFilter";
|
||||
|
||||
const container = document.getElementById("root");
|
||||
|
||||
|
@ -5,45 +5,45 @@
|
||||
"Sign Up": "Registrieren"
|
||||
},
|
||||
"adapter": {
|
||||
"Edit Adapter": "Edit Adapter",
|
||||
"Failed to sync policies": "Failed to sync policies",
|
||||
"New Adapter": "New Adapter",
|
||||
"Policies": "Policies",
|
||||
"Policies - Tooltip": "Policies - Tooltip",
|
||||
"Repeated policy rules": "Repeated policy rules",
|
||||
"Sync": "Sync",
|
||||
"Sync policies successfully": "Sync policies successfully"
|
||||
"Edit Adapter": "Adapter bearbeiten",
|
||||
"Failed to sync policies": "Fehler beim Synchronisieren der Richtlinien",
|
||||
"New Adapter": "Neuer Adapter",
|
||||
"Policies": "Richtlinien",
|
||||
"Policies - Tooltip": "Richtlinien - Tooltip",
|
||||
"Repeated policy rules": "Wiederholte Regeln der Richtlinien",
|
||||
"Sync": "Synchronisation",
|
||||
"Sync policies successfully": "Richtlinien erfolgreich synchronisiert"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
"Auto signin": "Auto signin",
|
||||
"Auto signin - Tooltip": "Auto signin - Tooltip",
|
||||
"Background URL": "Background URL",
|
||||
"Background URL - Tooltip": "Background URL - Tooltip",
|
||||
"Center": "Center",
|
||||
"Copy SAML metadata URL": "Copy SAML metadata URL",
|
||||
"Always": "Immer",
|
||||
"Auto signin": "Automatische Anmeldung",
|
||||
"Auto signin - Tooltip": "Automatische Anmeldung - Tooltip",
|
||||
"Background URL": "Hintergrund-URL",
|
||||
"Background URL - Tooltip": "Hintergrund-URL - Kurzinfo",
|
||||
"Center": "Zentriert",
|
||||
"Copy SAML metadata URL": "SAML-Metadaten URL kopieren",
|
||||
"Copy prompt page URL": "Copy prompt page URL",
|
||||
"Copy signin page URL": "Copy signin page URL",
|
||||
"Copy signup page URL": "Copy signup page URL",
|
||||
"Copy signin page URL": "URL der Anmeldeseite kopieren",
|
||||
"Copy signup page URL": "URL der Registrierungsseite kopieren",
|
||||
"Edit Application": "Anwendung bearbeiten",
|
||||
"Enable SAML compress": "Enable SAML compress",
|
||||
"Enable SAML compress - Tooltip": "Enable SAML compress - Tooltip",
|
||||
"Enable WebAuthn signin": "Enable WebAuthn signin",
|
||||
"Enable WebAuthn signin - Tooltip": "Enable WebAuthn signin - Tooltip",
|
||||
"Enable SAML compress": "SAML-Komprimierung aktivieren",
|
||||
"Enable SAML compress - Tooltip": "SAML-Komprimierung aktivieren - Tooltip",
|
||||
"Enable WebAuthn signin": "WebAuthn Anmeldung aktivieren",
|
||||
"Enable WebAuthn signin - Tooltip": "WebAuthn Anmeldung aktivieren - Tooltip",
|
||||
"Enable code signin": "Code-Anmeldung aktivieren",
|
||||
"Enable code signin - Tooltip": "Aktiviere Codeanmeldung - Tooltip",
|
||||
"Enable link accounts that with the same email": "Enable link accounts that with the same email",
|
||||
"Enable link accounts that with the same email - Tooltip": "Enable link accounts that with the same email - Tooltip",
|
||||
"Enable side panel": "Enable side panel",
|
||||
"Enable side panel": "Seitenmenü aktivieren",
|
||||
"Enable signin session - Tooltip": "Aktiviere Anmeldesession - Tooltip",
|
||||
"Enable signup": "Anmeldung aktivieren",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"Failed to sign in": "Failed to sign in",
|
||||
"Failed to sign in": "Anmeldung fehlgeschlagen",
|
||||
"File uploaded successfully": "Datei erfolgreich hochgeladen",
|
||||
"Follow organization theme": "Follow organization theme",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
||||
"Form CSS": "Formular-CSS",
|
||||
"Form CSS - Edit": "Formular-CSS - Bearbeiten",
|
||||
"Form CSS - Tooltip": "Formular-CSS - Tooltip",
|
||||
"Form position": "Form position",
|
||||
"Form position - Tooltip": "Form position - Tooltip",
|
||||
"Grant types": "Grant types",
|
||||
@ -181,6 +181,8 @@
|
||||
"First name": "First name",
|
||||
"Forget URL": "URL vergessen",
|
||||
"Forget URL - Tooltip": "Unique string-style identifier",
|
||||
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
|
||||
"Go to writable demo site?": "Go to writable demo site?",
|
||||
"Home": "Zuhause",
|
||||
"Home - Tooltip": "Application homepage",
|
||||
"ID": "ID",
|
||||
@ -218,8 +220,6 @@
|
||||
"Permissions - Tooltip": "Permissions - Tooltip",
|
||||
"Phone": "Telefon",
|
||||
"Phone - Tooltip": "Phone",
|
||||
"Phone prefix": "Telefonpräfix",
|
||||
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
|
||||
"Preview": "Vorschau",
|
||||
"Preview - Tooltip": "The form in which the password is stored in the database",
|
||||
"Products": "Products",
|
||||
@ -251,10 +251,13 @@
|
||||
"Successfully added": "Successfully added",
|
||||
"Successfully deleted": "Successfully deleted",
|
||||
"Successfully saved": "Successfully saved",
|
||||
"Supported country codes": "Supported country codes",
|
||||
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Syncers",
|
||||
"SysInfo": "SysInfo",
|
||||
"This is a read-only demo site!": "This is a read-only demo site!",
|
||||
"Timestamp": "Zeitstempel",
|
||||
"Tokens": "Token",
|
||||
"URL": "URL",
|
||||
@ -309,10 +312,10 @@
|
||||
"Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an",
|
||||
"Password": "Passwort",
|
||||
"Password - Tooltip": "Passwort - Tooltip",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "Bitte gib deinen Code ein!",
|
||||
"Please input your password!": "Bitte geben Sie Ihr Passwort ein!",
|
||||
"Please input your password, at least 6 characters!": "Bitte geben Sie Ihr Passwort ein, mindestens 6 Zeichen!",
|
||||
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Anmelden",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
@ -638,6 +641,7 @@
|
||||
"Please input your last name!": "Please input your last name!",
|
||||
"Please input your phone number!": "Bitte geben Sie Ihre Telefonnummer ein!",
|
||||
"Please input your real name!": "Bitte geben Sie Ihren persönlichen Namen ein!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Bitte wählen Sie Ihr Land/Ihre Region!",
|
||||
"Terms of Use": "Nutzungsbedingungen",
|
||||
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
|
||||
@ -724,6 +728,7 @@
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Code gesendet",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Land/Region",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
"Edit User": "Benutzer bearbeiten",
|
||||
|
@ -181,6 +181,8 @@
|
||||
"First name": "First name",
|
||||
"Forget URL": "Forget URL",
|
||||
"Forget URL - Tooltip": "Forget URL - Tooltip",
|
||||
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
|
||||
"Go to writable demo site?": "Go to writable demo site?",
|
||||
"Home": "Home",
|
||||
"Home - Tooltip": "Home - Tooltip",
|
||||
"ID": "ID",
|
||||
@ -218,8 +220,6 @@
|
||||
"Permissions - Tooltip": "Permissions - Tooltip",
|
||||
"Phone": "Phone",
|
||||
"Phone - Tooltip": "Phone - Tooltip",
|
||||
"Phone prefix": "Phone prefix",
|
||||
"Phone prefix - Tooltip": "Phone prefix - Tooltip",
|
||||
"Preview": "Preview",
|
||||
"Preview - Tooltip": "Preview - Tooltip",
|
||||
"Products": "Products",
|
||||
@ -251,10 +251,13 @@
|
||||
"Successfully added": "Successfully added",
|
||||
"Successfully deleted": "Successfully deleted",
|
||||
"Successfully saved": "Successfully saved",
|
||||
"Supported country codes": "Supported country codes",
|
||||
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Syncers",
|
||||
"SysInfo": "SysInfo",
|
||||
"This is a read-only demo site!": "This is a read-only demo site!",
|
||||
"Timestamp": "Timestamp",
|
||||
"Tokens": "Tokens",
|
||||
"URL": "URL",
|
||||
@ -309,10 +312,10 @@
|
||||
"Or sign in with another account": "Or sign in with another account",
|
||||
"Password": "Password",
|
||||
"Password - Tooltip": "Password - Tooltip",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "Please input your code!",
|
||||
"Please input your password!": "Please input your password!",
|
||||
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
|
||||
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Sign In",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
@ -638,6 +641,7 @@
|
||||
"Please input your last name!": "Please input your last name!",
|
||||
"Please input your phone number!": "Please input your phone number!",
|
||||
"Please input your real name!": "Please input your real name!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Please select your country/region!",
|
||||
"Terms of Use": "Terms of Use",
|
||||
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
|
||||
@ -724,6 +728,7 @@
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Code Sent",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Country/Region",
|
||||
"Country/Region - Tooltip": "Country/Region - Tooltip",
|
||||
"Edit User": "Edit User",
|
||||
|
@ -181,6 +181,8 @@
|
||||
"First name": "Nombre",
|
||||
"Forget URL": "URL de olvido",
|
||||
"Forget URL - Tooltip": "URL de olvido - Tooltip",
|
||||
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
|
||||
"Go to writable demo site?": "Go to writable demo site?",
|
||||
"Home": "Inicio",
|
||||
"Home - Tooltip": "Inicio - Tooltip",
|
||||
"ID": "ID",
|
||||
@ -218,8 +220,6 @@
|
||||
"Permissions - Tooltip": "Permisos - Tooltip",
|
||||
"Phone": "Teléfono",
|
||||
"Phone - Tooltip": "Teléfono - Tooltip",
|
||||
"Phone prefix": "Prefijo teléfonico",
|
||||
"Phone prefix - Tooltip": "Prefijo teléfonico - Tooltip",
|
||||
"Preview": "Previsualizar",
|
||||
"Preview - Tooltip": "Previsualizar - Tooltip",
|
||||
"Products": "Productos",
|
||||
@ -251,10 +251,13 @@
|
||||
"Successfully added": "Successfully added",
|
||||
"Successfully deleted": "Successfully deleted",
|
||||
"Successfully saved": "Successfully saved",
|
||||
"Supported country codes": "Supported country codes",
|
||||
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sincronizador",
|
||||
"Syncers": "Sincronizadores",
|
||||
"SysInfo": "SysInfo",
|
||||
"This is a read-only demo site!": "This is a read-only demo site!",
|
||||
"Timestamp": "Timestamp",
|
||||
"Tokens": "Tokens",
|
||||
"URL": "URL",
|
||||
@ -309,10 +312,10 @@
|
||||
"Or sign in with another account": "O inicia sesión con otra cuenta",
|
||||
"Password": "Contraseña",
|
||||
"Password - Tooltip": "Contraseña - Tooltip",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "¡Por favor ingrese su código!",
|
||||
"Please input your password!": "¡Por favor ingrese su contraseña!",
|
||||
"Please input your password, at least 6 characters!": "Su contraseña debe contener al menos 6 caracteres.",
|
||||
"Please input your username, Email or phone!": "¡Ingrese su nombre de usuario, correo electrónico o teléfono!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Iniciar de sesión",
|
||||
"Sign in with WebAuthn": "Iniciar de sesión con WebAuthn",
|
||||
@ -638,6 +641,7 @@
|
||||
"Please input your last name!": "Por favor, ingrese su apellido!",
|
||||
"Please input your phone number!": "Por favor, ingrese su número teléfonico!",
|
||||
"Please input your real name!": "Por favor, ingrese un nombre real!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Por favor, seleccione su pais/region!",
|
||||
"Terms of Use": "Términos de Uso",
|
||||
"The input is not invoice Tax ID!": "El valor ingresado no es un número de identificación fiscal de factura!",
|
||||
@ -724,6 +728,7 @@
|
||||
"Captcha Verify Failed": "Fallo la verificación del Captcha",
|
||||
"Captcha Verify Success": "Captcha verificado con éxito",
|
||||
"Code Sent": "Código enviado",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Pais/Región",
|
||||
"Country/Region - Tooltip": "Pais/Región - Tooltip",
|
||||
"Edit User": "Editar usuario",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user