diff options
-rw-r--r-- | synapse/handlers/register.py | 62 | ||||
-rw-r--r-- | synapse/http/client.py | 40 | ||||
-rw-r--r-- | synapse/rest/register.py | 7 | ||||
-rw-r--r-- | webclient/app-controller.js | 2 | ||||
-rw-r--r-- | webclient/components/matrix/matrix-service.js | 14 | ||||
-rw-r--r-- | webclient/login/login-controller.js | 33 | ||||
-rw-r--r-- | webclient/login/register-controller.js | 55 | ||||
-rw-r--r-- | webclient/login/register.html | 36 |
8 files changed, 213 insertions, 36 deletions
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py index 432bbcdcb5..bee052274f 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -20,9 +20,13 @@ from synapse.types import UserID from synapse.api.errors import SynapseError, RegistrationError from ._base import BaseHandler import synapse.util.stringutils as stringutils +from synapse.http.client import PlainHttpClient import base64 import bcrypt +import logging + +logger = logging.getLogger(__name__) class RegistrationHandler(BaseHandler): @@ -34,7 +38,7 @@ class RegistrationHandler(BaseHandler): self.distributor.declare("registered_user") @defer.inlineCallbacks - def register(self, localpart=None, password=None): + def register(self, localpart=None, password=None, threepidCreds=None): """Registers a new client on the server. Args: @@ -47,6 +51,20 @@ class RegistrationHandler(BaseHandler): Raises: RegistrationError if there was a problem registering. """ + + if threepidCreds: + for c in threepidCreds: + logger.info("validating theeepidcred sid %s on id server %s", c['sid'], c['idServer']) + try: + threepid = yield self._threepid_from_creds(c) + except: + logger.err() + raise RegistrationError(400, "Couldn't validate 3pid") + + if not threepid: + raise RegistrationError(400, "Couldn't validate 3pid") + logger.info("got threepid medium %s address %s", threepid['medium'], threepid['address']) + password_hash = None if password: password_hash = bcrypt.hashpw(password, bcrypt.gensalt()) @@ -61,7 +79,6 @@ class RegistrationHandler(BaseHandler): password_hash=password_hash) self.distributor.fire("registered_user", user) - defer.returnValue((user_id, token)) else: # autogen a random user ID attempts = 0 @@ -80,7 +97,6 @@ class RegistrationHandler(BaseHandler): password_hash=password_hash) self.distributor.fire("registered_user", user) - defer.returnValue((user_id, token)) except SynapseError: # if user id is taken, just generate another user_id = None @@ -90,6 +106,15 @@ class RegistrationHandler(BaseHandler): raise RegistrationError( 500, "Cannot generate user ID.") + # Now we have a matrix ID, bind it to the threepids we were given + if threepidCreds: + for c in threepidCreds: + # XXX: This should be a deferred list, shouldn't it? + yield self._bind_threepid(c, user_id) + + + defer.returnValue((user_id, token)) + def _generate_token(self, user_id): # urlsafe variant uses _ and - so use . as the separator and replace # all =s with .s so http clients don't quote =s when it is used as @@ -99,3 +124,34 @@ class RegistrationHandler(BaseHandler): def _generate_user_id(self): return "-" + stringutils.random_string(18) + + @defer.inlineCallbacks + def _threepid_from_creds(self, creds): + httpCli = PlainHttpClient(self.hs) + # XXX: make this configurable! + trustedIdServers = [ 'matrix.org:8090' ] + if not creds['idServer'] in trustedIdServers: + logger.warn('%s is not a trusted ID server: rejecting 3pid credentials', creds['idServer']) + defer.returnValue(None) + data = yield httpCli.get_json( + creds['idServer'], + "/_matrix/identity/api/v1/3pid/getValidated3pid", + { 'sid': creds['sid'], 'clientSecret': creds['clientSecret'] } + ) + + if 'medium' in data: + defer.returnValue(data) + defer.returnValue(None) + + @defer.inlineCallbacks + def _bind_threepid(self, creds, mxid): + httpCli = PlainHttpClient(self.hs) + data = yield httpCli.post_urlencoded_get_json( + creds['idServer'], + "/_matrix/identity/api/v1/3pid/bind", + { 'sid': creds['sid'], 'clientSecret': creds['clientSecret'], 'mxid':mxid } + ) + defer.returnValue(data) + + + diff --git a/synapse/http/client.py b/synapse/http/client.py index 79e17de157..ebf1aa47c4 100644 --- a/synapse/http/client.py +++ b/synapse/http/client.py @@ -16,7 +16,7 @@ from twisted.internet import defer, reactor from twisted.internet.error import DNSLookupError -from twisted.web.client import _AgentBase, _URI, readBody +from twisted.web.client import _AgentBase, _URI, readBody, FileBodyProducer from twisted.web.http_headers import Headers from synapse.http.endpoint import matrix_endpoint @@ -26,6 +26,8 @@ from syutil.jsonutil import encode_canonical_json from synapse.api.errors import CodeMessageException, SynapseError +from StringIO import StringIO + import json import logging import urllib @@ -168,6 +170,26 @@ class TwistedHttpClient(HttpClient): defer.returnValue(json.loads(body)) @defer.inlineCallbacks + def post_urlencoded_get_json(self, destination, path, args={}): + if destination in _destination_mappings: + destination = _destination_mappings[destination] + + logger.debug("post_urlencoded_get_json args: %s", args) + query_bytes = urllib.urlencode(args, True) + + response = yield self._create_request( + destination.encode("ascii"), + "POST", + path.encode("ascii"), + producer=FileBodyProducer(StringIO(urllib.urlencode(args))), + headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]} + ) + + body = yield readBody(response) + + defer.returnValue(json.loads(body)) + + @defer.inlineCallbacks def _create_request(self, destination, method, path_bytes, param_bytes=b"", query_bytes=b"", producer=None, headers_dict={}, retry_on_dns_fail=True): @@ -191,10 +213,7 @@ class TwistedHttpClient(HttpClient): retries_left = 5 # TODO: setup and pass in an ssl_context to enable TLS - endpoint = matrix_endpoint( - reactor, destination, timeout=10, - ssl_context_factory=self.hs.tls_context_factory - ) + endpoint = self._getEndpoint(reactor, destination); while True: try: @@ -241,6 +260,17 @@ class TwistedHttpClient(HttpClient): defer.returnValue(response) + def _getEndpoint(self, reactor, destination): + return matrix_endpoint( + reactor, destination, timeout=10, + ssl_context_factory=self.hs.tls_context_factory + ) + + +class PlainHttpClient(TwistedHttpClient): + def _getEndpoint(self, reactor, destination): + return matrix_endpoint(reactor, destination, timeout=10) + def _print_ex(e): if hasattr(e, "reasons") and e.reasons: diff --git a/synapse/rest/register.py b/synapse/rest/register.py index 965d1c452f..b8de3b250d 100644 --- a/synapse/rest/register.py +++ b/synapse/rest/register.py @@ -47,10 +47,15 @@ class RegisterRestServlet(RestServlet): except KeyError: pass # user_id is optional + threepidCreds = None + if 'threepidCreds' in register_json: + threepidCreds = register_json['threepidCreds'] + handler = self.handlers.registration_handler (user_id, token) = yield handler.register( localpart=desired_user_id, - password=password) + password=password, + threepidCreds=threepidCreds) result = { "user_id": user_id, diff --git a/webclient/app-controller.js b/webclient/app-controller.js index 36b56aa032..ea48cbb011 100644 --- a/webclient/app-controller.js +++ b/webclient/app-controller.js @@ -85,7 +85,7 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even $scope.logout(); }); - $scope.updateHeader = function() { + $rootScope.updateHeader = function() { $scope.user_id = matrixService.config().user_id; }; diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 9ca4135f7f..7c6d4ae50f 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -84,13 +84,14 @@ angular.module('matrixService', []) prefix: prefixPath, // Register an user - register: function(user_name, password) { + register: function(user_name, password, threepidCreds) { // The REST path spec var path = "/register"; return doRequest("POST", path, undefined, { user_id: user_name, - password: password + password: password, + threepidCreds: threepidCreds }); }, @@ -338,9 +339,9 @@ angular.module('matrixService', []) return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); }, - authEmail: function(clientSecret, tokenId, code) { + authEmail: function(clientSecret, sid, code) { var path = "/_matrix/identity/api/v1/validate/email/submitToken"; - var data = "token="+code+"&sid="+tokenId+"&clientSecret="+clientSecret; + var data = "token="+code+"&sid="+sid+"&clientSecret="+clientSecret; var headers = {}; headers["Content-Type"] = "application/x-www-form-urlencoded"; return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); @@ -353,6 +354,11 @@ angular.module('matrixService', []) headers["Content-Type"] = "application/x-www-form-urlencoded"; return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); }, + + lookup3pid: function(medium, address) { + var path = "/_matrix/identity/api/v1/lookup?medium="+encodeURIComponent(medium)+"&address="+encodeURIComponent(address); + return doBaseRequest(config.identityServer, "GET", path, {}, undefined, {}); + }, uploadContent: function(file) { var path = "/_matrix/content"; diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index 8f8414af2a..5ef39a7122 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -15,8 +15,8 @@ */ angular.module('LoginController', ['matrixService']) -.controller('LoginController', ['$scope', '$location', 'matrixService', 'eventStreamService', - function($scope, $location, matrixService, eventStreamService) { +.controller('LoginController', ['$scope', '$rootScope', '$location', 'matrixService', 'eventStreamService', + function($scope, $rootScope, $location, matrixService, eventStreamService) { 'use strict'; @@ -51,10 +51,36 @@ angular.module('LoginController', ['matrixService']) matrixService.setConfig({ homeserver: $scope.account.homeserver, identityServer: $scope.account.identityServer, + }); + switch ($scope.login_type) { + case 'mxid': + $scope.login_with_mxid($scope.account.user_id, $scope.account.password); + break; + case 'email': + matrixService.lookup3pid('email', $scope.account.user_id).then( + function(response) { + if (response.data['address'] == undefined) { + $scope.login_error_msg = "Invalid email address / password"; + } else { + console.log("Got address "+response.data['mxid']+" for email "+$scope.account.user_id); + $scope.login_with_mxid(response.data['mxid'], $scope.account.password); + } + }, + function() { + $scope.login_error_msg = "Couldn't look up email address. Is your identity server set correctly?"; + } + ); + } + }; + + $scope.login_with_mxid = function(mxid, password) { + matrixService.setConfig({ + homeserver: $scope.account.homeserver, + identityServer: $scope.account.identityServer, user_id: $scope.account.user_id }); // try to login - matrixService.login($scope.account.user_id, $scope.account.password).then( + matrixService.login(mxid, password).then( function(response) { if ("access_token" in response.data) { $scope.feedback = "Login successful."; @@ -65,6 +91,7 @@ angular.module('LoginController', ['matrixService']) access_token: response.data.access_token }); matrixService.saveConfig(); + $rootScope.updateHeader(); eventStreamService.resume(); $location.url("home"); } diff --git a/webclient/login/register-controller.js b/webclient/login/register-controller.js index 42b14a3d40..b7584a7d33 100644 --- a/webclient/login/register-controller.js +++ b/webclient/login/register-controller.js @@ -15,8 +15,8 @@ */ angular.module('RegisterController', ['matrixService']) -.controller('RegisterController', ['$scope', '$location', 'matrixService', 'eventStreamService', - function($scope, $location, matrixService, eventStreamService) { +.controller('RegisterController', ['$scope', '$rootScope', '$location', 'matrixService', 'eventStreamService', + function($scope, $rootScope, $location, matrixService, eventStreamService) { 'use strict'; // FIXME: factor out duplication with login-controller.js @@ -30,6 +30,17 @@ angular.module('RegisterController', ['matrixService']) { hs_url += ":" + $location.port(); } + + var generateClientSecret = function() { + var ret = ""; + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (var i = 0; i < 32; i++) { + ret += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return ret; + }; $scope.account = { homeserver: hs_url, @@ -43,7 +54,6 @@ angular.module('RegisterController', ['matrixService']) }; $scope.register = function() { - // Set the urls matrixService.setConfig({ homeserver: $scope.account.homeserver, @@ -59,7 +69,25 @@ angular.module('RegisterController', ['matrixService']) return; } - matrixService.register($scope.account.desired_user_id, $scope.account.pwd1).then( + if ($scope.account.email) { + $scope.clientSecret = generateClientSecret(); + matrixService.linkEmail($scope.account.email, $scope.clientSecret, 1).then( + function(response) { + $scope.wait_3pid_code = true; + $scope.sid = response.data.sid; + $scope.feedback = ""; + }, + function(response) { + $scope.feedback = "Couldn't request verification email!"; + } + ); + } else { + registerWithMxidAndPassword($scope.account.desired_user_id, $scope.account.pwd1); + } + }; + + $scope.registerWithMxidAndPassword = function(mxid, password, threepidCreds) { + matrixService.register(mxid, password, threepidCreds).then( function(response) { $scope.feedback = "Success"; // Update the current config @@ -74,7 +102,7 @@ angular.module('RegisterController', ['matrixService']) matrixService.saveConfig(); // Update the global scoped used_id var (used in the app header) - $scope.updateHeader(); + $rootScope.updateHeader(); eventStreamService.resume(); @@ -87,15 +115,32 @@ angular.module('RegisterController', ['matrixService']) $location.url("home"); }, function(error) { + console.trace("Registration error: "+error); if (error.data) { if (error.data.errcode === "M_USER_IN_USE") { $scope.feedback = "Username already taken."; + $scope.reenter_username = true; } } else if (error.status === 0) { $scope.feedback = "Unable to talk to the server."; } }); + } + + $scope.verifyToken = function() { + matrixService.authEmail($scope.clientSecret, $scope.sid, $scope.account.threepidtoken).then( + function(response) { + if (!response.data.success) { + $scope.feedback = "Unable to verify code."; + } else { + $scope.registerWithMxidAndPassword($scope.account.desired_user_id, $scope.account.pwd1, [{'sid':$scope.sid, 'clientSecret':$scope.clientSecret, 'idServer': $scope.account.identityServer.split('//')[1]}]); + } + }, + function(error) { + $scope.feedback = "Unable to verify code."; + } + ); }; }]); diff --git a/webclient/login/register.html b/webclient/login/register.html index 81995f1ae0..06a6526b70 100644 --- a/webclient/login/register.html +++ b/webclient/login/register.html @@ -12,26 +12,34 @@ <div style="text-align: center"> <br/> - <input id="email" size="32" type="text" ng-focus="true" ng-model="account.email" placeholder="Email address (optional)" style="display: none"/> - <div class="smallPrint" style="display: none;">Specifying an email address lets other users find you on Matrix more easily,<br/> - and gives you a way to reset your password</div> - <input id="desired_user_id" size="32" type="text" ng-model="account.desired_user_id" placeholder="Matrix ID (e.g. bob)"/> - <br/> - <input id="pwd1" size="32" type="password" ng-model="account.pwd1" placeholder="Type a password"/> - <br/> - <input id="pwd2" size="32" type="password" ng-model="account.pwd2" placeholder="Confirm your password"/> - <br/> - <input id="displayName" size="32" type="text" ng-model="account.displayName" placeholder="Display name (e.g. Bob Obson)"/> - <br/> - <br/> + + <input ng-show="!wait_3pid_code" id="email" size="32" type="text" ng-focus="true" ng-model="account.email" placeholder="Email address (optional)"/> + <div ng-show="!wait_3pid_code" class="smallPrint">Specifying an email address lets other users find you on Matrix more easily,<br/> + and will give you a way to reset your password in the future</div> + <span ng-show="reenter_username">Choose another username:</span> + <input ng-show="!wait_3pid_code || reenter_username" id="desired_user_id" size="32" type="text" ng-model="account.desired_user_id" placeholder="Matrix ID (e.g. bob)"/> + <br ng-show="!wait_3pid_code" /> + <input ng-show="!wait_3pid_code" id="pwd1" size="32" type="password" ng-model="account.pwd1" placeholder="Type a password"/> + <br ng-show="!wait_3pid_code" /> + <input ng-show="!wait_3pid_code" id="pwd2" size="32" type="password" ng-model="account.pwd2" placeholder="Confirm your password"/> + <br ng-show="!wait_3pid_code" /> + <input ng-show="!wait_3pid_code" id="displayName" size="32" type="text" ng-model="account.displayName" placeholder="Display name (e.g. Bob Obson)"/> + <br ng-show="!wait_3pid_code" /> + <br ng-show="!wait_3pid_code" /> - <button ng-click="register()" ng-disabled="!account.desired_user_id || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Sign up</button> + <button ng-show="!wait_3pid_code" ng-click="register()" ng-disabled="!account.desired_user_id || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Sign up</button> + + <div ng-show="wait_3pid_code"> + <span>Please enter the verification code sent to {{ account.email }}</span><br /> + <input id="threepidtoken" size="32" type="text" ng-focus="true" ng-model="account.threepidtoken" placeholder="Verification Code"/><br /> + <button ng-click="verifyToken()" ng-disabled="!account.threepidtoken">Validate</button> + </div> <br/><br/> </div> <div class="feedback">{{ feedback }} {{ login_error_msg }}</div> - <div id="serverConfig"> + <div id="serverConfig" ng-show="!wait_3pid_code"> <label for="homeserver">Home Server:</label> <input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/> <div class="smallPrint">Your home server stores all your conversation and account data.</div> |