From 400e335e6839e7dde4e276f4b92a9b90f2502c12 Mon Sep 17 00:00:00 2001 From: Kininaru Date: Wed, 12 May 2021 21:38:31 +0800 Subject: [PATCH 1/4] feat: add reset email by verification code Signed-off-by: Kininaru --- controllers/verification.go | 69 +++++++++++++++++++ object/adapter.go | 5 ++ object/verification.go | 106 +++++++++++++++++++++++++++++ routers/router.go | 2 + web/src/ResetModal.js | 118 +++++++++++++++++++++++++++++++++ web/src/UserEditPage.js | 6 +- web/src/backend/UserBackend.js | 23 +++++++ 7 files changed, 326 insertions(+), 3 deletions(-) create mode 100644 controllers/verification.go create mode 100644 object/verification.go create mode 100644 web/src/ResetModal.js diff --git a/controllers/verification.go b/controllers/verification.go new file mode 100644 index 00000000..195d0d16 --- /dev/null +++ b/controllers/verification.go @@ -0,0 +1,69 @@ +package controllers + +import "github.com/casdoor/casdoor/object" + +func (c *ApiController) SendVerificationCode() { + destType := c.Ctx.Request.Form.Get("type") + dest := c.Ctx.Request.Form.Get("dest") + remoteAddr := c.Ctx.Request.RemoteAddr + + if len(destType) == 0 || len(dest) == 0 { + c.Data["json"] = Response{Status: "error", Msg: "Missing parameter."} + c.ServeJSON() + return + } + + ret := "Invalid dest type." + switch destType { + case "email": + ret = object.SendVerificationCodeToEmail(remoteAddr, dest) + } + + var status string + if len(ret) == 0 { + status = "ok" + } else { + status = "error" + } + + c.Data["json"] = Response{Status: status, Msg: ret} + c.ServeJSON() +} + +func (c *ApiController) ResetEmailOrPhone() { + userId := c.GetSessionUser() + if len(userId) == 0 { + c.ResponseError("Please sign in first") + return + } + user := object.GetUser(userId) + if user == nil { + c.ResponseError("No such user.") + return + } + + 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 { + c.ResponseError("Missing parameter.") + return + } + + if ret := object.CheckVerificationCode(dest, code); len(ret) != 0 { + c.ResponseError(ret) + return + } + + switch destType { + case "email": + user.Email = dest + object.SetUserField(user, "email", user.Email) + default: + c.ResponseError("Unknown type.") + return + } + + c.Data["json"] = Response{Status: "ok"} + c.ServeJSON() +} diff --git a/object/adapter.go b/object/adapter.go index 6242f88a..b6dfb125 100644 --- a/object/adapter.go +++ b/object/adapter.go @@ -128,4 +128,9 @@ func (a *Adapter) createTable() { if err != nil { panic(err) } + + err = a.Engine.Sync2(new(VerificationRecord)) + if err != nil { + panic(err) + } } diff --git a/object/verification.go b/object/verification.go new file mode 100644 index 00000000..8007ba8d --- /dev/null +++ b/object/verification.go @@ -0,0 +1,106 @@ +package object + +import ( + "fmt" + "math/rand" + "time" +) + +type VerificationRecord struct { + RemoteAddr string `xorm:"varchar(100) notnull pk"` + Receiver string `xorm:"varchar(100) notnull"` + Code string `xorm:"varchar(10) notnull"` + Time int64 `xorm:"notnull"` + IsUsed bool +} + +func SendVerificationCodeToEmail(remoteAddr, dest string) string { + title := "Casdoor Code" + sender := "Casdoor Admin" + code := getRandomCode(5) + content := fmt.Sprintf("You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.", code) + + if result := AddToVerificationRecord(remoteAddr, dest, code); len(result) != 0 { + return result + } + + if err := SendEmail(title, content, dest, sender); err != nil { + panic(err) + } + + return "" +} + +func AddToVerificationRecord(remoteAddr, dest, code string) string { + var record VerificationRecord + record.RemoteAddr = remoteAddr + has, err := adapter.Engine.Get(&record) + if err != nil { + panic(err) + } + + now := time.Now().Unix() + + if has && now - record.Time < 60 { + return "You can only send one code in 60s." + } + + record.Receiver = dest + record.Code = code + record.Time = now + record.IsUsed = false + + if has { + _, err = adapter.Engine.ID(record.RemoteAddr).AllCols().Update(record) + } else { + _, err = adapter.Engine.Insert(record) + } + + if err != nil { + panic(err) + } + + return "" +} + +func CheckVerificationCode(dest, code string) string { + var record VerificationRecord + record.Receiver = dest + has, err := adapter.Engine.Desc("time").Where("is_used = 0").Get(&record) + if err != nil { + panic(err) + } + + if !has { + return "Code has not been sent yet!" + } + + now := time.Now().Unix() + if now-record.Time > 5*60 { + return "You should verify your code in 5 min!" + } + + if record.Code != code { + return "Wrong code!" + } + + record.IsUsed = true + _, err = adapter.Engine.ID(record.RemoteAddr).AllCols().Update(record) + if err != nil { + panic(err) + } + + return "" +} + +// from Casnode/object/validateCode.go line 116 +var stdNums = []byte("0123456789") + +func getRandomCode(length int) string { + var result []byte + r := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := 0; i < length; i++ { + result = append(result, stdNums[r.Intn(len(stdNums))]) + } + return string(result) +} diff --git a/routers/router.go b/routers/router.go index 875f6af2..a68be07d 100644 --- a/routers/router.go +++ b/routers/router.go @@ -60,6 +60,8 @@ func initAPI() { beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser") beego.Router("/api/upload-avatar", &controllers.ApiController{}, "POST:UploadAvatar") beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword") + beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode") + beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone") beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders") beego.Router("/api/get-default-providers", &controllers.ApiController{}, "GET:GetDefaultProviders") diff --git a/web/src/ResetModal.js b/web/src/ResetModal.js new file mode 100644 index 00000000..644c3c5f --- /dev/null +++ b/web/src/ResetModal.js @@ -0,0 +1,118 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {Button, Col, Modal, Row, Input,} from "antd"; +import i18next from "i18next"; +import React from "react"; +import * as Setting from "./Setting" +import * as UserBackend from "./backend/UserBackend" + +export const ResetModal = (props) => { + const [visible, setVisible] = React.useState(false); + const [confirmLoading, setConfirmLoading] = React.useState(false); + const [sendButtonText, setSendButtonText] = React.useState(i18next.t("user:Send Code")); + const [sendCodeCoolDown, setCoolDown] = React.useState(false); + const {buttonText, destType, coolDownTime} = props; + + const showModal = () => { + setVisible(true); + }; + + const handleCancel = () => { + setVisible(false); + }; + + const handleOk = () => { + let dest = document.getElementById("dest").value; + let code = document.getElementById("code").value; + if (dest === "") { + Setting.showMessage("error", i18next.t("user:Empty ") + destType); + return; + } + if (code === "") { + Setting.showMessage("error", i18next.t("user:Empty Code")); + return; + } + setConfirmLoading(true); + UserBackend.resetEmailOrPhone(dest, destType, code).then(res => { + if (res.status === "ok") { + Setting.showMessage("success", i18next.t(destType + " reset")); + window.location.reload(); + } else { + Setting.showMessage("error", i18next.t(res.msg)); + setConfirmLoading(false); + } + }) + } + + const countDown = (second) => { + if (second <= 0) { + setSendButtonText(i18next.t("user:Send Code")); + setCoolDown(false); + return; + } + setSendButtonText(second); + setTimeout(() => countDown(second - 1), 1000); + } + + const sendCode = () => { + if (sendCodeCoolDown) return; + let dest = document.getElementById("dest").value; + if (dest === "") { + Setting.showMessage("error", i18next.t("user:Empty ") + destType); + return; + } + UserBackend.sendCode(dest, destType).then(res => { + if (res.status === "ok") { + Setting.showMessage("success", i18next.t("user:Code Sent")); + setCoolDown(true); + countDown(coolDownTime); + } else { + Setting.showMessage("error", i18next.t("user:" + res.msg)); + } + }) + } + + return ( + + + + + + {" " + sendButtonText + " "}} + /> + + + + + + + + + ) +} + +export default ResetModal; \ No newline at end of file diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index 2f7b1e3d..d48a348d 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -25,6 +25,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend"; import * as ProviderBackend from "./backend/ProviderBackend"; import * as Provider from "./auth/Provider"; import PasswordModal from "./PasswordModal"; +import ResetModal from "./ResetModal"; const { Option } = Select; @@ -275,9 +276,8 @@ class UserEditPage extends React.Component { {i18next.t("general:Email")}: - { - this.updateUserField('email', e.target.value); - }} /> + + { this.state.user.id === this.props.account.id ? () : null} diff --git a/web/src/backend/UserBackend.js b/web/src/backend/UserBackend.js index ac11b5e0..f0b159f6 100644 --- a/web/src/backend/UserBackend.js +++ b/web/src/backend/UserBackend.js @@ -92,3 +92,26 @@ export function setPassword(userOwner, userName, oldPassword, newPassword) { body: formData }).then(res => res.json()); } + +export function sendCode(dest, type) { + let formData = new FormData(); + formData.append("dest", dest); + formData.append("type", type); + return fetch(`${Setting.ServerUrl}/api/send-verification-code`, { + method: "POST", + credentials: "include", + body: formData + }).then(res => res.json()); +} + +export function resetEmailOrPhone(dest, type, code) { + let formData = new FormData(); + formData.append("dest", dest); + formData.append("type", type); + formData.append("code", code); + return fetch(`${Setting.ServerUrl}/api/reset-email-or-phone`, { + method: "POST", + credentials: "include", + body: formData + }).then(res => res.json()); +} From 827930a02087fce816dbefe1cef709323b898456 Mon Sep 17 00:00:00 2001 From: Kininaru Date: Wed, 12 May 2021 22:09:41 +0800 Subject: [PATCH 2/4] feat: add reset phone by verification code Signed-off-by: Kininaru fixed client bug Signed-off-by: Kininaru feat: add i18n Signed-off-by: Kininaru add Casbin License Signed-off-by: Kininaru removed port from remoteaddr Signed-off-by: Kininaru --- conf/app.conf | 9 ++++++- controllers/verification.go | 33 ++++++++++++++++++++++- go.mod | 3 +++ go.sum | 18 +++++++++++++ object/sms.go | 53 +++++++++++++++++++++++++++++++++++++ object/verification.go | 28 ++++++++++++++++++++ web/src/ResetModal.js | 10 ++++--- web/src/UserEditPage.js | 10 ++----- web/src/locales/en.json | 21 ++++++++++++++- web/src/locales/zh.json | 21 ++++++++++++++- 10 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 object/sms.go diff --git a/conf/app.conf b/conf/app.conf index 9c50d7be..9ee15ac8 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -11,4 +11,11 @@ useProxy = false mailUser = "" mailPass = "" mailHost = "" -mailPort = "" \ No newline at end of file +mailPort = "" +smsProvider = "" +smsAccessId = "" +smsAccessKey = "" +smsAppId = "" +smsSign = "" +smsRegion = "" +smsTemplateId = "" \ No newline at end of file diff --git a/controllers/verification.go b/controllers/verification.go index 195d0d16..045aebb8 100644 --- a/controllers/verification.go +++ b/controllers/verification.go @@ -1,11 +1,30 @@ +// Copyright 2021 The casbin 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 controllers -import "github.com/casdoor/casdoor/object" +import ( + "strings" + + "github.com/casdoor/casdoor/object" +) func (c *ApiController) SendVerificationCode() { destType := c.Ctx.Request.Form.Get("type") dest := c.Ctx.Request.Form.Get("dest") remoteAddr := c.Ctx.Request.RemoteAddr + remoteAddr = remoteAddr[:strings.LastIndex(remoteAddr, ":")] if len(destType) == 0 || len(dest) == 0 { c.Data["json"] = Response{Status: "error", Msg: "Missing parameter."} @@ -17,6 +36,8 @@ func (c *ApiController) SendVerificationCode() { switch destType { case "email": ret = object.SendVerificationCodeToEmail(remoteAddr, dest) + case "phone": + ret = object.SendVerificationCodeToPhone(remoteAddr, dest) } var status string @@ -59,6 +80,16 @@ func (c *ApiController) ResetEmailOrPhone() { case "email": user.Email = dest object.SetUserField(user, "email", user.Email) + case "phone": + if strings.Index(dest, "+86") == 0 { + user.PhonePrefix = "86" + user.Phone = dest[3:] + } else if strings.Index(dest, "+1") == 0 { + user.PhonePrefix = "1" + user.Phone = dest[2:] + } + object.SetUserField(user, "phone", user.Phone) + object.SetUserField(user, "phone_prefix", user.PhonePrefix) default: c.ResponseError("Unknown type.") return diff --git a/go.mod b/go.mod index e5e4f5f6..1cb3cc65 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/casbin/casbin/v2 v2.23.4 github.com/casbin/xorm-adapter/v2 v2.2.0 + github.com/casdoor/go-sms-sender v0.0.1 github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df @@ -24,6 +25,8 @@ require ( golang.org/x/net v0.0.0-20201110031124-69a78807bb2b golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect gopkg.in/ini.v1 v1.62.0 xorm.io/core v0.7.2 xorm.io/xorm v1.0.3 diff --git a/go.sum b/go.sum index 26a023ea..755365d5 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4= github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= @@ -37,6 +39,8 @@ github.com/casbin/casbin/v2 v2.23.4 h1:izvAG3KA49C3/m1zpYfkLcZlkYQO5VeHj7dhwurwZ github.com/casbin/casbin/v2 v2.23.4/go.mod h1:wUgota0cQbTXE6Vd+KWpg41726jFRi7upxio0sR+Xd0= github.com/casbin/xorm-adapter/v2 v2.2.0 h1:wAuYpCDRPUSFxdRqcRrGS0664UC7RKE21x7wrIl3rLQ= github.com/casbin/xorm-adapter/v2 v2.2.0/go.mod h1:9bPGOgjA/qbtjXHt3FC1rRyyRMkt9c+m8vlfLWjdSXU= +github.com/casdoor/go-sms-sender v0.0.1 h1:n/r6fGgXsV+6uMxXvb0XLZnUCjmbUB1uSB817Ej0/gI= +github.com/casdoor/go-sms-sender v0.0.1/go.mod h1:rr4na8Zc+0vgPVY5JPB0LZkRVuj5AhNVhE1G7W8lDk8= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= @@ -71,6 +75,7 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= @@ -103,11 +108,14 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko= github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -132,8 +140,10 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mileusna/crontab v1.0.1 h1:YrDLc7l3xOiznmXq2FtAgg+1YQ3yC6pfFVPe+ywXNtg= github.com/mileusna/crontab v1.0.1/go.mod h1:dbns64w/u3tUnGZGf8pAa76ZqOfeBX4olW4U1ZwExmc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -183,6 +193,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= @@ -197,6 +208,8 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI= +github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= @@ -262,12 +275,17 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= diff --git a/object/sms.go b/object/sms.go new file mode 100644 index 00000000..98c0b0f1 --- /dev/null +++ b/object/sms.go @@ -0,0 +1,53 @@ +// Copyright 2021 The casbin 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/astaxie/beego" + "github.com/casdoor/go-sms-sender" +) + +var client go_sms_sender.SmsClient +var provider string + +func InitSmsClient() { + provider = beego.AppConfig.String("smsProvider") + accessId := beego.AppConfig.String("smsAccessId") + accessKey := beego.AppConfig.String("smsAccessKey") + appId := beego.AppConfig.String("smsAppId") + sign := beego.AppConfig.String("smsSign") + region := beego.AppConfig.String("smsRegion") + templateId := beego.AppConfig.String("smsTemplateId") + client = go_sms_sender.NewSmsClient(provider, accessId, accessKey, sign, region, templateId, appId) +} + +func SendCodeToPhone(phone, code string) { + if client == nil { + InitSmsClient() + if client == nil { + fmt.Println("Sms Config Error") + return + } + } + param := make(map[string]string) + if provider == "tencent" { + param["0"] = code + } else { + param["code"] = code + } + client.SendMessage(param, phone) +} diff --git a/object/verification.go b/object/verification.go index 8007ba8d..b2c17706 100644 --- a/object/verification.go +++ b/object/verification.go @@ -1,8 +1,23 @@ +// Copyright 2021 The casbin 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" "math/rand" + "strings" "time" ) @@ -15,6 +30,9 @@ type VerificationRecord struct { } func SendVerificationCodeToEmail(remoteAddr, dest string) string { + if strings.Index(dest, "@") < 0 { + return "Invalid Email address" + } title := "Casdoor Code" sender := "Casdoor Admin" code := getRandomCode(5) @@ -31,6 +49,16 @@ func SendVerificationCodeToEmail(remoteAddr, dest string) string { return "" } +func SendVerificationCodeToPhone(remoteAddr, dest string) string { + code := getRandomCode(5) + if result := AddToVerificationRecord(remoteAddr, dest, code); len(result) != 0 { + return result + } + + SendCodeToPhone(dest, code) + return "" +} + func AddToVerificationRecord(remoteAddr, dest, code string) string { var record VerificationRecord record.RemoteAddr = remoteAddr diff --git a/web/src/ResetModal.js b/web/src/ResetModal.js index 644c3c5f..ec0a9a5b 100644 --- a/web/src/ResetModal.js +++ b/web/src/ResetModal.js @@ -47,10 +47,10 @@ export const ResetModal = (props) => { setConfirmLoading(true); UserBackend.resetEmailOrPhone(dest, destType, code).then(res => { if (res.status === "ok") { - Setting.showMessage("success", i18next.t(destType + " reset")); + Setting.showMessage("success", i18next.t("user:" + destType + " reset")); window.location.reload(); } else { - Setting.showMessage("error", i18next.t(res.msg)); + Setting.showMessage("error", i18next.t("user:" + res.msg)); setConfirmLoading(false); } }) @@ -84,6 +84,10 @@ export const ResetModal = (props) => { }) } + let placeHolder = ""; + if (destType === "email") placeHolder = i18next.t("user:Input your email"); + else if (destType === "phone") placeHolder = i18next.t("user:Phone prefix is needed"); + return ( } /> diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index d48a348d..ec2d9fa9 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -285,14 +285,8 @@ class UserEditPage extends React.Component { {i18next.t("general:Phone")}: - {this.updateUserField('phonePrefix', value);})}> - - - - } value={this.state.user.phone} onChange={e => { - this.updateUserField('phone', e.target.value); - }} /> + + { this.state.user.id === this.props.account.id ? () : null} diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 0ffffcb2..4d79b7c1 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -115,7 +115,26 @@ "Invalid new password": "Invalid new password", "Password": "Password", "Cancel": "Cancel", - "input password": "input password" + "input password": "input password", + "Reset Email": "Reset Email", + "Reset Phone": "Reset Phone", + "Send Code": "Send Code", + "Empty email": "Empty Email", + "Empty phone": "Empty Phone", + "Empty Code": "Empty Code", + "phone reset": "Phone Reset", + "email reset": "Email Reset", + "Code Sent": "Code Sent", + "Input your email": "Input your email", + "Phone prefix is needed": "Phone prefix is needed", + "New phone": "New Phone", + "New email": "New Email", + "Code You Received": "Code You Received", + "Enter your code": "Enter your code", + "You can only send one code in 60s.": "You can only send one code in 60s.", + "Code has not been sent yet!": "Code has not been sent yet!", + "You should verify your code in 5 min!": "You should verify your code in 5 min!", + "Wrong code!": "Wrong code!" }, "application": { diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 3e11c445..1c6c47cd 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -117,7 +117,26 @@ "Invalid new password": "非法的新密码。", "Password": "密码", "Cancel": "取消", - "input password": "输入密码" + "input password": "输入密码", + "Reset Email": "重设邮箱", + "Reset Phone": "重设电话", + "Send Code": "发送验证码", + "Empty email": "邮箱为空", + "Empty phone": "电话为空", + "Empty Code": "验证码为空", + "phone reset": "电话已设置", + "email reset": "邮箱已设置", + "Code Sent": "验证码已发送", + "Input your email": "请输入邮箱", + "Phone prefix is needed": "记得输入地区前缀(+86)", + "New phone": "新的电话号", + "New email": "新的邮箱", + "Code You Received": "你收到的验证码", + "Enter your code": "输入你的验证码", + "You can only send one code in 60s.": "每分钟你只能发送一次验证码", + "Code has not been sent yet!": "你还没有发送验证码", + "You should verify your code in 5 min!": "验证码已超时。你应该在 5 分钟内完成验证。", + "Wrong code!": "验证码错误!" }, "application": { From 892cb39e3e12c5df73b7e7ef34fd9f9bed128115 Mon Sep 17 00:00:00 2001 From: Kininaru Date: Thu, 13 May 2021 09:39:07 +0800 Subject: [PATCH 3/4] feat: move User.PhonePrefix to Organization.PhonePrefix Signed-off-by: Kininaru --- controllers/account.go | 1 - controllers/verification.go | 38 +++++++++++++++++++++++++-------- object/organization.go | 14 ++++++++++++ object/user.go | 1 - web/src/OrganizationEditPage.js | 10 +++++++++ web/src/ResetModal.js | 6 +++--- web/src/UserEditPage.js | 2 +- web/src/UserListPage.js | 1 - web/src/auth/SignupPage.js | 15 ------------- web/src/locales/en.json | 5 +++-- web/src/locales/zh.json | 5 +++-- 11 files changed, 63 insertions(+), 35 deletions(-) diff --git a/controllers/account.go b/controllers/account.go index 1df84639..11fdfaa2 100644 --- a/controllers/account.go +++ b/controllers/account.go @@ -101,7 +101,6 @@ func (c *ApiController) Signup() { DisplayName: form.Name, Avatar: "https://casbin.org/img/casbin.svg", Email: form.Email, - PhonePrefix: form.PhonePrefix, Phone: form.Phone, Affiliation: form.Affiliation, IsAdmin: false, diff --git a/controllers/verification.go b/controllers/verification.go index 045aebb8..7c39b008 100644 --- a/controllers/verification.go +++ b/controllers/verification.go @@ -15,12 +15,24 @@ package controllers import ( + "fmt" "strings" "github.com/casdoor/casdoor/object" ) func (c *ApiController) SendVerificationCode() { + userId := c.GetSessionUser() + if len(userId) == 0 { + c.ResponseError("Please sign in first") + return + } + user := object.GetUser(userId) + if user == nil { + c.ResponseError("No such user.") + return + } + destType := c.Ctx.Request.Form.Get("type") dest := c.Ctx.Request.Form.Get("dest") remoteAddr := c.Ctx.Request.RemoteAddr @@ -37,6 +49,12 @@ func (c *ApiController) SendVerificationCode() { case "email": ret = object.SendVerificationCodeToEmail(remoteAddr, dest) case "phone": + org := object.GetOrganizationByName(user.Owner) + phonePrefix := "86" + if org != nil && org.PhonePrefix != "" { + phonePrefix = org.PhonePrefix + } + dest = fmt.Sprintf("+%s%s", phonePrefix, dest) ret = object.SendVerificationCodeToPhone(remoteAddr, dest) } @@ -71,7 +89,16 @@ func (c *ApiController) ResetEmailOrPhone() { return } - if ret := object.CheckVerificationCode(dest, code); len(ret) != 0 { + checkDest := dest + if destType == "phone" { + org := object.GetOrganizationByName(user.Owner) + phonePrefix := "86" + if org != nil && org.PhonePrefix != "" { + phonePrefix = org.PhonePrefix + } + checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest) + } + if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 { c.ResponseError(ret) return } @@ -81,15 +108,8 @@ func (c *ApiController) ResetEmailOrPhone() { user.Email = dest object.SetUserField(user, "email", user.Email) case "phone": - if strings.Index(dest, "+86") == 0 { - user.PhonePrefix = "86" - user.Phone = dest[3:] - } else if strings.Index(dest, "+1") == 0 { - user.PhonePrefix = "1" - user.Phone = dest[2:] - } + user.Phone = dest object.SetUserField(user, "phone", user.Phone) - object.SetUserField(user, "phone_prefix", user.PhonePrefix) default: c.ResponseError("Unknown type.") return diff --git a/object/organization.go b/object/organization.go index a8323d22..b9c40c44 100644 --- a/object/organization.go +++ b/object/organization.go @@ -29,6 +29,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"` } func GetOrganizations(owner string) []*Organization { @@ -91,3 +92,16 @@ func DeleteOrganization(organization *Organization) bool { return affected != 0 } + +func GetOrganizationByName(name string) *Organization { + var ret Organization + ret.Name = name + has, err := adapter.Engine.Get(&ret) + if err != nil { + panic(err) + } + if !has { + return nil + } + return &ret +} diff --git a/object/user.go b/object/user.go index 89bf6d71..2d5c088c 100644 --- a/object/user.go +++ b/object/user.go @@ -34,7 +34,6 @@ type User struct { DisplayName string `xorm:"varchar(100)" json:"displayName"` Avatar string `xorm:"varchar(255)" json:"avatar"` Email string `xorm:"varchar(100)" json:"email"` - PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"` Phone string `xorm:"varchar(100)" json:"phone"` Affiliation string `xorm:"varchar(100)" json:"affiliation"` Tag string `xorm:"varchar(100)" json:"tag"` diff --git a/web/src/OrganizationEditPage.js b/web/src/OrganizationEditPage.js index 49d496f7..e68b65fc 100644 --- a/web/src/OrganizationEditPage.js +++ b/web/src/OrganizationEditPage.js @@ -149,6 +149,16 @@ class OrganizationEditPage extends React.Component { }} /> + + + {i18next.t("general:Phone Prefix")}: + + + { + this.updateOrganizationField('phonePrefix', e.target.value); + }} /> + + ) } diff --git a/web/src/ResetModal.js b/web/src/ResetModal.js index ec0a9a5b..794f8c4e 100644 --- a/web/src/ResetModal.js +++ b/web/src/ResetModal.js @@ -37,7 +37,7 @@ export const ResetModal = (props) => { let dest = document.getElementById("dest").value; let code = document.getElementById("code").value; if (dest === "") { - Setting.showMessage("error", i18next.t("user:Empty ") + destType); + Setting.showMessage("error", i18next.t("user:Empty " + destType)); return; } if (code === "") { @@ -70,7 +70,7 @@ export const ResetModal = (props) => { if (sendCodeCoolDown) return; let dest = document.getElementById("dest").value; if (dest === "") { - Setting.showMessage("error", i18next.t("user:Empty ") + destType); + Setting.showMessage("error", i18next.t("user:Empty " + destType)); return; } UserBackend.sendCode(dest, destType).then(res => { @@ -86,7 +86,7 @@ export const ResetModal = (props) => { let placeHolder = ""; if (destType === "email") placeHolder = i18next.t("user:Input your email"); - else if (destType === "phone") placeHolder = i18next.t("user:Phone prefix is needed"); + else if (destType === "phone") placeHolder = i18next.t("user:Input your phone number"); return ( diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index ec2d9fa9..413909ac 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -285,7 +285,7 @@ class UserEditPage extends React.Component { {i18next.t("general:Phone")}: - + { this.state.user.id === this.props.account.id ? () : null} diff --git a/web/src/UserListPage.js b/web/src/UserListPage.js index a4f50fd2..c8e930d2 100644 --- a/web/src/UserListPage.js +++ b/web/src/UserListPage.js @@ -52,7 +52,6 @@ class UserListPage extends React.Component { displayName: `New User - ${this.state.users.length}`, avatar: "https://casbin.org/img/casbin.svg", email: "user@example.com", - phonePrefix: "86", phone: "12345678", affiliation: "Example Inc.", tag: "staff", diff --git a/web/src/auth/SignupPage.js b/web/src/auth/SignupPage.js index cd62f517..89716164 100644 --- a/web/src/auth/SignupPage.js +++ b/web/src/auth/SignupPage.js @@ -133,19 +133,6 @@ class SignupPage extends React.Component { ) } - const prefixSelector = ( - - - - ); - return (
Date: Thu, 13 May 2021 09:55:37 +0800 Subject: [PATCH 4/4] fix: check cn phone regex bug and add check to verification code Signed-off-by: Kininaru --- controllers/verification.go | 9 +++++++++ object/verification.go | 4 ---- util/regex.go | 2 +- web/src/locales/en.json | 4 +++- web/src/locales/zh.json | 4 +++- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/controllers/verification.go b/controllers/verification.go index 7c39b008..95c4e9e2 100644 --- a/controllers/verification.go +++ b/controllers/verification.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/casdoor/casdoor/object" + "github.com/casdoor/casdoor/util" ) func (c *ApiController) SendVerificationCode() { @@ -47,8 +48,16 @@ func (c *ApiController) SendVerificationCode() { ret := "Invalid dest type." switch destType { case "email": + if !util.IsEmailValid(dest) { + c.ResponseError("Invalid Email address") + return + } ret = object.SendVerificationCodeToEmail(remoteAddr, dest) case "phone": + if !util.IsPhoneCnValid(dest) { + c.ResponseError("Invalid phone number") + return + } org := object.GetOrganizationByName(user.Owner) phonePrefix := "86" if org != nil && org.PhonePrefix != "" { diff --git a/object/verification.go b/object/verification.go index b2c17706..47afb128 100644 --- a/object/verification.go +++ b/object/verification.go @@ -17,7 +17,6 @@ package object import ( "fmt" "math/rand" - "strings" "time" ) @@ -30,9 +29,6 @@ type VerificationRecord struct { } func SendVerificationCodeToEmail(remoteAddr, dest string) string { - if strings.Index(dest, "@") < 0 { - return "Invalid Email address" - } title := "Casdoor Code" sender := "Casdoor Admin" code := getRandomCode(5) diff --git a/util/regex.go b/util/regex.go index a399a10d..2437c5d7 100644 --- a/util/regex.go +++ b/util/regex.go @@ -21,7 +21,7 @@ var rePhoneCn *regexp.Regexp func init() { reEmail, _ = regexp.Compile(`^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$`) - rePhoneCn, _ = regexp.Compile("^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$") + rePhoneCn, _ = regexp.Compile("^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|191|198|199|(147))\\d{8}$") } func IsEmailValid(email string) bool { diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 7a204735..b2cd9a8d 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -135,7 +135,9 @@ "You can only send one code in 60s.": "You can only send one code in 60s.", "Code has not been sent yet!": "Code has not been sent yet!", "You should verify your code in 5 min!": "You should verify your code in 5 min!", - "Wrong code!": "Wrong code!" + "Wrong code!": "Wrong code!", + "Invalid phone number": "Invalid phone number", + "Invalid Email address": "Invalid Email address" }, "application": { diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index a4fafe32..83b11051 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -137,7 +137,9 @@ "You can only send one code in 60s.": "每分钟你只能发送一次验证码", "Code has not been sent yet!": "你还没有发送验证码", "You should verify your code in 5 min!": "验证码已超时。你应该在 5 分钟内完成验证。", - "Wrong code!": "验证码错误!" + "Wrong code!": "验证码错误!", + "Invalid phone number": "手机号格式错误", + "Invalid Email address": "邮箱格式错误" }, "application": {