Get dummy registrations working
This means you can now register on localhost without needing an email. Email and Recaptcha are still broken.
This commit is contained in:
parent
1fca3f6606
commit
991a96cfc5
2 changed files with 314 additions and 3 deletions
192
src/Signup.js
192
src/Signup.js
|
@ -1,8 +1,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
|
var SignupStages = require("./SignupStages");
|
||||||
var dis = require("./dispatcher");
|
var dis = require("./dispatcher");
|
||||||
var q = require("q");
|
var q = require("q");
|
||||||
|
|
||||||
|
const EMAIL_STAGE_TYPE = "m.login.email.identity";
|
||||||
|
|
||||||
class Signup {
|
class Signup {
|
||||||
constructor(hsUrl, isUrl) {
|
constructor(hsUrl, isUrl) {
|
||||||
this._hsUrl = hsUrl;
|
this._hsUrl = hsUrl;
|
||||||
|
@ -26,17 +29,200 @@ class Signup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Register extends Signup {
|
class Register extends Signup {
|
||||||
constructor(hsUrl, isUrl) {
|
constructor(hsUrl, isUrl) {
|
||||||
super(hsUrl, isUrl);
|
super(hsUrl, isUrl);
|
||||||
this._state = "Register.START";
|
this.setStep("START");
|
||||||
|
this.data = null; // from the server
|
||||||
|
this.username = null; // desired
|
||||||
|
this.email = null; // desired
|
||||||
|
this.password = null; // desired
|
||||||
|
this.params = {}; // random other stuff (e.g. query params)
|
||||||
|
this.credentials = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getState() {
|
setClientSecret(secret) {
|
||||||
return this._state;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
console.log("_tryRegister %s", JSON.stringify(authDict));
|
||||||
|
var self = this;
|
||||||
|
return MatrixClientPeg.get().register(
|
||||||
|
this.username, this.password, this._sessionId, authDict
|
||||||
|
).then(function(result) {
|
||||||
|
console.log("Got a final response");
|
||||||
|
self.credentials = result;
|
||||||
|
self.setStep("COMPLETE");
|
||||||
|
return result; // contains the credentials
|
||||||
|
}, function(error) {
|
||||||
|
console.error(error);
|
||||||
|
if (error.httpStatus === 401 && error.data && error.data.flows) {
|
||||||
|
self.data = error.data || {};
|
||||||
|
var flow = self.chooseFlow(error.data.flows);
|
||||||
|
|
||||||
|
if (flow) {
|
||||||
|
var flowStage = self.firstUncompletedStageIndex(flow);
|
||||||
|
return self.startStage(flow.stages[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."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUncompletedStageIndex(flow) {
|
||||||
|
if (!this.completedStages) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < flow.stages.length; ++i) {
|
||||||
|
if (this.completedStages.indexOf(flow.stages[i]) == -1) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
numCompletedStages(flow) {
|
||||||
|
if (!this.completedStages) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return stage.complete().then(function(request) {
|
||||||
|
if (request.auth) {
|
||||||
|
return self._tryRegister(request.auth);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCompletedStage(stageType) {
|
||||||
|
var completed = (this.data || {}).completed || [];
|
||||||
|
return completed.indexOf(stageType) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Login extends Signup {
|
class Login extends Signup {
|
||||||
constructor(hsUrl, isUrl) {
|
constructor(hsUrl, isUrl) {
|
||||||
super(hsUrl, isUrl);
|
super(hsUrl, isUrl);
|
||||||
|
|
125
src/SignupStages.js
Normal file
125
src/SignupStages.js
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
"use strict";
|
||||||
|
var q = require("q");
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Stage.TYPE = "NOT IMPLEMENTED";
|
||||||
|
|
||||||
|
|
||||||
|
class DummyStage extends Stage {
|
||||||
|
constructor(matrixClient, signupInstance) {
|
||||||
|
super(DummyStage.TYPE, matrixClient, signupInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
complete() {
|
||||||
|
return q({
|
||||||
|
auth: {
|
||||||
|
type: DummyStage.TYPE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DummyStage.TYPE = "m.login.dummy";
|
||||||
|
|
||||||
|
|
||||||
|
class RecaptchaStage extends Stage {
|
||||||
|
constructor(matrixClient, signupInstance) {
|
||||||
|
super(RecaptchaStage.TYPE, matrixClient, signupInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
complete() {
|
||||||
|
var publicKey;
|
||||||
|
if (this.signupInstance.params['m.login.recaptcha']) {
|
||||||
|
publicKey = this.signupInstance.params['m.login.recaptcha'].public_key;
|
||||||
|
}
|
||||||
|
if (!publicKey) {
|
||||||
|
return q.reject({
|
||||||
|
message: "This server has not supplied enough information for Recaptcha " +
|
||||||
|
"authentication",
|
||||||
|
isFatal: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var defer = q.defer();
|
||||||
|
global.grecaptcha.render('mx_recaptcha', {
|
||||||
|
sitekey: publicKey,
|
||||||
|
callback: function(response) {
|
||||||
|
return defer.resolve({
|
||||||
|
auth: {
|
||||||
|
type: 'm.login.recaptcha',
|
||||||
|
response: response
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return defer.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RecaptchaStage.TYPE = "m.login.recaptcha";
|
||||||
|
|
||||||
|
|
||||||
|
class EmailIdentityStage extends Stage {
|
||||||
|
constructor(matrixClient, signupInstance) {
|
||||||
|
super(EmailIdentityStage.TYPE, matrixClient, signupInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
complete() {
|
||||||
|
var config = {
|
||||||
|
clientSecret: this.client.generateClientSecret(),
|
||||||
|
sendAttempt: 1
|
||||||
|
};
|
||||||
|
this.signupInstance.params[EmailIdentityStage.TYPE] = config;
|
||||||
|
|
||||||
|
var nextLink = this.signupInstance.params.registrationUrl +
|
||||||
|
'?client_secret=' +
|
||||||
|
encodeURIComponent(config.clientSecret) +
|
||||||
|
"&hs_url=" +
|
||||||
|
encodeURIComponent(this.signupInstance.getHomeserverUrl()) +
|
||||||
|
"&is_url=" +
|
||||||
|
encodeURIComponent(this.signupInstance.getIdentityServerUrl()) +
|
||||||
|
"&session_id=" +
|
||||||
|
encodeURIComponent(this.signupInstance.getSessionId());
|
||||||
|
|
||||||
|
return this.client.requestEmailToken(
|
||||||
|
this.signupInstance.email,
|
||||||
|
config.clientSecret,
|
||||||
|
config.sendAttempt,
|
||||||
|
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
|
||||||
|
};
|
Loading…
Reference in a new issue