feat: support multiple country codes for sending SMS (#1557)

* feat: support multiple country code

* feat: improve UI

* feat: migrate accountItem

* fix: Aliyun compatible

* fix: phone validate

* fix: typo
This commit is contained in:
Yaodong Yu
2023-02-16 22:53:28 +08:00
committed by GitHub
parent 4605938f8e
commit eb72c9f273
52 changed files with 560 additions and 258 deletions

View File

@ -58,7 +58,7 @@ type RequestForm struct {
EmailCode string `json:"emailCode"` EmailCode string `json:"emailCode"`
PhoneCode string `json:"phoneCode"` PhoneCode string `json:"phoneCode"`
PhonePrefix string `json:"phonePrefix"` CountryCode string `json:"countryCode"`
AutoSignin bool `json:"autoSignin"` AutoSignin bool `json:"autoSignin"`
@ -121,7 +121,7 @@ func (c *ApiController) Signup() {
} }
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization)) 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 != "" { if msg != "" {
c.ResponseError(msg) c.ResponseError(msg)
return return
@ -137,7 +137,7 @@ func (c *ApiController) Signup() {
var checkPhone string var checkPhone string
if application.IsSignupItemVisible("Phone") && form.Phone != "" { 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()) checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
if len(checkResult) != 0 { if len(checkResult) != 0 {
c.ResponseError(c.T("account:Phone: %s"), checkResult) c.ResponseError(c.T("account:Phone: %s"), checkResult)
@ -179,6 +179,7 @@ func (c *ApiController) Signup() {
Avatar: organization.DefaultAvatar, Avatar: organization.DefaultAvatar,
Email: form.Email, Email: form.Email,
Phone: form.Phone, Phone: form.Phone,
CountryCode: form.CountryCode,
Address: []string{}, Address: []string{},
Affiliation: form.Affiliation, Affiliation: form.Affiliation,
IdCard: form.IdCard, IdCard: form.IdCard,

View File

@ -263,34 +263,32 @@ func (c *ApiController) Login() {
checkDest = form.Username checkDest = form.Username
} else { } else {
verificationCodeType = "phone" 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 { if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
form.Username = user.Phone 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))) c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
return 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()) checkResult = object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
if len(checkResult) != 0 { if len(checkResult) != 0 {
responseText := fmt.Sprintf("%s - %s", verificationCodeType, checkResult) c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
c.ResponseError(responseText)
return return
} }
// disable the verification code // disable the verification code
if strings.Contains(form.Username, "@") { object.DisableVerificationCode(checkDest)
object.DisableVerificationCode(form.Username)
} else {
object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username))
}
} else { } else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil { if application == nil {

View File

@ -130,13 +130,13 @@ func (c *ApiController) SendSms() {
return return
} }
org := object.GetOrganization(smsForm.OrgId)
var invalidReceivers []string var invalidReceivers []string
for idx, receiver := range smsForm.Receivers { 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) invalidReceivers = append(invalidReceivers, receiver)
} else { } else {
smsForm.Receivers[idx] = fmt.Sprintf("+%s%s", org.PhonePrefix, receiver) smsForm.Receivers[idx] = receiver
} }
} }

View File

@ -62,6 +62,10 @@ func checkPermissionForUpdateUser(userId string, newUser object.User, c *ApiCont
item := object.GetAccountItemByName("Phone", organization) item := object.GetAccountItemByName("Phone", organization)
itemsChanged = append(itemsChanged, item) itemsChanged = append(itemsChanged, item)
} }
if oldUser.CountryCode != newUser.CountryCode {
item := object.GetAccountItemByName("Country code", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Region != newUser.Region { if oldUser.Region != newUser.Region {
item := object.GetAccountItemByName("Country/Region", organization) item := object.GetAccountItemByName("Country/Region", organization)
itemsChanged = append(itemsChanged, item) itemsChanged = append(itemsChanged, item)

View File

@ -24,6 +24,13 @@ import (
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
const (
SignupVerification = "signup"
ResetVerification = "reset"
LoginVerification = "login"
ForgetVerification = "forget"
)
func (c *ApiController) getCurrentUser() *object.User { func (c *ApiController) getCurrentUser() *object.User {
var user *object.User var user *object.User
userId := c.GetSessionUsername() userId := c.GetSessionUsername()
@ -42,18 +49,15 @@ func (c *ApiController) getCurrentUser() *object.User {
func (c *ApiController) SendVerificationCode() { func (c *ApiController) SendVerificationCode() {
destType := c.Ctx.Request.Form.Get("type") destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest") dest := c.Ctx.Request.Form.Get("dest")
countryCode := c.Ctx.Request.Form.Get("countryCode")
checkType := c.Ctx.Request.Form.Get("checkType") checkType := c.Ctx.Request.Form.Get("checkType")
checkId := c.Ctx.Request.Form.Get("checkId") checkId := c.Ctx.Request.Form.Get("checkId")
checkKey := c.Ctx.Request.Form.Get("checkKey") checkKey := c.Ctx.Request.Form.Get("checkKey")
checkUser := c.Ctx.Request.Form.Get("checkUser")
applicationId := c.Ctx.Request.Form.Get("applicationId") applicationId := c.Ctx.Request.Form.Get("applicationId")
method := c.Ctx.Request.Form.Get("method") method := c.Ctx.Request.Form.Get("method")
checkUser := c.Ctx.Request.Form.Get("checkUser")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request) remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if destType == "" {
c.ResponseError(c.T("general:Missing parameter") + ": type.")
return
}
if dest == "" { if dest == "" {
c.ResponseError(c.T("general:Missing parameter") + ": dest.") c.ResponseError(c.T("general:Missing parameter") + ": dest.")
return return
@ -62,98 +66,101 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError(c.T("general:Missing parameter") + ": applicationId.") c.ResponseError(c.T("general:Missing parameter") + ": applicationId.")
return return
} }
if !strings.Contains(applicationId, "/") {
c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.")
return
}
if checkType == "" { if checkType == "" {
c.ResponseError(c.T("general:Missing parameter") + ": checkType.") c.ResponseError(c.T("general:Missing parameter") + ": checkType.")
return return
} }
captchaProvider := captcha.GetCaptchaProvider(checkType)
if captchaProvider != nil {
if checkKey == "" { if checkKey == "" {
c.ResponseError(c.T("general:Missing parameter") + ": checkKey.") c.ResponseError(c.T("general:Missing parameter") + ": checkKey.")
return return
} }
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId) if !strings.Contains(applicationId, "/") {
if err != nil { c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.")
c.ResponseError(err.Error())
return return
} }
if !isHuman { 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
} else if !isHuman {
c.ResponseError(c.T("verification:Turing test failed.")) c.ResponseError(c.T("verification:Turing test failed."))
return return
} }
}
user := c.getCurrentUser()
application := object.GetApplication(applicationId) 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 { if organization == nil {
c.ResponseError(c.T("verification:Organization does not exist")) c.ResponseError(c.T("verification:Organization does not exist"))
return return
} }
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil { var user *object.User
c.ResponseError(c.T("general:Please login first")) // checkUser != "", means method is ForgetVerification
return if checkUser != "" {
owner := application.Organization
user = object.GetUser(util.GetId(owner, checkUser))
} }
sendResp := errors.New("invalid dest type") 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 { switch destType {
case "email": case "email":
if user != nil && util.GetMaskedEmail(user.Email) == dest {
dest = user.Email
}
if !util.IsEmailValid(dest) { if !util.IsEmailValid(dest) {
c.ResponseError(c.T("verification:Email is invalid")) c.ResponseError(c.T("verification:Email is invalid"))
return return
} }
userByEmail := object.GetUserByEmail(organization.Name, dest) if method == LoginVerification || method == ForgetVerification {
if userByEmail == nil && method != "signup" && method != "reset" { 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")) c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
return return
} }
} else if method == ResetVerification {
user = c.getCurrentUser()
}
provider := application.GetEmailProvider() provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest) sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone": case "phone":
if method == LoginVerification || method == ForgetVerification {
if user != nil && util.GetMaskedPhone(user.Phone) == dest { if user != nil && util.GetMaskedPhone(user.Phone) == dest {
dest = user.Phone dest = user.Phone
} }
if !util.IsPhoneCnValid(dest) {
c.ResponseError(c.T("verification:Phone number is invalid"))
return
}
userByPhone := object.GetUserByPhone(organization.Name, dest) if user = object.GetUserByPhone(organization.Name, dest); user == nil {
if userByPhone == nil && method != "signup" && method != "reset" {
c.ResponseError(c.T("verification:the user does not exist, please sign up first")) c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
return return
} }
dest = fmt.Sprintf("+%s%s", organization.PhonePrefix, dest) countryCode = user.GetCountryCode(countryCode)
} else if method == ResetVerification {
if user = c.getCurrentUser(); user != nil {
countryCode = user.GetCountryCode(countryCode)
}
}
provider := application.GetSmsProvider() 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 { if sendResp != nil {
c.Data["json"] = Response{Status: "error", Msg: sendResp.Error()} c.ResponseError(sendResp.Error())
} else { } else {
c.Data["json"] = Response{Status: "ok"} c.ResponseOk()
} }
c.ServeJSON()
} }
// ResetEmailOrPhone ... // ResetEmailOrPhone ...
@ -169,7 +176,8 @@ func (c *ApiController) ResetEmailOrPhone() {
destType := c.Ctx.Request.Form.Get("type") destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest") dest := c.Ctx.Request.Form.Get("dest")
code := c.Ctx.Request.Form.Get("code") code := c.Ctx.Request.Form.Get("code")
if len(dest) == 0 || len(code) == 0 || len(destType) == 0 {
if util.IsStrsEmpty(destType, dest, code) {
c.ResponseError(c.T("general:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }
@ -192,12 +200,10 @@ func (c *ApiController) ResetEmailOrPhone() {
c.ResponseError(errMsg) c.ResponseError(errMsg)
return return
} }
if checkDest, ok = util.GetE164Number(dest, user.GetCountryCode("")); !ok {
phonePrefix := "86" c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
if organization != nil && organization.PhonePrefix != "" { return
phonePrefix = organization.PhonePrefix
} }
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
} else if destType == "email" { } else if destType == "email" {
if object.HasUserByField(user.Owner, "email", user.Email) { if object.HasUserByField(user.Owner, "email", user.Email) {
c.ResponseError(c.T("check:Email already exists")) c.ResponseError(c.T("check:Email already exists"))
@ -215,8 +221,8 @@ func (c *ApiController) ResetEmailOrPhone() {
return return
} }
} }
if ret := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); len(ret) != 0 { if msg := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); len(msg) != 0 {
c.ResponseError(ret) c.ResponseError(msg)
return return
} }
@ -233,8 +239,7 @@ func (c *ApiController) ResetEmailOrPhone() {
} }
object.DisableVerificationCode(checkDest) object.DisableVerificationCode(checkDest)
c.Data["json"] = Response{Status: "ok"} c.ResponseOk()
c.ServeJSON()
} }
// VerifyCaptcha ... // VerifyCaptcha ...

1
go.mod
View File

@ -30,6 +30,7 @@ require (
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/markbates/goth v1.75.2 github.com/markbates/goth v1.75.2
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect 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/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.6.0 github.com/russellhaering/gosaml2 v0.6.0

2
go.sum
View File

@ -354,6 +354,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/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 h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= 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.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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=

View File

@ -9,7 +9,6 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "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 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", "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 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": "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", "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", "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" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter", "Missing parameter": "Missing parameter",
"Please login first": "Please login first", "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": {
"Ldap server exist": "Ldap server exist" "Ldap server exist": "Ldap server exist"
@ -130,7 +131,7 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Organization does not exist": "Organization does not exist", "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.", "Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.", "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.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.",

View File

@ -9,7 +9,6 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "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 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", "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 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": "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", "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", "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" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter", "Missing parameter": "Missing parameter",
"Please login first": "Please login first", "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": {
"Ldap server exist": "Ldap server exist" "Ldap server exist": "Ldap server exist"
@ -130,7 +131,7 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Organization does not exist": "Organization does not exist", "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.", "Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.", "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.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.",

View File

@ -9,7 +9,6 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "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 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", "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 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": "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", "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", "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" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter", "Missing parameter": "Missing parameter",
"Please login first": "Please login first", "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": {
"Ldap server exist": "Ldap server exist" "Ldap server exist": "Ldap server exist"
@ -130,7 +131,7 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Organization does not exist": "Organization does not exist", "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.", "Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.", "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.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.",

View File

@ -9,7 +9,6 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "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 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", "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 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": "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", "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", "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" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter", "Missing parameter": "Missing parameter",
"Please login first": "Please login first", "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": {
"Ldap server exist": "Ldap server exist" "Ldap server exist": "Ldap server exist"
@ -130,7 +131,7 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Organization does not exist": "Organization does not exist", "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.", "Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.", "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.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.",

View File

@ -9,7 +9,6 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "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 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", "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 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": "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", "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", "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" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter", "Missing parameter": "Missing parameter",
"Please login first": "Please login first", "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": {
"Ldap server exist": "Ldap server exist" "Ldap server exist": "Ldap server exist"
@ -130,7 +131,7 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Organization does not exist": "Organization does not exist", "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.", "Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.", "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.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.",

View File

@ -9,7 +9,6 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "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 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", "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 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": "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", "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", "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" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter", "Missing parameter": "Missing parameter",
"Please login first": "Please login first", "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": {
"Ldap server exist": "Ldap server exist" "Ldap server exist": "Ldap server exist"
@ -130,7 +131,7 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Organization does not exist": "Organization does not exist", "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.", "Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.", "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.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.",

View File

@ -9,7 +9,6 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "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 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", "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 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": "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", "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", "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" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter", "Missing parameter": "Missing parameter",
"Please login first": "Please login first", "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": {
"Ldap server exist": "Ldap server exist" "Ldap server exist": "Ldap server exist"
@ -130,7 +131,7 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Organization does not exist": "Organization does not exist", "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.", "Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.", "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.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.",

View File

@ -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": { "auth": {
"%s No phone prefix": "%s 无此手机号前缀",
"Challenge method should be S256": "Challenge 方法应该为 S256", "Challenge method should be S256": "Challenge 方法应该为 S256",
"Failed to create user, user information is invalid: %s": "创建用户失败,用户信息无效: %s", "Failed to create user, user information is invalid: %s": "创建用户失败,用户信息无效: %s",
"Failed to login in: %s": "登录失败: %s", "Failed to login in: %s": "登录失败: %s",
@ -57,13 +56,15 @@
"Username is too long (maximum is 39 characters).": "用户名过长最大允许长度为39个字符", "Username is too long (maximum is 39 characters).": "用户名过长最大允许长度为39个字符",
"Username must have at least 2 characters": "用户名至少要有2个字符", "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 分后重试", "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 次尝试的机会", "password or code is incorrect, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会",
"unsupported password type: %s": "不支持的密码类型: %s" "unsupported password type: %s": "不支持的密码类型: %s"
}, },
"general": { "general": {
"Missing parameter": "缺少参数", "Missing parameter": "缺少参数",
"Please login first": "请先登录", "Please login first": "请先登录",
"The user: %s doesn't exist": "用户: %s 不存在" "The user: %s doesn't exist": "用户: %s 不存在",
"don't support captchaProvider: ": "不支持验证码提供商: "
}, },
"ldap": { "ldap": {
"Ldap server exist": "LDAP服务器已存在" "Ldap server exist": "LDAP服务器已存在"
@ -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 in your region %s": "您所在地区的电话号码无效 %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.": "无法获取手机号修改规则",

View File

@ -7,7 +7,7 @@
"websiteUrl": "", "websiteUrl": "",
"favicon": "", "favicon": "",
"passwordType": "", "passwordType": "",
"phonePrefix": "", "countryCodes": [""],
"defaultAvatar": "", "defaultAvatar": "",
"tags": [""] "tags": [""]
} }
@ -107,6 +107,7 @@
"avatar": "", "avatar": "",
"email": "", "email": "",
"phone": "", "phone": "",
"countryCode": "",
"address": [], "address": [],
"affiliation": "", "affiliation": "",
"tag": "", "tag": "",

View File

@ -42,7 +42,7 @@ func init() {
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`) 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 { if organization == nil {
return i18n.Translate(lang, "check:Organization does not exist") 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) { if HasUserByField(organization.Name, "phone", phone) {
return i18n.Translate(lang, "check:Phone already exists") 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") return i18n.Translate(lang, "check:Phone number is invalid")
} }
} }

View File

@ -53,7 +53,7 @@ func initBuiltInOrganization() bool {
WebsiteUrl: "https://example.com", WebsiteUrl: "https://example.com",
Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")), Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")),
PasswordType: "plain", PasswordType: "plain",
PhonePrefix: "86", CountryCodes: []string{"CN"},
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{}, Tags: []string{},
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"}, Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"},
@ -68,6 +68,7 @@ func initBuiltInOrganization() bool {
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "CountryCode", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
@ -109,6 +110,7 @@ func initBuiltInUser() {
Avatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), Avatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Email: "admin@example.com", Email: "admin@example.com",
Phone: "12345678910", Phone: "12345678910",
CountryCode: "CN",
Address: []string{}, Address: []string{},
Affiliation: "Example Inc.", Affiliation: "Example Inc.",
Tag: "staff", Tag: "staff",

View File

@ -189,6 +189,7 @@ func initDefinedOrganization(organization *Organization) {
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "CountryCode", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},

View File

@ -26,6 +26,7 @@ func DoMigration() {
&Migrator_1_101_0_PR_1083{}, &Migrator_1_101_0_PR_1083{},
&Migrator_1_235_0_PR_1530{}, &Migrator_1_235_0_PR_1530{},
&Migrator_1_240_0_PR_1539{}, &Migrator_1_240_0_PR_1539{},
&Migrator_1_245_0_PR_1557{},
// more migrators add here in chronological order... // more migrators add here in chronological order...
} }

View File

@ -0,0 +1,92 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/nyaruka/phonenumbers"
"github.com/xorm-io/xorm"
"github.com/xorm-io/xorm/migrate"
)
type Migrator_1_245_0_PR_1557 struct{}
func (*Migrator_1_245_0_PR_1557) IsMigrationNeeded() bool {
exist, _ := adapter.Engine.IsTableExist("organization")
if exist {
return true
}
return false
}
func (*Migrator_1_245_0_PR_1557) DoMigration() *migrate.Migration {
migration := migrate.Migration{
ID: "20230215organization--transfer phonePrefix to defaultCountryCode, countryCodes",
Migrate: func(engine *xorm.Engine) error {
organizations := []*Organization{}
err := engine.Table("organization").Find(&organizations, &Organization{})
if err != nil {
panic(err)
}
for _, organization := range organizations {
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: "CountryCode", 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"},
}
sql := fmt.Sprintf("select phone_prefix from organization where owner='%s' and name='%s'", organization.Owner, organization.Name)
results, _ := engine.Query(sql)
phonePrefix := util.ParseInt(string(results[0]["phone_prefix"]))
organization.CountryCodes = []string{phonenumbers.GetRegionCodeForCountryCode(phonePrefix)}
UpdateOrganization(util.GetId(organization.Owner, organization.Name), organization)
}
return err
},
}
return &migration
}

View File

@ -49,7 +49,7 @@ type Organization struct {
Favicon string `xorm:"varchar(100)" json:"favicon"` Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"` PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"` 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"` DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"` DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`

View File

@ -14,7 +14,11 @@
package object 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 { 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) 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 return err
} }
if provider.Type == go_sms_sender.Aliyun {
for i, number := range phoneNumbers {
phoneNumbers[i] = strings.TrimPrefix(number, "+")
}
}
params := map[string]string{} params := map[string]string{}
if provider.Type == go_sms_sender.TencentCloud { if provider.Type == go_sms_sender.TencentCloud {
params["0"] = content params["0"] = content

View File

@ -46,7 +46,8 @@ type User struct {
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"` PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"` Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"` 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"` Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"` Address []string `json:"address"`
Affiliation string `xorm:"varchar(100)" json:"affiliation"` Affiliation string `xorm:"varchar(100)" json:"affiliation"`
@ -454,7 +455,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
} }
} }
if isGlobalAdmin { 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) affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)

View File

@ -170,3 +170,18 @@ func ClearUserOAuthProperties(user *User, providerType string) bool {
return affected != 0 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 ""
}

View File

@ -14,6 +14,8 @@
package util package util
import "sort"
func DeleteVal(values []string, val string) []string { func DeleteVal(values []string, val string) []string {
newValues := []string{} newValues := []string{}
for _, v := range values { for _, v := range values {
@ -23,3 +25,8 @@ func DeleteVal(values []string, val string) []string {
} }
return newValues return newValues
} }
func ContainsString(values []string, val string) bool {
sort.Strings(values)
return sort.SearchStrings(values, val) != len(values)
}

View File

@ -227,7 +227,7 @@ func IsChinese(str string) bool {
} }
func GetMaskedPhone(phone string) string { func GetMaskedPhone(phone string) string {
return getMaskedPhone(phone) return rePhone.ReplaceAllString(phone, "$1****$2")
} }
func GetMaskedEmail(email string) string { func GetMaskedEmail(email string) string {

View File

@ -17,16 +17,13 @@ package util
import ( import (
"net/mail" "net/mail"
"regexp" "regexp"
"github.com/nyaruka/phonenumbers"
) )
var ( var rePhone *regexp.Regexp
rePhoneCn *regexp.Regexp
rePhone *regexp.Regexp
)
func init() { 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})") rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})")
} }
@ -35,10 +32,19 @@ func IsEmailValid(email string) bool {
return err == nil return err == nil
} }
func IsPhoneCnValid(phone string) bool { func IsPhoneValid(phone string, countryCode string) bool {
return rePhoneCn.MatchString(phone) phoneNumber, err := phonenumbers.Parse(phone, countryCode)
if err != nil {
return false
}
return phonenumbers.IsValidNumber(phoneNumber)
} }
func getMaskedPhone(phone string) string { func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
return rePhone.ReplaceAllString(phone, "$1****$2") 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)
} }

View File

@ -15,7 +15,6 @@ describe("Login test", () => {
"password": "123", "password": "123",
"autoSignin": true, "autoSignin": true,
"type": "login", "type": "login",
"phonePrefix": "86",
}, },
}).then((Response) => { }).then((Response) => {
expect(Response).property("body").property("status").to.equal("ok"); expect(Response).property("body").property("status").to.equal("ok");
@ -40,7 +39,6 @@ describe("Login test", () => {
"password": "1234", "password": "1234",
"autoSignin": true, "autoSignin": true,
"type": "login", "type": "login",
"phonePrefix": "86",
}, },
}).then((Response) => { }).then((Response) => {
expect(Response).property("body").property("status").to.equal("error"); expect(Response).property("body").property("status").to.equal("error");

View File

@ -34,7 +34,6 @@ Cypress.Commands.add('login', ()=>{
"password": "123", "password": "123",
"autoSignin": true, "autoSignin": true,
"type": "login", "type": "login",
"phonePrefix": "86",
}, },
}).then((Response) => { }).then((Response) => {
expect(Response).property("body").property("status").to.equal("ok"); expect(Response).property("body").property("status").to.equal("ok");

View File

@ -21,6 +21,7 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18n-iso-countries": "^7.0.0", "i18n-iso-countries": "^7.0.0",
"i18next": "^19.8.9", "i18next": "^19.8.9",
"libphonenumber-js": "^1.10.19",
"moment": "^2.29.1", "moment": "^2.29.1",
"qs": "^6.10.2", "qs": "^6.10.2",
"react": "^18.2.0", "react": "^18.2.0",

View File

@ -78,6 +78,7 @@ class AccountTable extends React.Component {
{name: "Password", displayName: i18next.t("general:Password")}, {name: "Password", displayName: i18next.t("general:Password")},
{name: "Email", displayName: i18next.t("general:Email")}, {name: "Email", displayName: i18next.t("general:Email")},
{name: "Phone", displayName: i18next.t("general:Phone")}, {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: "Country/Region", displayName: i18next.t("user:Country/Region")},
{name: "Location", displayName: i18next.t("user:Location")}, {name: "Location", displayName: i18next.t("user:Location")},
{name: "Affiliation", displayName: i18next.t("user:Affiliation")}, {name: "Affiliation", displayName: i18next.t("user:Affiliation")},

View File

@ -184,11 +184,19 @@ class OrganizationEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Phone prefix"), i18next.t("general:Phone prefix - Tooltip"))} : {Setting.getLabel(i18next.t("general:Supported country code"), i18next.t("general:Supported country code - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input addonBefore={"+"} value={this.state.organization.phonePrefix} onChange={e => { <Select virtual={false} mode={"multiple"} style={{width: "100%"}} value={this.state.organization.countryCodes ?? []}
this.updateOrganizationField("phonePrefix", e.target.value); options={Setting.getCountriesData().map(country => {
return Setting.getOption(
<>
{Setting.countryFlag(country)}
{`${country.name} +${country.phone}`}
</>,
country.code);
})} onChange={value => {
this.updateOrganizationField("countryCodes", value);
}} /> }} />
</Col> </Col>
</Row> </Row>
@ -257,22 +265,13 @@ class OrganizationEditPage extends React.Component {
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}} <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 => { onChange={(value => {
this.updateOrganizationField("languages", 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> </Select>
</Col> </Col>
</Row> </Row>

View File

@ -33,7 +33,7 @@ class OrganizationListPage extends BaseListPage {
favicon: `${Setting.StaticBaseUrl}/img/favicon.png`, favicon: `${Setting.StaticBaseUrl}/img/favicon.png`,
passwordType: "plain", passwordType: "plain",
PasswordSalt: "", PasswordSalt: "",
phonePrefix: "86", countryCodes: ["CN"],
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`, defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
defaultApplication: "", defaultApplication: "",
tags: [], tags: [],

View File

@ -18,16 +18,6 @@ import {Dropdown} from "antd";
import "./App.less"; import "./App.less";
import {GlobalOutlined} from "@ant-design/icons"; 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) { function flagIcon(country, alt) {
return ( return (
<img width={24} alt={alt} src={`${Setting.StaticBaseUrl}/flag-icons/${country}.svg`} /> <img width={24} alt={alt} src={`${Setting.StaticBaseUrl}/flag-icons/${country}.svg`} />
@ -39,15 +29,15 @@ class SelectLanguageBox extends React.Component {
super(props); super(props);
this.state = { this.state = {
classes: props, 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`; 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) { getOrganizationLanguages(languages) {
const select = []; const select = [];

View File

@ -49,9 +49,9 @@ class SelectRegionBox extends React.Component {
} }
> >
{ {
Setting.getCountryNames().map((item) => ( Setting.getCountriesData().map((item) => (
<Option key={item.code} value={item.code} label={`${item.name} (${item.code})`} > <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.countryFlag(item)}
{`${item.name} (${item.code})`} {`${item.name} (${item.code})`}
</Option> </Option>
)) ))

View File

@ -23,6 +23,7 @@ import copy from "copy-to-clipboard";
import {authConfig} from "./auth/Auth"; import {authConfig} from "./auth/Auth";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import * as Conf from "./Conf"; import * as Conf from "./Conf";
import * as phoneNumber from "libphonenumber-js";
import * as path from "path-browserify"; import * as path from "path-browserify";
export const ServerUrl = ""; export const ServerUrl = "";
@ -30,6 +31,16 @@ export const ServerUrl = "";
// export const StaticBaseUrl = "https://cdn.jsdelivr.net/gh/casbin/static"; // export const StaticBaseUrl = "https://cdn.jsdelivr.net/gh/casbin/static";
export const StaticBaseUrl = "https://cdn.casbin.org"; 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: "Русский"},
];
export function getThemeData(organization, application) { export function getThemeData(organization, application) {
if (application?.themeData?.isEnabled) { if (application?.themeData?.isEnabled) {
return application.themeData; return application.themeData;
@ -188,20 +199,33 @@ export const OtherProviderInfo = {
}, },
}; };
export function getCountriesData() { export function initCountries() {
const countries = require("i18n-iso-countries"); const countries = require("i18n-iso-countries");
countries.registerLocale(require("i18n-iso-countries/langs/" + getLanguage() + ".json")); countries.registerLocale(require("i18n-iso-countries/langs/" + getLanguage() + ".json"));
return countries; return countries;
} }
export function getCountryNames() { export function getCountriesData(countryCodes = phoneNumber.getCountries()) {
const data = getCountriesData().getNames(getLanguage(), {select: "official"}); return countryCodes?.map((countryCode) => {
if (phoneNumber.isSupportedCountry(countryCode)) {
return Object.entries(data).map(items => { const name = initCountries().getName(countryCode, getLanguage());
return {code: items[0], name: items[1]}; return {
code: countryCode,
name: name || "",
phone: phoneNumber.getCountryCallingCode(countryCode),
};
}
}); });
} }
export function countryFlag(country) {
return <img src={`${StaticBaseUrl}/flag-icons/${country.code}.svg`} alt={country.name} height={20} style={{marginRight: 10}} />;
}
export function getPhoneCodeFromCountryCode(countryCode) {
return phoneNumber.isSupportedCountry(countryCode) ? phoneNumber.getCountryCallingCode(countryCode) : "";
}
export function initServerUrl() { export function initServerUrl() {
// const hostname = window.location.hostname; // const hostname = window.location.hostname;
// if (hostname === "localhost") { // if (hostname === "localhost") {
@ -299,16 +323,14 @@ export function isValidEmail(email) {
return emailRegex.test(email); return emailRegex.test(email);
} }
export function isValidPhone(phone) { export function isValidPhone(phone, countryCode = "") {
return phone !== ""; if (countryCode !== "") {
return phoneNumber.isValidPhoneNumber(phone, countryCode);
}
// if (phone === "") {
// return false;
// }
//
// // https://learnku.com/articles/31543, `^s*$` filter empty email individually. // // 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}$/; const phoneRegex = /[0-9]{4,15}$/;
// return phoneRegex.test(phone); return phoneRegex.test(phone);
} }
export function isValidInvoiceTitle(invoiceTitle) { export function isValidInvoiceTitle(invoiceTitle) {

View File

@ -29,6 +29,7 @@ import SelectRegionBox from "./SelectRegionBox";
import WebAuthnCredentialTable from "./WebauthnCredentialTable"; import WebAuthnCredentialTable from "./WebauthnCredentialTable";
import ManagedAccountTable from "./ManagedAccountTable"; import ManagedAccountTable from "./ManagedAccountTable";
import PropertyTable from "./propertyTable"; import PropertyTable from "./propertyTable";
import PhoneNumberInput from "./common/PhoneNumberInput";
const {Option} = Select; const {Option} = Select;
@ -286,11 +287,13 @@ class UserEditPage extends React.Component {
<Col style={{paddingRight: "20px"}} span={11} > <Col style={{paddingRight: "20px"}} span={11} >
{Setting.isLocalAdminUser(this.props.account) ? {Setting.isLocalAdminUser(this.props.account) ?
(<Input value={this.state.user.email} (<Input value={this.state.user.email}
style={{width: "280Px"}}
disabled={disabled} disabled={disabled}
onChange={e => { onChange={e => {
this.updateUserField("email", e.target.value); this.updateUserField("email", e.target.value);
}} />) : }} />) :
(<Select virtual={false} value={this.state.user.email} (<Select virtual={false} value={this.state.user.email}
style={{width: "280Px"}}
options={[Setting.getItem(this.state.user.email, this.state.user.email)]} options={[Setting.getItem(this.state.user.email, this.state.user.email)]}
disabled={disabled} disabled={disabled}
onChange={e => { onChange={e => {
@ -298,7 +301,7 @@ class UserEditPage extends React.Component {
}} />) }} />)
} }
</Col> </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*/} {/* 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} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} /> : null}
</Col> </Col>
@ -307,24 +310,37 @@ class UserEditPage extends React.Component {
} else if (accountItem.name === "Phone") { } else if (accountItem.name === "Phone") {
return ( return (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={Setting.isMobile() ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} : {Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
</Col> </Col>
<Col style={{paddingRight: "20px"}} span={11} > <Col style={{paddingRight: "20px"}} span={11} >
{Setting.isLocalAdminUser(this.props.account) ? {Setting.isLocalAdminUser(this.props.account) ?
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`} <Input.Group compact style={{width: "280Px"}}>
<PhoneNumberInput
style={{width: "30%"}}
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={disabled} disabled={disabled}
onChange={e => { onChange={e => {
this.updateUserField("phone", e.target.value); this.updateUserField("phone", e.target.value);
}} /> : }} />
(<Select virtual={false} value={`+${this.state.application?.organizationObj.phonePrefix} ${this.state.user.phone}`} </Input.Group>
options={[Setting.getItem(`+${this.state.application?.organizationObj.phonePrefix} ${this.state.user.phone}`, this.state.user.phone)]} :
(<Select virtual={false} value={this.state.user.phone === "" ? null : `+${Setting.getPhoneCodeFromCountryCode(this.state.user.countryCode)} ${this.state.user.phone}`}
options={this.state.user.phone === "" ? null : [Setting.getItem(`+${Setting.getPhoneCodeFromCountryCode(this.state.user.countryCode)} ${this.state.user.phone}`, this.state.user.phone)]}
disabled={disabled} disabled={disabled}
style={{width: "280px"}}
onChange={e => { onChange={e => {
this.updateUserField("phone", e.target.value); this.updateUserField("phone", e.target.value);
}} />)} }} />)}
</Col> </Col>
<Col span={11} > <Col span={Setting.isMobile() ? 24 : 11} >
{this.isSelf() ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null} {this.isSelf() ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
</Col> </Col>
</Row> </Row>

View File

@ -49,6 +49,7 @@ class UserListPage extends BaseListPage {
avatar: `${Setting.StaticBaseUrl}/img/casbin.svg`, avatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
email: `${randomName}@example.com`, email: `${randomName}@example.com`,
phone: Setting.getRandomNumber(), phone: Setting.getRandomNumber(),
countryCode: this.state.organization.countryCodes?.length > 0 ? this.state.organization.countryCodes[0] : "",
address: [], address: [],
affiliation: "Example Inc.", affiliation: "Example Inc.",
tag: "staff", tag: "staff",
@ -261,7 +262,7 @@ class UserListPage extends BaseListPage {
sorter: true, sorter: true,
...this.getColumnSearchProps("region"), ...this.getColumnSearchProps("region"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getCountriesData().getName(record.region, Setting.getLanguage(), {select: "official"}); return Setting.initCountries().getName(record.region, Setting.getLanguage(), {select: "official"});
}, },
}, },
{ {

View File

@ -140,7 +140,6 @@ class ForgetPage extends React.Component {
username: this.state.username, username: this.state.username,
name: this.state.name, name: this.state.name,
code: forms.step2.getFieldValue("emailCode"), code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.getApplicationObj()?.organizationObj.phonePrefix,
type: "login", type: "login",
}, oAuthParams).then(res => { }, oAuthParams).then(res => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@ -189,7 +189,6 @@ class LoginPage extends React.Component {
} else { } else {
values["type"] = this.state.type; values["type"] = this.state.type;
} }
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
if (oAuthParams !== null) { if (oAuthParams !== null) {
values["samlRequest"] = oAuthParams.samlRequest; values["samlRequest"] = oAuthParams.samlRequest;
@ -204,6 +203,7 @@ class LoginPage extends React.Component {
values["organization"] = this.getApplicationObj().organization; values["organization"] = this.getApplicationObj().organization;
} }
} }
postCodeLoginAction(res) { postCodeLoginAction(res) {
const application = this.getApplicationObj(); const application = this.getApplicationObj();
const ths = this; const ths = this;
@ -364,7 +364,8 @@ class LoginPage extends React.Component {
title={i18next.t("application:Sign Up Error")} title={i18next.t("application:Sign Up Error")}
subTitle={i18next.t("application:The application does not allow to sign up new account")} subTitle={i18next.t("application:The application does not allow to sign up new account")}
extra={[ 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") i18next.t("login:Sign In")
} }
@ -384,7 +385,9 @@ class LoginPage extends React.Component {
application: application.name, application: application.name,
autoSignin: true, autoSignin: true,
}} }}
onFinish={(values) => {this.onFinish(values);}} onFinish={(values) => {
this.onFinish(values);
}}
style={{width: "300px"}} style={{width: "300px"}}
size="large" size="large"
ref={this.form} ref={this.form}
@ -424,7 +427,7 @@ class LoginPage extends React.Component {
{ {
validator: (_, value) => { validator: (_, value) => {
if (this.state.loginMethod === "verificationCode") { 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}); this.setState({validEmailOrPhone: false});
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!")); return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
} }
@ -774,13 +777,18 @@ class LoginPage extends React.Component {
const items = [ const items = [
{label: i18next.t("login:Password"), key: "password"}, {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; application.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null;
if (application.enableCodeSignin || application.enableWebAuthn) { if (application.enableCodeSignin || application.enableWebAuthn) {
return ( return (
<div> <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> </Tabs>
</div> </div>
); );

View File

@ -26,6 +26,7 @@ import SelectRegionBox from "../SelectRegionBox";
import CustomGithubCorner from "../CustomGithubCorner"; import CustomGithubCorner from "../CustomGithubCorner";
import SelectLanguageBox from "../SelectLanguageBox"; import SelectLanguageBox from "../SelectLanguageBox";
import {withRouter} from "react-router-dom"; import {withRouter} from "react-router-dom";
import PhoneNumberInput from "../common/PhoneNumberInput";
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
@ -68,6 +69,7 @@ class SignupPage extends React.Component {
application: null, application: null,
email: "", email: "",
phone: "", phone: "",
countryCode: "",
emailCode: "", emailCode: "",
phoneCode: "", phoneCode: "",
validEmail: false, validEmail: false,
@ -157,7 +159,6 @@ class SignupPage extends React.Component {
onFinish(values) { onFinish(values) {
const application = this.getApplicationObj(); const application = this.getApplicationObj();
values.phonePrefix = application.organizationObj.phonePrefix;
AuthBackend.signup(values) AuthBackend.signup(values)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
@ -378,10 +379,42 @@ class SignupPage extends React.Component {
} else if (signupItem.name === "Phone") { } else if (signupItem.name === "Phone") {
return ( return (
<React.Fragment> <React.Fragment>
<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!"),
},
{
validator: (_, value) => {
if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone, this.state.countryCode)) {
this.setState({validPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
}
this.setState({validPhone: true});
return Promise.resolve();
},
},
]}
>
<PhoneNumberInput
showSearsh={true}
style={{width: "35%"}}
value={this.state.countryCode}
onChange={(value) => {this.setState({countryCode: value});}}
countryCodes={this.getApplicationObj().organizationObj.countryCodes}
/>
</Form.Item>
<Form.Item <Form.Item
name="phone" name="phone"
key="phone" key="phone"
label={i18next.t("general:Phone")} noStyle
rules={[ rules={[
{ {
required: required, required: required,
@ -389,7 +422,7 @@ class SignupPage extends React.Component {
}, },
{ {
validator: (_, value) => { validator: (_, value) => {
if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone)) { if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone, this.state.countryCode)) {
this.setState({validPhone: false}); this.setState({validPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!")); return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
} }
@ -401,13 +434,12 @@ class SignupPage extends React.Component {
]} ]}
> >
<Input <Input
style={{ style={{width: "65%"}}
width: "100%",
}}
addonBefore={`+${this.getApplicationObj()?.organizationObj.phonePrefix}`}
onChange={e => this.setState({phone: e.target.value})} onChange={e => this.setState({phone: e.target.value})}
/> />
</Form.Item> </Form.Item>
</Input.Group>
</Form.Item>
<Form.Item <Form.Item
name="phoneCode" name="phoneCode"
key="phoneCode" key="phoneCode"

View File

@ -0,0 +1,58 @@
// 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";
const {Option} = Select;
export default function PhoneNumberInput(props) {
const {onChange, style, showSearch} = props;
const value = props.value ?? "CN";
const countryCodes = props.countryCodes ?? [];
const handleOnChange = (e) => {
onChange?.(e);
};
return (
<Select
virtual={false}
style={style}
value={value}
dropdownMatchSelectWidth={false}
optionLabelProp={"label"}
showSearch={showSearch}
onChange={handleOnChange}
filterOption={(input, option) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
>
{
Setting.getCountriesData(countryCodes).map((country) => (
<Option key={country.code} value={country.code} label={`+${country.phone}`} >
<div style={{display: "flex", justifyContent: "space-between"}}>
<div>
{Setting.countryFlag(country)}
{`${country.name}`}
</div>
{`+${country.phone}`}
</div>
</Option>
))
}
</Select>
);
}

View File

@ -219,8 +219,6 @@
"Permissions - Tooltip": "Permissions - Tooltip", "Permissions - Tooltip": "Permissions - Tooltip",
"Phone": "Telefon", "Phone": "Telefon",
"Phone - Tooltip": "Phone", "Phone - Tooltip": "Phone",
"Phone prefix": "Telefonpräfix",
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
"Preview": "Vorschau", "Preview": "Vorschau",
"Preview - Tooltip": "The form in which the password is stored in the database", "Preview - Tooltip": "The form in which the password is stored in the database",
"Products": "Products", "Products": "Products",
@ -252,6 +250,8 @@
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted", "Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved", "Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Swagger": "Swagger", "Swagger": "Swagger",
"Sync": "Sync", "Sync": "Sync",
"Syncers": "Syncers", "Syncers": "Syncers",
@ -311,10 +311,10 @@
"Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an", "Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an",
"Password": "Passwort", "Password": "Passwort",
"Password - Tooltip": "Passwort - Tooltip", "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 code!": "Bitte gib deinen Code ein!",
"Please input your password!": "Bitte geben Sie Ihr Passwort 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 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.", "Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Anmelden", "Sign In": "Anmelden",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
@ -640,6 +640,7 @@
"Please input your last name!": "Please input your last name!", "Please input your last name!": "Please input your last name!",
"Please input your phone number!": "Bitte geben Sie Ihre Telefonnummer ein!", "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 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!", "Please select your country/region!": "Bitte wählen Sie Ihr Land/Ihre Region!",
"Terms of Use": "Nutzungsbedingungen", "Terms of Use": "Nutzungsbedingungen",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!", "The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
@ -726,6 +727,7 @@
"Captcha Verify Failed": "Captcha Verify Failed", "Captcha Verify Failed": "Captcha Verify Failed",
"Captcha Verify Success": "Captcha Verify Success", "Captcha Verify Success": "Captcha Verify Success",
"Code Sent": "Code gesendet", "Code Sent": "Code gesendet",
"Country code": "Country code",
"Country/Region": "Land/Region", "Country/Region": "Land/Region",
"Country/Region - Tooltip": "Country/Region", "Country/Region - Tooltip": "Country/Region",
"Edit User": "Benutzer bearbeiten", "Edit User": "Benutzer bearbeiten",

View File

@ -219,8 +219,6 @@
"Permissions - Tooltip": "Permissions - Tooltip", "Permissions - Tooltip": "Permissions - Tooltip",
"Phone": "Phone", "Phone": "Phone",
"Phone - Tooltip": "Phone - Tooltip", "Phone - Tooltip": "Phone - Tooltip",
"Phone prefix": "Phone prefix",
"Phone prefix - Tooltip": "Phone prefix - Tooltip",
"Preview": "Preview", "Preview": "Preview",
"Preview - Tooltip": "Preview - Tooltip", "Preview - Tooltip": "Preview - Tooltip",
"Products": "Products", "Products": "Products",
@ -252,6 +250,8 @@
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted", "Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved", "Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Swagger": "Swagger", "Swagger": "Swagger",
"Sync": "Sync", "Sync": "Sync",
"Syncers": "Syncers", "Syncers": "Syncers",
@ -311,10 +311,10 @@
"Or sign in with another account": "Or sign in with another account", "Or sign in with another account": "Or sign in with another account",
"Password": "Password", "Password": "Password",
"Password - Tooltip": "Password - Tooltip", "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 code!": "Please input your code!",
"Please input your password!": "Please input your password!", "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 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.", "Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In", "Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
@ -640,6 +640,7 @@
"Please input your last name!": "Please input your last name!", "Please input your last name!": "Please input your last name!",
"Please input your phone number!": "Please input your phone number!", "Please input your phone number!": "Please input your phone number!",
"Please input your real name!": "Please input your real name!", "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!", "Please select your country/region!": "Please select your country/region!",
"Terms of Use": "Terms of Use", "Terms of Use": "Terms of Use",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!", "The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
@ -726,6 +727,7 @@
"Captcha Verify Failed": "Captcha Verify Failed", "Captcha Verify Failed": "Captcha Verify Failed",
"Captcha Verify Success": "Captcha Verify Success", "Captcha Verify Success": "Captcha Verify Success",
"Code Sent": "Code Sent", "Code Sent": "Code Sent",
"Country code": "Country code",
"Country/Region": "Country/Region", "Country/Region": "Country/Region",
"Country/Region - Tooltip": "Country/Region - Tooltip", "Country/Region - Tooltip": "Country/Region - Tooltip",
"Edit User": "Edit User", "Edit User": "Edit User",

View File

@ -219,8 +219,6 @@
"Permissions - Tooltip": "Permisos - Tooltip", "Permissions - Tooltip": "Permisos - Tooltip",
"Phone": "Teléfono", "Phone": "Teléfono",
"Phone - Tooltip": "Teléfono - Tooltip", "Phone - Tooltip": "Teléfono - Tooltip",
"Phone prefix": "Prefijo teléfonico",
"Phone prefix - Tooltip": "Prefijo teléfonico - Tooltip",
"Preview": "Previsualizar", "Preview": "Previsualizar",
"Preview - Tooltip": "Previsualizar - Tooltip", "Preview - Tooltip": "Previsualizar - Tooltip",
"Products": "Productos", "Products": "Productos",
@ -252,6 +250,8 @@
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted", "Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved", "Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Swagger": "Swagger", "Swagger": "Swagger",
"Sync": "Sincronizador", "Sync": "Sincronizador",
"Syncers": "Sincronizadores", "Syncers": "Sincronizadores",
@ -311,10 +311,10 @@
"Or sign in with another account": "O inicia sesión con otra cuenta", "Or sign in with another account": "O inicia sesión con otra cuenta",
"Password": "Contraseña", "Password": "Contraseña",
"Password - Tooltip": "Contraseña - Tooltip", "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 code!": "¡Por favor ingrese su código!",
"Please input your password!": "¡Por favor ingrese su contraseña!", "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 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.", "Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Iniciar de sesión", "Sign In": "Iniciar de sesión",
"Sign in with WebAuthn": "Iniciar de sesión con WebAuthn", "Sign in with WebAuthn": "Iniciar de sesión con WebAuthn",
@ -640,6 +640,7 @@
"Please input your last name!": "Por favor, ingrese su apellido!", "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 phone number!": "Por favor, ingrese su número teléfonico!",
"Please input your real name!": "Por favor, ingrese un nombre real!", "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!", "Please select your country/region!": "Por favor, seleccione su pais/region!",
"Terms of Use": "Términos de Uso", "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!", "The input is not invoice Tax ID!": "El valor ingresado no es un número de identificación fiscal de factura!",
@ -726,6 +727,7 @@
"Captcha Verify Failed": "Fallo la verificación del Captcha", "Captcha Verify Failed": "Fallo la verificación del Captcha",
"Captcha Verify Success": "Captcha verificado con éxito", "Captcha Verify Success": "Captcha verificado con éxito",
"Code Sent": "Código enviado", "Code Sent": "Código enviado",
"Country code": "Country code",
"Country/Region": "Pais/Región", "Country/Region": "Pais/Región",
"Country/Region - Tooltip": "Pais/Región - Tooltip", "Country/Region - Tooltip": "Pais/Región - Tooltip",
"Edit User": "Editar usuario", "Edit User": "Editar usuario",

View File

@ -219,8 +219,6 @@
"Permissions - Tooltip": "Permissions - Tooltip", "Permissions - Tooltip": "Permissions - Tooltip",
"Phone": "Téléphone", "Phone": "Téléphone",
"Phone - Tooltip": "Phone", "Phone - Tooltip": "Phone",
"Phone prefix": "Préfixe du téléphone",
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
"Preview": "Aperçu", "Preview": "Aperçu",
"Preview - Tooltip": "The form in which the password is stored in the database", "Preview - Tooltip": "The form in which the password is stored in the database",
"Products": "Products", "Products": "Products",
@ -252,6 +250,8 @@
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted", "Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved", "Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Swagger": "Swagger", "Swagger": "Swagger",
"Sync": "Sync", "Sync": "Sync",
"Syncers": "Synchronisateurs", "Syncers": "Synchronisateurs",
@ -311,10 +311,10 @@
"Or sign in with another account": "Ou connectez-vous avec un autre compte", "Or sign in with another account": "Ou connectez-vous avec un autre compte",
"Password": "Mot de passe", "Password": "Mot de passe",
"Password - Tooltip": "Mot de passe - Info-bulle", "Password - Tooltip": "Mot de passe - Info-bulle",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your code!": "Veuillez saisir votre code !", "Please input your code!": "Veuillez saisir votre code !",
"Please input your password!": "Veuillez saisir votre mot de passe !", "Please input your password!": "Veuillez saisir votre mot de passe !",
"Please input your password, at least 6 characters!": "Veuillez entrer votre mot de passe, au moins 6 caractères !", "Please input your password, at least 6 characters!": "Veuillez entrer votre mot de passe, au moins 6 caractères !",
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
"Redirecting, please wait.": "Redirecting, please wait.", "Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Se connecter", "Sign In": "Se connecter",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
@ -640,6 +640,7 @@
"Please input your last name!": "Please input your last name!", "Please input your last name!": "Please input your last name!",
"Please input your phone number!": "Veuillez entrer votre numéro de téléphone!", "Please input your phone number!": "Veuillez entrer votre numéro de téléphone!",
"Please input your real name!": "Veuillez entrer votre nom personnel !", "Please input your real name!": "Veuillez entrer votre nom personnel !",
"Please select your country code!": "Please select your country code!",
"Please select your country/region!": "Veuillez sélectionner votre pays/région!", "Please select your country/region!": "Veuillez sélectionner votre pays/région!",
"Terms of Use": "Conditions d'utilisation", "Terms of Use": "Conditions d'utilisation",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!", "The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
@ -726,6 +727,7 @@
"Captcha Verify Failed": "Captcha Verify Failed", "Captcha Verify Failed": "Captcha Verify Failed",
"Captcha Verify Success": "Captcha Verify Success", "Captcha Verify Success": "Captcha Verify Success",
"Code Sent": "Code envoyé", "Code Sent": "Code envoyé",
"Country code": "Country code",
"Country/Region": "Pays/Région", "Country/Region": "Pays/Région",
"Country/Region - Tooltip": "Country/Region", "Country/Region - Tooltip": "Country/Region",
"Edit User": "Editer l'utilisateur", "Edit User": "Editer l'utilisateur",

View File

@ -219,8 +219,6 @@
"Permissions - Tooltip": "Permissions - Tooltip", "Permissions - Tooltip": "Permissions - Tooltip",
"Phone": "電話番号", "Phone": "電話番号",
"Phone - Tooltip": "Phone", "Phone - Tooltip": "Phone",
"Phone prefix": "電話プレフィクス",
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
"Preview": "プレビュー", "Preview": "プレビュー",
"Preview - Tooltip": "The form in which the password is stored in the database", "Preview - Tooltip": "The form in which the password is stored in the database",
"Products": "Products", "Products": "Products",
@ -252,6 +250,8 @@
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted", "Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved", "Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Swagger": "Swagger", "Swagger": "Swagger",
"Sync": "Sync", "Sync": "Sync",
"Syncers": "Syncers", "Syncers": "Syncers",
@ -311,10 +311,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!": "6文字以上でパスワードを入力してください", "Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください",
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
"Redirecting, please wait.": "Redirecting, please wait.", "Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "サインイン", "Sign In": "サインイン",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
@ -640,6 +640,7 @@
"Please input your last name!": "Please input your last name!", "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!", "The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
@ -726,6 +727,7 @@
"Captcha Verify Failed": "Captcha Verify Failed", "Captcha Verify Failed": "Captcha Verify Failed",
"Captcha Verify Success": "Captcha Verify Success", "Captcha Verify Success": "Captcha Verify Success",
"Code Sent": "コードを送信しました", "Code Sent": "コードを送信しました",
"Country code": "Country code",
"Country/Region": "国/地域", "Country/Region": "国/地域",
"Country/Region - Tooltip": "Country/Region", "Country/Region - Tooltip": "Country/Region",
"Edit User": "ユーザーを編集", "Edit User": "ユーザーを編集",

View File

@ -219,8 +219,6 @@
"Permissions - Tooltip": "Permissions - Tooltip", "Permissions - Tooltip": "Permissions - Tooltip",
"Phone": "Phone", "Phone": "Phone",
"Phone - Tooltip": "Phone", "Phone - Tooltip": "Phone",
"Phone prefix": "Phone prefix",
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
"Preview": "Preview", "Preview": "Preview",
"Preview - Tooltip": "The form in which the password is stored in the database", "Preview - Tooltip": "The form in which the password is stored in the database",
"Products": "Products", "Products": "Products",
@ -252,6 +250,8 @@
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted", "Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved", "Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Swagger": "Swagger", "Swagger": "Swagger",
"Sync": "Sync", "Sync": "Sync",
"Syncers": "Syncers", "Syncers": "Syncers",
@ -311,10 +311,10 @@
"Or sign in with another account": "Or sign in with another account", "Or sign in with another account": "Or sign in with another account",
"Password": "Password", "Password": "Password",
"Password - Tooltip": "Password - Tooltip", "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 code!": "Please input your code!",
"Please input your password!": "Please input your password!", "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 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.", "Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In", "Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
@ -640,6 +640,7 @@
"Please input your last name!": "Please input your last name!", "Please input your last name!": "Please input your last name!",
"Please input your phone number!": "Please input your phone number!", "Please input your phone number!": "Please input your phone number!",
"Please input your real name!": "Please input your real name!", "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!", "Please select your country/region!": "Please select your country/region!",
"Terms of Use": "Terms of Use", "Terms of Use": "Terms of Use",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!", "The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
@ -726,6 +727,7 @@
"Captcha Verify Failed": "Captcha Verify Failed", "Captcha Verify Failed": "Captcha Verify Failed",
"Captcha Verify Success": "Captcha Verify Success", "Captcha Verify Success": "Captcha Verify Success",
"Code Sent": "Code Sent", "Code Sent": "Code Sent",
"Country code": "Country code",
"Country/Region": "Country/Region", "Country/Region": "Country/Region",
"Country/Region - Tooltip": "Country/Region", "Country/Region - Tooltip": "Country/Region",
"Edit User": "Edit User", "Edit User": "Edit User",

View File

@ -219,8 +219,6 @@
"Permissions - Tooltip": "Permissions - Tooltip", "Permissions - Tooltip": "Permissions - Tooltip",
"Phone": "Телефон", "Phone": "Телефон",
"Phone - Tooltip": "Phone", "Phone - Tooltip": "Phone",
"Phone prefix": "Префикс телефона",
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
"Preview": "Предпросмотр", "Preview": "Предпросмотр",
"Preview - Tooltip": "The form in which the password is stored in the database", "Preview - Tooltip": "The form in which the password is stored in the database",
"Products": "Продукты", "Products": "Продукты",
@ -252,6 +250,8 @@
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted", "Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved", "Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Swagger": "Swagger", "Swagger": "Swagger",
"Sync": "Sync", "Sync": "Sync",
"Syncers": "Синхронизаторы", "Syncers": "Синхронизаторы",
@ -311,10 +311,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!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!", "Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!",
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
"Redirecting, please wait.": "Redirecting, please wait.", "Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Войти", "Sign In": "Войти",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
@ -640,6 +640,7 @@
"Please input your last name!": "Please input your last name!", "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!", "The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
@ -726,6 +727,7 @@
"Captcha Verify Failed": "Captcha Verify Failed", "Captcha Verify Failed": "Captcha Verify Failed",
"Captcha Verify Success": "Captcha Verify Success", "Captcha Verify Success": "Captcha Verify Success",
"Code Sent": "Код отправлен", "Code Sent": "Код отправлен",
"Country code": "Country code",
"Country/Region": "Страна/регион", "Country/Region": "Страна/регион",
"Country/Region - Tooltip": "Country/Region", "Country/Region - Tooltip": "Country/Region",
"Edit User": "Изменить пользователя", "Edit User": "Изменить пользователя",

View File

@ -219,8 +219,6 @@
"Permissions - Tooltip": "权限", "Permissions - Tooltip": "权限",
"Phone": "手机号", "Phone": "手机号",
"Phone - Tooltip": "手机号", "Phone - Tooltip": "手机号",
"Phone prefix": "手机号前缀",
"Phone prefix - Tooltip": "移动电话号码前缀,用于区分国家或地区",
"Preview": "预览", "Preview": "预览",
"Preview - Tooltip": "预览", "Preview - Tooltip": "预览",
"Products": "商品", "Products": "商品",
@ -252,6 +250,8 @@
"Successfully added": "添加成功", "Successfully added": "添加成功",
"Successfully deleted": "删除成功", "Successfully deleted": "删除成功",
"Successfully saved": "保存成功", "Successfully saved": "保存成功",
"Supported country code": "支持的国家代码",
"Supported country code - Tooltip": "支持发送短信的国家 - Tooltip",
"Swagger": "API文档", "Swagger": "API文档",
"Sync": "同步", "Sync": "同步",
"Syncers": "同步器", "Syncers": "同步器",
@ -311,10 +311,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!": "请输入您的Email或手机号!",
"Please input your code!": "请输入您的验证码!", "Please input your code!": "请输入您的验证码!",
"Please input your password!": "请输入您的密码!", "Please input your password!": "请输入您的密码!",
"Please input your password, at least 6 characters!": "请输入您的密码不少于6位", "Please input your password, at least 6 characters!": "请输入您的密码不少于6位",
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号",
"Redirecting, please wait.": "正在跳转, 请稍等.", "Redirecting, please wait.": "正在跳转, 请稍等.",
"Sign In": "登录", "Sign In": "登录",
"Sign in with WebAuthn": "WebAuthn登录", "Sign in with WebAuthn": "WebAuthn登录",
@ -640,6 +640,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/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!": "您输入的纳税人识别号有误!",
@ -726,6 +727,7 @@
"Captcha Verify Failed": "验证码校验失败", "Captcha Verify Failed": "验证码校验失败",
"Captcha Verify Success": "验证码校验成功", "Captcha Verify Success": "验证码校验成功",
"Code Sent": "验证码已发送", "Code Sent": "验证码已发送",
"Country code": "国家代码",
"Country/Region": "国家/地区", "Country/Region": "国家/地区",
"Country/Region - Tooltip": "国家/地区", "Country/Region - Tooltip": "国家/地区",
"Edit User": "编辑用户", "Edit User": "编辑用户",

View File

@ -7829,6 +7829,11 @@ levn@~0.3.0:
prelude-ls "~1.1.2" prelude-ls "~1.1.2"
type-check "~0.3.2" type-check "~0.3.2"
libphonenumber-js@^1.10.19:
version "1.10.20"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.20.tgz#03c310adf83381eeceb4bd6830442fd14e64964d"
integrity sha512-kQovlKNdLcVzerbTPmJ+Fx4R+7/pYXmPDIllHjg7IxL4X6MsMG7jaT5opfYrBok0uqkByVif//JUR8e11l/V7w==
lilconfig@2.0.5: lilconfig@2.0.5:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"