diff --git a/synapse/res/templates/sso_auth_account_details.html b/synapse/res/templates/sso_auth_account_details.html
new file mode 100644
index 0000000000..f22b09aec1
--- /dev/null
+++ b/synapse/res/templates/sso_auth_account_details.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>Synapse Login</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, user-scalable=no">
+ <style type="text/css">
+ {% include "sso.css" without context %}
+
+ .username_input {
+ display: flex;
+ border: 2px solid #418DED;
+ border-radius: 8px;
+ padding: 12px;
+ position: relative;
+ margin: 16px 0;
+ align-items: center;
+ font-size: 12px;
+ }
+
+ .username_input label {
+ position: absolute;
+ top: -8px;
+ left: 14px;
+ font-size: 80%;
+ background: white;
+ padding: 2px;
+ }
+
+ .username_input input {
+ flex: 1;
+ display: block;
+ min-width: 0;
+ border: none;
+ }
+
+ .username_input div {
+ color: #8D99A5;
+ }
+
+ .idp-pick-details {
+ border: 1px solid #E9ECF1;
+ border-radius: 8px;
+ margin: 24px 0;
+ }
+
+ .idp-pick-details h2 {
+ margin: 0;
+ padding: 8px 12px;
+ }
+
+ .idp-pick-details .idp-detail {
+ border-top: 1px solid #E9ECF1;
+ padding: 12px;
+ }
+
+ .idp-pick-details .use, .idp-pick-details .idp-value {
+ color: #737D8C;
+ }
+
+ .idp-pick-details .idp-value {
+ margin: 0;
+ margin-top: 8px;
+ }
+
+ .idp-pick-details .avatar {
+ width: 53px;
+ height: 53px;
+ border-radius: 100%;
+ display: block;
+ margin-top: 8px;
+ }
+ </style>
+ </head>
+ <body>
+ <header>
+ <h1>Your account is nearly ready</h1>
+ <p>Check your details before creating an account on {{ server_name }}</p>
+ </header>
+ <main>
+ <form method="post" class="form__input" id="form">
+ <div class="username_input">
+ <label for="field-username">Username</label>
+ <div class="prefix">@</div>
+ <input type="text" name="username" id="field-username" autofocus required pattern="[a-z0-9\-=_\/\.]+">
+ <div class="postfix">:{{ server_name }}</div>
+ </div>
+ <input type="submit" value="Continue" class="primary-button">
+ {% if user_attributes %}
+ <section class="idp-pick-details">
+ <h2><img src="{{ idp.idp_icon | mxc_to_http(24, 24) }}"/>Information from {{ idp.idp_name }}</h2>
+ {% if user_attributes.avatar_url %}
+ <div class="idp-detail idp-avatar">
+ <img src="{{ user_attributes.avatar_url }}" class="avatar" />
+ </div>
+ {% endif %}
+ {% if user_attributes.display_name %}
+ <div class="idp-detail">
+ <p class="idp-value">{{ user_attributes.display_name }}</p>
+ </div>
+ {% endif %}
+ {% for email in user_attributes.emails %}
+ <div class="idp-detail">
+ <p class="idp-value">{{ email }}</p>
+ </div>
+ {% endfor %}
+ </section>
+ {% endif %}
+ </form>
+ </main>
+ <script type="text/javascript">
+ {% include "sso_auth_account_details.js" without context %}
+ </script>
+ </body>
+</html>
diff --git a/synapse/res/templates/sso_auth_account_details.js b/synapse/res/templates/sso_auth_account_details.js
new file mode 100644
index 0000000000..deef419bb6
--- /dev/null
+++ b/synapse/res/templates/sso_auth_account_details.js
@@ -0,0 +1,76 @@
+const usernameField = document.getElementById("field-username");
+
+function throttle(fn, wait) {
+ let timeout;
+ return function() {
+ const args = Array.from(arguments);
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ timeout = setTimeout(fn.bind.apply(fn, [null].concat(args)), wait);
+ }
+}
+
+function checkUsernameAvailable(username) {
+ let check_uri = 'check?username=' + encodeURIComponent(username);
+ return 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 new Error(text); });
+ } else {
+ return response.json();
+ }
+ }).then((json) => {
+ if(json.error) {
+ return {message: json.error};
+ } else if(json.available) {
+ return {available: true};
+ } else {
+ return {message: username + " is not available, please choose another."};
+ }
+ });
+}
+
+function validateUsername(username) {
+ usernameField.setCustomValidity("");
+ if (usernameField.validity.valueMissing) {
+ usernameField.setCustomValidity("Please provide a username");
+ return;
+ }
+ if (usernameField.validity.patternMismatch) {
+ usernameField.setCustomValidity("Invalid username, please only use " + allowedCharactersString);
+ return;
+ }
+ usernameField.setCustomValidity("Checking if username is available …");
+ throttledCheckUsernameAvailable(username);
+}
+
+const throttledCheckUsernameAvailable = throttle(function(username) {
+ const handleError = function(err) {
+ // don't prevent form submission on error
+ usernameField.setCustomValidity("");
+ console.log(err.message);
+ };
+ try {
+ checkUsernameAvailable(username).then(function(result) {
+ if (!result.available) {
+ usernameField.setCustomValidity(result.message);
+ usernameField.reportValidity();
+ } else {
+ usernameField.setCustomValidity("");
+ }
+ }, handleError);
+ } catch (err) {
+ handleError(err);
+ }
+}, 500);
+
+usernameField.addEventListener("input", function(evt) {
+ validateUsername(usernameField.value);
+});
+usernameField.addEventListener("change", function(evt) {
+ validateUsername(usernameField.value);
+});
diff --git a/synapse/res/username_picker/index.html b/synapse/res/username_picker/index.html
deleted file mode 100644
index 37ea8bb6d8..0000000000
--- a/synapse/res/username_picker/index.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!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
deleted file mode 100644
index 416a7c6f41..0000000000
--- a/synapse/res/username_picker/script.js
+++ /dev/null
@@ -1,95 +0,0 @@
-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
deleted file mode 100644
index 745bd4c684..0000000000
--- a/synapse/res/username_picker/style.css
+++ /dev/null
@@ -1,27 +0,0 @@
-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;
-}
-
|