From ff5597d4bef8108df8a9136de4d2be3b1537c898 Mon Sep 17 00:00:00 2001 From: Simeon Keske Date: Thu, 4 Jun 2020 15:43:39 +0200 Subject: [PATCH] add auth to connect form --- matrix_synapse_saml_mozilla/res/index.html | 11 +- matrix_synapse_saml_mozilla/res/script.js | 195 +++++++++++++----- .../username_picker.py | 82 +++++++- 3 files changed, 219 insertions(+), 69 deletions(-) diff --git a/matrix_synapse_saml_mozilla/res/index.html b/matrix_synapse_saml_mozilla/res/index.html index 0077cb8..392d013 100644 --- a/matrix_synapse_saml_mozilla/res/index.html +++ b/matrix_synapse_saml_mozilla/res/index.html @@ -14,16 +14,17 @@
-
+ - +
-
+

Connect your existing Matrix-Account:

+

- +
- +
diff --git a/matrix_synapse_saml_mozilla/res/script.js b/matrix_synapse_saml_mozilla/res/script.js index e01e6ed..c40147d 100644 --- a/matrix_synapse_saml_mozilla/res/script.js +++ b/matrix_synapse_saml_mozilla/res/script.js @@ -1,60 +1,80 @@ -let regiterFields = document.getElementById("field-username"); +let registerField = document.getElementById("field-username"); +let connectUsernameField = document.getElementById("connect-field-username"); +let connectPasswordField = document.getElementById("connect-field-password"); let inputFields = document.getElementsByClassName("field"); -let registerForm = document.getElementById("form"); -let submitButton = document.getElementById("button-submit"); +let registerForm = document.getElementById("register-form"); +let registerButton = document.getElementById("button-register-submit"); +let connectForm = document.getElementById("connect-form"); +let connectButton = document.getElementById("button-connect-submit"); let message = document.getElementById("message"); let tabLinkButtons = document.getElementsByClassName("tablinks"); let tabContentBlocks = document.getElementsByClassName("tabcontent"); // Remove input field placeholder if the text field is not empty let switchClass = function(input) { - if (input.value.length > 0) { - input.classList.add('has-contents'); - } - else { - input.classList.remove('has-contents'); - } + if (input.value.length > 0) { + input.classList.add('has-contents'); + } + else { + input.classList.remove('has-contents'); + } }; // Submit username and receive response let showMessage = function(messageText) { - // Unhide the message text - message.classList.remove("hidden"); + // Unhide the message text + message.classList.remove("hidden"); - message.innerHTML = messageText; + message.innerHTML = messageText; }; let hideMessage = function() { - // Hide the message text - message.classList.add("hidden"); + // Hide the message text + message.classList.add("hidden"); }; -let onResponse = function(response, success) { - // Display message - showMessage(response); +let onRegisterResponse = function(response, success) { + // Display message + showMessage(response); - if(success) { - registerForm.submit(); - return; - } + if(success) { + registerForm.submit(); + return; + } - // Enable submit button and input field - submitButton.classList.remove('button--disabled'); - submitButton.value = "Submit" + // Enable submit button and input field + registerButton.classList.remove('button--disabled'); + registerButton.value = "Submit" }; + +let onConnectResponse = function(response, success) { + // Display message + showMessage(response); + + if(success) { + connectForm.submit(); + return; + } + + // Enable submit button and input field + connectButton.classList.remove('button--disabled'); + connectButton.value = "Submit" +}; + + let allowedUsernameCharacters = RegExp("[^a-z0-9\\.\\_\\=\\-\\/]"); let usernameIsValid = function(username) { - return !allowedUsernameCharacters.test(username); + return !allowedUsernameCharacters.test(username); } let allowedCharactersString = "" + -"lowercase letters, " + -"digits, " + -"., " + -"_, " + -"-, " + -"/, " + -"="; + "lowercase letters, " + + "digits, " + + "., " + + "_, " + + "-, " + + "/, " + + "="; let buildQueryString = function(params) { return Object.keys(params) @@ -62,15 +82,15 @@ let buildQueryString = function(params) { .join('&'); } -let submitUsername = function(username) { - if(username.length == 0) { - onResponse("Please enter a username.", false); - return; - } - if(!usernameIsValid(username)) { - onResponse("Invalid username. Only the following characters are allowed: " + allowedCharactersString, false); - return; - } +let submitRegister = function(username) { + if(username.length == 0) { + onRegisterResponse("Please enter a username.", false); + return; + } + if(!usernameIsValid(username)) { + onRegisterResponse("Invalid username. Only the following characters are allowed: " + allowedCharactersString, false); + return; + } let check_uri = 'check?' + buildQueryString({"username": username}); fetch(check_uri, { @@ -86,27 +106,92 @@ let submitUsername = function(username) { if(json.error) { throw json.error; } else if(json.available) { - onResponse("Success. Please wait a moment for your browser to redirect.", true); + onRegisterResponse("Success. Please wait a moment for your browser to redirect.", true); + } else { + onRegisterResponse("This username is not available, please choose another.", false); + } + }).catch((err) => { + onRegisterResponse("Error checking username availability: " + err, false); + }); +} + +let submitConnect = function(username, password) { + if(username.length == 0) { + onConnectResponse("Please enter a username.", false); + return; + } + if(!usernameIsValid(username)) { + onConnectResponse("Invalid username. Only the following characters are allowed: " + allowedCharactersString, false); + return; + } + if(password.length == 0) { + onConnectResponse("Please enter a password.", false); + return; + } + + details = { + username: username, + password: password + } + + var formBody = []; + for (var property in details) { + var encodedKey = encodeURIComponent(property); + var encodedValue = encodeURIComponent(details[property]); + formBody.push(encodedKey + "=" + encodedValue); + } + formBody = formBody.join("&"); + + fetch('check', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: formBody + }).then((response) => { + if(!response.ok) { + // for non-200 responses, raise the body of the response as an exception + return response.text().then((text) => { throw text }); } else { - onResponse("This username is not available, please choose another.", false); + return response.json() + } + }).then((json) => { + if(json.error) { + throw json.error; + } else if(json.success) { + onConnectResponse("Success. Please wait a moment for your browser to redirect.", true); + } else { + onConnectResponse("The credentials you entered are not valid.", false); } }).catch((err) => { - onResponse("Error checking username availability: " + err, false); + onConnectResponse("Error checking credentials: " + err, false); }); } -let clickSubmit = function() { - if(submitButton.classList.contains('button--disabled')) { return; } +let registerClickSubmit = function() { + if(registerButton.classList.contains('button--disabled')) { return; } + + // Disable submit button and input field + registerButton.classList.add('button--disabled'); + + // Submit username + registerButton.value = "Checking..."; + submitRegister(registerField.value); +}; + +let connectClickSubmit = function() { + if(connectButton.classList.contains('button--disabled')) { return; } - // Disable submit button and input field - submitButton.classList.add('button--disabled'); + // Disable submit button and input field + connectButton.classList.add('button--disabled'); - // Submit username - submitButton.value = "Checking..."; - submitUsername(regiterFields.value); + // Submit data + connectButton.value = "Checking..."; + submitConnect(connectUsernameField.value, connectPasswordField.value); }; -submitButton.onclick = clickSubmit; +registerButton.onclick = registerClickSubmit; +connectButton.onclick = connectClickSubmit; // Listen for events on inputFields for (let i = 0; i < inputFields.length; i++) { @@ -114,7 +199,11 @@ for (let i = 0; i < inputFields.length; i++) { // Listen for Enter on input field if (event.which === 13) { event.preventDefault(); - clickSubmit(); + if (inputFields[i].id === "field-username") { + registerClickSubmit(); + } else { + connectClickSubmit(); + } return true; } switchClass(inputFields[i]); diff --git a/matrix_synapse_saml_mozilla/username_picker.py b/matrix_synapse_saml_mozilla/username_picker.py index 3945e30..5d2eeba 100644 --- a/matrix_synapse_saml_mozilla/username_picker.py +++ b/matrix_synapse_saml_mozilla/username_picker.py @@ -27,6 +27,8 @@ import synapse.module_api from synapse.module_api import run_in_background from synapse.module_api.errors import SynapseError +from synapse.api.errors import Codes, LoginError + from matrix_synapse_saml_mozilla._sessions import ( SESSION_COOKIE_NAME, get_mapping_session, @@ -56,7 +58,7 @@ def pick_username_resource( base_path = pkg_resources.resource_filename("matrix_synapse_saml_mozilla", "res") res = File(base_path) res.putChild(b"submit", SubmitResource(module_api)) - res.putChild(b"check", AvailabilityCheckResource(module_api)) + res.putChild(b"check", CheckResource(module_api)) return res @@ -155,15 +157,32 @@ class SubmitResource(AsyncResource): _return_html_error(400, "missing username", request) return localpart = request.args[b"username"][0].decode("utf-8", errors="replace") - logger.info("Registering username %s", localpart) - try: - registered_user_id = await self._module_api.register_user( - localpart=localpart, displayname=localpart - ) - except SynapseError as e: - logger.warning("Error during registration: %s", e) - _return_html_error(e.code, e.msg, request) - return + + if b"password" not in request.args: + logger.info("Registering username %s", localpart) + try: + registered_user_id = await self._module_api.register_user( + localpart=localpart, displayname=localpart + ) + except SynapseError as e: + logger.warning("Error during registration: %s", e) + _return_html_error(e.code, e.msg, request) + return + else: + password = request.args[b"password"][0].decode("utf-8", errors="replace") + registered_user_id = '@{}:localhost'.format(localpart) + + success = False + try: + passwd_response = await self._module_api._auth_handler._check_local_password(registered_user_id, password) + if passwd_response != registered_user_id: + raise LoginError(403, "Invalid password", errcode=Codes.FORBIDDEN) + except Exception as e: + logger.warning( + "Error checking credentials of %s: %s %s" % (localpart, type(e), e) + ) + _return_html_error(e.code, e.msg, request) + return await self._module_api.record_user_external_id( "saml", session.remote_user_id, registered_user_id @@ -186,7 +205,7 @@ class SubmitResource(AsyncResource): ) -class AvailabilityCheckResource(AsyncResource): +class CheckResource(AsyncResource): def __init__(self, module_api: synapse.module_api.ModuleApi): super().__init__() self._module_api = module_api @@ -224,6 +243,47 @@ class AvailabilityCheckResource(AsyncResource): response = {"available": available} _return_json(response, request) + @_wrap_for_text_exceptions + async def async_render_POST(self, request: Request): + # make sure that there is a valid mapping session, to stop people dictionary- + # scanning for accounts + # session_id = request.getCookie(SESSION_COOKIE_NAME) + # if not session_id: + # _return_json({"error": "missing session_id"}, request) + # return + # + # session_id = session_id.decode("ascii", errors="replace") + # session = get_mapping_session(session_id) + # if not session: + # logger.info("Couldn't find session id %s", session_id) + # _return_json({"error": "unknown session"}, request) + # return + + if b"username" not in request.args: + _return_json({"error": "missing username"}, request) + return + localpart = request.args[b"username"][0].decode("utf-8", errors="replace") + + if b"password" not in request.args: + _return_json({"error": "missing password"}, request) + return + password = request.args[b"password"][0].decode("utf-8", errors="replace") + + uid = '@{}:localhost'.format(localpart) + + success = False + try: + passwd_response = await self._module_api._auth_handler._check_local_password(uid, password) + if passwd_response == uid: + success = True + except Exception as e: + logger.warning( + "Error checking credentials of %s: %s %s" % (localpart, type(e), e) + ) + + response = {"success": success} + _return_json(response, request) + def _add_login_token_to_redirect_url(url, token): url_parts = list(urllib.parse.urlparse(url))