summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--synapse/handlers/register.py62
-rw-r--r--synapse/http/client.py40
-rw-r--r--synapse/rest/register.py7
-rw-r--r--webclient/app-controller.js2
-rw-r--r--webclient/components/matrix/matrix-service.js14
-rw-r--r--webclient/login/login-controller.js33
-rw-r--r--webclient/login/register-controller.js55
-rw-r--r--webclient/login/register.html36
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>