Get dummy registrations working
This means you can now register on localhost without needing an email. Email and Recaptcha are still broken.pull/21833/head
							parent
							
								
									1fca3f6606
								
							
						
					
					
						commit
						991a96cfc5
					
				
							
								
								
									
										192
									
								
								src/Signup.js
								
								
								
								
							
							
						
						
									
										192
									
								
								src/Signup.js
								
								
								
								
							| 
						 | 
				
			
			@ -1,8 +1,11 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
var MatrixClientPeg = require("./MatrixClientPeg");
 | 
			
		||||
var SignupStages = require("./SignupStages");
 | 
			
		||||
var dis = require("./dispatcher");
 | 
			
		||||
var q = require("q");
 | 
			
		||||
 | 
			
		||||
const EMAIL_STAGE_TYPE = "m.login.email.identity";
 | 
			
		||||
 | 
			
		||||
class Signup {
 | 
			
		||||
    constructor(hsUrl, isUrl) {
 | 
			
		||||
        this._hsUrl = hsUrl;
 | 
			
		||||
| 
						 | 
				
			
			@ -26,17 +29,200 @@ class Signup {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Register extends Signup {
 | 
			
		||||
    constructor(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() {
 | 
			
		||||
        return this._state;
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 {
 | 
			
		||||
    constructor(hsUrl, isUrl) {
 | 
			
		||||
        super(hsUrl, isUrl);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 New Issue