Merge pull request #729 from matrix-org/dbkr/register_ui_auth

Port registration over to use InteractiveAuth
This commit is contained in:
David Baker
2017-03-03 13:37:41 +00:00
committed by GitHub
14 changed files with 673 additions and 964 deletions

View File

@@ -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>
);
},

View File

@@ -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) {

View File

@@ -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.&nbsp;
Email us on&nbsp;
<a href={"mailto:" + this.props.teamsConfig.supportEmail}>
{this.props.teamsConfig.supportEmail}
</a>&nbsp;
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.&nbsp;
Email us on&nbsp;
<a href={"mailto:" + this.props.teamsConfig.supportEmail}>
{this.props.teamsConfig.supportEmail}
</a>&nbsp;
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 + ")";
}

View File

@@ -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,
});