Port registration over to use InteractiveAuth
These changes are moved over from the dbkr/msisdn_signin branch
This commit is contained in:
parent
fd3c1ab9e1
commit
51467506f8
14 changed files with 484 additions and 963 deletions
|
@ -155,7 +155,7 @@ function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
||||||
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
||||||
console.log("Doing guest login on %s", hsUrl);
|
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.
|
// Not really sure where the right home for it is.
|
||||||
|
|
||||||
// create a temporary MatrixClient to do the login
|
// create a temporary MatrixClient to do the login
|
||||||
|
@ -316,6 +316,9 @@ export function setLoggedIn(credentials) {
|
||||||
console.warn("No local storage available: can't persist session!");
|
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);
|
MatrixClientPeg.replaceUsingCreds(credentials);
|
||||||
|
|
||||||
dis.dispatch({action: 'on_logged_in'});
|
dis.dispatch({action: 'on_logged_in'});
|
||||||
|
|
177
src/Login.js
Normal file
177
src/Login.js
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
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 = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
465
src/Signup.js
465
src/Signup.js
|
@ -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;
|
|
|
@ -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
|
|
||||||
};
|
|
|
@ -27,6 +27,9 @@ export default React.createClass({
|
||||||
displayName: 'InteractiveAuth',
|
displayName: 'InteractiveAuth',
|
||||||
|
|
||||||
propTypes: {
|
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
|
// response from initial request. If not supplied, will do a request on
|
||||||
// mount.
|
// mount.
|
||||||
authData: React.PropTypes.shape({
|
authData: React.PropTypes.shape({
|
||||||
|
@ -43,6 +46,17 @@ export default React.createClass({
|
||||||
// auth was completed sucessfully, false if canceled.
|
// auth was completed sucessfully, false if canceled.
|
||||||
// @param result The result of the authenticated call
|
// @param result The result of the authenticated call
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: 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,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -61,6 +75,13 @@ export default React.createClass({
|
||||||
authData: this.props.authData,
|
authData: this.props.authData,
|
||||||
doRequest: this._requestCallback,
|
doRequest: this._requestCallback,
|
||||||
startAuthStage: this._startAuthStage,
|
startAuthStage: this._startAuthStage,
|
||||||
|
inputs: this.props.inputs,
|
||||||
|
stateUpdated: this._authStateUpdated,
|
||||||
|
makeRegistrationUrl: this.props.makeRegistrationUrl,
|
||||||
|
matrixClient: this.props.matrixClient,
|
||||||
|
sessionId: this.props.sessionId,
|
||||||
|
clientSecret: this.props.clientSecret,
|
||||||
|
emailSid: this.props.emailSid,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._authLogic.attemptAuth().then((result) => {
|
this._authLogic.attemptAuth().then((result) => {
|
||||||
|
@ -82,11 +103,14 @@ export default React.createClass({
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_startAuthStage: function(stageType, error) {
|
_authStateUpdated: function(stageType, stageState) {
|
||||||
|
const oldStage = this.state.authStage;
|
||||||
this.setState({
|
this.setState({
|
||||||
authStage: stageType,
|
authStage: stageType,
|
||||||
errorText: error ? error.error : null,
|
stageState: stageState,
|
||||||
}, this._setFocus);
|
}, () => {
|
||||||
|
if (oldStage != stageType) this._setFocus();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_requestCallback: function(auth) {
|
_requestCallback: function(auth) {
|
||||||
|
@ -117,15 +141,20 @@ export default React.createClass({
|
||||||
|
|
||||||
_renderCurrentStage: function() {
|
_renderCurrentStage: function() {
|
||||||
const stage = this.state.authStage;
|
const stage = this.state.authStage;
|
||||||
var StageComponent = getEntryComponentForLoginType(stage);
|
if (!stage) return null;
|
||||||
|
|
||||||
|
const StageComponent = getEntryComponentForLoginType(stage);
|
||||||
return (
|
return (
|
||||||
<StageComponent ref="stageComponent"
|
<StageComponent ref="stageComponent"
|
||||||
loginType={stage}
|
loginType={stage}
|
||||||
|
matrixClient={this.props.matrixClient}
|
||||||
authSessionId={this._authLogic.getSessionId()}
|
authSessionId={this._authLogic.getSessionId()}
|
||||||
stageParams={this._authLogic.getStageParams(stage)}
|
stageParams={this._authLogic.getStageParams(stage)}
|
||||||
submitAuthDict={this._submitAuthDict}
|
submitAuthDict={this._submitAuthDict}
|
||||||
errorText={this.state.stageErrorText}
|
errorText={this.state.stageErrorText}
|
||||||
busy={this.state.busy}
|
busy={this.state.busy}
|
||||||
|
inputs={this.props.inputs}
|
||||||
|
stageState={this.state.stageState}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -321,23 +322,19 @@ module.exports = React.createClass({
|
||||||
Lifecycle.logout();
|
Lifecycle.logout();
|
||||||
break;
|
break;
|
||||||
case 'start_registration':
|
case 'start_registration':
|
||||||
var newState = payload.params || {};
|
const params = payload.params || {};
|
||||||
newState.screen = 'register';
|
this.setStateForNewScreen({
|
||||||
if (
|
screen: 'register',
|
||||||
payload.params &&
|
// these params may be undefined, but if they are,
|
||||||
payload.params.client_secret &&
|
// unset them from our state: we don't want to
|
||||||
payload.params.session_id &&
|
// resume a previous registration session if the
|
||||||
payload.params.hs_url &&
|
// user just clicked 'register'
|
||||||
payload.params.is_url &&
|
register_client_secret: params.client_secret,
|
||||||
payload.params.sid
|
register_session_id: params.session_id,
|
||||||
) {
|
register_hs_url: params.hs_url,
|
||||||
newState.register_client_secret = payload.params.client_secret;
|
register_is_url: params.is_url,
|
||||||
newState.register_session_id = payload.params.session_id;
|
register_id_sid: params.sid,
|
||||||
newState.register_hs_url = payload.params.hs_url;
|
});
|
||||||
newState.register_is_url = payload.params.is_url;
|
|
||||||
newState.register_id_sid = payload.params.sid;
|
|
||||||
}
|
|
||||||
this.setStateForNewScreen(newState);
|
|
||||||
this.notifyNewScreen('register');
|
this.notifyNewScreen('register');
|
||||||
break;
|
break;
|
||||||
case 'start_login':
|
case 'start_login':
|
||||||
|
@ -1070,6 +1067,13 @@ module.exports = React.createClass({
|
||||||
this.setState({currentRoomId: room_id});
|
this.setState({currentRoomId: room_id});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_makeRegistrationUrl: function(params) {
|
||||||
|
if (this.props.startingFragmentQueryParams.referrer) {
|
||||||
|
params.referrer = this.props.startingFragmentQueryParams.referrer;
|
||||||
|
}
|
||||||
|
return this.props.makeRegistrationUrl(params);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
|
var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
|
||||||
var LoggedInView = sdk.getComponent('structures.LoggedInView');
|
var LoggedInView = sdk.getComponent('structures.LoggedInView');
|
||||||
|
@ -1124,7 +1128,6 @@ module.exports = React.createClass({
|
||||||
sessionId={this.state.register_session_id}
|
sessionId={this.state.register_session_id}
|
||||||
idSid={this.state.register_id_sid}
|
idSid={this.state.register_id_sid}
|
||||||
email={this.props.startingFragmentQueryParams.email}
|
email={this.props.startingFragmentQueryParams.email}
|
||||||
referrer={this.props.startingFragmentQueryParams.referrer}
|
|
||||||
username={this.state.upgradeUsername}
|
username={this.state.upgradeUsername}
|
||||||
guestAccessToken={this.state.guestAccessToken}
|
guestAccessToken={this.state.guestAccessToken}
|
||||||
defaultHsUrl={this.getDefaultHsUrl()}
|
defaultHsUrl={this.getDefaultHsUrl()}
|
||||||
|
@ -1133,7 +1136,7 @@ module.exports = React.createClass({
|
||||||
teamServerConfig={this.props.config.teamServerConfig}
|
teamServerConfig={this.props.config.teamServerConfig}
|
||||||
customHsUrl={this.getCurrentHsUrl()}
|
customHsUrl={this.getCurrentHsUrl()}
|
||||||
customIsUrl={this.getCurrentIsUrl()}
|
customIsUrl={this.getCurrentIsUrl()}
|
||||||
registrationUrl={this.props.registrationUrl}
|
makeRegistrationUrl={this.props.makeRegistrationUrl}
|
||||||
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
||||||
onTeamMemberRegistered={this.onTeamMemberRegistered}
|
onTeamMemberRegistered={this.onTeamMemberRegistered}
|
||||||
onLoggedIn={this.onRegistered}
|
onLoggedIn={this.onRegistered}
|
||||||
|
|
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var ReactDOM = require('react-dom');
|
var ReactDOM = require('react-dom');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var Signup = require("../../../Signup");
|
var Login = require("../../../Login");
|
||||||
var PasswordLogin = require("../../views/login/PasswordLogin");
|
var PasswordLogin = require("../../views/login/PasswordLogin");
|
||||||
var CasLogin = require("../../views/login/CasLogin");
|
var CasLogin = require("../../views/login/CasLogin");
|
||||||
var ServerConfig = require("../../views/login/ServerConfig");
|
var ServerConfig = require("../../views/login/ServerConfig");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wire component which glues together login UI components and Signup logic
|
* A wire component which glues together login UI components and Login logic
|
||||||
*/
|
*/
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'Login',
|
displayName: 'Login',
|
||||||
|
@ -146,7 +146,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;
|
var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;
|
||||||
|
|
||||||
var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl, {
|
var loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
|
||||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
||||||
});
|
});
|
||||||
this._loginLogic = loginLogic;
|
this._loginLogic = loginLogic;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,13 +15,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
import Matrix from 'matrix-js-sdk';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var dis = require('../../../dispatcher');
|
|
||||||
var Signup = require("../../../Signup");
|
|
||||||
var ServerConfig = require("../../views/login/ServerConfig");
|
var ServerConfig = require("../../views/login/ServerConfig");
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var RegistrationForm = require("../../views/login/RegistrationForm");
|
var RegistrationForm = require("../../views/login/RegistrationForm");
|
||||||
|
@ -40,7 +39,7 @@ module.exports = React.createClass({
|
||||||
onLoggedIn: React.PropTypes.func.isRequired,
|
onLoggedIn: React.PropTypes.func.isRequired,
|
||||||
clientSecret: React.PropTypes.string,
|
clientSecret: React.PropTypes.string,
|
||||||
sessionId: React.PropTypes.string,
|
sessionId: React.PropTypes.string,
|
||||||
registrationUrl: React.PropTypes.string,
|
makeRegistrationUrl: React.PropTypes.func,
|
||||||
idSid: React.PropTypes.string,
|
idSid: React.PropTypes.string,
|
||||||
customHsUrl: React.PropTypes.string,
|
customHsUrl: React.PropTypes.string,
|
||||||
customIsUrl: React.PropTypes.string,
|
customIsUrl: React.PropTypes.string,
|
||||||
|
@ -82,27 +81,20 @@ module.exports = React.createClass({
|
||||||
formVals: {
|
formVals: {
|
||||||
email: this.props.email,
|
email: this.props.email,
|
||||||
},
|
},
|
||||||
|
// true if we're waiting for the user to complete
|
||||||
|
// user-interactive auth
|
||||||
|
// If we've been given a session ID, we're resuming
|
||||||
|
// straight back into UI auth
|
||||||
|
doingUIAuth: Boolean(this.props.sessionId),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
// attach this to the instance rather than this.state since it isn't UI
|
this._hsUrl = this.props.customHsUrl;
|
||||||
this.registerLogic = new Signup.Register(
|
this._isUrl = this.props.customIsUrl;
|
||||||
this.props.customHsUrl, this.props.customIsUrl, {
|
this._replaceClient();
|
||||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.registerLogic.setClientSecret(this.props.clientSecret);
|
|
||||||
this.registerLogic.setSessionId(this.props.sessionId);
|
|
||||||
this.registerLogic.setRegistrationUrl(this.props.registrationUrl);
|
|
||||||
this.registerLogic.setIdSid(this.props.idSid);
|
|
||||||
this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
|
|
||||||
if (this.props.referrer) {
|
|
||||||
this.registerLogic.setReferrer(this.props.referrer);
|
|
||||||
}
|
|
||||||
this.registerLogic.recheckState();
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.props.teamServerConfig &&
|
this.props.teamServerConfig &&
|
||||||
|
@ -134,37 +126,20 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
dis.unregister(this.dispatcherRef);
|
|
||||||
this._unmounted = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
// may have already done an HTTP hit (e.g. redirect from an email) so
|
|
||||||
// check for any pending response
|
|
||||||
var promise = this.registerLogic.getPromise();
|
|
||||||
if (promise) {
|
|
||||||
this.onProcessingRegistration(promise);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onHsUrlChanged: function(newHsUrl) {
|
onHsUrlChanged: function(newHsUrl) {
|
||||||
this.registerLogic.setHomeserverUrl(newHsUrl);
|
this._hsUrl = newHsUrl;
|
||||||
|
this._replaceClient();
|
||||||
},
|
},
|
||||||
|
|
||||||
onIsUrlChanged: function(newIsUrl) {
|
onIsUrlChanged: function(newIsUrl) {
|
||||||
this.registerLogic.setIdentityServerUrl(newIsUrl);
|
this._isUrl = newIsUrl;
|
||||||
|
this._replaceClient();
|
||||||
},
|
},
|
||||||
|
|
||||||
onAction: function(payload) {
|
_replaceClient: function() {
|
||||||
if (payload.action !== "registration_step_update") {
|
this._matrixClient = Matrix.createClient({
|
||||||
return;
|
baseUrl: this._hsUrl,
|
||||||
}
|
idBaseUrl: this._isUrl,
|
||||||
// If the registration state has changed, this means the
|
|
||||||
// user now needs to do something. It would be better
|
|
||||||
// to expose the explicitly in the register logic.
|
|
||||||
this.setState({
|
|
||||||
busy: false
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -174,44 +149,22 @@ module.exports = React.createClass({
|
||||||
errorText: "",
|
errorText: "",
|
||||||
busy: true,
|
busy: true,
|
||||||
formVals: formVals,
|
formVals: formVals,
|
||||||
|
doingUIAuth: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (formVals.username !== this.props.username) {
|
|
||||||
// don't try to upgrade if we changed our username
|
|
||||||
this.registerLogic.setGuestAccessToken(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onProcessingRegistration(this.registerLogic.register(formVals));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Promise is resolved when the registration process is FULLY COMPLETE
|
_onRegistered: function(success, response) {
|
||||||
onProcessingRegistration: function(promise) {
|
this.setState({
|
||||||
var self = this;
|
// we're still busy until we get unmounted: don't show the registration form again
|
||||||
promise.done(function(response) {
|
busy: true,
|
||||||
self.setState({
|
doingUIAuth: false,
|
||||||
busy: false
|
|
||||||
});
|
});
|
||||||
if (!response || !response.access_token) {
|
this.props.onLoggedIn({
|
||||||
console.warn(
|
|
||||||
"FIXME: Register fulfilled without a final response, " +
|
|
||||||
"did you break the promise chain?"
|
|
||||||
);
|
|
||||||
// no matter, we'll grab it direct
|
|
||||||
response = self.registerLogic.getCredentials();
|
|
||||||
}
|
|
||||||
if (!response || !response.user_id || !response.access_token) {
|
|
||||||
console.error("Final response is missing keys.");
|
|
||||||
self.setState({
|
|
||||||
errorText: "Registration failed on server"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.props.onLoggedIn({
|
|
||||||
userId: response.user_id,
|
userId: response.user_id,
|
||||||
deviceId: response.device_id,
|
deviceId: response.device_id,
|
||||||
homeserverUrl: self.registerLogic.getHomeserverUrl(),
|
homeserverUrl: this._hsUrl,
|
||||||
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
|
identityServerUrl: this._isUrl,
|
||||||
accessToken: response.access_token
|
accessToken: response.access_token,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Done regardless of `teamSelected`. People registering with non-team emails
|
// Done regardless of `teamSelected`. People registering with non-team emails
|
||||||
|
@ -253,6 +206,7 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set approipriate branding on the email pusher
|
||||||
if (self.props.brand) {
|
if (self.props.brand) {
|
||||||
MatrixClientPeg.get().getPushers().done((resp)=>{
|
MatrixClientPeg.get().getPushers().done((resp)=>{
|
||||||
var pushers = resp.pushers;
|
var pushers = resp.pushers;
|
||||||
|
@ -271,18 +225,6 @@ module.exports = React.createClass({
|
||||||
console.error("Couldn't get pushers: " + error);
|
console.error("Couldn't get pushers: " + error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}, function(err) {
|
|
||||||
if (err.message) {
|
|
||||||
self.setState({
|
|
||||||
errorText: err.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
self.setState({
|
|
||||||
busy: false
|
|
||||||
});
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onFormValidationFailed: function(errCode) {
|
onFormValidationFailed: function(errCode) {
|
||||||
|
@ -316,37 +258,66 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onCaptchaResponse: function(response) {
|
|
||||||
this.registerLogic.tellStage("m.login.recaptcha", {
|
|
||||||
response: response
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onTeamSelected: function(teamSelected) {
|
onTeamSelected: function(teamSelected) {
|
||||||
if (!this._unmounted) {
|
if (!this._unmounted) {
|
||||||
this.setState({ teamSelected });
|
this.setState({ teamSelected });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_getRegisterContentJsx: function() {
|
_makeRegisterRequest: function(auth) {
|
||||||
|
let guestAccessToken = this.props.guestAccessToken;
|
||||||
|
|
||||||
|
if (this.state.formVals.username !== this.props.username) {
|
||||||
|
// don't try to upgrade if we changed our username
|
||||||
|
guestAccessToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._matrixClient.register(
|
||||||
|
this.state.formVals.username,
|
||||||
|
this.state.formVals.password,
|
||||||
|
undefined, // session id: included in the auth dict already
|
||||||
|
auth,
|
||||||
|
// Only send the bind_email param if we're sending username / pw params
|
||||||
|
// (Since we need to send no params at all to use the ones saved in the
|
||||||
|
// session).
|
||||||
|
Boolean(this.state.formVals.username) || undefined,
|
||||||
|
guestAccessToken,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getUIAuthInputs() {
|
||||||
|
return {
|
||||||
|
emailAddress: this.state.formVals.email,
|
||||||
|
phoneCountry: this.state.formVals.phoneCountry,
|
||||||
|
phoneNumber: this.state.formVals.phoneNumber,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const LoginHeader = sdk.getComponent('login.LoginHeader');
|
||||||
|
const LoginFooter = sdk.getComponent('login.LoginFooter');
|
||||||
|
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
var currStep = this.registerLogic.getStep();
|
let registerBody;
|
||||||
var registerStep;
|
if (this.state.doingUIAuth) {
|
||||||
switch (currStep) {
|
registerBody = (
|
||||||
case "Register.COMPLETE":
|
<InteractiveAuth
|
||||||
break; // NOP
|
matrixClient={this._matrixClient}
|
||||||
case "Register.START":
|
makeRequest={this._makeRegisterRequest}
|
||||||
case "Register.STEP_m.login.dummy":
|
onFinished={this._onRegistered}
|
||||||
// NB. Our 'username' prop is specifically for upgrading
|
inputs={this._getUIAuthInputs()}
|
||||||
// a guest account
|
makeRegistrationUrl={this.props.makeRegistrationUrl}
|
||||||
if (this.state.teamServerBusy) {
|
sessionId={this.props.sessionId}
|
||||||
registerStep = <Spinner />;
|
clientSecret={this.props.clientSecret}
|
||||||
break;
|
emailSid={this.props.idSid}
|
||||||
}
|
/>
|
||||||
registerStep = (
|
);
|
||||||
|
} else if (this.state.busy || this.state.teamServerBusy) {
|
||||||
|
registerBody = <Spinner />;
|
||||||
|
} else {
|
||||||
|
registerBody = (
|
||||||
<RegistrationForm
|
<RegistrationForm
|
||||||
showEmail={true}
|
|
||||||
defaultUsername={this.state.formVals.username}
|
defaultUsername={this.state.formVals.username}
|
||||||
defaultEmail={this.state.formVals.email}
|
defaultEmail={this.state.formVals.email}
|
||||||
defaultPassword={this.state.formVals.password}
|
defaultPassword={this.state.formVals.password}
|
||||||
|
@ -358,74 +329,8 @@ module.exports = React.createClass({
|
||||||
onTeamSelected={this.onTeamSelected}
|
onTeamSelected={this.onTeamSelected}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
case "Register.STEP_m.login.email.identity":
|
|
||||||
registerStep = (
|
|
||||||
<div>
|
|
||||||
Please check your email to continue registration.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "Register.STEP_m.login.recaptcha":
|
|
||||||
var publicKey;
|
|
||||||
var serverParams = this.registerLogic.getServerData().params;
|
|
||||||
if (serverParams && serverParams["m.login.recaptcha"]) {
|
|
||||||
publicKey = serverParams["m.login.recaptcha"].public_key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerStep = (
|
|
||||||
<CaptchaForm sitePublicKey={publicKey}
|
|
||||||
onCaptchaResponse={this.onCaptchaResponse}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error("Unknown register state: %s", currStep);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var busySpinner;
|
|
||||||
if (this.state.busy) {
|
|
||||||
busySpinner = (
|
|
||||||
<Spinner />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnToAppJsx;
|
|
||||||
if (this.props.onCancelClick) {
|
|
||||||
returnToAppJsx =
|
|
||||||
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
|
||||||
Return to app
|
|
||||||
</a>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Create an account</h2>
|
|
||||||
{registerStep}
|
|
||||||
<div className="mx_Login_error">{this.state.errorText}</div>
|
|
||||||
{busySpinner}
|
|
||||||
<ServerConfig ref="serverConfig"
|
|
||||||
withToggleButton={ this.registerLogic.getStep() === "Register.START" }
|
|
||||||
customHsUrl={this.props.customHsUrl}
|
|
||||||
customIsUrl={this.props.customIsUrl}
|
|
||||||
defaultHsUrl={this.props.defaultHsUrl}
|
|
||||||
defaultIsUrl={this.props.defaultIsUrl}
|
|
||||||
onHsUrlChanged={this.onHsUrlChanged}
|
|
||||||
onIsUrlChanged={this.onIsUrlChanged}
|
|
||||||
delayTimeMs={1000} />
|
|
||||||
<div className="mx_Login_error">
|
|
||||||
</div>
|
|
||||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
|
||||||
I already have an account
|
|
||||||
</a>
|
|
||||||
{ returnToAppJsx }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
var LoginHeader = sdk.getComponent('login.LoginHeader');
|
|
||||||
var LoginFooter = sdk.getComponent('login.LoginFooter');
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_Login">
|
<div className="mx_Login">
|
||||||
<div className="mx_Login_box">
|
<div className="mx_Login_box">
|
||||||
|
@ -435,7 +340,7 @@ module.exports = React.createClass({
|
||||||
this.state.teamSelected.domain + "/icon.png" :
|
this.state.teamSelected.domain + "/icon.png" :
|
||||||
null}
|
null}
|
||||||
/>
|
/>
|
||||||
{this._getRegisterContentJsx()}
|
{registerBody}
|
||||||
<LoginFooter />
|
<LoginFooter />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,6 +27,9 @@ export default React.createClass({
|
||||||
displayName: 'InteractiveAuthDialog',
|
displayName: 'InteractiveAuthDialog',
|
||||||
|
|
||||||
propTypes: {
|
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
|
// response from initial request. If not supplied, will do a request on
|
||||||
// mount.
|
// mount.
|
||||||
authData: React.PropTypes.shape({
|
authData: React.PropTypes.shape({
|
||||||
|
@ -60,6 +63,7 @@ export default React.createClass({
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<InteractiveAuth ref={this._collectInteractiveAuth}
|
<InteractiveAuth ref={this._collectInteractiveAuth}
|
||||||
|
matrixClient={this.props.matrixClient}
|
||||||
authData={this.props.authData}
|
authData={this.props.authData}
|
||||||
makeRequest={this.props.makeRequest}
|
makeRequest={this.props.makeRequest}
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
|
||||||
|
|
||||||
/* This file contains a collection of components which are used by the
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -28,6 +27,9 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
* Call getEntryComponentForLoginType() to get a component suitable for a
|
* Call getEntryComponentForLoginType() to get a component suitable for a
|
||||||
* particular login type. Each component requires the same properties:
|
* 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
|
* loginType: the login type of the auth stage being attempted
|
||||||
* authSessionId: session id from the server
|
* authSessionId: session id from the server
|
||||||
* stageParams: params from the server for the stage being attempted
|
* stageParams: params from the server for the stage being attempted
|
||||||
|
@ -35,6 +37,10 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
* submitAuthDict: a function which will be called with the new auth dict
|
* submitAuthDict: a function which will be called with the new auth dict
|
||||||
* busy: a boolean indicating whether the auth logic is doing something
|
* busy: a boolean indicating whether the auth logic is doing something
|
||||||
* the user needs to wait for.
|
* 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.
|
||||||
*
|
*
|
||||||
* Each component may also provide the following functions (beyond the standard React ones):
|
* Each component may also provide the following functions (beyond the standard React ones):
|
||||||
* focus: set the input focus appropriately in the form.
|
* focus: set the input focus appropriately in the form.
|
||||||
|
@ -48,6 +54,7 @@ export const PasswordAuthEntry = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
matrixClient: React.PropTypes.object,
|
||||||
submitAuthDict: React.PropTypes.func.isRequired,
|
submitAuthDict: React.PropTypes.func.isRequired,
|
||||||
errorText: React.PropTypes.string,
|
errorText: React.PropTypes.string,
|
||||||
// is the auth logic currently waiting for something to
|
// is the auth logic currently waiting for something to
|
||||||
|
@ -73,7 +80,7 @@ export const PasswordAuthEntry = React.createClass({
|
||||||
|
|
||||||
this.props.submitAuthDict({
|
this.props.submitAuthDict({
|
||||||
type: PasswordAuthEntry.LOGIN_TYPE,
|
type: PasswordAuthEntry.LOGIN_TYPE,
|
||||||
user: MatrixClientPeg.get().credentials.userId,
|
user: this.props.matrixClient.credentials.userId,
|
||||||
password: this.refs.passwordField.value,
|
password: this.refs.passwordField.value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -164,6 +171,39 @@ export const RecaptchaAuthEntry = React.createClass({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const EmailIdentityAuthEntry = React.createClass({
|
||||||
|
displayName: 'EmailIdentityAuthEntry',
|
||||||
|
|
||||||
|
statics: {
|
||||||
|
LOGIN_TYPE: "m.login.email.identity",
|
||||||
|
},
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
matrixClient: React.PropTypes.object,
|
||||||
|
submitAuthDict: React.PropTypes.func.isRequired,
|
||||||
|
errorText: React.PropTypes.string,
|
||||||
|
authSessionId: React.PropTypes.string.isRequired,
|
||||||
|
inputs: React.PropTypes.object.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
// XXX: we now have 2 separate 'busy's - get rid of one
|
||||||
|
// why can't InteractiveAuth manage whether the general
|
||||||
|
// auth logic is busy?
|
||||||
|
if (this.props.stageState.busy) {
|
||||||
|
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({
|
export const FallbackAuthEntry = React.createClass({
|
||||||
displayName: 'FallbackAuthEntry',
|
displayName: 'FallbackAuthEntry',
|
||||||
|
|
||||||
|
@ -189,7 +229,7 @@ export const FallbackAuthEntry = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onShowFallbackClick: function() {
|
_onShowFallbackClick: function() {
|
||||||
var url = MatrixClientPeg.get().getFallbackAuthUrl(
|
var url = this.props.matrixClient.getFallbackAuthUrl(
|
||||||
this.props.loginType,
|
this.props.loginType,
|
||||||
this.props.authSessionId
|
this.props.authSessionId
|
||||||
);
|
);
|
||||||
|
@ -199,7 +239,7 @@ export const FallbackAuthEntry = React.createClass({
|
||||||
_onReceiveMessage: function(event) {
|
_onReceiveMessage: function(event) {
|
||||||
if (
|
if (
|
||||||
event.data === "authDone" &&
|
event.data === "authDone" &&
|
||||||
event.origin === MatrixClientPeg.get().getHomeserverUrl()
|
event.origin === this.props.matrixClient.getHomeserverUrl()
|
||||||
) {
|
) {
|
||||||
this.props.submitAuthDict({});
|
this.props.submitAuthDict({});
|
||||||
}
|
}
|
||||||
|
@ -220,6 +260,7 @@ export const FallbackAuthEntry = React.createClass({
|
||||||
const AuthEntryComponents = [
|
const AuthEntryComponents = [
|
||||||
PasswordAuthEntry,
|
PasswordAuthEntry,
|
||||||
RecaptchaAuthEntry,
|
RecaptchaAuthEntry,
|
||||||
|
EmailIdentityAuthEntry,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getEntryComponentForLoginType(loginType) {
|
export function getEntryComponentForLoginType(loginType) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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.
|
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');
|
const FIELD_EMAIL = 'field_email';
|
||||||
var UiEffects = require('../../../UiEffects');
|
const FIELD_USERNAME = 'field_username';
|
||||||
var sdk = require('../../../index');
|
const FIELD_PASSWORD = 'field_password';
|
||||||
var Email = require('../../../email');
|
const FIELD_PASSWORD_CONFIRM = 'field_password_confirm';
|
||||||
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';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pure UI component which displays a registration form.
|
* 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.
|
// a different username will cause a fresh account to be generated.
|
||||||
guestUsername: React.PropTypes.string,
|
guestUsername: React.PropTypes.string,
|
||||||
|
|
||||||
showEmail: React.PropTypes.bool,
|
|
||||||
minPasswordLength: React.PropTypes.number,
|
minPasswordLength: React.PropTypes.number,
|
||||||
onError: React.PropTypes.func,
|
onError: React.PropTypes.func,
|
||||||
onRegisterClick: React.PropTypes.func // onRegisterClick(Object) => ?Promise
|
onRegisterClick: React.PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
showEmail: false,
|
|
||||||
minPasswordLength: 6,
|
minPasswordLength: 6,
|
||||||
onError: function(e) {
|
onError: function(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -174,8 +171,8 @@ module.exports = React.createClass({
|
||||||
showSupportEmail: false,
|
showSupportEmail: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const valid = email === '' || Email.looksValid(email);
|
const emailValid = email === '' || Email.looksValid(email);
|
||||||
this.markFieldValid(field_id, valid, "RegistrationForm.ERR_EMAIL_INVALID");
|
this.markFieldValid(field_id, emailValid, "RegistrationForm.ERR_EMAIL_INVALID");
|
||||||
break;
|
break;
|
||||||
case FIELD_USERNAME:
|
case FIELD_USERNAME:
|
||||||
// XXX: SPEC-1
|
// XXX: SPEC-1
|
||||||
|
@ -227,7 +224,7 @@ module.exports = React.createClass({
|
||||||
fieldValid[field_id] = val;
|
fieldValid[field_id] = val;
|
||||||
this.setState({fieldValid: fieldValid});
|
this.setState({fieldValid: fieldValid});
|
||||||
if (!val) {
|
if (!val) {
|
||||||
UiEffects.field_input_incorrect(this.fieldElementById(field_id));
|
field_input_incorrect(this.fieldElementById(field_id));
|
||||||
this.props.onError(error_code);
|
this.props.onError(error_code);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -245,8 +242,8 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_classForField: function(field_id, baseClass) {
|
_classForField: function(field_id, ...baseClasses) {
|
||||||
let cls = baseClass || '';
|
let cls = baseClasses.join(' ');
|
||||||
if (this.state.fieldValid[field_id] === false) {
|
if (this.state.fieldValid[field_id] === false) {
|
||||||
if (cls) cls += ' ';
|
if (cls) cls += ' ';
|
||||||
cls += 'error';
|
cls += 'error';
|
||||||
|
@ -256,16 +253,18 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var emailSection, belowEmailSection, registerButton;
|
|
||||||
if (this.props.showEmail) {
|
const emailSection = (
|
||||||
emailSection = (
|
<div>
|
||||||
<input type="text" ref="email"
|
<input type="text" ref="email"
|
||||||
autoFocus={true} placeholder="Email address (optional)"
|
autoFocus={true} placeholder="Email address (optional)"
|
||||||
defaultValue={this.props.defaultEmail}
|
defaultValue={this.props.defaultEmail}
|
||||||
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
||||||
value={self.state.email}/>
|
value={self.state.email}/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
let belowEmailSection;
|
||||||
if (this.props.teamsConfig) {
|
if (this.props.teamsConfig) {
|
||||||
if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
|
if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
|
||||||
belowEmailSection = (
|
belowEmailSection = (
|
||||||
|
@ -286,14 +285,12 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (this.props.onRegisterClick) {
|
const registerButton = (
|
||||||
registerButton = (
|
|
||||||
<input className="mx_Login_submit" type="submit" value="Register" />
|
<input className="mx_Login_submit" type="submit" value="Register" />
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
var placeholderUserName = "User name";
|
let placeholderUserName = "User name";
|
||||||
if (this.props.guestUsername) {
|
if (this.props.guestUsername) {
|
||||||
placeholderUserName += " (default: " + this.props.guestUsername + ")";
|
placeholderUserName += " (default: " + this.props.guestUsername + ")";
|
||||||
}
|
}
|
||||||
|
@ -303,6 +300,7 @@ module.exports = React.createClass({
|
||||||
<form onSubmit={this.onSubmit}>
|
<form onSubmit={this.onSubmit}>
|
||||||
{emailSection}
|
{emailSection}
|
||||||
{belowEmailSection}
|
{belowEmailSection}
|
||||||
|
<br />
|
||||||
<input type="text" ref="username"
|
<input type="text" ref="username"
|
||||||
placeholder={ placeholderUserName } defaultValue={this.props.defaultUsername}
|
placeholder={ placeholderUserName } defaultValue={this.props.defaultUsername}
|
||||||
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
|
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
|
||||||
|
|
|
@ -71,6 +71,7 @@ export default class DevicesPanelEntry extends React.Component {
|
||||||
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||||
|
|
||||||
Modal.createDialog(InteractiveAuthDialog, {
|
Modal.createDialog(InteractiveAuthDialog, {
|
||||||
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: error.data,
|
authData: error.data,
|
||||||
makeRequest: this._makeDeleteRequest,
|
makeRequest: this._makeDeleteRequest,
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,6 +57,7 @@ describe('InteractiveAuthDialog', function () {
|
||||||
|
|
||||||
const dlg = ReactDOM.render(
|
const dlg = ReactDOM.render(
|
||||||
<InteractiveAuthDialog
|
<InteractiveAuthDialog
|
||||||
|
matrixClient={client}
|
||||||
authData={{
|
authData={{
|
||||||
session: "sess",
|
session: "sess",
|
||||||
flows: [
|
flows: [
|
||||||
|
|
|
@ -92,6 +92,7 @@ export function createTestClient() {
|
||||||
sendTextMessage: () => q({}),
|
sendTextMessage: () => q({}),
|
||||||
sendHtmlMessage: () => q({}),
|
sendHtmlMessage: () => q({}),
|
||||||
getSyncState: () => "SYNCING",
|
getSyncState: () => "SYNCING",
|
||||||
|
generateClientSecret: () => "t35tcl1Ent5ECr3T",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue