diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index e44f33a731..fc8087e12d 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -155,7 +155,7 @@ function _loginWithToken(queryParams, defaultDeviceDisplayName) {
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
console.log("Doing guest login on %s", hsUrl);
- // TODO: we should probably de-duplicate this and Signup.Login.loginAsGuest.
+ // TODO: we should probably de-duplicate this and Login.loginAsGuest.
// Not really sure where the right home for it is.
// create a temporary MatrixClient to do the login
@@ -315,6 +315,9 @@ export function setLoggedIn(credentials) {
console.warn("No local storage available: can't persist session!");
}
+ // stop any running clients before we create a new one with these new credentials
+ stopMatrixClient();
+
MatrixClientPeg.replaceUsingCreds(credentials);
teamPromise.then((teamToken) => {
diff --git a/src/Login.js b/src/Login.js
new file mode 100644
index 0000000000..96f953c130
--- /dev/null
+++ b/src/Login.js
@@ -0,0 +1,178 @@
+/*
+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.
+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 Matrix from "matrix-js-sdk";
+
+import q from 'q';
+import url from 'url';
+
+export default class Login {
+ constructor(hsUrl, isUrl, fallbackHsUrl, opts) {
+ this._hsUrl = hsUrl;
+ this._isUrl = isUrl;
+ this._fallbackHsUrl = fallbackHsUrl;
+ this._currentFlowIndex = 0;
+ this._flows = [];
+ this._defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
+ }
+
+ getHomeserverUrl() {
+ return this._hsUrl;
+ }
+
+ getIdentityServerUrl() {
+ return this._isUrl;
+ }
+
+ setHomeserverUrl(hsUrl) {
+ this._hsUrl = hsUrl;
+ }
+
+ setIdentityServerUrl(isUrl) {
+ this._isUrl = isUrl;
+ }
+
+ /**
+ * Get a temporary MatrixClient, which can be used for login or register
+ * requests.
+ */
+ _createTemporaryClient() {
+ return Matrix.createClient({
+ baseUrl: this._hsUrl,
+ idBaseUrl: this._isUrl,
+ });
+ }
+
+ getFlows() {
+ var self = this;
+ var client = this._createTemporaryClient();
+ return client.loginFlows().then(function(result) {
+ self._flows = result.flows;
+ self._currentFlowIndex = 0;
+ // technically the UI should display options for all flows for the
+ // user to then choose one, so return all the flows here.
+ return self._flows;
+ });
+ }
+
+ chooseFlow(flowIndex) {
+ this._currentFlowIndex = flowIndex;
+ }
+
+ getCurrentFlowStep() {
+ // technically the flow can have multiple steps, but no one does this
+ // for login so we can ignore it.
+ var flowStep = this._flows[this._currentFlowIndex];
+ return flowStep ? flowStep.type : null;
+ }
+
+ loginAsGuest() {
+ var client = this._createTemporaryClient();
+ return client.registerGuest({
+ body: {
+ initial_device_display_name: this._defaultDeviceDisplayName,
+ },
+ }).then((creds) => {
+ return {
+ userId: creds.user_id,
+ deviceId: creds.device_id,
+ accessToken: creds.access_token,
+ homeserverUrl: this._hsUrl,
+ identityServerUrl: this._isUrl,
+ guest: true
+ };
+ }, (error) => {
+ if (error.httpStatus === 403) {
+ error.friendlyText = "Guest access is disabled on this Home Server.";
+ } else {
+ error.friendlyText = "Failed to register as guest: " + error.data;
+ }
+ throw error;
+ });
+ }
+
+ loginViaPassword(username, pass) {
+ var self = this;
+ var isEmail = username.indexOf("@") > 0;
+ var loginParams = {
+ password: pass,
+ initial_device_display_name: this._defaultDeviceDisplayName,
+ };
+ if (isEmail) {
+ loginParams.medium = 'email';
+ loginParams.address = username;
+ } else {
+ loginParams.user = username;
+ }
+
+ var client = this._createTemporaryClient();
+ return client.login('m.login.password', loginParams).then(function(data) {
+ return q({
+ homeserverUrl: self._hsUrl,
+ identityServerUrl: self._isUrl,
+ userId: data.user_id,
+ deviceId: data.device_id,
+ accessToken: data.access_token
+ });
+ }, function(error) {
+ if (error.httpStatus == 400 && loginParams.medium) {
+ error.friendlyText = (
+ 'This Home Server does not support login using email address.'
+ );
+ }
+ else if (error.httpStatus === 403) {
+ error.friendlyText = (
+ 'Incorrect username and/or password.'
+ );
+ if (self._fallbackHsUrl) {
+ var fbClient = Matrix.createClient({
+ baseUrl: self._fallbackHsUrl,
+ idBaseUrl: this._isUrl,
+ });
+
+ return fbClient.login('m.login.password', loginParams).then(function(data) {
+ return q({
+ homeserverUrl: self._fallbackHsUrl,
+ identityServerUrl: self._isUrl,
+ userId: data.user_id,
+ deviceId: data.device_id,
+ accessToken: data.access_token
+ });
+ }, function(fallback_error) {
+ // throw the original error
+ throw error;
+ });
+ }
+ }
+ else {
+ error.friendlyText = (
+ 'There was a problem logging in. (HTTP ' + error.httpStatus + ")"
+ );
+ }
+ throw error;
+ });
+ }
+
+ redirectToCas() {
+ var client = this._createTemporaryClient();
+ var parsedUrl = url.parse(window.location.href, true);
+ parsedUrl.query["homeserver"] = client.getHomeserverUrl();
+ parsedUrl.query["identityServer"] = client.getIdentityServerUrl();
+ var casUrl = client.getCasLoginUrl(url.format(parsedUrl));
+ window.location.href = casUrl;
+ }
+}
diff --git a/src/Signup.js b/src/Signup.js
deleted file mode 100644
index 022a93524c..0000000000
--- a/src/Signup.js
+++ /dev/null
@@ -1,465 +0,0 @@
-"use strict";
-
-import Matrix from "matrix-js-sdk";
-
-var MatrixClientPeg = require("./MatrixClientPeg");
-var SignupStages = require("./SignupStages");
-var dis = require("./dispatcher");
-var q = require("q");
-var url = require("url");
-
-const EMAIL_STAGE_TYPE = "m.login.email.identity";
-
-/**
- * A base class for common functionality between Registration and Login e.g.
- * storage of HS/IS URLs.
- */
-class Signup {
- constructor(hsUrl, isUrl, opts) {
- this._hsUrl = hsUrl;
- this._isUrl = isUrl;
- this._defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
- }
-
- getHomeserverUrl() {
- return this._hsUrl;
- }
-
- getIdentityServerUrl() {
- return this._isUrl;
- }
-
- setHomeserverUrl(hsUrl) {
- this._hsUrl = hsUrl;
- }
-
- setIdentityServerUrl(isUrl) {
- this._isUrl = isUrl;
- }
-
- /**
- * Get a temporary MatrixClient, which can be used for login or register
- * requests.
- */
- _createTemporaryClient() {
- return Matrix.createClient({
- baseUrl: this._hsUrl,
- idBaseUrl: this._isUrl,
- });
- }
-}
-
-/**
- * Registration logic class
- * This exists for the lifetime of a user's attempt to register an account,
- * so if their registration attempt fails for whatever reason and they
- * try again, call register() on the same instance again.
- *
- * TODO: parts of this overlap heavily with InteractiveAuth in the js-sdk. It
- * would be nice to make use of that rather than rolling our own version of it.
- */
-class Register extends Signup {
- constructor(hsUrl, isUrl, opts) {
- super(hsUrl, isUrl, opts);
- this.setStep("START");
- this.data = null; // from the server
- // random other stuff (e.g. query params, NOT params from the server)
- this.params = {};
- this.credentials = null;
- this.activeStage = null;
- this.registrationPromise = null;
- // These values MUST be undefined else we'll send "username: null" which
- // will error on Synapse rather than having the key absent.
- this.username = undefined; // desired
- this.email = undefined; // desired
- this.password = undefined; // desired
- }
-
- setClientSecret(secret) {
- this.params.clientSecret = secret;
- }
-
- setSessionId(sessionId) {
- this.params.sessionId = sessionId;
- }
-
- setRegistrationUrl(regUrl) {
- this.params.registrationUrl = regUrl;
- }
-
- setIdSid(idSid) {
- this.params.idSid = idSid;
- }
-
- setReferrer(referrer) {
- this.params.referrer = referrer;
- }
-
- setGuestAccessToken(token) {
- this.guestAccessToken = token;
- }
-
- getStep() {
- return this._step;
- }
-
- getCredentials() {
- return this.credentials;
- }
-
- getServerData() {
- return this.data || {};
- }
-
- getPromise() {
- return this.registrationPromise;
- }
-
- setStep(step) {
- this._step = 'Register.' + step;
- // TODO:
- // It's a shame this is going to the global dispatcher, we only really
- // want things which have an instance of this class to be able to add
- // listeners...
- console.log("Dispatching 'registration_step_update' for step %s", this._step);
- dis.dispatch({
- action: "registration_step_update"
- });
- }
-
- /**
- * Starts the registration process from the first stage
- */
- register(formVals) {
- var {username, password, email} = formVals;
- this.email = email;
- this.username = username;
- this.password = password;
- const client = this._createTemporaryClient();
- this.activeStage = null;
-
- // If there hasn't been a client secret set by this point,
- // generate one for this session. It will only be used if
- // we do email verification, but far simpler to just make
- // sure we have one.
- // We re-use this same secret over multiple calls to register
- // so that the identity server can honour the sendAttempt
- // parameter and not re-send email unless we actually want
- // another mail to be sent.
- if (!this.params.clientSecret) {
- this.params.clientSecret = client.generateClientSecret();
- }
- return this._tryRegister(client);
- }
-
- _tryRegister(client, authDict, poll_for_success) {
- var self = this;
-
- var bindEmail;
-
- if (this.username && this.password) {
- // only need to bind_email when sending u/p - sending it at other
- // times clobbers the u/p resulting in M_MISSING_PARAM (password)
- bindEmail = true;
- }
-
- // TODO need to figure out how to send the device display name to /register.
- return client.register(
- this.username, this.password, this.params.sessionId, authDict, bindEmail,
- this.guestAccessToken
- ).then(function(result) {
- self.credentials = result;
- self.setStep("COMPLETE");
- return result; // contains the credentials
- }, function(error) {
- if (error.httpStatus === 401) {
- if (error.data && error.data.flows) {
- // Remember the session ID from the server:
- // Either this is our first 401 in which case we need to store the
- // session ID for future calls, or it isn't in which case this
- // is just a no-op since it ought to be the same (or if it isn't,
- // we should use the latest one from the server in any case).
- self.params.sessionId = error.data.session;
- self.data = error.data || {};
- var flow = self.chooseFlow(error.data.flows);
-
- if (flow) {
- console.log("Active flow => %s", JSON.stringify(flow));
- var flowStage = self.firstUncompletedStage(flow);
- if (!self.activeStage || flowStage != self.activeStage.type) {
- return self._startStage(client, flowStage).catch(function(err) {
- self.setStep('START');
- throw err;
- });
- }
- }
- }
- if (poll_for_success) {
- return q.delay(2000).then(function() {
- return self._tryRegister(client, authDict, poll_for_success);
- });
- } else {
- throw new Error("Authorisation failed!");
- }
- } else {
- if (error.errcode === 'M_USER_IN_USE') {
- throw new Error("Username in use");
- } else if (error.errcode == 'M_INVALID_USERNAME') {
- throw new Error("User names may only contain alphanumeric characters, underscores or dots!");
- } else if (error.httpStatus >= 400 && error.httpStatus < 500) {
- let msg = null;
- if (error.message) {
- msg = error.message;
- } else if (error.errcode) {
- msg = error.errcode;
- }
- if (msg) {
- throw new Error(`Registration failed! (${error.httpStatus}) - ${msg}`);
- } else {
- throw new Error(`Registration failed! (${error.httpStatus}) - That's all we know.`);
- }
- } else if (error.httpStatus >= 500 && error.httpStatus < 600) {
- throw new Error(
- `Server error during registration! (${error.httpStatus})`
- );
- } else if (error.name == "M_MISSING_PARAM") {
- // The HS hasn't remembered the login params from
- // the first try when the login email was sent.
- throw new Error(
- "This home server does not support resuming registration."
- );
- }
- }
- });
- }
-
- firstUncompletedStage(flow) {
- for (var i = 0; i < flow.stages.length; ++i) {
- if (!this.hasCompletedStage(flow.stages[i])) {
- return flow.stages[i];
- }
- }
- }
-
- hasCompletedStage(stageType) {
- var completed = (this.data || {}).completed || [];
- return completed.indexOf(stageType) !== -1;
- }
-
- _startStage(client, stageName) {
- var self = this;
- this.setStep(`STEP_${stageName}`);
- var StageClass = SignupStages[stageName];
- if (!StageClass) {
- // no idea how to handle this!
- throw new Error("Unknown stage: " + stageName);
- }
-
- var stage = new StageClass(client, this);
- this.activeStage = stage;
- return stage.complete().then(function(request) {
- if (request.auth) {
- console.log("Stage %s is returning an auth dict", stageName);
- return self._tryRegister(client, request.auth, request.poll_for_success);
- }
- else {
- // never resolve the promise chain. This is for things like email auth
- // which display a "check your email" message and relies on the
- // link in the email to actually register you.
- console.log("Waiting for external action.");
- return q.defer().promise;
- }
- });
- }
-
- chooseFlow(flows) {
- // If the user gave us an email then we want to pick an email
- // flow we can do, else any other flow.
- var emailFlow = null;
- var otherFlow = null;
- flows.forEach(function(flow) {
- var flowHasEmail = false;
- for (var stageI = 0; stageI < flow.stages.length; ++stageI) {
- var stage = flow.stages[stageI];
-
- if (!SignupStages[stage]) {
- // we can't do this flow, don't have a Stage impl.
- return;
- }
-
- if (stage === EMAIL_STAGE_TYPE) {
- flowHasEmail = true;
- }
- }
-
- if (flowHasEmail) {
- emailFlow = flow;
- } else {
- otherFlow = flow;
- }
- });
-
- if (this.email || this.hasCompletedStage(EMAIL_STAGE_TYPE)) {
- // we've been given an email or we've already done an email part
- return emailFlow;
- } else {
- return otherFlow;
- }
- }
-
- recheckState() {
- // We've been given a bunch of data from a previous register step,
- // this only happens for email auth currently. It's kinda ming we need
- // to know this though. A better solution would be to ask the stages if
- // they are ready to do something rather than accepting that we know about
- // email auth and its internals.
- this.params.hasEmailInfo = (
- this.params.clientSecret && this.params.sessionId && this.params.idSid
- );
-
- if (this.params.hasEmailInfo) {
- const client = this._createTemporaryClient();
- this.registrationPromise = this._startStage(client, EMAIL_STAGE_TYPE);
- }
- return this.registrationPromise;
- }
-
- tellStage(stageName, data) {
- if (this.activeStage && this.activeStage.type === stageName) {
- console.log("Telling stage %s about something..", stageName);
- this.activeStage.onReceiveData(data);
- }
- }
-}
-
-
-class Login extends Signup {
- constructor(hsUrl, isUrl, fallbackHsUrl, opts) {
- super(hsUrl, isUrl, opts);
- this._fallbackHsUrl = fallbackHsUrl;
- this._currentFlowIndex = 0;
- this._flows = [];
- }
-
- getFlows() {
- var self = this;
- var client = this._createTemporaryClient();
- return client.loginFlows().then(function(result) {
- self._flows = result.flows;
- self._currentFlowIndex = 0;
- // technically the UI should display options for all flows for the
- // user to then choose one, so return all the flows here.
- return self._flows;
- });
- }
-
- chooseFlow(flowIndex) {
- this._currentFlowIndex = flowIndex;
- }
-
- getCurrentFlowStep() {
- // technically the flow can have multiple steps, but no one does this
- // for login so we can ignore it.
- var flowStep = this._flows[this._currentFlowIndex];
- return flowStep ? flowStep.type : null;
- }
-
- loginAsGuest() {
- var client = this._createTemporaryClient();
- return client.registerGuest({
- body: {
- initial_device_display_name: this._defaultDeviceDisplayName,
- },
- }).then((creds) => {
- return {
- userId: creds.user_id,
- deviceId: creds.device_id,
- accessToken: creds.access_token,
- homeserverUrl: this._hsUrl,
- identityServerUrl: this._isUrl,
- guest: true
- };
- }, (error) => {
- if (error.httpStatus === 403) {
- error.friendlyText = "Guest access is disabled on this Home Server.";
- } else {
- error.friendlyText = "Failed to register as guest: " + error.data;
- }
- throw error;
- });
- }
-
- loginViaPassword(username, pass) {
- var self = this;
- var isEmail = username.indexOf("@") > 0;
- var loginParams = {
- password: pass,
- initial_device_display_name: this._defaultDeviceDisplayName,
- };
- if (isEmail) {
- loginParams.medium = 'email';
- loginParams.address = username;
- } else {
- loginParams.user = username;
- }
-
- var client = this._createTemporaryClient();
- return client.login('m.login.password', loginParams).then(function(data) {
- return q({
- homeserverUrl: self._hsUrl,
- identityServerUrl: self._isUrl,
- userId: data.user_id,
- deviceId: data.device_id,
- accessToken: data.access_token
- });
- }, function(error) {
- if (error.httpStatus == 400 && loginParams.medium) {
- error.friendlyText = (
- 'This Home Server does not support login using email address.'
- );
- }
- else if (error.httpStatus === 403) {
- error.friendlyText = (
- 'Incorrect username and/or password.'
- );
- if (self._fallbackHsUrl) {
- var fbClient = Matrix.createClient({
- baseUrl: self._fallbackHsUrl,
- idBaseUrl: this._isUrl,
- });
-
- return fbClient.login('m.login.password', loginParams).then(function(data) {
- return q({
- homeserverUrl: self._fallbackHsUrl,
- identityServerUrl: self._isUrl,
- userId: data.user_id,
- deviceId: data.device_id,
- accessToken: data.access_token
- });
- }, function(fallback_error) {
- // throw the original error
- throw error;
- });
- }
- }
- else {
- error.friendlyText = (
- 'There was a problem logging in. (HTTP ' + error.httpStatus + ")"
- );
- }
- throw error;
- });
- }
-
- redirectToCas() {
- var client = this._createTemporaryClient();
- var parsedUrl = url.parse(window.location.href, true);
- parsedUrl.query["homeserver"] = client.getHomeserverUrl();
- parsedUrl.query["identityServer"] = client.getIdentityServerUrl();
- var casUrl = client.getCasLoginUrl(url.format(parsedUrl));
- window.location.href = casUrl;
- }
-}
-
-module.exports.Register = Register;
-module.exports.Login = Login;
diff --git a/src/SignupStages.js b/src/SignupStages.js
deleted file mode 100644
index 1441682c85..0000000000
--- a/src/SignupStages.js
+++ /dev/null
@@ -1,177 +0,0 @@
-"use strict";
-var q = require("q");
-
-/**
- * An interface class which login types should abide by.
- */
-class Stage {
- constructor(type, matrixClient, signupInstance) {
- this.type = type;
- this.client = matrixClient;
- this.signupInstance = signupInstance;
- }
-
- complete() {
- // Return a promise which is:
- // RESOLVED => With an Object which has an 'auth' key which is the auth dict
- // to submit.
- // REJECTED => With an Error if there was a problem with this stage.
- // Has a "message" string and an "isFatal" flag.
- return q.reject("NOT IMPLEMENTED");
- }
-
- onReceiveData() {
- // NOP
- }
-}
-Stage.TYPE = "NOT IMPLEMENTED";
-
-
-/**
- * This stage requires no auth.
- */
-class DummyStage extends Stage {
- constructor(matrixClient, signupInstance) {
- super(DummyStage.TYPE, matrixClient, signupInstance);
- }
-
- complete() {
- return q({
- auth: {
- type: DummyStage.TYPE
- }
- });
- }
-}
-DummyStage.TYPE = "m.login.dummy";
-
-
-/**
- * This stage uses Google's Recaptcha to do auth.
- */
-class RecaptchaStage extends Stage {
- constructor(matrixClient, signupInstance) {
- super(RecaptchaStage.TYPE, matrixClient, signupInstance);
- this.authDict = {
- auth: {
- type: 'm.login.recaptcha',
- // we'll add in the response param if we get one from the local user.
- },
- poll_for_success: true,
- };
- }
-
- // called when the recaptcha has been completed.
- onReceiveData(data) {
- if (!data || !data.response) {
- return;
- }
- this.authDict.auth.response = data.response;
- }
-
- complete() {
- // we return the authDict with no response, telling Signup to keep polling
- // the server in case the captcha is filled in on another window (e.g. by
- // following a nextlink from an email signup). If the user completes the
- // captcha locally, then we return at the next poll.
- return q(this.authDict);
- }
-}
-RecaptchaStage.TYPE = "m.login.recaptcha";
-
-
-/**
- * This state uses the IS to verify email addresses.
- */
-class EmailIdentityStage extends Stage {
- constructor(matrixClient, signupInstance) {
- super(EmailIdentityStage.TYPE, matrixClient, signupInstance);
- }
-
- _completeVerify() {
- // pull out the host of the IS URL by creating an anchor element
- var isLocation = document.createElement('a');
- isLocation.href = this.signupInstance.getIdentityServerUrl();
-
- var clientSecret = this.clientSecret || this.signupInstance.params.clientSecret;
- var sid = this.sid || this.signupInstance.params.idSid;
-
- return q({
- auth: {
- type: 'm.login.email.identity',
- threepid_creds: {
- sid: sid,
- client_secret: clientSecret,
- id_server: isLocation.host
- }
- }
- });
- }
-
- /**
- * Complete the email stage.
- *
- * This is called twice under different circumstances:
- * 1) When requesting an email token from the IS
- * 2) When validating query parameters received from the link in the email
- */
- complete() {
- // TODO: The Registration class shouldn't really know this info.
- if (this.signupInstance.params.hasEmailInfo) {
- return this._completeVerify();
- }
-
- this.clientSecret = this.signupInstance.params.clientSecret;
- if (!this.clientSecret) {
- return q.reject(new Error("No client secret specified by Signup class!"));
- }
-
- var nextLink = this.signupInstance.params.registrationUrl +
- '?client_secret=' +
- encodeURIComponent(this.clientSecret) +
- "&hs_url=" +
- encodeURIComponent(this.signupInstance.getHomeserverUrl()) +
- "&is_url=" +
- encodeURIComponent(this.signupInstance.getIdentityServerUrl()) +
- "&session_id=" +
- encodeURIComponent(this.signupInstance.getServerData().session);
-
- // Add the user ID of the referring user, if set
- if (this.signupInstance.params.referrer) {
- nextLink += "&referrer=" + encodeURIComponent(this.signupInstance.params.referrer);
- }
-
- var self = this;
- return this.client.requestRegisterEmailToken(
- this.signupInstance.email,
- this.clientSecret,
- 1, // TODO: Multiple send attempts?
- nextLink
- ).then(function(response) {
- self.sid = response.sid;
- self.signupInstance.setIdSid(self.sid);
- return self._completeVerify();
- }).then(function(request) {
- request.poll_for_success = true;
- return request;
- }, function(error) {
- console.error(error);
- var e = {
- isFatal: true
- };
- if (error.errcode == 'M_THREEPID_IN_USE') {
- e.message = "This email address is already registered";
- } else {
- e.message = 'Unable to contact the given identity server';
- }
- throw e;
- });
- }
-}
-EmailIdentityStage.TYPE = "m.login.email.identity";
-
-module.exports = {
- [DummyStage.TYPE]: DummyStage,
- [RecaptchaStage.TYPE]: RecaptchaStage,
- [EmailIdentityStage.TYPE]: EmailIdentityStage
-};
diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js
index 70b3c2e306..4f050cc246 100644
--- a/src/components/structures/InteractiveAuth.js
+++ b/src/components/structures/InteractiveAuth.js
@@ -27,6 +27,9 @@ export default React.createClass({
displayName: 'InteractiveAuth',
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({
@@ -38,11 +41,27 @@ export default React.createClass({
// callback
makeRequest: React.PropTypes.func.isRequired,
- // callback called when the auth process has finished
+ // callback called when the auth process has finished,
+ // successfully or unsuccessfully.
// @param {bool} status True if the operation requiring
// auth was completed sucessfully, false if canceled.
// @param result The result of the authenticated call
- onFinished: React.PropTypes.func.isRequired,
+ onAuthFinished: React.PropTypes.func.isRequired,
+
+ // Inputs provided by the user to the auth process
+ // and used by various stages. As passed to js-sdk
+ // interactive-auth
+ inputs: React.PropTypes.object,
+
+ // As js-sdk interactive-auth
+ makeRegistrationUrl: React.PropTypes.func,
+ sessionId: React.PropTypes.string,
+ clientSecret: React.PropTypes.string,
+ emailSid: React.PropTypes.string,
+
+ // If true, poll to see if the auth flow has been completed
+ // out-of-band
+ poll: React.PropTypes.bool,
},
getInitialState: function() {
@@ -60,12 +79,18 @@ export default React.createClass({
this._authLogic = new InteractiveAuth({
authData: this.props.authData,
doRequest: this._requestCallback,
- startAuthStage: this._startAuthStage,
+ inputs: this.props.inputs,
+ stateUpdated: this._authStateUpdated,
+ matrixClient: this.props.matrixClient,
+ sessionId: this.props.sessionId,
+ clientSecret: this.props.clientSecret,
+ emailSid: this.props.emailSid,
});
this._authLogic.attemptAuth().then((result) => {
- this.props.onFinished(true, result);
+ this.props.onAuthFinished(true, result);
}).catch((error) => {
+ this.props.onAuthFinished(false, error);
console.error("Error during user-interactive auth:", error);
if (this._unmounted) {
return;
@@ -76,17 +101,32 @@ export default React.createClass({
errorText: msg
});
}).done();
+
+ this._intervalId = null;
+ if (this.props.poll) {
+ this._intervalId = setInterval(() => {
+ this._authLogic.poll();
+ }, 2000);
+ }
},
componentWillUnmount: function() {
this._unmounted = true;
+
+ if (this._intervalId !== null) {
+ clearInterval(this._intervalId);
+ }
},
- _startAuthStage: function(stageType, error) {
+ _authStateUpdated: function(stageType, stageState) {
+ const oldStage = this.state.authStage;
this.setState({
authStage: stageType,
- errorText: error ? error.error : null,
- }, this._setFocus);
+ stageState: stageState,
+ errorText: stageState.error,
+ }, () => {
+ if (oldStage != stageType) this._setFocus();
+ });
},
_requestCallback: function(auth) {
@@ -117,19 +157,35 @@ export default React.createClass({
_renderCurrentStage: function() {
const stage = this.state.authStage;
- var StageComponent = getEntryComponentForLoginType(stage);
+ if (!stage) return null;
+
+ const StageComponent = getEntryComponentForLoginType(stage);
return (
An email has been sent to {this.props.inputs.emailAddress}
+Please check your email to continue registration.
+- Sorry, but your university is not registered with us just yet. - Email us on - - {this.props.teamsConfig.supportEmail} - - to get your university signed up. Or continue to register with Riot to enjoy our open source platform. -
- ); - } else if (this.state.selectedTeam) { - belowEmailSection = ( -- You are registering with {this.state.selectedTeam.name} -
- ); - } ++ Sorry, but your university is not registered with us just yet. + Email us on + + {this.props.teamsConfig.supportEmail} + + to get your university signed up. Or continue to register with Riot to enjoy our open source platform. +
+ ); + } else if (this.state.selectedTeam) { + belowEmailSection = ( ++ You are registering with {this.state.selectedTeam.name} +
+ ); } } - if (this.props.onRegisterClick) { - registerButton = ( - - ); - } - var placeholderUserName = "User name"; + const registerButton = ( + + ); + + let placeholderUserName = "User name"; if (this.props.guestUsername) { placeholderUserName += " (default: " + this.props.guestUsername + ")"; } diff --git a/src/components/views/settings/DevicesPanelEntry.js b/src/components/views/settings/DevicesPanelEntry.js index 60501e326f..51f3a83010 100644 --- a/src/components/views/settings/DevicesPanelEntry.js +++ b/src/components/views/settings/DevicesPanelEntry.js @@ -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, }); diff --git a/test/components/views/dialogs/InteractiveAuthDialog-test.js b/test/components/views/dialogs/InteractiveAuthDialog-test.js index 80f027ab44..da8fc17001 100644 --- a/test/components/views/dialogs/InteractiveAuthDialog-test.js +++ b/test/components/views/dialogs/InteractiveAuthDialog-test.js @@ -57,6 +57,7 @@ describe('InteractiveAuthDialog', function () { const dlg = ReactDOM.render(