diff --git a/controllers/account.go b/controllers/account.go index d8ecf664..ec1f05b9 100644 --- a/controllers/account.go +++ b/controllers/account.go @@ -58,7 +58,7 @@ type RequestForm struct { EmailCode string `json:"emailCode"` PhoneCode string `json:"phoneCode"` - PhonePrefix string `json:"phonePrefix"` + CountryCode string `json:"countryCode"` AutoSignin bool `json:"autoSignin"` @@ -121,7 +121,7 @@ func (c *ApiController) Signup() { } organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization)) - msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.Affiliation, c.GetAcceptLanguage()) + msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.CountryCode, form.Affiliation, c.GetAcceptLanguage()) if msg != "" { c.ResponseError(msg) return @@ -137,7 +137,7 @@ func (c *ApiController) Signup() { var checkPhone string if application.IsSignupItemVisible("Phone") && form.Phone != "" { - checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone) + checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode) checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage()) if len(checkResult) != 0 { c.ResponseError(c.T("account:Phone: %s"), checkResult) @@ -179,6 +179,7 @@ func (c *ApiController) Signup() { Avatar: organization.DefaultAvatar, Email: form.Email, Phone: form.Phone, + CountryCode: form.CountryCode, Address: []string{}, Affiliation: form.Affiliation, IdCard: form.IdCard, diff --git a/controllers/auth.go b/controllers/auth.go index b487ca4d..5723ca3a 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -263,34 +263,32 @@ func (c *ApiController) Login() { checkDest = form.Username } else { verificationCodeType = "phone" - if len(form.PhonePrefix) == 0 { - responseText := fmt.Sprintf(c.T("auth:%s No phone prefix"), verificationCodeType) - c.ResponseError(responseText) - return - } if user != nil && util.GetMaskedPhone(user.Phone) == form.Username { form.Username = user.Phone } - checkDest = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username) } - user = object.GetUserByFields(form.Organization, form.Username) - if user == nil { + + if user = object.GetUserByFields(form.Organization, form.Username); user == nil { c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username))) return } + if verificationCodeType == "phone" { + form.CountryCode = user.GetCountryCode(form.CountryCode) + var ok bool + if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok { + c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), form.CountryCode)) + return + } + } + checkResult = object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage()) if len(checkResult) != 0 { - responseText := fmt.Sprintf("%s - %s", verificationCodeType, checkResult) - c.ResponseError(responseText) + c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult)) return } // disable the verification code - if strings.Contains(form.Username, "@") { - object.DisableVerificationCode(form.Username) - } else { - object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)) - } + object.DisableVerificationCode(checkDest) } else { application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) if application == nil { diff --git a/controllers/service.go b/controllers/service.go index 3d2fa660..209114d0 100644 --- a/controllers/service.go +++ b/controllers/service.go @@ -130,13 +130,13 @@ func (c *ApiController) SendSms() { return } - org := object.GetOrganization(smsForm.OrgId) var invalidReceivers []string for idx, receiver := range smsForm.Receivers { - if !util.IsPhoneCnValid(receiver) { + // The receiver phone format: E164 like +8613854673829 +441932567890 + if !util.IsPhoneValid(receiver, "") { invalidReceivers = append(invalidReceivers, receiver) } else { - smsForm.Receivers[idx] = fmt.Sprintf("+%s%s", org.PhonePrefix, receiver) + smsForm.Receivers[idx] = receiver } } diff --git a/controllers/user_util.go b/controllers/user_util.go index 794641ea..46ae0909 100644 --- a/controllers/user_util.go +++ b/controllers/user_util.go @@ -62,6 +62,10 @@ func checkPermissionForUpdateUser(userId string, newUser object.User, c *ApiCont item := object.GetAccountItemByName("Phone", organization) itemsChanged = append(itemsChanged, item) } + if oldUser.CountryCode != newUser.CountryCode { + item := object.GetAccountItemByName("Country code", organization) + itemsChanged = append(itemsChanged, item) + } if oldUser.Region != newUser.Region { item := object.GetAccountItemByName("Country/Region", organization) itemsChanged = append(itemsChanged, item) diff --git a/controllers/verification.go b/controllers/verification.go index e985ac4e..26cec533 100644 --- a/controllers/verification.go +++ b/controllers/verification.go @@ -24,6 +24,13 @@ import ( "github.com/casdoor/casdoor/util" ) +const ( + SignupVerification = "signup" + ResetVerification = "reset" + LoginVerification = "login" + ForgetVerification = "forget" +) + func (c *ApiController) getCurrentUser() *object.User { var user *object.User userId := c.GetSessionUsername() @@ -42,18 +49,15 @@ func (c *ApiController) getCurrentUser() *object.User { func (c *ApiController) SendVerificationCode() { destType := c.Ctx.Request.Form.Get("type") dest := c.Ctx.Request.Form.Get("dest") + countryCode := c.Ctx.Request.Form.Get("countryCode") checkType := c.Ctx.Request.Form.Get("checkType") checkId := c.Ctx.Request.Form.Get("checkId") checkKey := c.Ctx.Request.Form.Get("checkKey") - checkUser := c.Ctx.Request.Form.Get("checkUser") applicationId := c.Ctx.Request.Form.Get("applicationId") method := c.Ctx.Request.Form.Get("method") + checkUser := c.Ctx.Request.Form.Get("checkUser") remoteAddr := util.GetIPFromRequest(c.Ctx.Request) - if destType == "" { - c.ResponseError(c.T("general:Missing parameter") + ": type.") - return - } if dest == "" { c.ResponseError(c.T("general:Missing parameter") + ": dest.") return @@ -62,98 +66,101 @@ func (c *ApiController) SendVerificationCode() { c.ResponseError(c.T("general:Missing parameter") + ": applicationId.") return } - if !strings.Contains(applicationId, "/") { - c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.") - return - } if checkType == "" { c.ResponseError(c.T("general:Missing parameter") + ": checkType.") return } - - captchaProvider := captcha.GetCaptchaProvider(checkType) - - if captchaProvider != nil { - if checkKey == "" { - c.ResponseError(c.T("general:Missing parameter") + ": checkKey.") - return - } - isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId) - if err != nil { - c.ResponseError(err.Error()) - return - } - - if !isHuman { - c.ResponseError(c.T("verification:Turing test failed.")) - return - } + if checkKey == "" { + c.ResponseError(c.T("general:Missing parameter") + ": checkKey.") + return + } + if !strings.Contains(applicationId, "/") { + c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.") + return + } + + 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.")) + return } - user := c.getCurrentUser() application := object.GetApplication(applicationId) - organization := object.GetOrganization(fmt.Sprintf("%s/%s", application.Owner, application.Organization)) + organization := object.GetOrganization(util.GetId(application.Owner, application.Organization)) if organization == nil { c.ResponseError(c.T("verification:Organization does not exist")) return } - if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil { - c.ResponseError(c.T("general:Please login first")) - return + var user *object.User + // checkUser != "", means method is ForgetVerification + if checkUser != "" { + owner := application.Organization + user = object.GetUser(util.GetId(owner, checkUser)) } sendResp := errors.New("invalid dest type") - if user == nil && checkUser != "" && checkUser != "true" { - name := application.Organization - user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser)) - } switch destType { case "email": - if user != nil && util.GetMaskedEmail(user.Email) == dest { - dest = user.Email - } if !util.IsEmailValid(dest) { c.ResponseError(c.T("verification:Email is invalid")) return } - userByEmail := object.GetUserByEmail(organization.Name, dest) - if userByEmail == nil && method != "signup" && method != "reset" { - c.ResponseError(c.T("verification:the user does not exist, please sign up first")) - return + if method == LoginVerification || method == ForgetVerification { + if user != nil && util.GetMaskedEmail(user.Email) == dest { + dest = user.Email + } + + user = object.GetUserByEmail(organization.Name, dest) + if user == nil { + c.ResponseError(c.T("verification:the user does not exist, please sign up first")) + return + } + } else if method == ResetVerification { + user = c.getCurrentUser() } provider := application.GetEmailProvider() sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest) case "phone": - if user != nil && util.GetMaskedPhone(user.Phone) == dest { - dest = user.Phone - } - if !util.IsPhoneCnValid(dest) { - c.ResponseError(c.T("verification:Phone number is invalid")) - return + if method == LoginVerification || method == ForgetVerification { + if user != nil && util.GetMaskedPhone(user.Phone) == dest { + dest = user.Phone + } + + if user = object.GetUserByPhone(organization.Name, dest); user == nil { + c.ResponseError(c.T("verification:the user does not exist, please sign up first")) + return + } + + countryCode = user.GetCountryCode(countryCode) + } else if method == ResetVerification { + if user = c.getCurrentUser(); user != nil { + countryCode = user.GetCountryCode(countryCode) + } } - userByPhone := object.GetUserByPhone(organization.Name, dest) - if userByPhone == nil && method != "signup" && method != "reset" { - c.ResponseError(c.T("verification:the user does not exist, please sign up first")) - return - } - - dest = fmt.Sprintf("+%s%s", organization.PhonePrefix, dest) provider := application.GetSmsProvider() - sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest) + if phone, ok := util.GetE164Number(dest, countryCode); !ok { + c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), countryCode)) + return + } else { + sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, phone) + } } if sendResp != nil { - c.Data["json"] = Response{Status: "error", Msg: sendResp.Error()} + c.ResponseError(sendResp.Error()) } else { - c.Data["json"] = Response{Status: "ok"} + c.ResponseOk() } - - c.ServeJSON() } // ResetEmailOrPhone ... @@ -169,7 +176,8 @@ func (c *ApiController) ResetEmailOrPhone() { destType := c.Ctx.Request.Form.Get("type") dest := c.Ctx.Request.Form.Get("dest") code := c.Ctx.Request.Form.Get("code") - if len(dest) == 0 || len(code) == 0 || len(destType) == 0 { + + if util.IsStrsEmpty(destType, dest, code) { c.ResponseError(c.T("general:Missing parameter")) return } @@ -192,12 +200,10 @@ func (c *ApiController) ResetEmailOrPhone() { c.ResponseError(errMsg) return } - - phonePrefix := "86" - if organization != nil && organization.PhonePrefix != "" { - phonePrefix = organization.PhonePrefix + if checkDest, ok = util.GetE164Number(dest, user.GetCountryCode("")); !ok { + c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode)) + return } - checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest) } else if destType == "email" { if object.HasUserByField(user.Owner, "email", user.Email) { c.ResponseError(c.T("check:Email already exists")) @@ -215,8 +221,8 @@ func (c *ApiController) ResetEmailOrPhone() { return } } - if ret := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); len(ret) != 0 { - c.ResponseError(ret) + if msg := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); len(msg) != 0 { + c.ResponseError(msg) return } @@ -233,8 +239,7 @@ func (c *ApiController) ResetEmailOrPhone() { } object.DisableVerificationCode(checkDest) - c.Data["json"] = Response{Status: "ok"} - c.ServeJSON() + c.ResponseOk() } // VerifyCaptcha ... diff --git a/go.mod b/go.mod index f0180530..46f03cca 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/markbates/goth v1.75.2 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect + github.com/nyaruka/phonenumbers v1.1.5 github.com/qiangmzsx/string-adapter/v2 v2.1.0 github.com/robfig/cron/v3 v3.0.1 github.com/russellhaering/gosaml2 v0.6.0 diff --git a/go.sum b/go.sum index 0c022c8d..4031d470 100644 --- a/go.sum +++ b/go.sum @@ -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/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/nyaruka/phonenumbers v1.1.5 h1:vYy2DI+z5hdaemqVzXYJ4CVyK92IG484CirEY+40GTo= +github.com/nyaruka/phonenumbers v1.1.5/go.mod h1:yShPJHDSH3aTKzCbXyVxNpbl2kA+F+Ne5Pun/MvFRos= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= diff --git a/i18n/locales/de/data.json b/i18n/locales/de/data.json index 7f77e4c2..4b14cbb5 100644 --- a/i18n/locales/de/data.json +++ b/i18n/locales/de/data.json @@ -9,7 +9,6 @@ "The application does not allow to sign up new account": "The application does not allow to sign up new account" }, "auth": { - "%s No phone prefix": "%s No phone prefix", "Challenge method should be S256": "Challenge method should be S256", "Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s", "Failed to login in: %s": "Failed to login in: %s", @@ -57,13 +56,15 @@ "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username must have at least 2 characters": "Username must have at least 2 characters", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again", + "Your region is not allow to signup by phone": "Your region is not allow to signup by phone", "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances", "unsupported password type: %s": "unsupported password type: %s" }, "general": { "Missing parameter": "Missing parameter", "Please login first": "Please login first", - "The user: %s doesn't exist": "The user: %s doesn't exist" + "The user: %s doesn't exist": "The user: %s doesn't exist", + "don't support captchaProvider: ": "don't support captchaProvider: " }, "ldap": { "Ldap server exist": "Ldap server exist" @@ -130,7 +131,7 @@ "Email is invalid": "Email is invalid", "Invalid captcha provider.": "Invalid captcha provider.", "Organization does not exist": "Organization does not exist", - "Phone number is invalid": "Phone number is invalid", + "Phone number is invalid in your region %s": "Phone number is invalid in your region %s", "Turing test failed.": "Turing test failed.", "Unable to get the email modify rule.": "Unable to get the email modify rule.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.", diff --git a/i18n/locales/en/data.json b/i18n/locales/en/data.json index 7f77e4c2..4b14cbb5 100644 --- a/i18n/locales/en/data.json +++ b/i18n/locales/en/data.json @@ -9,7 +9,6 @@ "The application does not allow to sign up new account": "The application does not allow to sign up new account" }, "auth": { - "%s No phone prefix": "%s No phone prefix", "Challenge method should be S256": "Challenge method should be S256", "Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s", "Failed to login in: %s": "Failed to login in: %s", @@ -57,13 +56,15 @@ "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username must have at least 2 characters": "Username must have at least 2 characters", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again", + "Your region is not allow to signup by phone": "Your region is not allow to signup by phone", "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances", "unsupported password type: %s": "unsupported password type: %s" }, "general": { "Missing parameter": "Missing parameter", "Please login first": "Please login first", - "The user: %s doesn't exist": "The user: %s doesn't exist" + "The user: %s doesn't exist": "The user: %s doesn't exist", + "don't support captchaProvider: ": "don't support captchaProvider: " }, "ldap": { "Ldap server exist": "Ldap server exist" @@ -130,7 +131,7 @@ "Email is invalid": "Email is invalid", "Invalid captcha provider.": "Invalid captcha provider.", "Organization does not exist": "Organization does not exist", - "Phone number is invalid": "Phone number is invalid", + "Phone number is invalid in your region %s": "Phone number is invalid in your region %s", "Turing test failed.": "Turing test failed.", "Unable to get the email modify rule.": "Unable to get the email modify rule.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.", diff --git a/i18n/locales/es/data.json b/i18n/locales/es/data.json index 7f77e4c2..4b14cbb5 100644 --- a/i18n/locales/es/data.json +++ b/i18n/locales/es/data.json @@ -9,7 +9,6 @@ "The application does not allow to sign up new account": "The application does not allow to sign up new account" }, "auth": { - "%s No phone prefix": "%s No phone prefix", "Challenge method should be S256": "Challenge method should be S256", "Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s", "Failed to login in: %s": "Failed to login in: %s", @@ -57,13 +56,15 @@ "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username must have at least 2 characters": "Username must have at least 2 characters", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again", + "Your region is not allow to signup by phone": "Your region is not allow to signup by phone", "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances", "unsupported password type: %s": "unsupported password type: %s" }, "general": { "Missing parameter": "Missing parameter", "Please login first": "Please login first", - "The user: %s doesn't exist": "The user: %s doesn't exist" + "The user: %s doesn't exist": "The user: %s doesn't exist", + "don't support captchaProvider: ": "don't support captchaProvider: " }, "ldap": { "Ldap server exist": "Ldap server exist" @@ -130,7 +131,7 @@ "Email is invalid": "Email is invalid", "Invalid captcha provider.": "Invalid captcha provider.", "Organization does not exist": "Organization does not exist", - "Phone number is invalid": "Phone number is invalid", + "Phone number is invalid in your region %s": "Phone number is invalid in your region %s", "Turing test failed.": "Turing test failed.", "Unable to get the email modify rule.": "Unable to get the email modify rule.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.", diff --git a/i18n/locales/fr/data.json b/i18n/locales/fr/data.json index 7f77e4c2..4b14cbb5 100644 --- a/i18n/locales/fr/data.json +++ b/i18n/locales/fr/data.json @@ -9,7 +9,6 @@ "The application does not allow to sign up new account": "The application does not allow to sign up new account" }, "auth": { - "%s No phone prefix": "%s No phone prefix", "Challenge method should be S256": "Challenge method should be S256", "Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s", "Failed to login in: %s": "Failed to login in: %s", @@ -57,13 +56,15 @@ "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username must have at least 2 characters": "Username must have at least 2 characters", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again", + "Your region is not allow to signup by phone": "Your region is not allow to signup by phone", "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances", "unsupported password type: %s": "unsupported password type: %s" }, "general": { "Missing parameter": "Missing parameter", "Please login first": "Please login first", - "The user: %s doesn't exist": "The user: %s doesn't exist" + "The user: %s doesn't exist": "The user: %s doesn't exist", + "don't support captchaProvider: ": "don't support captchaProvider: " }, "ldap": { "Ldap server exist": "Ldap server exist" @@ -130,7 +131,7 @@ "Email is invalid": "Email is invalid", "Invalid captcha provider.": "Invalid captcha provider.", "Organization does not exist": "Organization does not exist", - "Phone number is invalid": "Phone number is invalid", + "Phone number is invalid in your region %s": "Phone number is invalid in your region %s", "Turing test failed.": "Turing test failed.", "Unable to get the email modify rule.": "Unable to get the email modify rule.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.", diff --git a/i18n/locales/ja/data.json b/i18n/locales/ja/data.json index 7f77e4c2..4b14cbb5 100644 --- a/i18n/locales/ja/data.json +++ b/i18n/locales/ja/data.json @@ -9,7 +9,6 @@ "The application does not allow to sign up new account": "The application does not allow to sign up new account" }, "auth": { - "%s No phone prefix": "%s No phone prefix", "Challenge method should be S256": "Challenge method should be S256", "Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s", "Failed to login in: %s": "Failed to login in: %s", @@ -57,13 +56,15 @@ "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username must have at least 2 characters": "Username must have at least 2 characters", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again", + "Your region is not allow to signup by phone": "Your region is not allow to signup by phone", "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances", "unsupported password type: %s": "unsupported password type: %s" }, "general": { "Missing parameter": "Missing parameter", "Please login first": "Please login first", - "The user: %s doesn't exist": "The user: %s doesn't exist" + "The user: %s doesn't exist": "The user: %s doesn't exist", + "don't support captchaProvider: ": "don't support captchaProvider: " }, "ldap": { "Ldap server exist": "Ldap server exist" @@ -130,7 +131,7 @@ "Email is invalid": "Email is invalid", "Invalid captcha provider.": "Invalid captcha provider.", "Organization does not exist": "Organization does not exist", - "Phone number is invalid": "Phone number is invalid", + "Phone number is invalid in your region %s": "Phone number is invalid in your region %s", "Turing test failed.": "Turing test failed.", "Unable to get the email modify rule.": "Unable to get the email modify rule.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.", diff --git a/i18n/locales/ko/data.json b/i18n/locales/ko/data.json index 7f77e4c2..4b14cbb5 100644 --- a/i18n/locales/ko/data.json +++ b/i18n/locales/ko/data.json @@ -9,7 +9,6 @@ "The application does not allow to sign up new account": "The application does not allow to sign up new account" }, "auth": { - "%s No phone prefix": "%s No phone prefix", "Challenge method should be S256": "Challenge method should be S256", "Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s", "Failed to login in: %s": "Failed to login in: %s", @@ -57,13 +56,15 @@ "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username must have at least 2 characters": "Username must have at least 2 characters", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again", + "Your region is not allow to signup by phone": "Your region is not allow to signup by phone", "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances", "unsupported password type: %s": "unsupported password type: %s" }, "general": { "Missing parameter": "Missing parameter", "Please login first": "Please login first", - "The user: %s doesn't exist": "The user: %s doesn't exist" + "The user: %s doesn't exist": "The user: %s doesn't exist", + "don't support captchaProvider: ": "don't support captchaProvider: " }, "ldap": { "Ldap server exist": "Ldap server exist" @@ -130,7 +131,7 @@ "Email is invalid": "Email is invalid", "Invalid captcha provider.": "Invalid captcha provider.", "Organization does not exist": "Organization does not exist", - "Phone number is invalid": "Phone number is invalid", + "Phone number is invalid in your region %s": "Phone number is invalid in your region %s", "Turing test failed.": "Turing test failed.", "Unable to get the email modify rule.": "Unable to get the email modify rule.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.", diff --git a/i18n/locales/ru/data.json b/i18n/locales/ru/data.json index 7f77e4c2..4b14cbb5 100644 --- a/i18n/locales/ru/data.json +++ b/i18n/locales/ru/data.json @@ -9,7 +9,6 @@ "The application does not allow to sign up new account": "The application does not allow to sign up new account" }, "auth": { - "%s No phone prefix": "%s No phone prefix", "Challenge method should be S256": "Challenge method should be S256", "Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s", "Failed to login in: %s": "Failed to login in: %s", @@ -57,13 +56,15 @@ "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username must have at least 2 characters": "Username must have at least 2 characters", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again", + "Your region is not allow to signup by phone": "Your region is not allow to signup by phone", "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances", "unsupported password type: %s": "unsupported password type: %s" }, "general": { "Missing parameter": "Missing parameter", "Please login first": "Please login first", - "The user: %s doesn't exist": "The user: %s doesn't exist" + "The user: %s doesn't exist": "The user: %s doesn't exist", + "don't support captchaProvider: ": "don't support captchaProvider: " }, "ldap": { "Ldap server exist": "Ldap server exist" @@ -130,7 +131,7 @@ "Email is invalid": "Email is invalid", "Invalid captcha provider.": "Invalid captcha provider.", "Organization does not exist": "Organization does not exist", - "Phone number is invalid": "Phone number is invalid", + "Phone number is invalid in your region %s": "Phone number is invalid in your region %s", "Turing test failed.": "Turing test failed.", "Unable to get the email modify rule.": "Unable to get the email modify rule.", "Unable to get the phone modify rule.": "Unable to get the phone modify rule.", diff --git a/i18n/locales/zh/data.json b/i18n/locales/zh/data.json index 4cb3e2c8..fc295a53 100644 --- a/i18n/locales/zh/data.json +++ b/i18n/locales/zh/data.json @@ -9,7 +9,6 @@ "The application does not allow to sign up new account": "该应用不允许注册新用户" }, "auth": { - "%s No phone prefix": "%s 无此手机号前缀", "Challenge method should be S256": "Challenge 方法应该为 S256", "Failed to create user, user information is invalid: %s": "创建用户失败,用户信息无效: %s", "Failed to login in: %s": "登录失败: %s", @@ -57,13 +56,15 @@ "Username is too long (maximum is 39 characters).": "用户名过长(最大允许长度为39个字符)", "Username must have at least 2 characters": "用户名至少要有2个字符", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "密码错误次数已达上限,请在 %d 分后重试", + "Your region is not allow to signup by phone": "所在地区不支持手机号注册", "password or code is incorrect, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会", "unsupported password type: %s": "不支持的密码类型: %s" }, "general": { "Missing parameter": "缺少参数", "Please login first": "请先登录", - "The user: %s doesn't exist": "用户: %s 不存在" + "The user: %s doesn't exist": "用户: %s 不存在", + "don't support captchaProvider: ": "不支持验证码提供商: " }, "ldap": { "Ldap server exist": "LDAP服务器已存在" @@ -130,7 +131,7 @@ "Email is invalid": "非法的邮箱", "Invalid captcha provider.": "非法的验证码提供商", "Organization does not exist": "组织不存在", - "Phone number is invalid": "非法的手机号码", + "Phone number is invalid in your region %s": "您所在地区的电话号码无效 %s", "Turing test failed.": "验证码还未发送", "Unable to get the email modify rule.": "无法获取邮箱修改规则", "Unable to get the phone modify rule.": "无法获取手机号修改规则", diff --git a/init_data.json.template b/init_data.json.template index a7f0f819..c576aab7 100644 --- a/init_data.json.template +++ b/init_data.json.template @@ -7,7 +7,7 @@ "websiteUrl": "", "favicon": "", "passwordType": "", - "phonePrefix": "", + "countryCodes": [""], "defaultAvatar": "", "tags": [""] } @@ -107,6 +107,7 @@ "avatar": "", "email": "", "phone": "", + "countryCode": "", "address": [], "affiliation": "", "tag": "", diff --git a/object/check.go b/object/check.go index 5068feac..ee4a89b9 100644 --- a/object/check.go +++ b/object/check.go @@ -42,7 +42,7 @@ func init() { reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`) } -func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, affiliation string, lang string) string { +func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, countryCode string, affiliation string, lang string) string { if organization == nil { return i18n.Translate(lang, "check:Organization does not exist") } @@ -107,7 +107,9 @@ func CheckUserSignup(application *Application, organization *Organization, usern if HasUserByField(organization.Name, "phone", phone) { return i18n.Translate(lang, "check:Phone already exists") - } else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) { + } else if !util.IsPhoneAllowInRegin(countryCode, organization.CountryCodes) { + return i18n.Translate(lang, "check:Your region is not allow to signup by phone") + } else if !util.IsPhoneValid(phone, countryCode) { return i18n.Translate(lang, "check:Phone number is invalid") } } diff --git a/object/init.go b/object/init.go index 6e50a59a..9c3944a1 100644 --- a/object/init.go +++ b/object/init.go @@ -53,7 +53,7 @@ func initBuiltInOrganization() bool { WebsiteUrl: "https://example.com", Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")), PasswordType: "plain", - PhonePrefix: "86", + CountryCodes: []string{"CN"}, DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), Tags: []string{}, Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"}, @@ -68,6 +68,7 @@ func initBuiltInOrganization() bool { {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"}, @@ -109,6 +110,7 @@ func initBuiltInUser() { Avatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), Email: "admin@example.com", Phone: "12345678910", + CountryCode: "CN", Address: []string{}, Affiliation: "Example Inc.", Tag: "staff", diff --git a/object/init_data.go b/object/init_data.go index e5d0e746..8f25fada 100644 --- a/object/init_data.go +++ b/object/init_data.go @@ -189,6 +189,7 @@ func initDefinedOrganization(organization *Organization) { {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"}, diff --git a/object/migrator.go b/object/migrator.go index cfe80506..befb585b 100644 --- a/object/migrator.go +++ b/object/migrator.go @@ -26,6 +26,7 @@ func DoMigration() { &Migrator_1_101_0_PR_1083{}, &Migrator_1_235_0_PR_1530{}, &Migrator_1_240_0_PR_1539{}, + &Migrator_1_245_0_PR_1557{}, // more migrators add here in chronological order... } diff --git a/object/migrator_1_245_0_PR_1557.go b/object/migrator_1_245_0_PR_1557.go new file mode 100644 index 00000000..988b8b73 --- /dev/null +++ b/object/migrator_1_245_0_PR_1557.go @@ -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 +} diff --git a/object/organization.go b/object/organization.go index ed710f57..31348a5d 100644 --- a/object/organization.go +++ b/object/organization.go @@ -49,7 +49,7 @@ type Organization struct { Favicon string `xorm:"varchar(100)" json:"favicon"` PasswordType string `xorm:"varchar(100)" json:"passwordType"` PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"` - PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"` + CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"` DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"` DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"` Tags []string `xorm:"mediumtext" json:"tags"` diff --git a/object/sms.go b/object/sms.go index 73df87b3..bbdb0bfa 100644 --- a/object/sms.go +++ b/object/sms.go @@ -14,7 +14,11 @@ package object -import "github.com/casdoor/go-sms-sender" +import ( + "strings" + + "github.com/casdoor/go-sms-sender" +) func SendSms(provider *Provider, content string, phoneNumbers ...string) error { client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId) @@ -25,6 +29,12 @@ func SendSms(provider *Provider, content string, phoneNumbers ...string) error { return err } + if provider.Type == go_sms_sender.Aliyun { + for i, number := range phoneNumbers { + phoneNumbers[i] = strings.TrimPrefix(number, "+") + } + } + params := map[string]string{} if provider.Type == go_sms_sender.TencentCloud { params["0"] = content diff --git a/object/user.go b/object/user.go index a0a781fa..b3ce72b1 100644 --- a/object/user.go +++ b/object/user.go @@ -46,7 +46,8 @@ type User struct { PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"` Email string `xorm:"varchar(100) index" json:"email"` EmailVerified bool `json:"emailVerified"` - Phone string `xorm:"varchar(100) index" json:"phone"` + Phone string `xorm:"varchar(20) index" json:"phone"` + CountryCode string `xorm:"varchar(6)" json:"countryCode"` Location string `xorm:"varchar(100)" json:"location"` Address []string `json:"address"` Affiliation string `xorm:"varchar(100)" json:"affiliation"` @@ -454,7 +455,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo } } 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) diff --git a/object/user_util.go b/object/user_util.go index eb09c0b5..35ff1827 100644 --- a/object/user_util.go +++ b/object/user_util.go @@ -170,3 +170,18 @@ func ClearUserOAuthProperties(user *User, providerType string) bool { return affected != 0 } + +func (user *User) GetCountryCode(countryCode string) string { + if countryCode != "" { + return countryCode + } + + if user != nil && user.CountryCode != "" { + return user.CountryCode + } + + if org := GetOrganizationByUser(user); org != nil && len(org.CountryCodes) > 0 { + return org.CountryCodes[0] + } + return "" +} diff --git a/util/slice.go b/util/slice.go index 9496a700..6fb95f8f 100644 --- a/util/slice.go +++ b/util/slice.go @@ -14,6 +14,8 @@ package util +import "sort" + func DeleteVal(values []string, val string) []string { newValues := []string{} for _, v := range values { @@ -23,3 +25,8 @@ func DeleteVal(values []string, val string) []string { } return newValues } + +func ContainsString(values []string, val string) bool { + sort.Strings(values) + return sort.SearchStrings(values, val) != len(values) +} diff --git a/util/string.go b/util/string.go index 36dc8cc9..8d51c984 100644 --- a/util/string.go +++ b/util/string.go @@ -227,7 +227,7 @@ func IsChinese(str string) bool { } func GetMaskedPhone(phone string) string { - return getMaskedPhone(phone) + return rePhone.ReplaceAllString(phone, "$1****$2") } func GetMaskedEmail(email string) string { diff --git a/util/regex.go b/util/validation.go similarity index 57% rename from util/regex.go rename to util/validation.go index bceaedb1..00aebf60 100644 --- a/util/regex.go +++ b/util/validation.go @@ -17,16 +17,13 @@ package util import ( "net/mail" "regexp" + + "github.com/nyaruka/phonenumbers" ) -var ( - rePhoneCn *regexp.Regexp - rePhone *regexp.Regexp -) +var rePhone *regexp.Regexp func init() { - // https://learnku.com/articles/31543 - rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`) rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})") } @@ -35,10 +32,19 @@ func IsEmailValid(email string) bool { return err == nil } -func IsPhoneCnValid(phone string) bool { - return rePhoneCn.MatchString(phone) +func IsPhoneValid(phone string, countryCode string) bool { + phoneNumber, err := phonenumbers.Parse(phone, countryCode) + if err != nil { + return false + } + return phonenumbers.IsValidNumber(phoneNumber) } -func getMaskedPhone(phone string) string { - return rePhone.ReplaceAllString(phone, "$1****$2") +func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool { + return !ContainsString(allowRegions, countryCode) +} + +func GetE164Number(phone string, countryCode string) (string, bool) { + phoneNumber, _ := phonenumbers.Parse(phone, countryCode) + return phonenumbers.Format(phoneNumber, phonenumbers.E164), phonenumbers.IsValidNumber(phoneNumber) } diff --git a/web/cypress/e2e/login.cy.js b/web/cypress/e2e/login.cy.js index 01f60865..849c5806 100644 --- a/web/cypress/e2e/login.cy.js +++ b/web/cypress/e2e/login.cy.js @@ -15,7 +15,6 @@ describe("Login test", () => { "password": "123", "autoSignin": true, "type": "login", - "phonePrefix": "86", }, }).then((Response) => { expect(Response).property("body").property("status").to.equal("ok"); @@ -40,7 +39,6 @@ describe("Login test", () => { "password": "1234", "autoSignin": true, "type": "login", - "phonePrefix": "86", }, }).then((Response) => { expect(Response).property("body").property("status").to.equal("error"); diff --git a/web/cypress/support/commands.js b/web/cypress/support/commands.js index 890a0668..4e2b41a5 100644 --- a/web/cypress/support/commands.js +++ b/web/cypress/support/commands.js @@ -34,7 +34,6 @@ Cypress.Commands.add('login', ()=>{ "password": "123", "autoSignin": true, "type": "login", - "phonePrefix": "86", }, }).then((Response) => { expect(Response).property("body").property("status").to.equal("ok"); diff --git a/web/package.json b/web/package.json index 12fa031a..174a69bd 100644 --- a/web/package.json +++ b/web/package.json @@ -21,6 +21,7 @@ "file-saver": "^2.0.5", "i18n-iso-countries": "^7.0.0", "i18next": "^19.8.9", + "libphonenumber-js": "^1.10.19", "moment": "^2.29.1", "qs": "^6.10.2", "react": "^18.2.0", diff --git a/web/src/AccountTable.js b/web/src/AccountTable.js index 0482fa7e..deb3dfd1 100644 --- a/web/src/AccountTable.js +++ b/web/src/AccountTable.js @@ -78,6 +78,7 @@ class AccountTable extends React.Component { {name: "Password", displayName: i18next.t("general:Password")}, {name: "Email", displayName: i18next.t("general:Email")}, {name: "Phone", displayName: i18next.t("general:Phone")}, + {name: "Country code", displayName: i18next.t("user:Country code")}, {name: "Country/Region", displayName: i18next.t("user:Country/Region")}, {name: "Location", displayName: i18next.t("user:Location")}, {name: "Affiliation", displayName: i18next.t("user:Affiliation")}, diff --git a/web/src/OrganizationEditPage.js b/web/src/OrganizationEditPage.js index 87495d92..d2df0a4b 100644 --- a/web/src/OrganizationEditPage.js +++ b/web/src/OrganizationEditPage.js @@ -184,12 +184,20 @@ class OrganizationEditPage extends React.Component { - {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"))} : - { - this.updateOrganizationField("phonePrefix", e.target.value); - }} /> + { + return Setting.getOption(item.label, item.key); + })} + value={this.state.organization.languages ?? []} onChange={(value => { this.updateOrganizationField("languages", value); })} > - { - [ - {value: "en", label: "English"}, - {value: "zh", label: "简体中文"}, - {value: "es", label: "Español"}, - {value: "fr", label: "Français"}, - {value: "de", label: "Deutsch"}, - {value: "ja", label: "日本語"}, - {value: "ko", label: "한국어"}, - {value: "ru", label: "Русский"}, - ].map((item, index) => ) - } diff --git a/web/src/OrganizationListPage.js b/web/src/OrganizationListPage.js index b93f3d44..9f6e59a0 100644 --- a/web/src/OrganizationListPage.js +++ b/web/src/OrganizationListPage.js @@ -33,7 +33,7 @@ class OrganizationListPage extends BaseListPage { favicon: `${Setting.StaticBaseUrl}/img/favicon.png`, passwordType: "plain", PasswordSalt: "", - phonePrefix: "86", + countryCodes: ["CN"], defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`, defaultApplication: "", tags: [], diff --git a/web/src/SelectLanguageBox.js b/web/src/SelectLanguageBox.js index b846a350..3d02875e 100644 --- a/web/src/SelectLanguageBox.js +++ b/web/src/SelectLanguageBox.js @@ -18,16 +18,6 @@ import {Dropdown} from "antd"; import "./App.less"; import {GlobalOutlined} from "@ant-design/icons"; -export const Countries = [{label: "English", key: "en", country: "US", alt: "English"}, - {label: "简体中文", key: "zh", country: "CN", alt: "简体中文"}, - {label: "Español", key: "es", country: "ES", alt: "Español"}, - {label: "Français", key: "fr", country: "FR", alt: "Français"}, - {label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"}, - {label: "日本語", key: "ja", country: "JP", alt: "日本語"}, - {label: "한국어", key: "ko", country: "KR", alt: "한국어"}, - {label: "Русский", key: "ru", country: "RU", alt: "Русский"}, -]; - function flagIcon(country, alt) { return ( {alt} @@ -39,15 +29,15 @@ class SelectLanguageBox extends React.Component { super(props); this.state = { classes: props, - languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"], + languages: props.languages ?? Setting.Countries.map(item => item.key), }; - Countries.forEach((country) => { + Setting.Countries.forEach((country) => { new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`; }); } - items = Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt))); + items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt))); getOrganizationLanguages(languages) { const select = []; diff --git a/web/src/SelectRegionBox.js b/web/src/SelectRegionBox.js index 077f603a..c80a2e15 100644 --- a/web/src/SelectRegionBox.js +++ b/web/src/SelectRegionBox.js @@ -49,9 +49,9 @@ class SelectRegionBox extends React.Component { } > { - Setting.getCountryNames().map((item) => ( + Setting.getCountriesData().map((item) => ( )) diff --git a/web/src/Setting.js b/web/src/Setting.js index a11baef9..def0b369 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -23,6 +23,7 @@ import copy from "copy-to-clipboard"; import {authConfig} from "./auth/Auth"; import {Helmet} from "react-helmet"; import * as Conf from "./Conf"; +import * as phoneNumber from "libphonenumber-js"; import * as path from "path-browserify"; 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.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) { if (application?.themeData?.isEnabled) { return application.themeData; @@ -188,20 +199,33 @@ export const OtherProviderInfo = { }, }; -export function getCountriesData() { +export function initCountries() { const countries = require("i18n-iso-countries"); countries.registerLocale(require("i18n-iso-countries/langs/" + getLanguage() + ".json")); return countries; } -export function getCountryNames() { - const data = getCountriesData().getNames(getLanguage(), {select: "official"}); - - return Object.entries(data).map(items => { - return {code: items[0], name: items[1]}; +export function getCountriesData(countryCodes = phoneNumber.getCountries()) { + return countryCodes?.map((countryCode) => { + if (phoneNumber.isSupportedCountry(countryCode)) { + const name = initCountries().getName(countryCode, getLanguage()); + return { + code: countryCode, + name: name || "", + phone: phoneNumber.getCountryCallingCode(countryCode), + }; + } }); } +export function countryFlag(country) { + return {country.name}; +} + +export function getPhoneCodeFromCountryCode(countryCode) { + return phoneNumber.isSupportedCountry(countryCode) ? phoneNumber.getCountryCallingCode(countryCode) : ""; +} + export function initServerUrl() { // const hostname = window.location.hostname; // if (hostname === "localhost") { @@ -299,16 +323,14 @@ export function isValidEmail(email) { return emailRegex.test(email); } -export function isValidPhone(phone) { - return phone !== ""; +export function isValidPhone(phone, countryCode = "") { + if (countryCode !== "") { + return phoneNumber.isValidPhoneNumber(phone, countryCode); + } - // if (phone === "") { - // return false; - // } - // // // https://learnku.com/articles/31543, `^s*$` filter empty email individually. - // const phoneRegex = /^\s*$|^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/; - // return phoneRegex.test(phone); + const phoneRegex = /[0-9]{4,15}$/; + return phoneRegex.test(phone); } export function isValidInvoiceTitle(invoiceTitle) { diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index d12f3154..2e7cc1ae 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -29,6 +29,7 @@ import SelectRegionBox from "./SelectRegionBox"; import WebAuthnCredentialTable from "./WebauthnCredentialTable"; import ManagedAccountTable from "./ManagedAccountTable"; import PropertyTable from "./propertyTable"; +import PhoneNumberInput from "./common/PhoneNumberInput"; const {Option} = Select; @@ -286,11 +287,13 @@ class UserEditPage extends React.Component { {Setting.isLocalAdminUser(this.props.account) ? ( { this.updateUserField("email", e.target.value); }} />) : ( { - this.updateUserField("phone", e.target.value); - }} /> : - ( { + this.updateUserField("phone", e.target.value); + }} /> + + : + (} placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")} disabled={!application.enablePassword} @@ -774,13 +777,18 @@ class LoginPage extends React.Component { const items = [ {label: i18next.t("login:Password"), key: "password"}, ]; - application.enableCodeSignin ? items.push({label: i18next.t("login:Verification Code"), key: "verificationCode"}) : null; + application.enableCodeSignin ? items.push({ + label: i18next.t("login:Verification Code"), + key: "verificationCode", + }) : null; application.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null; if (application.enableCodeSignin || application.enableWebAuthn) { return (
- {this.setState({loginMethod: key});}} centered> + { + this.setState({loginMethod: key}); + }} centered>
); @@ -823,7 +831,7 @@ class LoginPage extends React.Component {
-
+
{ Setting.renderHelmet(application) diff --git a/web/src/auth/SignupPage.js b/web/src/auth/SignupPage.js index a8959470..40ff4847 100644 --- a/web/src/auth/SignupPage.js +++ b/web/src/auth/SignupPage.js @@ -26,6 +26,7 @@ import SelectRegionBox from "../SelectRegionBox"; import CustomGithubCorner from "../CustomGithubCorner"; import SelectLanguageBox from "../SelectLanguageBox"; import {withRouter} from "react-router-dom"; +import PhoneNumberInput from "../common/PhoneNumberInput"; const formItemLayout = { labelCol: { @@ -68,6 +69,7 @@ class SignupPage extends React.Component { application: null, email: "", phone: "", + countryCode: "", emailCode: "", phoneCode: "", validEmail: false, @@ -157,7 +159,6 @@ class SignupPage extends React.Component { onFinish(values) { const application = this.getApplicationObj(); - values.phonePrefix = application.organizationObj.phonePrefix; AuthBackend.signup(values) .then((res) => { if (res.status === "ok") { @@ -378,35 +379,66 @@ class SignupPage extends React.Component { } else if (signupItem.name === "Phone") { return ( - { - if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone)) { - this.setState({validPhone: false}); - return Promise.reject(i18next.t("signup:The input is not valid Phone!")); - } + + + { + 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(); - }, - }, - ]} - > - this.setState({phone: e.target.value})} - /> + this.setState({validPhone: true}); + return Promise.resolve(); + }, + }, + ]} + > + {this.setState({countryCode: value});}} + countryCodes={this.getApplicationObj().organizationObj.countryCodes} + /> + + { + 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(); + }, + }, + ]} + > + this.setState({phone: e.target.value})} + /> + + { + onChange?.(e); + }; + + return ( + + ); +} diff --git a/web/src/locales/de/data.json b/web/src/locales/de/data.json index ac35671c..1ea158ae 100644 --- a/web/src/locales/de/data.json +++ b/web/src/locales/de/data.json @@ -219,8 +219,6 @@ "Permissions - Tooltip": "Permissions - Tooltip", "Phone": "Telefon", "Phone - Tooltip": "Phone", - "Phone prefix": "Telefonpräfix", - "Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions", "Preview": "Vorschau", "Preview - Tooltip": "The form in which the password is stored in the database", "Products": "Products", @@ -252,6 +250,8 @@ "Successfully added": "Successfully added", "Successfully deleted": "Successfully deleted", "Successfully saved": "Successfully saved", + "Supported country code": "Supported country code", + "Supported country code - Tooltip": "Supported country code - Tooltip", "Swagger": "Swagger", "Sync": "Sync", "Syncers": "Syncers", @@ -311,10 +311,10 @@ "Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an", "Password": "Passwort", "Password - Tooltip": "Passwort - Tooltip", + "Please input your Email or Phone!": "Please input your Email or Phone!", "Please input your code!": "Bitte gib deinen Code ein!", "Please input your password!": "Bitte geben Sie Ihr Passwort ein!", "Please input your password, at least 6 characters!": "Bitte geben Sie Ihr Passwort ein, mindestens 6 Zeichen!", - "Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!", "Redirecting, please wait.": "Redirecting, please wait.", "Sign In": "Anmelden", "Sign in with WebAuthn": "Sign in with WebAuthn", @@ -640,6 +640,7 @@ "Please input your last name!": "Please input your last name!", "Please input your phone number!": "Bitte geben Sie Ihre Telefonnummer ein!", "Please input your real name!": "Bitte geben Sie Ihren persönlichen Namen ein!", + "Please select your country code!": "Please select your country code!", "Please select your country/region!": "Bitte wählen Sie Ihr Land/Ihre Region!", "Terms of Use": "Nutzungsbedingungen", "The input is not invoice Tax ID!": "The input is not invoice Tax ID!", @@ -726,6 +727,7 @@ "Captcha Verify Failed": "Captcha Verify Failed", "Captcha Verify Success": "Captcha Verify Success", "Code Sent": "Code gesendet", + "Country code": "Country code", "Country/Region": "Land/Region", "Country/Region - Tooltip": "Country/Region", "Edit User": "Benutzer bearbeiten", diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json index feaf267c..92824501 100644 --- a/web/src/locales/en/data.json +++ b/web/src/locales/en/data.json @@ -219,8 +219,6 @@ "Permissions - Tooltip": "Permissions - Tooltip", "Phone": "Phone", "Phone - Tooltip": "Phone - Tooltip", - "Phone prefix": "Phone prefix", - "Phone prefix - Tooltip": "Phone prefix - Tooltip", "Preview": "Preview", "Preview - Tooltip": "Preview - Tooltip", "Products": "Products", @@ -252,6 +250,8 @@ "Successfully added": "Successfully added", "Successfully deleted": "Successfully deleted", "Successfully saved": "Successfully saved", + "Supported country code": "Supported country code", + "Supported country code - Tooltip": "Supported country code - Tooltip", "Swagger": "Swagger", "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!": "Please input your password, at least 6 characters!", - "Please input your username, Email or phone!": "Please input your username, Email or phone!", "Redirecting, please wait.": "Redirecting, please wait.", "Sign In": "Sign In", "Sign in with WebAuthn": "Sign in with WebAuthn", @@ -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 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 code", "Country/Region": "Country/Region", "Country/Region - Tooltip": "Country/Region - Tooltip", "Edit User": "Edit User", diff --git a/web/src/locales/es/data.json b/web/src/locales/es/data.json index 27e198a0..0beb4c37 100644 --- a/web/src/locales/es/data.json +++ b/web/src/locales/es/data.json @@ -219,8 +219,6 @@ "Permissions - Tooltip": "Permisos - Tooltip", "Phone": "Teléfono", "Phone - Tooltip": "Teléfono - Tooltip", - "Phone prefix": "Prefijo teléfonico", - "Phone prefix - Tooltip": "Prefijo teléfonico - Tooltip", "Preview": "Previsualizar", "Preview - Tooltip": "Previsualizar - Tooltip", "Products": "Productos", @@ -252,6 +250,8 @@ "Successfully added": "Successfully added", "Successfully deleted": "Successfully deleted", "Successfully saved": "Successfully saved", + "Supported country code": "Supported country code", + "Supported country code - Tooltip": "Supported country code - Tooltip", "Swagger": "Swagger", "Sync": "Sincronizador", "Syncers": "Sincronizadores", @@ -311,10 +311,10 @@ "Or sign in with another account": "O inicia sesión con otra cuenta", "Password": "Contraseña", "Password - Tooltip": "Contraseña - Tooltip", + "Please input your Email or Phone!": "Please input your Email or Phone!", "Please input your code!": "¡Por favor ingrese su código!", "Please input your password!": "¡Por favor ingrese su contraseña!", "Please input your password, at least 6 characters!": "Su contraseña debe contener al menos 6 caracteres.", - "Please input your username, Email or phone!": "¡Ingrese su nombre de usuario, correo electrónico o teléfono!", "Redirecting, please wait.": "Redirecting, please wait.", "Sign In": "Iniciar de sesión", "Sign in with WebAuthn": "Iniciar de sesión con WebAuthn", @@ -640,6 +640,7 @@ "Please input your last name!": "Por favor, ingrese su apellido!", "Please input your phone number!": "Por favor, ingrese su número teléfonico!", "Please input your real name!": "Por favor, ingrese un nombre real!", + "Please select your country code!": "Please select your country code!", "Please select your country/region!": "Por favor, seleccione su pais/region!", "Terms of Use": "Términos de Uso", "The input is not invoice Tax ID!": "El valor ingresado no es un número de identificación fiscal de factura!", @@ -726,6 +727,7 @@ "Captcha Verify Failed": "Fallo la verificación del Captcha", "Captcha Verify Success": "Captcha verificado con éxito", "Code Sent": "Código enviado", + "Country code": "Country code", "Country/Region": "Pais/Región", "Country/Region - Tooltip": "Pais/Región - Tooltip", "Edit User": "Editar usuario", diff --git a/web/src/locales/fr/data.json b/web/src/locales/fr/data.json index d25d1aa9..b14618fb 100644 --- a/web/src/locales/fr/data.json +++ b/web/src/locales/fr/data.json @@ -219,8 +219,6 @@ "Permissions - Tooltip": "Permissions - Tooltip", "Phone": "Télé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 - Tooltip": "The form in which the password is stored in the database", "Products": "Products", @@ -252,6 +250,8 @@ "Successfully added": "Successfully added", "Successfully deleted": "Successfully deleted", "Successfully saved": "Successfully saved", + "Supported country code": "Supported country code", + "Supported country code - Tooltip": "Supported country code - Tooltip", "Swagger": "Swagger", "Sync": "Sync", "Syncers": "Synchronisateurs", @@ -311,10 +311,10 @@ "Or sign in with another account": "Ou connectez-vous avec un autre compte", "Password": "Mot de passe", "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 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 username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!", "Redirecting, please wait.": "Redirecting, please wait.", "Sign In": "Se connecter", "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 phone number!": "Veuillez entrer votre numéro de téléphone!", "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!", "Terms of Use": "Conditions d'utilisation", "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 envoyé", + "Country code": "Country code", "Country/Region": "Pays/Région", "Country/Region - Tooltip": "Country/Region", "Edit User": "Editer l'utilisateur", diff --git a/web/src/locales/ja/data.json b/web/src/locales/ja/data.json index 4a713bf1..1d1b5270 100644 --- a/web/src/locales/ja/data.json +++ b/web/src/locales/ja/data.json @@ -219,8 +219,6 @@ "Permissions - Tooltip": "Permissions - Tooltip", "Phone": "電話番号", "Phone - Tooltip": "Phone", - "Phone prefix": "電話プレフィクス", - "Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions", "Preview": "プレビュー", "Preview - Tooltip": "The form in which the password is stored in the database", "Products": "Products", @@ -252,6 +250,8 @@ "Successfully added": "Successfully added", "Successfully deleted": "Successfully deleted", "Successfully saved": "Successfully saved", + "Supported country code": "Supported country code", + "Supported country code - Tooltip": "Supported country code - Tooltip", "Swagger": "Swagger", "Sync": "Sync", "Syncers": "Syncers", @@ -311,10 +311,10 @@ "Or sign in with another account": "または別のアカウントでサインイン", "Password": "パスワード", "Password - Tooltip": "パスワード → ツールチップ", + "Please input your Email or Phone!": "Please input your Email or Phone!", "Please input your code!": "コードを入力してください!", "Please input your password!": "パスワードを入力してください!", "Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください!", - "Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。", "Redirecting, please wait.": "Redirecting, please wait.", "Sign In": "サインイン", "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 phone number!": "電話番号を入力してください!", "Please input your real name!": "個人名を入力してください!", + "Please select your country code!": "Please select your country code!", "Please select your country/region!": "あなたの国/地域を選択してください!", "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": "コードを送信しました", + "Country code": "Country code", "Country/Region": "国/地域", "Country/Region - Tooltip": "Country/Region", "Edit User": "ユーザーを編集", diff --git a/web/src/locales/ko/data.json b/web/src/locales/ko/data.json index 5c3ab123..648ff7ee 100644 --- a/web/src/locales/ko/data.json +++ b/web/src/locales/ko/data.json @@ -219,8 +219,6 @@ "Permissions - Tooltip": "Permissions - Tooltip", "Phone": "Phone", "Phone - Tooltip": "Phone", - "Phone prefix": "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", "Products": "Products", @@ -252,6 +250,8 @@ "Successfully added": "Successfully added", "Successfully deleted": "Successfully deleted", "Successfully saved": "Successfully saved", + "Supported country code": "Supported country code", + "Supported country code - Tooltip": "Supported country code - Tooltip", "Swagger": "Swagger", "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!": "Please input your password, at least 6 characters!", - "Please input your username, Email or phone!": "Please input your username, Email or phone!", "Redirecting, please wait.": "Redirecting, please wait.", "Sign In": "Sign In", "Sign in with WebAuthn": "Sign in with WebAuthn", @@ -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 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 code", "Country/Region": "Country/Region", "Country/Region - Tooltip": "Country/Region", "Edit User": "Edit User", diff --git a/web/src/locales/ru/data.json b/web/src/locales/ru/data.json index a74d02a3..5420eab8 100644 --- a/web/src/locales/ru/data.json +++ b/web/src/locales/ru/data.json @@ -219,8 +219,6 @@ "Permissions - Tooltip": "Permissions - Tooltip", "Phone": "Телефон", "Phone - Tooltip": "Phone", - "Phone prefix": "Префикс телефона", - "Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions", "Preview": "Предпросмотр", "Preview - Tooltip": "The form in which the password is stored in the database", "Products": "Продукты", @@ -252,6 +250,8 @@ "Successfully added": "Successfully added", "Successfully deleted": "Successfully deleted", "Successfully saved": "Successfully saved", + "Supported country code": "Supported country code", + "Supported country code - Tooltip": "Supported country code - Tooltip", "Swagger": "Swagger", "Sync": "Sync", "Syncers": "Синхронизаторы", @@ -311,10 +311,10 @@ "Or sign in with another account": "Или войти с помощью другой учетной записи", "Password": "Пароль", "Password - Tooltip": "Пароль - Подсказка", + "Please input your Email or Phone!": "Please input your Email or Phone!", "Please input your code!": "Пожалуйста, введите ваш код!", "Please input your password!": "Пожалуйста, введите ваш пароль!", "Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!", - "Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!", "Redirecting, please wait.": "Redirecting, please wait.", "Sign In": "Войти", "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 phone number!": "Пожалуйста, введите ваш номер телефона!", "Please input your real name!": "Пожалуйста, введите ваше личное имя!", + "Please select your country code!": "Please select your country code!", "Please select your country/region!": "Пожалуйста, выберите вашу страну/регион!", "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": "Код отправлен", + "Country code": "Country code", "Country/Region": "Страна/регион", "Country/Region - Tooltip": "Country/Region", "Edit User": "Изменить пользователя", diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json index 0cb16fe3..0dcf3634 100644 --- a/web/src/locales/zh/data.json +++ b/web/src/locales/zh/data.json @@ -219,8 +219,6 @@ "Permissions - Tooltip": "权限", "Phone": "手机号", "Phone - Tooltip": "手机号", - "Phone prefix": "手机号前缀", - "Phone prefix - Tooltip": "移动电话号码前缀,用于区分国家或地区", "Preview": "预览", "Preview - Tooltip": "预览", "Products": "商品", @@ -252,6 +250,8 @@ "Successfully added": "添加成功", "Successfully deleted": "删除成功", "Successfully saved": "保存成功", + "Supported country code": "支持的国家代码", + "Supported country code - Tooltip": "支持发送短信的国家 - Tooltip", "Swagger": "API文档", "Sync": "同步", "Syncers": "同步器", @@ -311,10 +311,10 @@ "Or sign in with another account": "或者,登录其他账号", "Password": "密码", "Password - Tooltip": "密码", + "Please input your Email or Phone!": "请输入您的Email或手机号!", "Please input your code!": "请输入您的验证码!", "Please input your password!": "请输入您的密码!", "Please input your password, at least 6 characters!": "请输入您的密码,不少于6位", - "Please input your username, Email or phone!": "请输入您的用户名、Email或手机号!", "Redirecting, please wait.": "正在跳转, 请稍等.", "Sign In": "登录", "Sign in with WebAuthn": "WebAuthn登录", @@ -640,6 +640,7 @@ "Please input your last name!": "请输入您的姓氏!", "Please input your phone number!": "请输入您的手机号码!", "Please input your real name!": "请输入您的姓名!", + "Please select your country code!": "请选择国家代码!", "Please select your country/region!": "请选择您的国家/地区", "Terms of Use": "《用户协议》", "The input is not invoice Tax ID!": "您输入的纳税人识别号有误!", @@ -726,6 +727,7 @@ "Captcha Verify Failed": "验证码校验失败", "Captcha Verify Success": "验证码校验成功", "Code Sent": "验证码已发送", + "Country code": "国家代码", "Country/Region": "国家/地区", "Country/Region - Tooltip": "国家/地区", "Edit User": "编辑用户", diff --git a/web/yarn.lock b/web/yarn.lock index 00e80f9e..40e28bf4 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -7829,6 +7829,11 @@ levn@~0.3.0: prelude-ls "~1.1.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: version "2.0.5" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"