mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
feat: check user email and phone when signing up
Signed-off-by: Kininaru <shiftregister233@outlook.com> phone prefix error Signed-off-by: Kininaru <shiftregister233@outlook.com> fix i18n Signed-off-by: Kininaru <shiftregister233@outlook.com> fix i18n error Signed-off-by: Kininaru <shiftregister233@outlook.com> removed useless file Signed-off-by: Kininaru <shiftregister233@outlook.com> move timeout to app.conf Signed-off-by: Kininaru <shiftregister233@outlook.com> i18n Signed-off-by: Kininaru <shiftregister233@outlook.com> made verification code reusable Signed-off-by: Kininaru <shiftregister233@outlook.com>
This commit is contained in:
parent
9bc29e25ef
commit
66d953a6c1
@ -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)
|
||||
|
@ -8,3 +8,4 @@ dataSourceName = root:123@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
authState = "casdoor"
|
||||
useProxy = false
|
||||
verificationCodeTimeout = 10
|
@ -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}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -222,6 +222,18 @@ class ProviderEditPage extends React.Component {
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
{this.state.provider.category === "Phone" && this.state.provider.type === "tencent" ? (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{i18next.t("provider:App ID")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.appId} onChange={e => {
|
||||
this.updateProviderField('appId', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{i18next.t("provider:Provider URL")}:
|
||||
|
@ -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);
|
||||
|
@ -267,7 +267,7 @@ class UserEditPage extends React.Component {
|
||||
<Input value={this.state.user.email} disabled />
|
||||
</Col>
|
||||
<Col span={11} >
|
||||
{ this.state.user.id === this.props.account.id ? (<ResetModal buttonText={i18next.t("user:Reset Email")} destType={"email"} coolDownTime={60}/>) : null}
|
||||
{ this.state.user.id === this.props.account.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email")} destType={"email"} coolDownTime={60}/>) : null}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
@ -278,7 +278,7 @@ class UserEditPage extends React.Component {
|
||||
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`} disabled />
|
||||
</Col>
|
||||
<Col span={11} >
|
||||
{ this.state.user.id === this.props.account.id ? (<ResetModal buttonText={i18next.t("user:Reset Phone")} destType={"phone"} coolDownTime={60}/>) : null}
|
||||
{ this.state.user.id === this.props.account.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone")} destType={"phone"} coolDownTime={60}/>) : null}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
|
@ -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 {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Input onChange={e => this.setState({email: e.target.value})} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="emailCode"
|
||||
label={i18next.t("signup:email code")}
|
||||
rules={[{
|
||||
required: true,
|
||||
message: i18next.t("signup:Please input your verification code!"),
|
||||
}]}
|
||||
>
|
||||
<Input autoComplete="off" value={this.state.emailCode} addonAfter={<button onClick={() => this.sendCode("email")} style={{backgroundColor: "#fafafa", border: "none"}}>{i18next.t("signup:send code")}</button>} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
@ -273,8 +303,21 @@ class SignupPage extends React.Component {
|
||||
width: '100%',
|
||||
}}
|
||||
addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
||||
onChange={e => this.setState({phone: e.target.value})}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
label={i18next.t("signup:phone code")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t("signup:Please input your phone verification code!"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input autoComplete="off" value={this.state.phoneCode} addonAfter={<button onClick={() => this.sendCode("phone")} style={{border: "none", backgroundColor: "#fafafa"}}>{i18next.t("signup:send code")}</button>}/>
|
||||
</Form.Item>
|
||||
<Form.Item name="agreement" valuePropName="checked" {...tailFormItemLayout}>
|
||||
<Checkbox>
|
||||
{i18next.t("signup:Accept")}
|
||||
|
@ -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",
|
||||
|
@ -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":
|
||||
{
|
||||
|
@ -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":
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user