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 @@
.
, " +
-"_
, " +
-"-
, " +
-"/
, " +
-"=
";
+ "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))