diff --git a/synapse/handlers/__init__.py b/synapse/handlers/__init__.py
index 8d345bf936..336ce15701 100644
--- a/synapse/handlers/__init__.py
+++ b/synapse/handlers/__init__.py
@@ -29,6 +29,7 @@ from .typing import TypingNotificationHandler
from .admin import AdminHandler
from .appservice import ApplicationServicesHandler
from .sync import SyncHandler
+from .auth import AuthHandler
class Handlers(object):
@@ -58,3 +59,4 @@ class Handlers(object):
hs, ApplicationServiceApi(hs)
)
self.sync_handler = SyncHandler(hs)
+ self.auth_handler = AuthHandler(hs)
diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py
new file mode 100644
index 0000000000..e4a73da9a7
--- /dev/null
+++ b/synapse/handlers/auth.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014, 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.
+
+from twisted.internet import defer
+
+from ._base import BaseHandler
+from synapse.api.constants import LoginType
+from synapse.types import UserID
+from synapse.api.errors import LoginError, Codes
+
+import logging
+import bcrypt
+
+
+logger = logging.getLogger(__name__)
+
+
+class AuthHandler(BaseHandler):
+
+ def __init__(self, hs):
+ super(AuthHandler, self).__init__(hs)
+
+ @defer.inlineCallbacks
+ def check_auth(self, flows, clientdict):
+ """
+ Takes a dictionary sent by the client in the login / registration
+ protocol and handles the login flow.
+
+ Args:
+ flows: list of list of stages
+ authdict: The dictionary from the client root level, not the
+ 'auth' key: this method prompts for auth if none is sent.
+ Returns:
+ A tuple of authed, dict where authed is true if the client
+ has successfully completed an auth flow. If it is true, the dict
+ contains the authenticated credentials of each stage.
+ If authed is false, the dictionary is the server response to the
+ login request and should be passed back to the client.
+ """
+ types = {
+ LoginType.PASSWORD: self.check_password_auth
+ }
+
+ if 'auth' not in clientdict:
+ defer.returnValue((False, auth_dict_for_flows(flows)))
+
+ authdict = clientdict['auth']
+
+ # In future: support sessions & retrieve previously succeeded
+ # login types
+ creds = {}
+
+ # check auth type currently being presented
+ if 'type' not in authdict:
+ raise LoginError(400, "", Codes.MISSING_PARAM)
+ if authdict['type'] not in types:
+ raise LoginError(400, "", Codes.UNRECOGNIZED)
+ result = yield types[authdict['type']](authdict)
+ if result:
+ creds[authdict['type']] = result
+
+ for f in flows:
+ if len(set(f) - set(creds.keys())) == 0:
+ logger.info("Auth completed with creds: %r", creds)
+ defer.returnValue((True, creds))
+
+ ret = auth_dict_for_flows(flows)
+ ret['completed'] = creds.keys()
+ defer.returnValue((False, ret))
+
+ @defer.inlineCallbacks
+ def check_password_auth(self, authdict):
+ if "user" not in authdict or "password" not in authdict:
+ raise LoginError(400, "", Codes.MISSING_PARAM)
+
+ user = authdict["user"]
+ password = authdict["password"]
+ if not user.startswith('@'):
+ user = UserID.create(user, self.hs.hostname).to_string()
+
+ user_info = yield self.store.get_user_by_id(user_id=user)
+ if not user_info:
+ logger.warn("Attempted to login as %s but they do not exist", user)
+ raise LoginError(403, "", errcode=Codes.FORBIDDEN)
+
+ stored_hash = user_info[0]["password_hash"]
+ if bcrypt.checkpw(password, stored_hash):
+ defer.returnValue(user)
+ else:
+ logger.warn("Failed password login for user %s", user)
+ raise LoginError(403, "", errcode=Codes.FORBIDDEN)
+
+
+def auth_dict_for_flows(flows):
+ return {
+ "flows": {"stages": f for f in flows}
+ }
diff --git a/synapse/handlers/login.py b/synapse/handlers/login.py
index 7447800460..19b560d91e 100644
--- a/synapse/handlers/login.py
+++ b/synapse/handlers/login.py
@@ -69,48 +69,9 @@ class LoginHandler(BaseHandler):
raise LoginError(403, "", errcode=Codes.FORBIDDEN)
@defer.inlineCallbacks
- def reset_password(self, user_id, email):
- is_valid = yield self._check_valid_association(user_id, email)
- logger.info("reset_password user=%s email=%s valid=%s", user_id, email,
- is_valid)
- if is_valid:
- try:
- # send an email out
- emailutils.send_email(
- smtp_server=self.hs.config.email_smtp_server,
- from_addr=self.hs.config.email_from_address,
- to_addr=email,
- subject="Password Reset",
- body="TODO."
- )
- except EmailException as e:
- logger.exception(e)
+ def set_password(self, user_id, newpassword, token_id=None):
+ password_hash = bcrypt.hashpw(newpassword, bcrypt.gensalt())
- @defer.inlineCallbacks
- def _check_valid_association(self, user_id, email):
- identity = yield self._query_email(email)
- if identity and "mxid" in identity:
- if identity["mxid"] == user_id:
- defer.returnValue(True)
- return
- defer.returnValue(False)
-
- @defer.inlineCallbacks
- def _query_email(self, email):
- http_client = SimpleHttpClient(self.hs)
- try:
- data = yield http_client.get_json(
- # TODO FIXME This should be configurable.
- # XXX: ID servers need to use HTTPS
- "http://%s%s" % (
- "matrix.org:8090", "/_matrix/identity/api/v1/lookup"
- ),
- {
- 'medium': 'email',
- 'address': email
- }
- )
- defer.returnValue(data)
- except CodeMessageException as e:
- data = json.loads(e.msg)
- defer.returnValue(data)
+ yield self.store.user_set_password_hash(user_id, password_hash)
+ yield self.store.user_delete_access_tokens_apart_from(user_id, token_id)
+ yield self.store.flush_user(user_id)
|