mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 14:05:04 +08:00
Merge pull request #87 from matrix-org/kegan/password-reset
Implement password reset
This commit is contained in:
commit
d10c96ede8
104
src/PasswordReset.js
Normal file
104
src/PasswordReset.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Matrix = require("matrix-js-sdk");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a user to reset their password on a homeserver.
|
||||||
|
*
|
||||||
|
* This involves getting an email token from the identity server to "prove" that
|
||||||
|
* the client owns the given email address, which is then passed to the password
|
||||||
|
* API on the homeserver in question with the new password.
|
||||||
|
*/
|
||||||
|
class PasswordReset {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the endpoints for password resetting.
|
||||||
|
* @param {string} homeserverUrl The URL to the HS which has the account to reset.
|
||||||
|
* @param {string} identityUrl The URL to the IS which has linked the email -> mxid mapping.
|
||||||
|
*/
|
||||||
|
constructor(homeserverUrl, identityUrl) {
|
||||||
|
this.client = Matrix.createClient({
|
||||||
|
baseUrl: homeserverUrl,
|
||||||
|
idBaseUrl: identityUrl
|
||||||
|
});
|
||||||
|
this.clientSecret = generateClientSecret();
|
||||||
|
this.identityServerDomain = identityUrl.split("://")[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to reset the user's password. This will trigger a side-effect of
|
||||||
|
* sending an email to the provided email address.
|
||||||
|
* @param {string} emailAddress The email address
|
||||||
|
* @param {string} newPassword The new password for the account.
|
||||||
|
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
||||||
|
*/
|
||||||
|
resetPassword(emailAddress, newPassword) {
|
||||||
|
this.password = newPassword;
|
||||||
|
return this.client.requestEmailToken(emailAddress, this.clientSecret, 1).then((res) => {
|
||||||
|
this.sessionId = res.sid;
|
||||||
|
return res;
|
||||||
|
}, function(err) {
|
||||||
|
if (err.httpStatus) {
|
||||||
|
err.message = err.message + ` (Status ${err.httpStatus})`;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the email link has been clicked by attempting to change the password
|
||||||
|
* for the mxid linked to the email.
|
||||||
|
* @return {Promise} Resolves if the password was reset. Rejects with an object
|
||||||
|
* with a "message" property which contains a human-readable message detailing why
|
||||||
|
* the reset failed, e.g. "There is no mapped matrix user ID for the given email address".
|
||||||
|
*/
|
||||||
|
checkEmailLinkClicked() {
|
||||||
|
return this.client.setPassword({
|
||||||
|
type: "m.login.email.identity",
|
||||||
|
threepid_creds: {
|
||||||
|
sid: this.sessionId,
|
||||||
|
client_secret: this.clientSecret,
|
||||||
|
id_server: this.identityServerDomain
|
||||||
|
}
|
||||||
|
}, this.password).catch(function(err) {
|
||||||
|
if (err.httpStatus === 401) {
|
||||||
|
err.message = "Failed to verify email address: make sure you clicked the link in the email";
|
||||||
|
}
|
||||||
|
else if (err.httpStatus === 404) {
|
||||||
|
err.message = "Your email address does not appear to be associated with a Matrix ID on this Homeserver.";
|
||||||
|
}
|
||||||
|
else if (err.httpStatus) {
|
||||||
|
err.message += ` (Status ${err.httpStatus})`;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// from Angular SDK
|
||||||
|
function generateClientSecret() {
|
||||||
|
var ret = "";
|
||||||
|
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
|
for (var i = 0; i < 32; i++) {
|
||||||
|
ret += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PasswordReset;
|
@ -23,14 +23,15 @@ limitations under the License.
|
|||||||
|
|
||||||
module.exports.components = {};
|
module.exports.components = {};
|
||||||
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
|
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
|
||||||
|
module.exports.components['structures.login.ForgotPassword'] = require('./components/structures/login/ForgotPassword');
|
||||||
|
module.exports.components['structures.login.Login'] = require('./components/structures/login/Login');
|
||||||
|
module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration');
|
||||||
|
module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration');
|
||||||
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
|
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
|
||||||
module.exports.components['structures.RoomView'] = require('./components/structures/RoomView');
|
module.exports.components['structures.RoomView'] = require('./components/structures/RoomView');
|
||||||
module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel');
|
module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel');
|
||||||
module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar');
|
module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar');
|
||||||
module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings');
|
module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings');
|
||||||
module.exports.components['structures.login.Login'] = require('./components/structures/login/Login');
|
|
||||||
module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration');
|
|
||||||
module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration');
|
|
||||||
module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar');
|
module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar');
|
||||||
module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar');
|
module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar');
|
||||||
module.exports.components['views.create_room.CreateRoomButton'] = require('./components/views/create_room/CreateRoomButton');
|
module.exports.components['views.create_room.CreateRoomButton'] = require('./components/views/create_room/CreateRoomButton');
|
||||||
@ -51,10 +52,10 @@ module.exports.components['views.login.LoginHeader'] = require('./components/vie
|
|||||||
module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin');
|
module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin');
|
||||||
module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm');
|
module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm');
|
||||||
module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig');
|
module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig');
|
||||||
|
module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent');
|
||||||
module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody');
|
module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody');
|
||||||
module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody');
|
module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody');
|
||||||
module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody');
|
module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody');
|
||||||
module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent');
|
|
||||||
module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody');
|
module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody');
|
||||||
module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent');
|
module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent');
|
||||||
module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody');
|
module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody');
|
||||||
|
@ -233,6 +233,13 @@ module.exports = React.createClass({
|
|||||||
});
|
});
|
||||||
this.notifyNewScreen('register');
|
this.notifyNewScreen('register');
|
||||||
break;
|
break;
|
||||||
|
case 'start_password_recovery':
|
||||||
|
if (this.state.logged_in) return;
|
||||||
|
this.replaceState({
|
||||||
|
screen: 'forgot_password'
|
||||||
|
});
|
||||||
|
this.notifyNewScreen('forgot_password');
|
||||||
|
break;
|
||||||
case 'token_login':
|
case 'token_login':
|
||||||
if (this.state.logged_in) return;
|
if (this.state.logged_in) return;
|
||||||
|
|
||||||
@ -559,6 +566,11 @@ module.exports = React.createClass({
|
|||||||
action: 'token_login',
|
action: 'token_login',
|
||||||
params: params
|
params: params
|
||||||
});
|
});
|
||||||
|
} else if (screen == 'forgot_password') {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'start_password_recovery',
|
||||||
|
params: params
|
||||||
|
});
|
||||||
} else if (screen == 'new') {
|
} else if (screen == 'new') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_create_room',
|
action: 'view_create_room',
|
||||||
@ -668,6 +680,10 @@ module.exports = React.createClass({
|
|||||||
this.showScreen("login");
|
this.showScreen("login");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onForgotPasswordClick: function() {
|
||||||
|
this.showScreen("forgot_password");
|
||||||
|
},
|
||||||
|
|
||||||
onRegistered: function(credentials) {
|
onRegistered: function(credentials) {
|
||||||
this.onLoggedIn(credentials);
|
this.onLoggedIn(credentials);
|
||||||
// do post-registration stuff
|
// do post-registration stuff
|
||||||
@ -706,6 +722,7 @@ module.exports = React.createClass({
|
|||||||
var CreateRoom = sdk.getComponent('structures.CreateRoom');
|
var CreateRoom = sdk.getComponent('structures.CreateRoom');
|
||||||
var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
||||||
var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||||
|
var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
|
||||||
|
|
||||||
// needs to be before normal PageTypes as you are logged in technically
|
// needs to be before normal PageTypes as you are logged in technically
|
||||||
if (this.state.screen == 'post_registration') {
|
if (this.state.screen == 'post_registration') {
|
||||||
@ -801,13 +818,21 @@ module.exports = React.createClass({
|
|||||||
onLoggedIn={this.onRegistered}
|
onLoggedIn={this.onRegistered}
|
||||||
onLoginClick={this.onLoginClick} />
|
onLoginClick={this.onLoginClick} />
|
||||||
);
|
);
|
||||||
|
} else if (this.state.screen == 'forgot_password') {
|
||||||
|
return (
|
||||||
|
<ForgotPassword
|
||||||
|
homeserverUrl={this.props.config.default_hs_url}
|
||||||
|
identityServerUrl={this.props.config.default_is_url}
|
||||||
|
onComplete={this.onLoginClick} />
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Login
|
<Login
|
||||||
onLoggedIn={this.onLoggedIn}
|
onLoggedIn={this.onLoggedIn}
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
homeserverUrl={this.props.config.default_hs_url}
|
homeserverUrl={this.props.config.default_hs_url}
|
||||||
identityServerUrl={this.props.config.default_is_url} />
|
identityServerUrl={this.props.config.default_is_url}
|
||||||
|
onForgotPasswordClick={this.onForgotPasswordClick} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
199
src/components/structures/login/ForgotPassword.js
Normal file
199
src/components/structures/login/ForgotPassword.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react');
|
||||||
|
var sdk = require('../../../index');
|
||||||
|
var Modal = require("../../../Modal");
|
||||||
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
|
||||||
|
var PasswordReset = require("../../../PasswordReset");
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'ForgotPassword',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
homeserverUrl: React.PropTypes.string,
|
||||||
|
identityServerUrl: React.PropTypes.string,
|
||||||
|
onComplete: React.PropTypes.func.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
enteredHomeserverUrl: this.props.homeserverUrl,
|
||||||
|
enteredIdentityServerUrl: this.props.identityServerUrl,
|
||||||
|
progress: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
submitPasswordReset: function(hsUrl, identityUrl, email, password) {
|
||||||
|
this.setState({
|
||||||
|
progress: "sending_email"
|
||||||
|
});
|
||||||
|
this.reset = new PasswordReset(hsUrl, identityUrl);
|
||||||
|
this.reset.resetPassword(email, password).done(() => {
|
||||||
|
this.setState({
|
||||||
|
progress: "sent_email"
|
||||||
|
});
|
||||||
|
}, (err) => {
|
||||||
|
this.showErrorDialog("Failed to send email: " + err.message);
|
||||||
|
this.setState({
|
||||||
|
progress: null
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onVerify: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (!this.reset) {
|
||||||
|
console.error("onVerify called before submitPasswordReset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.reset.checkEmailLinkClicked().done((res) => {
|
||||||
|
this.setState({ progress: "complete" });
|
||||||
|
}, (err) => {
|
||||||
|
this.showErrorDialog(err.message);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmitForm: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (!this.state.email) {
|
||||||
|
this.showErrorDialog("The email address linked to your account must be entered.");
|
||||||
|
}
|
||||||
|
else if (!this.state.password || !this.state.password2) {
|
||||||
|
this.showErrorDialog("A new password must be entered.");
|
||||||
|
}
|
||||||
|
else if (this.state.password !== this.state.password2) {
|
||||||
|
this.showErrorDialog("New passwords must match each other.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.submitPasswordReset(
|
||||||
|
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl,
|
||||||
|
this.state.email, this.state.password
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onInputChanged: function(stateKey, ev) {
|
||||||
|
this.setState({
|
||||||
|
[stateKey]: ev.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onHsUrlChanged: function(newHsUrl) {
|
||||||
|
this.setState({
|
||||||
|
enteredHomeserverUrl: newHsUrl
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onIsUrlChanged: function(newIsUrl) {
|
||||||
|
this.setState({
|
||||||
|
enteredIdentityServerUrl: newIsUrl
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showErrorDialog: function(body, title) {
|
||||||
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: title,
|
||||||
|
description: body
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var LoginHeader = sdk.getComponent("login.LoginHeader");
|
||||||
|
var LoginFooter = sdk.getComponent("login.LoginFooter");
|
||||||
|
var ServerConfig = sdk.getComponent("login.ServerConfig");
|
||||||
|
var Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
|
var resetPasswordJsx;
|
||||||
|
|
||||||
|
if (this.state.progress === "sending_email") {
|
||||||
|
resetPasswordJsx = <Spinner />
|
||||||
|
}
|
||||||
|
else if (this.state.progress === "sent_email") {
|
||||||
|
resetPasswordJsx = (
|
||||||
|
<div>
|
||||||
|
An email has been sent to {this.state.email}. Once you've followed
|
||||||
|
the link it contains, click below.
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||||
|
value="I have verified my email address" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (this.state.progress === "complete") {
|
||||||
|
resetPasswordJsx = (
|
||||||
|
<div>
|
||||||
|
<p>Your password has been reset.</p>
|
||||||
|
<p>You have been logged out of all devices and will no longer receive push notifications.
|
||||||
|
To re-enable notifications, re-log in on each device.</p>
|
||||||
|
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
||||||
|
value="Return to login screen" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resetPasswordJsx = (
|
||||||
|
<div>
|
||||||
|
To reset your password, enter the email address linked to your account:
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<form onSubmit={this.onSubmitForm}>
|
||||||
|
<input className="mx_Login_field" ref="user" type="text"
|
||||||
|
value={this.state.email}
|
||||||
|
onChange={this.onInputChanged.bind(this, "email")}
|
||||||
|
placeholder="Email address" autoFocus />
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_field" ref="pass" type="password"
|
||||||
|
value={this.state.password}
|
||||||
|
onChange={this.onInputChanged.bind(this, "password")}
|
||||||
|
placeholder="New password" />
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_field" ref="pass" type="password"
|
||||||
|
value={this.state.password2}
|
||||||
|
onChange={this.onInputChanged.bind(this, "password2")}
|
||||||
|
placeholder="Confirm your new password" />
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_submit" type="submit" value="Send Reset Email" />
|
||||||
|
</form>
|
||||||
|
<ServerConfig ref="serverConfig"
|
||||||
|
withToggleButton={true}
|
||||||
|
defaultHsUrl={this.props.homeserverUrl}
|
||||||
|
defaultIsUrl={this.props.identityServerUrl}
|
||||||
|
onHsUrlChanged={this.onHsUrlChanged}
|
||||||
|
onIsUrlChanged={this.onIsUrlChanged}
|
||||||
|
delayTimeMs={0}/>
|
||||||
|
<LoginFooter />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx_Login">
|
||||||
|
<div className="mx_Login_box">
|
||||||
|
<LoginHeader />
|
||||||
|
{resetPasswordJsx}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -33,7 +33,9 @@ module.exports = React.createClass({displayName: 'Login',
|
|||||||
homeserverUrl: React.PropTypes.string,
|
homeserverUrl: React.PropTypes.string,
|
||||||
identityServerUrl: React.PropTypes.string,
|
identityServerUrl: React.PropTypes.string,
|
||||||
// login shouldn't know or care how registration is done.
|
// login shouldn't know or care how registration is done.
|
||||||
onRegisterClick: React.PropTypes.func.isRequired
|
onRegisterClick: React.PropTypes.func.isRequired,
|
||||||
|
// login shouldn't care how password recovery is done.
|
||||||
|
onForgotPasswordClick: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@ -138,7 +140,9 @@ module.exports = React.createClass({displayName: 'Login',
|
|||||||
switch (step) {
|
switch (step) {
|
||||||
case 'm.login.password':
|
case 'm.login.password':
|
||||||
return (
|
return (
|
||||||
<PasswordLogin onSubmit={this.onPasswordLogin} />
|
<PasswordLogin
|
||||||
|
onSubmit={this.onPasswordLogin}
|
||||||
|
onForgotPasswordClick={this.props.onForgotPasswordClick} />
|
||||||
);
|
);
|
||||||
case 'm.login.cas':
|
case 'm.login.cas':
|
||||||
return (
|
return (
|
||||||
|
@ -22,7 +22,8 @@ var ReactDOM = require('react-dom');
|
|||||||
*/
|
*/
|
||||||
module.exports = React.createClass({displayName: 'PasswordLogin',
|
module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onSubmit: React.PropTypes.func.isRequired // fn(username, password)
|
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
|
||||||
|
onForgotPasswordClick: React.PropTypes.func // fn()
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@ -46,6 +47,16 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
var forgotPasswordJsx;
|
||||||
|
|
||||||
|
if (this.props.onForgotPasswordClick) {
|
||||||
|
forgotPasswordJsx = (
|
||||||
|
<a className="mx_Login_forgot" onClick={this.props.onForgotPasswordClick} href="#">
|
||||||
|
Forgot your password?
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this.onSubmitForm}>
|
<form onSubmit={this.onSubmitForm}>
|
||||||
@ -57,6 +68,7 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
|||||||
value={this.state.password} onChange={this.onPasswordChanged}
|
value={this.state.password} onChange={this.onPasswordChanged}
|
||||||
placeholder="Password" />
|
placeholder="Password" />
|
||||||
<br />
|
<br />
|
||||||
|
{forgotPasswordJsx}
|
||||||
<input className="mx_Login_submit" type="submit" value="Log in" />
|
<input className="mx_Login_submit" type="submit" value="Log in" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user