summary refs log tree commit diff
path: root/synapse/res/username_picker
diff options
context:
space:
mode:
authorRichard van der Hoff <1389908+richvdh@users.noreply.github.com>2020-12-18 14:19:46 +0000
committerGitHub <noreply@github.com>2020-12-18 14:19:46 +0000
commit28877fade90a5cfb3457c9e6c70924dbbe8af715 (patch)
tree77e5c2e21144fb58026b98a6ad340b95a7419bb4 /synapse/res/username_picker
parentAllow re-using a UI auth validation for a period of time (#8970) (diff)
downloadsynapse-28877fade90a5cfb3457c9e6c70924dbbe8af715.tar.xz
Implement a username picker for synapse (#8942)
The final part (for now) of my work to implement a username picker in synapse itself. The idea is that we allow
`UsernameMappingProvider`s to return `localpart=None`, in which case, rather than redirecting the browser
back to the client, we redirect to a username-picker resource, which allows the user to enter a username.
We *then* complete the SSO flow (including doing the client permission checks).

The static resources for the username picker itself (in 
https://github.com/matrix-org/synapse/tree/rav/username_picker/synapse/res/username_picker)
are essentially lifted wholesale from
https://github.com/matrix-org/matrix-synapse-saml-mozilla/tree/master/matrix_synapse_saml_mozilla/res. 
As the comment says, we might want to think about making them customisable, but that can be a follow-up. 

Fixes #8876.
Diffstat (limited to 'synapse/res/username_picker')
-rw-r--r--synapse/res/username_picker/index.html19
-rw-r--r--synapse/res/username_picker/script.js95
-rw-r--r--synapse/res/username_picker/style.css27
3 files changed, 141 insertions, 0 deletions
diff --git a/synapse/res/username_picker/index.html b/synapse/res/username_picker/index.html
new file mode 100644
index 0000000000..37ea8bb6d8
--- /dev/null
+++ b/synapse/res/username_picker/index.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Synapse Login</title>
+    <link rel="stylesheet" href="style.css" type="text/css" />
+  </head>
+  <body>
+    <div class="card">
+      <form method="post" class="form__input" id="form" action="submit">
+        <label for="field-username">Please pick your username:</label>
+        <input type="text" name="username" id="field-username" autofocus="">
+        <input type="submit" class="button button--full-width" id="button-submit" value="Submit">
+      </form>
+      <!-- this is used for feedback -->
+      <div role=alert class="tooltip hidden" id="message"></div>
+      <script src="script.js"></script>
+    </div>
+  </body>
+</html>
diff --git a/synapse/res/username_picker/script.js b/synapse/res/username_picker/script.js
new file mode 100644
index 0000000000..416a7c6f41
--- /dev/null
+++ b/synapse/res/username_picker/script.js
@@ -0,0 +1,95 @@
+let inputField = document.getElementById("field-username");
+let inputForm = document.getElementById("form");
+let submitButton = document.getElementById("button-submit");
+let message = document.getElementById("message");
+
+// Submit username and receive response
+function showMessage(messageText) {
+    // Unhide the message text
+    message.classList.remove("hidden");
+
+    message.textContent = messageText;
+};
+
+function doSubmit() {
+    showMessage("Success. Please wait a moment for your browser to redirect.");
+
+    // remove the event handler before re-submitting the form.
+    delete inputForm.onsubmit;
+    inputForm.submit();
+}
+
+function onResponse(response) {
+    // Display message
+    showMessage(response);
+
+    // Enable submit button and input field
+    submitButton.classList.remove('button--disabled');
+    submitButton.value = "Submit";
+};
+
+let allowedUsernameCharacters = RegExp("[^a-z0-9\\.\\_\\=\\-\\/]");
+function usernameIsValid(username) {
+    return !allowedUsernameCharacters.test(username);
+}
+let allowedCharactersString = "lowercase letters, digits, ., _, -, /, =";
+
+function buildQueryString(params) {
+    return Object.keys(params)
+        .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
+        .join('&');
+}
+
+function submitUsername(username) {
+    if(username.length == 0) {
+        onResponse("Please enter a username.");
+        return;
+    }
+    if(!usernameIsValid(username)) {
+        onResponse("Invalid username. Only the following characters are allowed: " + allowedCharactersString);
+        return;
+    }
+
+    // if this browser doesn't support fetch, skip the availability check.
+    if(!window.fetch) {
+        doSubmit();
+        return;
+    }
+
+    let check_uri = 'check?' + buildQueryString({"username": username});
+    fetch(check_uri, {
+        // include the cookie
+        "credentials": "same-origin",
+    }).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 {
+            return response.json();
+        }
+    }).then((json) => {
+        if(json.error) {
+            throw json.error;
+        } else if(json.available) {
+            doSubmit();
+        } else {
+            onResponse("This username is not available, please choose another.");
+        }
+    }).catch((err) => {
+        onResponse("Error checking username availability: " + err);
+    });
+}
+
+function clickSubmit() {
+    event.preventDefault();
+    if(submitButton.classList.contains('button--disabled')) { return; }
+
+    // Disable submit button and input field
+    submitButton.classList.add('button--disabled');
+
+    // Submit username
+    submitButton.value = "Checking...";
+    submitUsername(inputField.value);
+};
+
+inputForm.onsubmit = clickSubmit;
diff --git a/synapse/res/username_picker/style.css b/synapse/res/username_picker/style.css
new file mode 100644
index 0000000000..745bd4c684
--- /dev/null
+++ b/synapse/res/username_picker/style.css
@@ -0,0 +1,27 @@
+input[type="text"] {
+  font-size: 100%;
+  background-color: #ededf0;
+  border: 1px solid #fff;
+  border-radius: .2em;
+  padding: .5em .9em;
+  display: block;
+  width: 26em;
+}
+
+.button--disabled {
+  border-color: #fff;
+  background-color: transparent;
+  color: #000;
+  text-transform: none;
+}
+
+.hidden {
+  display: none;
+}
+
+.tooltip {
+  background-color: #f9f9fa;
+  padding: 1em;
+  margin: 1em 0;
+}
+