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 {
@@ -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
;
+}
+
+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 {