From 66d953a6c1b648b28f89c0f45a50451d14505880 Mon Sep 17 00:00:00 2001 From: Kininaru Date: Tue, 18 May 2021 20:11:03 +0800 Subject: [PATCH] feat: check user email and phone when signing up Signed-off-by: Kininaru phone prefix error Signed-off-by: Kininaru fix i18n Signed-off-by: Kininaru fix i18n error Signed-off-by: Kininaru removed useless file Signed-off-by: Kininaru move timeout to app.conf Signed-off-by: Kininaru i18n Signed-off-by: Kininaru made verification code reusable Signed-off-by: Kininaru --- authz/authz.go | 1 + conf/app.conf | 3 ++- controllers/account.go | 21 +++++++++++++++ controllers/verification.go | 28 +++++++------------- object/provider.go | 1 + object/sms.go | 2 +- object/verification.go | 35 ++++++++++++++++++++----- web/src/ProviderEditPage.js | 12 +++++++++ web/src/ResetModal.js | 5 ++-- web/src/UserEditPage.js | 4 +-- web/src/auth/SignupPage.js | 47 ++++++++++++++++++++++++++++++++-- web/src/backend/UserBackend.js | 3 ++- web/src/locales/en.json | 13 +++++++++- web/src/locales/zh.json | 13 +++++++++- 14 files changed, 151 insertions(+), 37 deletions(-) diff --git a/authz/authz.go b/authz/authz.go index ec834ec7..9c0c1a6f 100644 --- a/authz/authz.go +++ b/authz/authz.go @@ -86,6 +86,7 @@ p, *, *, GET, /api/get-default-providers, *, * p, *, *, POST, /api/upload-avatar, *, * p, *, *, POST, /api/unlink, *, * p, *, *, POST, /api/set-password, *, * +p, *, *, POST, /api/send-verification-code, *, * ` sa := stringadapter.NewAdapter(ruleText) diff --git a/conf/app.conf b/conf/app.conf index 69b26930..6fab3b1d 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -7,4 +7,5 @@ driverName = mysql dataSourceName = root:123@tcp(localhost:3306)/ dbName = casdoor authState = "casdoor" -useProxy = false \ No newline at end of file +useProxy = false +verificationCodeTimeout = 10 \ No newline at end of file diff --git a/controllers/account.go b/controllers/account.go index 7a7cb3c0..ec8e6612 100644 --- a/controllers/account.go +++ b/controllers/account.go @@ -46,6 +46,10 @@ type RequestForm struct { State string `json:"state"` RedirectUri string `json:"redirectUri"` Method string `json:"method"` + + EmailCode string `json:"emailCode"` + PhoneCode string `json:"phoneCode"` + PhonePrefix string `json:"phonePrefix"` } type Response struct { @@ -77,6 +81,21 @@ func (c *ApiController) Signup() { panic(err) } + checkResult := object.CheckVerificationCode(form.Email, form.EmailCode) + if len(checkResult) != 0 { + responseText := fmt.Sprintf("Email%s", checkResult) + c.ResponseError(responseText) + return + } + + checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone) + checkResult = object.CheckVerificationCode(checkPhone, form.PhoneCode) + if len(checkResult) != 0 { + responseText := fmt.Sprintf("Phone%s", checkResult) + c.ResponseError(responseText) + return + } + application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) if !application.EnableSignUp { resp = Response{Status: "error", Msg: "The application does not allow to sign up new account", Data: c.GetSessionUser()} @@ -110,6 +129,8 @@ func (c *ApiController) Signup() { //c.SetSessionUser(user) + object.DisableVerificationCode(form.Email) + object.DisableVerificationCode(checkPhone) util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId) resp = Response{Status: "ok", Msg: "", Data: userId} } diff --git a/controllers/verification.go b/controllers/verification.go index b0cb5dab..a84fdd17 100644 --- a/controllers/verification.go +++ b/controllers/verification.go @@ -23,25 +23,14 @@ import ( ) func (c *ApiController) SendVerificationCode() { - userId, ok := c.RequireSignedIn() - if !ok { - 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") + orgId := c.Ctx.Request.Form.Get("organizationId") 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."} - c.ServeJSON() + if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || strings.Index(orgId, "/") < 0 { + c.ResponseError("Missing parameter.") return } @@ -58,12 +47,12 @@ func (c *ApiController) SendVerificationCode() { c.ResponseError("Invalid phone number") return } - org := object.GetOrganizationByUser(user) - phonePrefix := "86" - if org != nil && org.PhonePrefix != "" { - phonePrefix = org.PhonePrefix + org := object.GetOrganization(orgId) + if org == nil { + c.ResponseError("Missing parameter.") + return } - dest = fmt.Sprintf("+%s%s", phonePrefix, dest) + dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest) msg = object.SendVerificationCodeToPhone(remoteAddr, dest) } @@ -122,6 +111,7 @@ func (c *ApiController) ResetEmailOrPhone() { return } + object.DisableVerificationCode(checkDest) c.Data["json"] = Response{Status: "ok"} c.ServeJSON() } diff --git a/object/provider.go b/object/provider.go index 5f6e67fa..85f76a04 100644 --- a/object/provider.go +++ b/object/provider.go @@ -36,6 +36,7 @@ type Provider struct { RegionId string `xorm:"varchar(100)" json:"regionId"` SignName string `xorm:"varchar(100)" json:"signName"` TemplateCode string `xorm:"varchar(100)" json:"templateCode"` + AppId string `xorm:"varchar(100)" json:"appId"` ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"` } diff --git a/object/sms.go b/object/sms.go index 6e159dba..1187a398 100644 --- a/object/sms.go +++ b/object/sms.go @@ -25,7 +25,7 @@ func SendCodeToPhone(phone, code string) string { if provider == nil { return "Please set an phone provider first" } - client := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.RegionId, provider.TemplateCode) + client := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.RegionId, provider.TemplateCode, provider.AppId) if client == nil { return fmt.Sprintf("Unsupported provide type: %s", provider.Type) } diff --git a/object/verification.go b/object/verification.go index 24fd924d..fe0f5868 100644 --- a/object/verification.go +++ b/object/verification.go @@ -19,6 +19,7 @@ import ( "math/rand" "time" + "github.com/astaxie/beego" "xorm.io/core" ) @@ -94,34 +95,54 @@ func AddToVerificationRecord(remoteAddr, recordType, dest, code string) string { return "" } -func CheckVerificationCode(dest, code string) string { +func getVerificationRecord(dest string) *VerificationRecord { 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 nil + } + return &record +} + +func CheckVerificationCode(dest, code string) string { + record := getVerificationRecord(dest) + + if record == nil { return "Code has not been sent yet!" } + timeout, err := beego.AppConfig.Int64("verificationCodeTimeout") + if err != nil { + panic(err) + } + now := time.Now().Unix() - if now-record.Time > 5*60 { - return "You should verify your code in 5 min!" + if now-record.Time > timeout*60 { + return fmt.Sprintf("You should verify your code in %d min!", timeout) } if record.Code != code { return "Wrong code!" } + return "" +} + +func DisableVerificationCode(dest string) { + record := getVerificationRecord(dest) + if record == nil { + return + } + record.IsUsed = true - _, err = adapter.Engine.ID(core.PK{record.RemoteAddr, record.Type}).AllCols().Update(record) + _, err := adapter.Engine.ID(core.PK{record.RemoteAddr, record.Type}).AllCols().Update(record) if err != nil { panic(err) } - - return "" } // from Casnode/object/validateCode.go line 116 diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index 0a04a12d..e0bf209f 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -222,6 +222,18 @@ class ProviderEditPage extends React.Component { ) : null } + {this.state.provider.category === "Phone" && this.state.provider.type === "tencent" ? ( + + + {i18next.t("provider:App ID")}: + + + { + this.updateProviderField('appId', e.target.value); + }} /> + + + ) : null} {i18next.t("provider:Provider URL")}: diff --git a/web/src/ResetModal.js b/web/src/ResetModal.js index d91f2726..e83e6fe8 100644 --- a/web/src/ResetModal.js +++ b/web/src/ResetModal.js @@ -25,7 +25,7 @@ export const ResetModal = (props) => { const [sendCodeCoolDown, setCoolDown] = React.useState(false); const [dest, setDest] = React.useState(""); const [code, setCode] = React.useState(""); - const {buttonText, destType, coolDownTime} = props; + const {buttonText, destType, coolDownTime, org} = props; const showModal = () => { setVisible(true); @@ -72,7 +72,8 @@ export const ResetModal = (props) => { Setting.showMessage("error", i18next.t("user:Empty " + destType)); return; } - UserBackend.sendCode(dest, destType).then(res => { + let orgId = org.owner + "/" + org.name; + UserBackend.sendCode(dest, destType, orgId).then(res => { if (res.status === "ok") { Setting.showMessage("success", i18next.t("user:Code Sent")); setCoolDown(true); diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index e977f82b..70a0e738 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -267,7 +267,7 @@ class UserEditPage extends React.Component { - { this.state.user.id === this.props.account.id ? () : null} + { this.state.user.id === this.props.account.id ? () : null} @@ -278,7 +278,7 @@ class UserEditPage extends React.Component { - { this.state.user.id === this.props.account.id ? () : null} + { this.state.user.id === this.props.account.id ? () : null} diff --git a/web/src/auth/SignupPage.js b/web/src/auth/SignupPage.js index c5780cd8..c8e46f64 100644 --- a/web/src/auth/SignupPage.js +++ b/web/src/auth/SignupPage.js @@ -21,6 +21,7 @@ import i18next from "i18next"; import * as Util from "./Util"; import {authConfig} from "./Auth"; import * as ApplicationBackend from "../backend/ApplicationBackend"; +import * as UserBackend from "../backend/UserBackend"; const formItemLayout = { labelCol: { @@ -61,6 +62,8 @@ class SignupPage extends React.Component { classes: props, applicationName: props.match.params.applicationName !== undefined ? props.match.params.applicationName : authConfig.appName, application: null, + email: "", + phone: "" }; this.form = React.createRef(); @@ -96,12 +99,13 @@ class SignupPage extends React.Component { } onFinish(values) { + values.phonePrefix = this.state.application?.organizationObj.phonePrefix; AuthBackend.signup(values) .then((res) => { if (res.status === 'ok') { Setting.goToLinkSoft(this, this.getResultPath(this.state.application)); } else { - Setting.showMessage("error", `Failed to sign up: ${res.msg}`); + Setting.showMessage("error", i18next.t(`signup:${res.msg}`)); } }); } @@ -110,6 +114,22 @@ class SignupPage extends React.Component { this.form.current.scrollToField(errorFields[0].name); } + sendCode(type) { + let dest, orgId; + if (type === "email") { + dest = this.state.email; + } else if (type === "phone") { + dest = this.state.phone; + } else return; + + orgId = this.state.application?.organizationObj.owner + "/" + this.state.application?.organizationObj.name + + UserBackend.sendCode(dest, type, orgId).then(res => { + if (res.status === "ok") Setting.showMessage("success", i18next.t("signup:code sent")); + else Setting.showMessage("error", i18next.t("signup:" + res.msg)); + }) + } + renderForm(application) { if (!application.enableSignUp) { return ( @@ -220,7 +240,17 @@ class SignupPage extends React.Component { }, ]} > - + this.setState({email: e.target.value})} /> + + + this.sendCode("email")} style={{backgroundColor: "#fafafa", border: "none"}}>{i18next.t("signup:send code")}} /> this.setState({phone: e.target.value})} /> + + this.sendCode("phone")} style={{border: "none", backgroundColor: "#fafafa"}}>{i18next.t("signup:send code")}}/> + {i18next.t("signup:Accept")}  diff --git a/web/src/backend/UserBackend.js b/web/src/backend/UserBackend.js index 5860df5d..b038f603 100644 --- a/web/src/backend/UserBackend.js +++ b/web/src/backend/UserBackend.js @@ -93,10 +93,11 @@ export function setPassword(userOwner, userName, oldPassword, newPassword) { }).then(res => res.json()); } -export function sendCode(dest, type) { +export function sendCode(dest, type, orgId) { let formData = new FormData(); formData.append("dest", dest); formData.append("type", type); + formData.append("organizationId", orgId); return fetch(`${Setting.ServerUrl}/api/send-verification-code`, { method: "POST", credentials: "include", diff --git a/web/src/locales/en.json b/web/src/locales/en.json index f04f43bf..69bdb92e 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -48,7 +48,18 @@ "Have account?": "Have account?", "sign in now": "sign in now", "Your account has been created!": "Your account has been created!", - "Please click the below button to sign in": "Please click the below button to sign in" + "Please click the below button to sign in": "Please click the below button to sign in", + "code sent": "code sent", + "send code": "send code", + "email code": "email code", + "phone code": "phone code", + "PhoneCode has not been sent yet!": "Phone code has not been sent yet!", + "EmailCode has not been sent yet!": "Email code has not been sent yet!", + "PhoneYou should verify your code in 10 min!": "You should verify your phone verification code in 10 min!", + "EmailYou should verify your code in 10 min!": "You should verify your email verification code in 10 min!", + "PhoneWrong code!": "Wrong phone verification code!", + "EmailWrong code!": "Wrong email verification code!", + "Missing parameter.": "Missing parameter." }, "login": { diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 49c1836a..cffdffe1 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -48,7 +48,18 @@ "Have account?": "已有账号?", "sign in now": "立即登录", "Your account has been created!": "您的账号已创建!", - "Please click the below button to sign in": "请点击下方按钮登录" + "Please click the below button to sign in": "请点击下方按钮登录", + "code sent": "验证码已发送", + "send code": "发送验证码", + "email code": "邮箱验证码", + "phone code": "手机验证码", + "PhoneCode has not been sent yet!": "尚未发送验证码至手机", + "EmailCode has not been sent yet!": "尚未发送验证码至邮箱", + "PhoneYou should verify your code in 10 min!": "你应该在 10 分钟之内验证手机号", + "EmailYou should verify your code in 10 min!": "你应该在 10 分钟之内验证邮箱", + "PhoneWrong code!": "手机验证码错误", + "EmailWrong code!": "邮箱验证码错误", + "Missing parameter.": "缺少参数" }, "login": {