mirror of https://github.com/vector-im/riot-web
commit
8ea0117a09
264
src/Signup.js
264
src/Signup.js
|
@ -1,16 +1,272 @@
|
|||
"use strict";
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
var SignupStages = require("./SignupStages");
|
||||
var dis = require("./dispatcher");
|
||||
var q = require("q");
|
||||
|
||||
class Register {
|
||||
const EMAIL_STAGE_TYPE = "m.login.email.identity";
|
||||
|
||||
}
|
||||
|
||||
class Login {
|
||||
/**
|
||||
* A base class for common functionality between Registration and Login e.g.
|
||||
* storage of HS/IS URLs.
|
||||
*/
|
||||
class Signup {
|
||||
constructor(hsUrl, isUrl) {
|
||||
this._hsUrl = hsUrl;
|
||||
this._isUrl = isUrl;
|
||||
}
|
||||
|
||||
getHomeserverUrl() {
|
||||
return this._hsUrl;
|
||||
}
|
||||
|
||||
getIdentityServerUrl() {
|
||||
return this._isUrl;
|
||||
}
|
||||
|
||||
setHomeserverUrl(hsUrl) {
|
||||
this._hsUrl = hsUrl;
|
||||
}
|
||||
|
||||
setIdentityServerUrl(isUrl) {
|
||||
this._isUrl = isUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registration logic class
|
||||
*/
|
||||
class Register extends Signup {
|
||||
constructor(hsUrl, isUrl) {
|
||||
super(hsUrl, isUrl);
|
||||
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;
|
||||
}
|
||||
|
||||
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"
|
||||
});
|
||||
}
|
||||
|
||||
register(formVals) {
|
||||
var {username, password, email} = formVals;
|
||||
this.email = email;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
|
||||
// feels a bit wrong to be clobbering the global client for something we
|
||||
// don't even know if it'll work, but we'll leave this here for now to
|
||||
// not complicate matters further. It would be nicer to isolate this
|
||||
// logic entirely from the rest of the app though.
|
||||
MatrixClientPeg.replaceUsingUrls(
|
||||
this._hsUrl,
|
||||
this._isUrl
|
||||
);
|
||||
return this._tryRegister();
|
||||
}
|
||||
|
||||
_tryRegister(authDict) {
|
||||
var self = this;
|
||||
return MatrixClientPeg.get().register(
|
||||
this.username, this.password, this.params.sessionId, authDict
|
||||
).then(function(result) {
|
||||
self.credentials = result;
|
||||
self.setStep("COMPLETE");
|
||||
return result; // contains the credentials
|
||||
}, function(error) {
|
||||
if (error.httpStatus === 401 && error.data && error.data.flows) {
|
||||
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);
|
||||
return self.startStage(flowStage);
|
||||
}
|
||||
else {
|
||||
throw new Error("Unable to register - missing email address?");
|
||||
}
|
||||
} else {
|
||||
if (error.errcode === 'M_USER_IN_USE') {
|
||||
throw new Error("Username in use");
|
||||
} else if (error.httpStatus == 401) {
|
||||
throw new Error("Authorisation failed!");
|
||||
} else if (error.httpStatus >= 400 && error.httpStatus < 500) {
|
||||
throw new Error(`Registration failed! (${error.httpStatus})`);
|
||||
} 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(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(MatrixClientPeg.get(), 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(request.auth);
|
||||
}
|
||||
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() {
|
||||
// feels a bit wrong to be clobbering the global client for something we
|
||||
// don't even know if it'll work, but we'll leave this here for now to
|
||||
// not complicate matters further. It would be nicer to isolate this
|
||||
// logic entirely from the rest of the app though.
|
||||
MatrixClientPeg.replaceUsingUrls(
|
||||
this._hsUrl,
|
||||
this._isUrl
|
||||
);
|
||||
// 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) {
|
||||
this.registrationPromise = this.startStage(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) {
|
||||
super(hsUrl, isUrl);
|
||||
this._currentFlowIndex = 0;
|
||||
this._flows = [];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
"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.defer = q.defer(); // resolved with the captcha response
|
||||
this.publicKey = null; // from the HS
|
||||
this.divId = null; // from the UI component
|
||||
}
|
||||
|
||||
// called when the UI component has loaded the recaptcha <div> so we can
|
||||
// render to it.
|
||||
onReceiveData(data) {
|
||||
if (!data || !data.divId) {
|
||||
return;
|
||||
}
|
||||
this.divId = data.divId;
|
||||
this._attemptRender();
|
||||
}
|
||||
|
||||
complete() {
|
||||
var publicKey;
|
||||
var serverParams = this.signupInstance.getServerData().params;
|
||||
if (serverParams && serverParams["m.login.recaptcha"]) {
|
||||
publicKey = serverParams["m.login.recaptcha"].public_key;
|
||||
}
|
||||
if (!publicKey) {
|
||||
return q.reject({
|
||||
message: "This server has not supplied enough information for Recaptcha " +
|
||||
"authentication",
|
||||
isFatal: true
|
||||
});
|
||||
}
|
||||
this.publicKey = publicKey;
|
||||
this._attemptRender();
|
||||
return this.defer.promise;
|
||||
}
|
||||
|
||||
_attemptRender() {
|
||||
if (!global.grecaptcha) {
|
||||
console.error("grecaptcha not loaded!");
|
||||
return;
|
||||
}
|
||||
if (!this.publicKey) {
|
||||
console.error("No public key for recaptcha!");
|
||||
return;
|
||||
}
|
||||
if (!this.divId) {
|
||||
console.error("No div ID specified!");
|
||||
return;
|
||||
}
|
||||
console.log("Rendering to %s", this.divId);
|
||||
var self = this;
|
||||
global.grecaptcha.render(this.divId, {
|
||||
sitekey: this.publicKey,
|
||||
callback: function(response) {
|
||||
console.log("Received captcha response");
|
||||
self.defer.resolve({
|
||||
auth: {
|
||||
type: 'm.login.recaptcha',
|
||||
response: response
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
return q({
|
||||
auth: {
|
||||
type: 'm.login.email.identity',
|
||||
threepid_creds: {
|
||||
sid: this.signupInstance.params.idSid,
|
||||
client_secret: this.signupInstance.params.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();
|
||||
}
|
||||
|
||||
var clientSecret = this.client.generateClientSecret();
|
||||
var nextLink = this.signupInstance.params.registrationUrl +
|
||||
'?client_secret=' +
|
||||
encodeURIComponent(clientSecret) +
|
||||
"&hs_url=" +
|
||||
encodeURIComponent(this.signupInstance.getHomeserverUrl()) +
|
||||
"&is_url=" +
|
||||
encodeURIComponent(this.signupInstance.getIdentityServerUrl()) +
|
||||
"&session_id=" +
|
||||
encodeURIComponent(this.signupInstance.getServerData().session);
|
||||
|
||||
return this.client.requestEmailToken(
|
||||
this.signupInstance.email,
|
||||
clientSecret,
|
||||
1, // TODO: Multiple send attempts?
|
||||
nextLink
|
||||
).then(function(response) {
|
||||
return {}; // don't want to make a request
|
||||
}, function(error) {
|
||||
console.error(error);
|
||||
var e = {
|
||||
isFatal: true
|
||||
};
|
||||
if (error.errcode == 'THREEPID_IN_USE') {
|
||||
e.message = "Email in use";
|
||||
} else {
|
||||
e.message = 'Unable to contact the given identity server';
|
||||
}
|
||||
return e;
|
||||
});
|
||||
}
|
||||
}
|
||||
EmailIdentityStage.TYPE = "m.login.email.identity";
|
||||
|
||||
module.exports = {
|
||||
[DummyStage.TYPE]: DummyStage,
|
||||
[RecaptchaStage.TYPE]: RecaptchaStage,
|
||||
[EmailIdentityStage.TYPE]: EmailIdentityStage
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var DIV_ID = 'mx_recaptcha';
|
||||
|
||||
/**
|
||||
* A pure UI component which displays a captcha form.
|
||||
*/
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CaptchaForm',
|
||||
|
||||
propTypes: {
|
||||
onCaptchaLoaded: React.PropTypes.func.isRequired // called with div id name
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onCaptchaLoaded: function() {
|
||||
console.error("Unhandled onCaptchaLoaded");
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// Just putting a script tag into the returned jsx doesn't work, annoyingly,
|
||||
// so we do this instead.
|
||||
var self = this;
|
||||
if (this.refs.recaptchaContainer) {
|
||||
console.log("Loading recaptcha script...");
|
||||
var scriptTag = document.createElement('script');
|
||||
window.mx_on_recaptcha_loaded = function() {
|
||||
console.log("Loaded recaptcha script.");
|
||||
self.props.onCaptchaLoaded(DIV_ID);
|
||||
};
|
||||
scriptTag.setAttribute(
|
||||
'src', global.location.protocol+"//www.google.com/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit"
|
||||
);
|
||||
this.refs.recaptchaContainer.appendChild(scriptTag);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// FIXME: Tight coupling with the div id and SignupStages.js
|
||||
return (
|
||||
<div ref="recaptchaContainer">
|
||||
This Home Server would like to make sure you are not a robot
|
||||
<div id={DIV_ID}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,352 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var dis = require("../../dispatcher");
|
||||
|
||||
module.exports = {
|
||||
FieldErrors: {
|
||||
PasswordMismatch: 'PasswordMismatch',
|
||||
TooShort: 'TooShort',
|
||||
Missing: 'Missing',
|
||||
InUse: 'InUse',
|
||||
Length: 'Length'
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
step: 'initial',
|
||||
busy: false,
|
||||
currentStep: 0,
|
||||
totalSteps: 1
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.savedParams = {
|
||||
email: '',
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
};
|
||||
this.readNewProps();
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function() {
|
||||
this.readNewProps();
|
||||
},
|
||||
|
||||
readNewProps: function() {
|
||||
if (this.props.clientSecret && this.props.hsUrl &&
|
||||
this.props.isUrl && this.props.sessionId &&
|
||||
this.props.idSid) {
|
||||
this.authSessionId = this.props.sessionId;
|
||||
MatrixClientPeg.replaceUsingUrls(
|
||||
this.props.hsUrl,
|
||||
this.props.isUrl
|
||||
);
|
||||
this.setState({
|
||||
hs_url: this.props.hsUrl,
|
||||
is_url: this.props.isUrl
|
||||
});
|
||||
this.savedParams = {client_secret: this.props.clientSecret};
|
||||
this.setState({busy: true});
|
||||
|
||||
var isLocation = document.createElement('a');
|
||||
isLocation.href = this.props.isUrl;
|
||||
|
||||
var auth = {
|
||||
type: 'm.login.email.identity',
|
||||
threepid_creds: {
|
||||
sid: this.props.idSid,
|
||||
client_secret: this.savedParams.client_secret,
|
||||
id_server: isLocation.host
|
||||
}
|
||||
};
|
||||
this.tryRegister(auth);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
// Just putting a script tag into the returned jsx doesn't work, annoyingly,
|
||||
// so we do this instead.
|
||||
if (this.refs.recaptchaContainer) {
|
||||
var scriptTag = document.createElement('script');
|
||||
window.mx_on_recaptcha_loaded = this.onCaptchaLoaded;
|
||||
scriptTag.setAttribute('src', global.location.protocol+"//www.google.com/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit");
|
||||
this.refs.recaptchaContainer.appendChild(scriptTag);
|
||||
}
|
||||
},
|
||||
|
||||
setStep: function(step) {
|
||||
this.setState({ step: step, errorText: '', busy: false });
|
||||
},
|
||||
|
||||
getSupportedStageTypes: function() {
|
||||
return ['m.login.email.identity', 'm.login.recaptcha'];
|
||||
},
|
||||
|
||||
chooseFlow: function(flows) {
|
||||
// this is fairly simple right now
|
||||
var supportedTypes = this.getSupportedStageTypes();
|
||||
|
||||
var emailFlow = null;
|
||||
var otherFlow = null;
|
||||
for (var flowI = 0; flowI < flows.length; ++flowI) {
|
||||
var flow = flows[flowI];
|
||||
var flowHasEmail = false;
|
||||
var flowSupported = true;
|
||||
for (var stageI = 0; stageI < flow.stages.length; ++stageI) {
|
||||
var stage = flow.stages[stageI];
|
||||
|
||||
if (supportedTypes.indexOf(stage) == -1) {
|
||||
flowSupported = false;
|
||||
}
|
||||
|
||||
if (stage == 'm.login.email.identity') {
|
||||
flowHasEmail = true;
|
||||
}
|
||||
}
|
||||
if (flowSupported) {
|
||||
if (flowHasEmail) {
|
||||
emailFlow = flow;
|
||||
} else {
|
||||
otherFlow = flow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.savedParams.email != '' ||
|
||||
this.completedStages.indexOf('m.login.email.identity') > -1
|
||||
) {
|
||||
return emailFlow;
|
||||
} else {
|
||||
return otherFlow;
|
||||
}
|
||||
},
|
||||
|
||||
firstUncompletedStageIndex: function(flow) {
|
||||
if (this.completedStages === undefined) return 0;
|
||||
for (var i = 0; i < flow.stages.length; ++i) {
|
||||
if (this.completedStages.indexOf(flow.stages[i]) == -1) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
numCompletedStages: function(flow) {
|
||||
if (this.completedStages === undefined) return 0;
|
||||
var nCompleted = 0;
|
||||
for (var i = 0; i < flow.stages.length; ++i) {
|
||||
if (this.completedStages.indexOf(flow.stages[i]) > -1) {
|
||||
++nCompleted;
|
||||
}
|
||||
}
|
||||
return nCompleted;
|
||||
},
|
||||
|
||||
onInitialStageSubmit: function(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
var formVals = this.getRegFormVals();
|
||||
this.savedParams = formVals;
|
||||
|
||||
var badFields = {};
|
||||
if (formVals.password != formVals.confirmPassword) {
|
||||
badFields.confirmPassword = this.FieldErrors.PasswordMismatch;
|
||||
}
|
||||
if (formVals.password == '') {
|
||||
badFields.password = this.FieldErrors.Missing;
|
||||
} else if (formVals.password.length < 6) {
|
||||
badFields.password = this.FieldErrors.Length;
|
||||
}
|
||||
if (formVals.username == '') {
|
||||
badFields.username = this.FieldErrors.Missing;
|
||||
}
|
||||
if (formVals.email == '') {
|
||||
badFields.email = this.FieldErrors.Missing;
|
||||
}
|
||||
if (Object.keys(badFields).length > 0) {
|
||||
this.onBadFields(badFields);
|
||||
return;
|
||||
}
|
||||
|
||||
MatrixClientPeg.replaceUsingUrls(
|
||||
this.getHsUrl(),
|
||||
this.getIsUrl()
|
||||
);
|
||||
this.setState({
|
||||
hs_url: this.getHsUrl(),
|
||||
is_url: this.getIsUrl()
|
||||
});
|
||||
this.setState({busy: true});
|
||||
|
||||
this.tryRegister();
|
||||
},
|
||||
|
||||
startStage: function(stageName) {
|
||||
var self = this;
|
||||
this.setStep('stage_'+stageName);
|
||||
switch(stageName) {
|
||||
case 'm.login.email.identity':
|
||||
self.setState({
|
||||
busy: true
|
||||
});
|
||||
var cli = MatrixClientPeg.get();
|
||||
this.savedParams.client_secret = cli.generateClientSecret();
|
||||
this.savedParams.send_attempt = 1;
|
||||
|
||||
var nextLink = this.props.registrationUrl +
|
||||
'?client_secret=' +
|
||||
encodeURIComponent(this.savedParams.client_secret) +
|
||||
"&hs_url=" +
|
||||
encodeURIComponent(this.state.hs_url) +
|
||||
"&is_url=" +
|
||||
encodeURIComponent(this.state.is_url) +
|
||||
"&session_id=" +
|
||||
encodeURIComponent(this.authSessionId);
|
||||
|
||||
cli.requestEmailToken(
|
||||
this.savedParams.email,
|
||||
this.savedParams.client_secret,
|
||||
this.savedParams.send_attempt,
|
||||
nextLink
|
||||
).done(function(response) {
|
||||
self.setState({
|
||||
busy: false,
|
||||
});
|
||||
self.setStep('stage_m.login.email.identity');
|
||||
}, function(error) {
|
||||
console.error(error);
|
||||
self.setStep('initial');
|
||||
var newState = {busy: false};
|
||||
if (error.errcode == 'THREEPID_IN_USE') {
|
||||
self.onBadFields({email: self.FieldErrors.InUse});
|
||||
} else {
|
||||
newState.errorText = 'Unable to contact the given identity server';
|
||||
}
|
||||
self.setState(newState);
|
||||
});
|
||||
break;
|
||||
case 'm.login.recaptcha':
|
||||
if (!this.authParams || !this.authParams['m.login.recaptcha'].public_key) {
|
||||
this.setState({
|
||||
errorText: "This server has not supplied enough information for Recaptcha authentication"
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onRegistered: function(user_id, access_token) {
|
||||
MatrixClientPeg.replaceUsingAccessToken(
|
||||
this.state.hs_url, this.state.is_url, user_id, access_token
|
||||
);
|
||||
if (this.props.onLoggedIn) {
|
||||
this.props.onLoggedIn();
|
||||
}
|
||||
},
|
||||
|
||||
onCaptchaLoaded: function() {
|
||||
if (this.refs.recaptchaContainer) {
|
||||
var sitekey = this.authParams['m.login.recaptcha'].public_key;
|
||||
global.grecaptcha.render('mx_recaptcha', {
|
||||
'sitekey': sitekey,
|
||||
'callback': this.onCaptchaDone
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onCaptchaDone: function(captcha_response) {
|
||||
this.tryRegister({
|
||||
type: 'm.login.recaptcha',
|
||||
response: captcha_response
|
||||
});
|
||||
},
|
||||
|
||||
tryRegister: function(auth) {
|
||||
var self = this;
|
||||
MatrixClientPeg.get().register(
|
||||
this.savedParams.username,
|
||||
this.savedParams.password,
|
||||
this.authSessionId,
|
||||
auth
|
||||
).done(function(result) {
|
||||
self.onRegistered(result.user_id, result.access_token);
|
||||
}, function(error) {
|
||||
if (error.httpStatus == 401 && error.data.flows) {
|
||||
self.authParams = error.data.params;
|
||||
self.authSessionId = error.data.session;
|
||||
|
||||
self.completedStages = error.data.completed || [];
|
||||
|
||||
var flow = self.chooseFlow(error.data.flows);
|
||||
|
||||
if (flow) {
|
||||
var flowStage = self.firstUncompletedStageIndex(flow);
|
||||
var numDone = self.numCompletedStages(flow);
|
||||
|
||||
self.setState({
|
||||
busy: false,
|
||||
flows: flow,
|
||||
currentStep: 1+numDone,
|
||||
totalSteps: flow.stages.length+1,
|
||||
flowStage: flowStage
|
||||
});
|
||||
self.startStage(flow.stages[flowStage]);
|
||||
}
|
||||
else {
|
||||
self.setState({
|
||||
busy: false,
|
||||
errorText: "Unable to register - missing email address?"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(error);
|
||||
self.setStep("initial");
|
||||
var newState = {
|
||||
busy: false,
|
||||
errorText: "Unable to contact the given Home Server"
|
||||
};
|
||||
if (error.name == 'M_USER_IN_USE') {
|
||||
delete newState.errorText;
|
||||
self.onBadFields({
|
||||
username: self.FieldErrors.InUse
|
||||
});
|
||||
} else if (error.httpStatus == 401) {
|
||||
newState.errorText = "Authorisation failed!";
|
||||
} else if (error.httpStatus >= 400 && error.httpStatus < 500) {
|
||||
newState.errorText = "Registration failed!";
|
||||
} else if (error.httpStatus >= 500 && error.httpStatus < 600) {
|
||||
newState.errorText = "Server error during registration!";
|
||||
} 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.
|
||||
newState.errorText = "This home server does not support resuming registration.";
|
||||
}
|
||||
self.setState(newState);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showLogin: function(ev) {
|
||||
ev.preventDefault();
|
||||
dis.dispatch({
|
||||
action: 'start_login'
|
||||
});
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue