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/upload-avatar, *, *
|
||||||
p, *, *, POST, /api/unlink, *, *
|
p, *, *, POST, /api/unlink, *, *
|
||||||
p, *, *, POST, /api/set-password, *, *
|
p, *, *, POST, /api/set-password, *, *
|
||||||
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
`
|
`
|
||||||
|
|
||||||
sa := stringadapter.NewAdapter(ruleText)
|
sa := stringadapter.NewAdapter(ruleText)
|
||||||
|
@ -7,4 +7,5 @@ driverName = mysql
|
|||||||
dataSourceName = root:123@tcp(localhost:3306)/
|
dataSourceName = root:123@tcp(localhost:3306)/
|
||||||
dbName = casdoor
|
dbName = casdoor
|
||||||
authState = "casdoor"
|
authState = "casdoor"
|
||||||
useProxy = false
|
useProxy = false
|
||||||
|
verificationCodeTimeout = 10
|
@ -46,6 +46,10 @@ type RequestForm struct {
|
|||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
RedirectUri string `json:"redirectUri"`
|
RedirectUri string `json:"redirectUri"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
|
|
||||||
|
EmailCode string `json:"emailCode"`
|
||||||
|
PhoneCode string `json:"phoneCode"`
|
||||||
|
PhonePrefix string `json:"phonePrefix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
@ -77,6 +81,21 @@ func (c *ApiController) Signup() {
|
|||||||
panic(err)
|
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))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
if !application.EnableSignUp {
|
if !application.EnableSignUp {
|
||||||
resp = Response{Status: "error", Msg: "The application does not allow to sign up new account", Data: c.GetSessionUser()}
|
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)
|
//c.SetSessionUser(user)
|
||||||
|
|
||||||
|
object.DisableVerificationCode(form.Email)
|
||||||
|
object.DisableVerificationCode(checkPhone)
|
||||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||||
resp = Response{Status: "ok", Msg: "", Data: userId}
|
resp = Response{Status: "ok", Msg: "", Data: userId}
|
||||||
}
|
}
|
||||||
|
@ -23,25 +23,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *ApiController) SendVerificationCode() {
|
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")
|
destType := c.Ctx.Request.Form.Get("type")
|
||||||
dest := c.Ctx.Request.Form.Get("dest")
|
dest := c.Ctx.Request.Form.Get("dest")
|
||||||
|
orgId := c.Ctx.Request.Form.Get("organizationId")
|
||||||
remoteAddr := c.Ctx.Request.RemoteAddr
|
remoteAddr := c.Ctx.Request.RemoteAddr
|
||||||
remoteAddr = remoteAddr[:strings.LastIndex(remoteAddr, ":")]
|
remoteAddr = remoteAddr[:strings.LastIndex(remoteAddr, ":")]
|
||||||
|
|
||||||
if len(destType) == 0 || len(dest) == 0 {
|
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || strings.Index(orgId, "/") < 0 {
|
||||||
c.Data["json"] = Response{Status: "error", Msg: "Missing parameter."}
|
c.ResponseError("Missing parameter.")
|
||||||
c.ServeJSON()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,12 +47,12 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
c.ResponseError("Invalid phone number")
|
c.ResponseError("Invalid phone number")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
org := object.GetOrganizationByUser(user)
|
org := object.GetOrganization(orgId)
|
||||||
phonePrefix := "86"
|
if org == nil {
|
||||||
if org != nil && org.PhonePrefix != "" {
|
c.ResponseError("Missing parameter.")
|
||||||
phonePrefix = org.PhonePrefix
|
return
|
||||||
}
|
}
|
||||||
dest = fmt.Sprintf("+%s%s", phonePrefix, dest)
|
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
|
||||||
msg = object.SendVerificationCodeToPhone(remoteAddr, dest)
|
msg = object.SendVerificationCodeToPhone(remoteAddr, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +111,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object.DisableVerificationCode(checkDest)
|
||||||
c.Data["json"] = Response{Status: "ok"}
|
c.Data["json"] = Response{Status: "ok"}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ type Provider struct {
|
|||||||
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
||||||
SignName string `xorm:"varchar(100)" json:"signName"`
|
SignName string `xorm:"varchar(100)" json:"signName"`
|
||||||
TemplateCode string `xorm:"varchar(100)" json:"templateCode"`
|
TemplateCode string `xorm:"varchar(100)" json:"templateCode"`
|
||||||
|
AppId string `xorm:"varchar(100)" json:"appId"`
|
||||||
|
|
||||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ func SendCodeToPhone(phone, code string) string {
|
|||||||
if provider == nil {
|
if provider == nil {
|
||||||
return "Please set an phone provider first"
|
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 {
|
if client == nil {
|
||||||
return fmt.Sprintf("Unsupported provide type: %s", provider.Type)
|
return fmt.Sprintf("Unsupported provide type: %s", provider.Type)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -94,34 +95,54 @@ func AddToVerificationRecord(remoteAddr, recordType, dest, code string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckVerificationCode(dest, code string) string {
|
func getVerificationRecord(dest string) *VerificationRecord {
|
||||||
var record VerificationRecord
|
var record VerificationRecord
|
||||||
record.Receiver = dest
|
record.Receiver = dest
|
||||||
has, err := adapter.Engine.Desc("time").Where("is_used = 0").Get(&record)
|
has, err := adapter.Engine.Desc("time").Where("is_used = 0").Get(&record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !has {
|
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!"
|
return "Code has not been sent yet!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timeout, err := beego.AppConfig.Int64("verificationCodeTimeout")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if now-record.Time > 5*60 {
|
if now-record.Time > timeout*60 {
|
||||||
return "You should verify your code in 5 min!"
|
return fmt.Sprintf("You should verify your code in %d min!", timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if record.Code != code {
|
if record.Code != code {
|
||||||
return "Wrong code!"
|
return "Wrong code!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableVerificationCode(dest string) {
|
||||||
|
record := getVerificationRecord(dest)
|
||||||
|
if record == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
record.IsUsed = true
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// from Casnode/object/validateCode.go line 116
|
// from Casnode/object/validateCode.go line 116
|
||||||
|
@ -222,6 +222,18 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : null
|
) : 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'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={2}>
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
{i18next.t("provider:Provider URL")}:
|
{i18next.t("provider:Provider URL")}:
|
||||||
|
@ -25,7 +25,7 @@ export const ResetModal = (props) => {
|
|||||||
const [sendCodeCoolDown, setCoolDown] = React.useState(false);
|
const [sendCodeCoolDown, setCoolDown] = React.useState(false);
|
||||||
const [dest, setDest] = React.useState("");
|
const [dest, setDest] = React.useState("");
|
||||||
const [code, setCode] = React.useState("");
|
const [code, setCode] = React.useState("");
|
||||||
const {buttonText, destType, coolDownTime} = props;
|
const {buttonText, destType, coolDownTime, org} = props;
|
||||||
|
|
||||||
const showModal = () => {
|
const showModal = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
@ -72,7 +72,8 @@ export const ResetModal = (props) => {
|
|||||||
Setting.showMessage("error", i18next.t("user:Empty " + destType));
|
Setting.showMessage("error", i18next.t("user:Empty " + destType));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UserBackend.sendCode(dest, destType).then(res => {
|
let orgId = org.owner + "/" + org.name;
|
||||||
|
UserBackend.sendCode(dest, destType, orgId).then(res => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("user:Code Sent"));
|
Setting.showMessage("success", i18next.t("user:Code Sent"));
|
||||||
setCoolDown(true);
|
setCoolDown(true);
|
||||||
|
@ -267,7 +267,7 @@ class UserEditPage extends React.Component {
|
|||||||
<Input value={this.state.user.email} disabled />
|
<Input value={this.state.user.email} disabled />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} >
|
<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>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<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 />
|
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`} disabled />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} >
|
<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>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
@ -21,6 +21,7 @@ import i18next from "i18next";
|
|||||||
import * as Util from "./Util";
|
import * as Util from "./Util";
|
||||||
import {authConfig} from "./Auth";
|
import {authConfig} from "./Auth";
|
||||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||||
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
|
|
||||||
const formItemLayout = {
|
const formItemLayout = {
|
||||||
labelCol: {
|
labelCol: {
|
||||||
@ -61,6 +62,8 @@ class SignupPage extends React.Component {
|
|||||||
classes: props,
|
classes: props,
|
||||||
applicationName: props.match.params.applicationName !== undefined ? props.match.params.applicationName : authConfig.appName,
|
applicationName: props.match.params.applicationName !== undefined ? props.match.params.applicationName : authConfig.appName,
|
||||||
application: null,
|
application: null,
|
||||||
|
email: "",
|
||||||
|
phone: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
this.form = React.createRef();
|
this.form = React.createRef();
|
||||||
@ -96,12 +99,13 @@ class SignupPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onFinish(values) {
|
onFinish(values) {
|
||||||
|
values.phonePrefix = this.state.application?.organizationObj.phonePrefix;
|
||||||
AuthBackend.signup(values)
|
AuthBackend.signup(values)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 'ok') {
|
if (res.status === 'ok') {
|
||||||
Setting.goToLinkSoft(this, this.getResultPath(this.state.application));
|
Setting.goToLinkSoft(this, this.getResultPath(this.state.application));
|
||||||
} else {
|
} 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);
|
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) {
|
renderForm(application) {
|
||||||
if (!application.enableSignUp) {
|
if (!application.enableSignUp) {
|
||||||
return (
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
@ -273,8 +303,21 @@ class SignupPage extends React.Component {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
||||||
|
onChange={e => this.setState({phone: e.target.value})}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</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}>
|
<Form.Item name="agreement" valuePropName="checked" {...tailFormItemLayout}>
|
||||||
<Checkbox>
|
<Checkbox>
|
||||||
{i18next.t("signup:Accept")}
|
{i18next.t("signup:Accept")}
|
||||||
|
@ -93,10 +93,11 @@ export function setPassword(userOwner, userName, oldPassword, newPassword) {
|
|||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendCode(dest, type) {
|
export function sendCode(dest, type, orgId) {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append("dest", dest);
|
formData.append("dest", dest);
|
||||||
formData.append("type", type);
|
formData.append("type", type);
|
||||||
|
formData.append("organizationId", orgId);
|
||||||
return fetch(`${Setting.ServerUrl}/api/send-verification-code`, {
|
return fetch(`${Setting.ServerUrl}/api/send-verification-code`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
@ -48,7 +48,18 @@
|
|||||||
"Have account?": "Have account?",
|
"Have account?": "Have account?",
|
||||||
"sign in now": "sign in now",
|
"sign in now": "sign in now",
|
||||||
"Your account has been created!": "Your account has been created!",
|
"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":
|
"login":
|
||||||
{
|
{
|
||||||
|
@ -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": "请点击下方按钮登录",
|
||||||
|
"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":
|
"login":
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user