mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
feat: add "forget password" [front & backend] (#75)
* feat: add "forget password" [front & backend] Signed-off-by: Weihao <1340908470@qq.com> * fix: verification code can be sent even if no mobile phone or email is selected refactor: forgetPassword -> forget; GetEmailAndPhoneByUsername -> GetEmailAndPhone; remove useless note Signed-off-by: Weihao <1340908470@qq.com>
This commit is contained in:
parent
29049297d8
commit
1cb5ae54c5
@ -72,6 +72,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
|
||||
ruleText := `
|
||||
p, built-in, *, *, *, *, *
|
||||
p, *, *, POST, /api/signup, *, *
|
||||
p, *, *, POST, /api/get-email-and-phone, *, *
|
||||
p, *, *, POST, /api/login, *, *
|
||||
p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
|
@ -17,6 +17,7 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
@ -106,8 +107,65 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
|
||||
password := form.Password
|
||||
user, msg := object.CheckUserLogin(form.Organization, form.Username, password)
|
||||
var user *object.User
|
||||
var msg string
|
||||
|
||||
if form.Password == "" {
|
||||
var verificationCodeType string
|
||||
|
||||
// check result through Email or Phone
|
||||
if strings.Contains(form.Email, "@") {
|
||||
verificationCodeType = "email"
|
||||
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
|
||||
if len(checkResult) != 0 {
|
||||
responseText := fmt.Sprintf("Email%s", checkResult)
|
||||
c.ResponseError(responseText)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
verificationCodeType = "phone"
|
||||
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Email)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, form.EmailCode)
|
||||
if len(checkResult) != 0 {
|
||||
responseText := fmt.Sprintf("Phone%s", checkResult)
|
||||
c.ResponseError(responseText)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// get user
|
||||
var userId string
|
||||
if form.Username == "" {
|
||||
userId, _ = c.RequireSignedIn()
|
||||
} else {
|
||||
userId = fmt.Sprintf("%s/%s", form.Organization, form.Username)
|
||||
}
|
||||
|
||||
user = object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
// disable the verification code
|
||||
switch verificationCodeType {
|
||||
case "email":
|
||||
if user.Email != form.Email {
|
||||
c.ResponseError("wrong email!")
|
||||
}
|
||||
object.DisableVerificationCode(form.Email)
|
||||
break
|
||||
case "phone":
|
||||
if user.Phone != form.Email {
|
||||
c.ResponseError("wrong phone!")
|
||||
}
|
||||
object.DisableVerificationCode(form.Email)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
password := form.Password
|
||||
user, msg = object.CheckUserLogin(form.Organization, form.Username, password)
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
resp = &Response{Status: "error", Msg: msg, Data: ""}
|
||||
|
@ -111,6 +111,43 @@ func (c *ApiController) DeleteUser() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetEmailAndPhone
|
||||
// @Description get email and phone by username
|
||||
// @Param username formData string true "The username of the user"
|
||||
// @Param organization formData string true "The organization of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-email-and-phone [post]
|
||||
func (c *ApiController) GetEmailAndPhone() {
|
||||
var resp Response
|
||||
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// get user
|
||||
var userId string
|
||||
if form.Username == "" {
|
||||
userId, _ = c.RequireSignedIn()
|
||||
} else {
|
||||
userId = fmt.Sprintf("%s/%s", form.Organization, form.Username)
|
||||
}
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
phone := user.Phone
|
||||
email := user.Email
|
||||
|
||||
resp = Response{Status: "ok", Msg: "", Data: phone, Data2: email}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title SetPassword
|
||||
// @Description set password
|
||||
// @Param userOwner formData string true "The owner of the user"
|
||||
@ -158,10 +195,14 @@ func (c *ApiController) SetPassword() {
|
||||
return
|
||||
}
|
||||
|
||||
msg := object.CheckPassword(targetUser, oldPassword)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
if oldPassword != "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
if strings.Index(newPassword, " ") >= 0 {
|
||||
@ -174,6 +215,8 @@ func (c *ApiController) SetPassword() {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSessionUser("")
|
||||
|
||||
targetUser.Password = newPassword
|
||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
|
@ -60,6 +60,7 @@ 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/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||
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-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
|
||||
|
@ -38,6 +38,8 @@ import SignupPage from "./auth/SignupPage";
|
||||
import ResultPage from "./auth/ResultPage";
|
||||
import LoginPage from "./auth/LoginPage";
|
||||
import SelfLoginPage from "./auth/SelfLoginPage";
|
||||
import SelfForgetPage from "./auth/SelfForgetPage";
|
||||
import ForgetPage from "./auth/ForgetPage";
|
||||
import * as AuthBackend from "./auth/AuthBackend";
|
||||
import AuthCallback from "./auth/AuthCallback";
|
||||
import SelectLanguageBox from './SelectLanguageBox';
|
||||
@ -374,6 +376,8 @@ class App extends Component {
|
||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)}/>
|
||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...props} />)}/>
|
||||
<Route exact path="/callback" component={AuthCallback}/>
|
||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/>
|
||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)}/>
|
||||
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)}/>
|
||||
|
@ -29,6 +29,14 @@ export function signup(values) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getEmailAndPhone(values) {
|
||||
return fetch(`${authConfig.serverUrl}/api/get-email-and-phone`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(values),
|
||||
}).then((res) => res.json());
|
||||
}
|
||||
|
||||
function oAuthParamsToQuery(oAuthParams) {
|
||||
return `?clientId=${oAuthParams.clientId}&responseType=${oAuthParams.responseType}&redirectUri=${oAuthParams.redirectUri}&scope=${oAuthParams.scope}&state=${oAuthParams.state}`;
|
||||
}
|
||||
|
499
web/src/auth/ForgetPage.js
Normal file
499
web/src/auth/ForgetPage.js
Normal file
@ -0,0 +1,499 @@
|
||||
// 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 React from "react";
|
||||
import { Button, Col, Divider, Form, Select, Input, Row, Steps } from "antd";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as Util from "./Util";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import { CountDownInput } from "../component/CountDownInput";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
KeyOutlined,
|
||||
LockOutlined,
|
||||
SolutionOutlined,
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
const { Step } = Steps;
|
||||
const { Option } = Select;
|
||||
|
||||
class ForgetPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
account: props.account,
|
||||
applicationName:
|
||||
props.applicationName !== undefined
|
||||
? props.applicationName
|
||||
: props.match === undefined
|
||||
? null
|
||||
: props.match.params.applicationName,
|
||||
application: null,
|
||||
msg: null,
|
||||
userId: "",
|
||||
username: "",
|
||||
email: "",
|
||||
token: "",
|
||||
phone: "",
|
||||
emailCode: "",
|
||||
phoneCode: "",
|
||||
verifyType: "", // "email" or "phone"
|
||||
current: 0,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
if (this.state.applicationName !== undefined) {
|
||||
this.getApplication();
|
||||
} else {
|
||||
Util.showMessage(
|
||||
"error",
|
||||
i18next.t(`forget:Unknown forgot type: `) + this.state.type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getApplication() {
|
||||
if (this.state.applicationName === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName).then(
|
||||
(application) => {
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getApplicationObj() {
|
||||
if (this.props.application !== undefined) {
|
||||
return this.props.application;
|
||||
} else {
|
||||
return this.state.application;
|
||||
}
|
||||
}
|
||||
|
||||
onFinishStep1(values) {
|
||||
AuthBackend.getEmailAndPhone(values).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
username: values.username,
|
||||
phone: res.data.toString(),
|
||||
email: res.data2.toString(),
|
||||
current: 1,
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onFinishStep2(values) {
|
||||
values.phonePrefix = this.state.application?.organizationObj.phonePrefix;
|
||||
values.username = this.state.username;
|
||||
values.type = "login"
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
AuthBackend.login(values, oAuthParams).then(res => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({current: 2, userId: res.data})
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onFinish(values) {
|
||||
values.username = this.state.username;
|
||||
values.userOwner = this.state.application?.organizationObj.name
|
||||
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
|
||||
if (res.status === "ok") {
|
||||
Setting.goToLogin(this, this.state.application);
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onFinishFailed(values, errorFields) {}
|
||||
|
||||
onChange = (current) => {
|
||||
this.setState({ current: current });
|
||||
};
|
||||
|
||||
renderForm(application) {
|
||||
return (
|
||||
<>
|
||||
{/* STEP 1: input username -> get email & phone */}
|
||||
<Form
|
||||
hidden={this.state.current !== 0}
|
||||
ref={this.form}
|
||||
name="get-email-and-Phone"
|
||||
onFinish={(values) => this.onFinishStep1(values)}
|
||||
onFinishFailed={(errorInfo) => console.log(errorInfo)}
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
}}
|
||||
style={{ width: "300px" }}
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
style={{ height: 0, visibility: "hidden" }}
|
||||
name="application"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
`forget:Please input your application!`
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
style={{ height: 0, visibility: "hidden" }}
|
||||
name="organization"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
`forget:Please input your organization!`
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your username!"
|
||||
),
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
onChange={(e) => {
|
||||
this.setState({
|
||||
username: e.target.value,
|
||||
});
|
||||
}}
|
||||
prefix={<UserOutlined />}
|
||||
placeholder={i18next.t("signup:Username")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item>
|
||||
<Button block type="primary" htmlType="submit">
|
||||
{i18next.t("forget:Next Step")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
{/* STEP 2: verify email or phone */}
|
||||
<Form
|
||||
hidden={this.state.current !== 1}
|
||||
ref={this.form}
|
||||
name="forgetPassword"
|
||||
onFinish={(values) => this.onFinishStep2(values)}
|
||||
onFinishFailed={(errorInfo) =>
|
||||
this.onFinishFailed(
|
||||
errorInfo.values,
|
||||
errorInfo.errorFields,
|
||||
errorInfo.outOfDate
|
||||
)
|
||||
}
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
}}
|
||||
style={{ width: "300px" }}
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
style={{ height: 0, visibility: "hidden" }}
|
||||
name="application"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
`forget:Please input your application!`
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
style={{ height: 0, visibility: "hidden" }}
|
||||
name="organization"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
`forget:Please input your organization!`
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="email" //use email instead of email/phone to adapt to RequestForm in account.go
|
||||
validateFirst
|
||||
hasFeedback
|
||||
>
|
||||
<Select
|
||||
disabled={this.state.username === ""}
|
||||
placeholder={i18next.t(
|
||||
"forget:Choose email verification or mobile verification"
|
||||
)}
|
||||
onChange={(value) => {
|
||||
if (value === this.state.phone) {
|
||||
this.setState({ verifyType: "phone" });
|
||||
}
|
||||
if (value === this.state.email) {
|
||||
this.setState({ verifyType: "email" });
|
||||
}
|
||||
}}
|
||||
allowClear
|
||||
style={{ textAlign: "left" }}
|
||||
>
|
||||
<Option key={1} value={this.state.phone}>
|
||||
{this.state.phone.replace(/(\d{3})\d*(\d{4})/,'$1****$2')}
|
||||
</Option>
|
||||
<Option key={2} value={this.state.email}>
|
||||
{this.state.email.split("@")[0].length>2?
|
||||
this.state.email.replace(/(?<=.)[^@]+(?=.@)/, "*****"):
|
||||
this.state.email.replace(/(\w?@)/, "*@")}
|
||||
</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="emailCode" //use emailCode instead of email/phoneCode to adapt to RequestForm in account.go
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your verification code!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{this.state.verifyType === "email" ? (
|
||||
<CountDownInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
placeHolder={i18next.t("forget:Verify code")}
|
||||
defaultButtonText={i18next.t("forget:send code")}
|
||||
onButtonClick={UserBackend.sendCode}
|
||||
onButtonClickArgs={[
|
||||
this.state.email,
|
||||
"email",
|
||||
this.state.application?.organizationObj.owner +
|
||||
"/" +
|
||||
this.state.application?.organizationObj.name,
|
||||
]}
|
||||
coolDownTime={60}
|
||||
/>
|
||||
) : (
|
||||
<CountDownInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
placeHolder={i18next.t("forget:Verify code")}
|
||||
defaultButtonText={i18next.t("forget:send code")}
|
||||
onButtonClick={UserBackend.sendCode}
|
||||
onButtonClickArgs={[
|
||||
this.state.phone,
|
||||
"phone",
|
||||
this.state.application?.organizationObj.owner +
|
||||
"/" +
|
||||
this.state.application?.organizationObj.name,
|
||||
]}
|
||||
coolDownTime={60}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item>
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
disabled={this.state.phone === "" || this.state.verifyType === ""}
|
||||
htmlType="submit"
|
||||
>
|
||||
{i18next.t("forget:Next Step")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
{/* STEP 3 */}
|
||||
<Form
|
||||
hidden={this.state.current !== 2}
|
||||
ref={this.form}
|
||||
name="forgetPassword"
|
||||
onFinish={(values) => this.onFinish(values)}
|
||||
onFinishFailed={(errorInfo) =>
|
||||
this.onFinishFailed(
|
||||
errorInfo.values,
|
||||
errorInfo.errorFields,
|
||||
errorInfo.outOfDate
|
||||
)
|
||||
}
|
||||
initialValues={{
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
}}
|
||||
style={{ width: "300px" }}
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
style={{ height: 0, visibility: "hidden" }}
|
||||
name="application"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
`forget:Please input your application!`
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
style={{ height: 0, visibility: "hidden" }}
|
||||
name="organization"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
`forget:Please input your organization!`
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Form.Item
|
||||
name="newPassword"
|
||||
hidden={this.state.current !== 2}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please input your password!"
|
||||
),
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password
|
||||
disabled={this.state.userId === ""}
|
||||
prefix={<LockOutlined />}
|
||||
placeholder={i18next.t("forget:Password")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="confirm"
|
||||
dependencies={["newPassword"]}
|
||||
hasFeedback
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: i18next.t(
|
||||
"forget:Please confirm your password!"
|
||||
),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (!value || getFieldValue("newPassword") === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
i18next.t(
|
||||
"forget:Your confirmed password is inconsistent with the password!"
|
||||
)
|
||||
);
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
disabled={this.state.userId === ""}
|
||||
prefix={<CheckCircleOutlined />}
|
||||
placeholder={i18next.t("forget:Confirm")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item hidden={this.state.current !== 2}>
|
||||
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
|
||||
{i18next.t("forget:Change Password")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const application = this.getApplicationObj();
|
||||
if (application === null) {
|
||||
return Util.renderMessageLarge(this, this.state.msg);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider style={{ fontSize: "28px" }}>
|
||||
{i18next.t("forget:Retrieve password")}
|
||||
</Divider>
|
||||
<Row>
|
||||
<Col span={24} style={{ display: "flex", justifyContent: "center" }}>
|
||||
<Steps
|
||||
current={this.state.current}
|
||||
onChange={this.onChange}
|
||||
style={{
|
||||
width: "90%",
|
||||
maxWidth: "500px",
|
||||
margin: "auto",
|
||||
marginTop: "80px",
|
||||
}}
|
||||
>
|
||||
<Step
|
||||
title={i18next.t("forget:Account")}
|
||||
icon={<UserOutlined />}
|
||||
/>
|
||||
<Step
|
||||
title={i18next.t("forget:Verify")}
|
||||
icon={<SolutionOutlined />}
|
||||
/>
|
||||
<Step
|
||||
title={i18next.t("forget:Reset")}
|
||||
icon={<KeyOutlined />}
|
||||
/>
|
||||
</Steps>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={24} style={{ display: "flex", justifyContent: "center" }}>
|
||||
<div style={{ marginTop: "10px", textAlign: "center" }}>
|
||||
{Setting.renderHelmet(application)}
|
||||
{this.renderForm(application)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ForgetPage;
|
32
web/src/auth/SelfForgetPage.js
Normal file
32
web/src/auth/SelfForgetPage.js
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 React from "react";
|
||||
import { authConfig } from "./Auth";
|
||||
import ForgetPage from "./ForgetPage";
|
||||
|
||||
class SelfForgetPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ForgetPage
|
||||
type={"forgotPassword"}
|
||||
applicationName={authConfig.appName}
|
||||
account={this.props.account}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelfForgetPage;
|
@ -17,9 +17,10 @@ import React from "react";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import { AuditOutlined, VerifiedOutlined } from "@ant-design/icons";
|
||||
|
||||
export const CountDownInput = (props) => {
|
||||
const {defaultButtonText, textBefore, placeHolder, onChange, coolDownTime, onButtonClick, onButtonClickArgs} = props;
|
||||
const {defaultButtonText, disabled, prefix, textBefore, placeHolder, onChange, coolDownTime, onButtonClick, onButtonClickArgs} = props;
|
||||
const [buttonText, setButtonText] = React.useState(defaultButtonText);
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [key, setKey] = React.useState("");
|
||||
@ -101,14 +102,26 @@ export const CountDownInput = (props) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getIcon = (prefix) => {
|
||||
switch (prefix) {
|
||||
case "VerifiedOutlined":
|
||||
return <VerifiedOutlined />;
|
||||
case "AuditOutlined":
|
||||
return <AuditOutlined />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Input
|
||||
addonBefore={textBefore}
|
||||
disabled={disabled}
|
||||
prefix={prefix !== null ? getIcon(prefix) : null}
|
||||
placeholder={placeHolder}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
addonAfter={
|
||||
<div>
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={clickButton}
|
||||
style={{backgroundColor: "#fafafa", border: "none"}}>
|
||||
{buttonText}
|
||||
|
@ -169,5 +169,30 @@
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable password": "Enable password",
|
||||
"Login page preview": "Login page preview"
|
||||
},
|
||||
"forget":
|
||||
{
|
||||
"Please input your application!": "Please input your application!",
|
||||
"Please input your organization!": "Please input your organization!",
|
||||
"Unknown forgot type:": "Unknown forgot type:",
|
||||
"Please input your username!": "Please input your username!",
|
||||
"Please input your password!": "Please input your password!",
|
||||
"Please confirm your password!": "Please confirm your password!",
|
||||
"Please input your Email/Phone string!": "Please input your Email/Phone string!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Your confirmed password is inconsistent with the password!": "Your confirmed password is inconsistent with the password!",
|
||||
"send code": "send code",
|
||||
"Account": "Account",
|
||||
"Verify": "Verify",
|
||||
"Retrieve password": "Retrieve password",
|
||||
"Reset": "Reset",
|
||||
"Password": "Password",
|
||||
"Next Step": "Next Step",
|
||||
"Confirm": "Confirm",
|
||||
"Verify code": "Verify code",
|
||||
"Email/Phone's format wrong!": "Email/Phone's format wrong!",
|
||||
"Email/Phone": "Email/Phone",
|
||||
"Change Password": "Change Password",
|
||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
||||
}
|
||||
}
|
||||
|
@ -169,5 +169,30 @@
|
||||
"Edit Application": "修改应用",
|
||||
"Enable password": "开启密码",
|
||||
"Login page preview": "登录页面预览"
|
||||
},
|
||||
"forget":
|
||||
{
|
||||
"Please input your application!": "请输入您的应用名称!",
|
||||
"Please input your organization!": "请输入您的组织名称!",
|
||||
"Unknown forgot type:": "未知的忘记类型:",
|
||||
"Please input your username!": "请输入您的用户名!",
|
||||
"Please input your password!": "请输入您的密码!",
|
||||
"Please confirm your password!": "请确认您的密码!",
|
||||
"Please input your Email/Phone string!": "请输入您的邮箱/手机号!",
|
||||
"Please input your verification code!": "请输入您的验证码",
|
||||
"Your confirmed password is inconsistent with the password!": "您确认的密码与密码不一致!",
|
||||
"send code": "发送验证码",
|
||||
"Verify code": "验证码",
|
||||
"Password": "密码",
|
||||
"Next Step": "下一步",
|
||||
"Choose email verification or mobile verification": "选择邮箱验证或手机验证",
|
||||
"Retrieve password": "找回密码",
|
||||
"Confirm": "验证密码",
|
||||
"Email/Phone's format wrong!": "邮箱/手机号格式错误!",
|
||||
"Change Password": "修改密码",
|
||||
"Account": "账号",
|
||||
"Email/Phone": "邮箱/手机号",
|
||||
"Verify": "验证",
|
||||
"Reset": "重置"
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user