Merge pull request #729 from matrix-org/dbkr/register_ui_auth
Port registration over to use InteractiveAuth
This commit is contained in:
@@ -27,6 +27,9 @@ export default React.createClass({
|
||||
displayName: 'InteractiveAuthDialog',
|
||||
|
||||
propTypes: {
|
||||
// matrix client to use for UI auth requests
|
||||
matrixClient: React.PropTypes.object.isRequired,
|
||||
|
||||
// response from initial request. If not supplied, will do a request on
|
||||
// mount.
|
||||
authData: React.PropTypes.shape({
|
||||
@@ -49,22 +52,62 @@ export default React.createClass({
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
authError: null,
|
||||
}
|
||||
},
|
||||
|
||||
_onAuthFinished: function(success, result) {
|
||||
if (success) {
|
||||
this.props.onFinished(true);
|
||||
} else {
|
||||
this.setState({
|
||||
authError: result,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onDismissClick: function() {
|
||||
this.props.onFinished(false);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
|
||||
let content;
|
||||
if (this.state.authError) {
|
||||
content = (
|
||||
<div>
|
||||
<div>{this.state.authError.message || this.state.authError.toString()}</div>
|
||||
<br />
|
||||
<AccessibleButton onClick={this._onDismissClick}
|
||||
className="mx_UserSettings_button"
|
||||
>
|
||||
Dismiss
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<div>
|
||||
<InteractiveAuth ref={this._collectInteractiveAuth}
|
||||
matrixClient={this.props.matrixClient}
|
||||
authData={this.props.authData}
|
||||
makeRequest={this.props.makeRequest}
|
||||
onAuthFinished={this._onAuthFinished}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_InteractiveAuthDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={this.props.title}
|
||||
title={this.state.authError ? 'Error' : this.props.title}
|
||||
>
|
||||
<div>
|
||||
<InteractiveAuth ref={this._collectInteractiveAuth}
|
||||
authData={this.props.authData}
|
||||
makeRequest={this.props.makeRequest}
|
||||
onFinished={this.props.onFinished}
|
||||
/>
|
||||
</div>
|
||||
{content}
|
||||
</BaseDialog>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -18,7 +18,6 @@ limitations under the License.
|
||||
import React from 'react';
|
||||
|
||||
import sdk from '../../../index';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
|
||||
/* This file contains a collection of components which are used by the
|
||||
* InteractiveAuth to prompt the user to enter the information needed
|
||||
@@ -28,13 +27,32 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
* Call getEntryComponentForLoginType() to get a component suitable for a
|
||||
* particular login type. Each component requires the same properties:
|
||||
*
|
||||
* matrixClient: A matrix client. May be a different one to the one
|
||||
* currently being used generally (eg. to register with
|
||||
* one HS whilst beign a guest on another).
|
||||
* loginType: the login type of the auth stage being attempted
|
||||
* authSessionId: session id from the server
|
||||
* clientSecret: The client secret in use for ID server auth sessions
|
||||
* stageParams: params from the server for the stage being attempted
|
||||
* errorText: error message from a previous attempt to authenticate
|
||||
* submitAuthDict: a function which will be called with the new auth dict
|
||||
* busy: a boolean indicating whether the auth logic is doing something
|
||||
* the user needs to wait for.
|
||||
* inputs: Object of inputs provided by the user, as in js-sdk
|
||||
* interactive-auth
|
||||
* stageState: Stage-specific object used for communicating state information
|
||||
* to the UI from the state-specific auth logic.
|
||||
* Defined keys for stages are:
|
||||
* m.login.email.identity:
|
||||
* * emailSid: string representing the sid of the active
|
||||
* verification session from the ID server, or
|
||||
* null if no session is active.
|
||||
* fail: a function which should be called with an error object if an
|
||||
* error occurred during the auth stage. This will cause the auth
|
||||
* session to be failed and the process to go back to the start.
|
||||
* setEmailSid: m.login.email.identity only: a function to be called with the
|
||||
* email sid after a token is requested.
|
||||
* makeRegistrationUrl A function that makes a registration URL
|
||||
*
|
||||
* Each component may also provide the following functions (beyond the standard React ones):
|
||||
* focus: set the input focus appropriately in the form.
|
||||
@@ -48,6 +66,7 @@ export const PasswordAuthEntry = React.createClass({
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
matrixClient: React.PropTypes.object.isRequired,
|
||||
submitAuthDict: React.PropTypes.func.isRequired,
|
||||
errorText: React.PropTypes.string,
|
||||
// is the auth logic currently waiting for something to
|
||||
@@ -73,7 +92,7 @@ export const PasswordAuthEntry = React.createClass({
|
||||
|
||||
this.props.submitAuthDict({
|
||||
type: PasswordAuthEntry.LOGIN_TYPE,
|
||||
user: MatrixClientPeg.get().credentials.userId,
|
||||
user: this.props.matrixClient.credentials.userId,
|
||||
password: this.refs.passwordField.value,
|
||||
});
|
||||
},
|
||||
@@ -164,10 +183,83 @@ export const RecaptchaAuthEntry = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
export const EmailIdentityAuthEntry = React.createClass({
|
||||
displayName: 'EmailIdentityAuthEntry',
|
||||
|
||||
statics: {
|
||||
LOGIN_TYPE: "m.login.email.identity",
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
matrixClient: React.PropTypes.object.isRequired,
|
||||
submitAuthDict: React.PropTypes.func.isRequired,
|
||||
authSessionId: React.PropTypes.string.isRequired,
|
||||
clientSecret: React.PropTypes.string.isRequired,
|
||||
inputs: React.PropTypes.object.isRequired,
|
||||
stageState: React.PropTypes.object.isRequired,
|
||||
fail: React.PropTypes.func.isRequired,
|
||||
setEmailSid: React.PropTypes.func.isRequired,
|
||||
makeRegistrationUrl: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
requestingToken: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
if (this.props.stageState.emailSid === null) {
|
||||
this.setState({requestingToken: true});
|
||||
this._requestEmailToken().catch((e) => {
|
||||
this.props.fail(e);
|
||||
}).finally(() => {
|
||||
this.setState({requestingToken: false});
|
||||
}).done();
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Requests a verification token by email.
|
||||
*/
|
||||
_requestEmailToken: function() {
|
||||
const nextLink = this.props.makeRegistrationUrl({
|
||||
client_secret: this.props.clientSecret,
|
||||
hs_url: this.props.matrixClient.getHomeserverUrl(),
|
||||
is_url: this.props.matrixClient.getIdentityServerUrl(),
|
||||
session_id: this.props.authSessionId,
|
||||
});
|
||||
|
||||
return this.props.matrixClient.requestRegisterEmailToken(
|
||||
this.props.inputs.emailAddress,
|
||||
this.props.clientSecret,
|
||||
1, // TODO: Multiple send attempts?
|
||||
nextLink,
|
||||
).then((result) => {
|
||||
this.props.setEmailSid(result.sid);
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.requestingToken) {
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
return <Loader />;
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<p>An email has been sent to <i>{this.props.inputs.emailAddress}</i></p>
|
||||
<p>Please check your email to continue registration.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const FallbackAuthEntry = React.createClass({
|
||||
displayName: 'FallbackAuthEntry',
|
||||
|
||||
propTypes: {
|
||||
matrixClient: React.PropTypes.object.isRequired,
|
||||
authSessionId: React.PropTypes.string.isRequired,
|
||||
loginType: React.PropTypes.string.isRequired,
|
||||
submitAuthDict: React.PropTypes.func.isRequired,
|
||||
@@ -189,7 +281,7 @@ export const FallbackAuthEntry = React.createClass({
|
||||
},
|
||||
|
||||
_onShowFallbackClick: function() {
|
||||
var url = MatrixClientPeg.get().getFallbackAuthUrl(
|
||||
var url = this.props.matrixClient.getFallbackAuthUrl(
|
||||
this.props.loginType,
|
||||
this.props.authSessionId
|
||||
);
|
||||
@@ -199,7 +291,7 @@ export const FallbackAuthEntry = React.createClass({
|
||||
_onReceiveMessage: function(event) {
|
||||
if (
|
||||
event.data === "authDone" &&
|
||||
event.origin === MatrixClientPeg.get().getHomeserverUrl()
|
||||
event.origin === this.props.matrixClient.getHomeserverUrl()
|
||||
) {
|
||||
this.props.submitAuthDict({});
|
||||
}
|
||||
@@ -220,6 +312,7 @@ export const FallbackAuthEntry = React.createClass({
|
||||
const AuthEntryComponents = [
|
||||
PasswordAuthEntry,
|
||||
RecaptchaAuthEntry,
|
||||
EmailIdentityAuthEntry,
|
||||
];
|
||||
|
||||
export function getEntryComponentForLoginType(loginType) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,18 +15,16 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
import React from 'react';
|
||||
import { field_input_incorrect } from '../../../UiEffects';
|
||||
import sdk from '../../../index';
|
||||
import Email from '../../../email';
|
||||
import Modal from '../../../Modal';
|
||||
|
||||
var React = require('react');
|
||||
var UiEffects = require('../../../UiEffects');
|
||||
var sdk = require('../../../index');
|
||||
var Email = require('../../../email');
|
||||
var Modal = require("../../../Modal");
|
||||
|
||||
var FIELD_EMAIL = 'field_email';
|
||||
var FIELD_USERNAME = 'field_username';
|
||||
var FIELD_PASSWORD = 'field_password';
|
||||
var FIELD_PASSWORD_CONFIRM = 'field_password_confirm';
|
||||
const FIELD_EMAIL = 'field_email';
|
||||
const FIELD_USERNAME = 'field_username';
|
||||
const FIELD_PASSWORD = 'field_password';
|
||||
const FIELD_PASSWORD_CONFIRM = 'field_password_confirm';
|
||||
|
||||
/**
|
||||
* A pure UI component which displays a registration form.
|
||||
@@ -54,15 +53,13 @@ module.exports = React.createClass({
|
||||
// a different username will cause a fresh account to be generated.
|
||||
guestUsername: React.PropTypes.string,
|
||||
|
||||
showEmail: React.PropTypes.bool,
|
||||
minPasswordLength: React.PropTypes.number,
|
||||
onError: React.PropTypes.func,
|
||||
onRegisterClick: React.PropTypes.func // onRegisterClick(Object) => ?Promise
|
||||
onRegisterClick: React.PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
showEmail: false,
|
||||
minPasswordLength: 6,
|
||||
onError: function(e) {
|
||||
console.error(e);
|
||||
@@ -174,8 +171,8 @@ module.exports = React.createClass({
|
||||
showSupportEmail: false,
|
||||
});
|
||||
}
|
||||
const valid = email === '' || Email.looksValid(email);
|
||||
this.markFieldValid(field_id, valid, "RegistrationForm.ERR_EMAIL_INVALID");
|
||||
const emailValid = email === '' || Email.looksValid(email);
|
||||
this.markFieldValid(field_id, emailValid, "RegistrationForm.ERR_EMAIL_INVALID");
|
||||
break;
|
||||
case FIELD_USERNAME:
|
||||
// XXX: SPEC-1
|
||||
@@ -227,7 +224,7 @@ module.exports = React.createClass({
|
||||
fieldValid[field_id] = val;
|
||||
this.setState({fieldValid: fieldValid});
|
||||
if (!val) {
|
||||
UiEffects.field_input_incorrect(this.fieldElementById(field_id));
|
||||
field_input_incorrect(this.fieldElementById(field_id));
|
||||
this.props.onError(error_code);
|
||||
}
|
||||
},
|
||||
@@ -245,8 +242,8 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_classForField: function(field_id, baseClass) {
|
||||
let cls = baseClass || '';
|
||||
_classForField: function(field_id, ...baseClasses) {
|
||||
let cls = baseClasses.join(' ');
|
||||
if (this.state.fieldValid[field_id] === false) {
|
||||
if (cls) cls += ' ';
|
||||
cls += 'error';
|
||||
@@ -256,44 +253,44 @@ module.exports = React.createClass({
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
var emailSection, belowEmailSection, registerButton;
|
||||
if (this.props.showEmail) {
|
||||
emailSection = (
|
||||
|
||||
const emailSection = (
|
||||
<div>
|
||||
<input type="text" ref="email"
|
||||
autoFocus={true} placeholder="Email address (optional)"
|
||||
defaultValue={this.props.defaultEmail}
|
||||
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
||||
value={self.state.email}/>
|
||||
);
|
||||
if (this.props.teamsConfig) {
|
||||
if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
|
||||
belowEmailSection = (
|
||||
<p className="mx_Login_support">
|
||||
Sorry, but your university is not registered with us just yet.
|
||||
Email us on
|
||||
<a href={"mailto:" + this.props.teamsConfig.supportEmail}>
|
||||
{this.props.teamsConfig.supportEmail}
|
||||
</a>
|
||||
to get your university signed up. Or continue to register with Riot to enjoy our open source platform.
|
||||
</p>
|
||||
);
|
||||
} else if (this.state.selectedTeam) {
|
||||
belowEmailSection = (
|
||||
<p className="mx_Login_support">
|
||||
You are registering with {this.state.selectedTeam.name}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
let belowEmailSection;
|
||||
if (this.props.teamsConfig) {
|
||||
if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
|
||||
belowEmailSection = (
|
||||
<p className="mx_Login_support">
|
||||
Sorry, but your university is not registered with us just yet.
|
||||
Email us on
|
||||
<a href={"mailto:" + this.props.teamsConfig.supportEmail}>
|
||||
{this.props.teamsConfig.supportEmail}
|
||||
</a>
|
||||
to get your university signed up. Or continue to register with Riot to enjoy our open source platform.
|
||||
</p>
|
||||
);
|
||||
} else if (this.state.selectedTeam) {
|
||||
belowEmailSection = (
|
||||
<p className="mx_Login_support">
|
||||
You are registering with {this.state.selectedTeam.name}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.props.onRegisterClick) {
|
||||
registerButton = (
|
||||
<input className="mx_Login_submit" type="submit" value="Register" />
|
||||
);
|
||||
}
|
||||
|
||||
var placeholderUserName = "User name";
|
||||
const registerButton = (
|
||||
<input className="mx_Login_submit" type="submit" value="Register" />
|
||||
);
|
||||
|
||||
let placeholderUserName = "User name";
|
||||
if (this.props.guestUsername) {
|
||||
placeholderUserName += " (default: " + this.props.guestUsername + ")";
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ export default class DevicesPanelEntry extends React.Component {
|
||||
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||
|
||||
Modal.createDialog(InteractiveAuthDialog, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
authData: error.data,
|
||||
makeRequest: this._makeDeleteRequest,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user