Clean-up the fallback login code. (#7657)

This commit is contained in:
Patrick Cloke 2020-06-10 09:50:39 -04:00 committed by GitHub
parent 236d2d699d
commit 191dc98f80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 64 deletions

1
changelog.d/7657.misc Normal file
View file

@ -0,0 +1 @@
Clean-up the login fallback code.

View file

@ -1,24 +1,24 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<title> Login </title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'> <title> Login </title>
<link rel="stylesheet" href="style.css"> <meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
<script src="js/jquery-3.4.1.min.js"></script> <link rel="stylesheet" href="style.css">
<script src="js/login.js"></script> <script src="js/jquery-3.4.1.min.js"></script>
<script src="js/login.js"></script>
</head> </head>
<body onload="matrixLogin.onLoad()"> <body onload="matrixLogin.onLoad()">
<center> <div id="container">
<br/>
<h1 id="title"></h1> <h1 id="title"></h1>
<span id="feedback" style="color: #f00"></span> <span id="feedback"></span>
<div id="loading"> <div id="loading">
<img src="spinner.gif" /> <img src="spinner.gif" />
</div> </div>
<div id="sso_flow" class="login_flow" style="display:none"> <div id="sso_flow" class="login_flow" style="display: none;">
Single-sign on: Single-sign on:
<form id="sso_form" action="/_matrix/client/r0/login/sso/redirect" method="get"> <form id="sso_form" action="/_matrix/client/r0/login/sso/redirect" method="get">
<input id="sso_redirect_url" type="hidden" name="redirectUrl" value=""/> <input id="sso_redirect_url" type="hidden" name="redirectUrl" value=""/>
@ -26,9 +26,9 @@
</form> </form>
</div> </div>
<div id="password_flow" class="login_flow" style="display:none"> <div id="password_flow" class="login_flow" style="display: none;">
Password Authentication: Password Authentication:
<form onsubmit="matrixLogin.password_login(); return false;"> <form onsubmit="matrixLogin.passwordLogin(); return false;">
<input id="user_id" size="32" type="text" placeholder="Matrix ID (e.g. bob)" autocapitalize="off" autocorrect="off" /> <input id="user_id" size="32" type="text" placeholder="Matrix ID (e.g. bob)" autocapitalize="off" autocorrect="off" />
<br/> <br/>
<input id="password" size="32" type="password" placeholder="Password"/> <input id="password" size="32" type="password" placeholder="Password"/>
@ -38,9 +38,9 @@
</form> </form>
</div> </div>
<div id="no_login_types" type="button" class="login_flow" style="display:none"> <div id="no_login_types" type="button" class="login_flow" style="display: none;">
Log in currently unavailable. Log in currently unavailable.
</div> </div>
</center> </div>
</body> </body>
</html> </html>

View file

@ -5,11 +5,11 @@ window.matrixLogin = {
}; };
// Titles get updated through the process to give users feedback. // Titles get updated through the process to give users feedback.
var TITLE_PRE_AUTH = "Log in with one of the following methods"; const TITLE_PRE_AUTH = "Log in with one of the following methods";
var TITLE_POST_AUTH = "Logging in..."; const TITLE_POST_AUTH = "Logging in...";
// The cookie used to store the original query parameters when using SSO. // The cookie used to store the original query parameters when using SSO.
var COOKIE_KEY = "synapse_login_fallback_qs"; const COOKIE_KEY = "synapse_login_fallback_qs";
/* /*
* Submit a login request. * Submit a login request.
@ -20,9 +20,9 @@ var COOKIE_KEY = "synapse_login_fallback_qs";
* login request, e.g. device_id. * login request, e.g. device_id.
* callback: (Optional) Function to call on successful login. * callback: (Optional) Function to call on successful login.
*/ */
var submitLogin = function(type, data, extra, callback) { function submitLogin(type, data, extra, callback) {
console.log("Logging in with " + type); console.log("Logging in with " + type);
set_title(TITLE_POST_AUTH); setTitle(TITLE_POST_AUTH);
// Add the login type. // Add the login type.
data.type = type; data.type = type;
@ -41,12 +41,15 @@ var submitLogin = function(type, data, extra, callback) {
} }
matrixLogin.onLogin(response); matrixLogin.onLogin(response);
}).fail(errorFunc); }).fail(errorFunc);
}; }
var errorFunc = function(err) { /*
* Display an error to the user and show the login form again.
*/
function errorFunc(err) {
// We want to show the error to the user rather than redirecting immediately to the // We want to show the error to the user rather than redirecting immediately to the
// SSO portal (if SSO is the only login option), so we inhibit the redirect. // SSO portal (if SSO is the only login option), so we inhibit the redirect.
show_login(true); showLogin(true);
if (err.responseJSON && err.responseJSON.error) { if (err.responseJSON && err.responseJSON.error) {
setFeedbackString(err.responseJSON.error + " (" + err.responseJSON.errcode + ")"); setFeedbackString(err.responseJSON.error + " (" + err.responseJSON.errcode + ")");
@ -54,27 +57,42 @@ var errorFunc = function(err) {
else { else {
setFeedbackString("Request failed: " + err.status); setFeedbackString("Request failed: " + err.status);
} }
}; }
var setFeedbackString = function(text) { /*
* Display an error to the user.
*/
function setFeedbackString(text) {
$("#feedback").text(text); $("#feedback").text(text);
}; }
var show_login = function(inhibit_redirect) { /*
// Set the redirect to come back to this page, a login token will get added * (Maybe) Show the login forms.
// and handled after the redirect. *
var this_page = window.location.origin + window.location.pathname; * This actually does a few unrelated functions:
$("#sso_redirect_url").val(this_page); *
* * Configures the SSO redirect URL to come back to this page.
* * Configures and shows the SSO form, if the server supports SSO.
* * Otherwise, shows the password form.
*/
function showLogin(inhibitRedirect) {
setTitle(TITLE_PRE_AUTH);
// If inhibit_redirect is false, and SSO is the only supported login method, // If inhibitRedirect is false, and SSO is the only supported login method,
// we can redirect straight to the SSO page. // we can redirect straight to the SSO page.
if (matrixLogin.serverAcceptsSso) { if (matrixLogin.serverAcceptsSso) {
// Set the redirect to come back to this page, a login token will get
// added as a query parameter and handled after the redirect.
$("#sso_redirect_url").val(window.location.origin + window.location.pathname);
// Before submitting SSO, set the current query parameters into a cookie // Before submitting SSO, set the current query parameters into a cookie
// for retrieval later. // for retrieval later.
var qs = parseQsFromUrl(); var qs = parseQsFromUrl();
setCookie(COOKIE_KEY, JSON.stringify(qs)); setCookie(COOKIE_KEY, JSON.stringify(qs));
if (!inhibit_redirect && !matrixLogin.serverAcceptsPassword) { // If password is not supported and redirects are allowed, then submit
// the form (redirecting to the SSO provider).
if (!inhibitRedirect && !matrixLogin.serverAcceptsPassword) {
$("#sso_form").submit(); $("#sso_form").submit();
return; return;
} }
@ -87,30 +105,39 @@ var show_login = function(inhibit_redirect) {
$("#password_flow").show(); $("#password_flow").show();
} }
// If neither password or SSO are supported, show an error to the user.
if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsSso) { if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsSso) {
$("#no_login_types").show(); $("#no_login_types").show();
} }
set_title(TITLE_PRE_AUTH);
$("#loading").hide(); $("#loading").hide();
}; }
var show_spinner = function() { /*
* Hides the forms and shows a loading throbber.
*/
function showSpinner() {
$("#password_flow").hide(); $("#password_flow").hide();
$("#sso_flow").hide(); $("#sso_flow").hide();
$("#no_login_types").hide(); $("#no_login_types").hide();
$("#loading").show(); $("#loading").show();
}; }
var set_title = function(title) { /*
* Helper to show the page's main title.
*/
function setTitle(title) {
$("#title").text(title); $("#title").text(title);
}; }
var fetch_info = function(cb) { /*
* Query the login endpoint for the homeserver's supported flows.
*
* This populates matrixLogin.serverAccepts* variables.
*/
function fetchLoginFlows(cb) {
$.get(matrixLogin.endpoint, function(response) { $.get(matrixLogin.endpoint, function(response) {
var serverAcceptsPassword = false; for (var i = 0; i < response.flows.length; i++) {
for (var i=0; i<response.flows.length; i++) {
var flow = response.flows[i]; var flow = response.flows[i];
if ("m.login.sso" === flow.type) { if ("m.login.sso" === flow.type) {
matrixLogin.serverAcceptsSso = true; matrixLogin.serverAcceptsSso = true;
@ -126,27 +153,41 @@ var fetch_info = function(cb) {
}).fail(errorFunc); }).fail(errorFunc);
} }
/*
* Called on load to fetch login flows and attempt SSO login (if a token is available).
*/
matrixLogin.onLoad = function() { matrixLogin.onLoad = function() {
fetch_info(function() { fetchLoginFlows(function() {
if (!try_token()) { // (Maybe) attempt logging in via SSO if a token is available.
show_login(false); if (!tryTokenLogin()) {
showLogin(false);
} }
}); });
}; };
matrixLogin.password_login = function() { /*
* Submit simple user & password login.
*/
matrixLogin.passwordLogin = function() {
var user = $("#user_id").val(); var user = $("#user_id").val();
var pwd = $("#password").val(); var pwd = $("#password").val();
setFeedbackString(""); setFeedbackString("");
show_spinner(); showSpinner();
submitLogin( submitLogin(
"m.login.password", "m.login.password",
{user: user, password: pwd}, {user: user, password: pwd},
parseQsFromUrl()); parseQsFromUrl());
}; };
/*
* The onLogin function gets called after a succesful login.
*
* It is expected that implementations override this to be notified when the
* login is complete. The response to the login call is provided as the single
* parameter.
*/
matrixLogin.onLogin = function(response) { matrixLogin.onLogin = function(response) {
// clobber this function // clobber this function
console.warn("onLogin - This function should be replaced to proceed."); console.warn("onLogin - This function should be replaced to proceed.");
@ -155,7 +196,7 @@ matrixLogin.onLogin = function(response) {
/* /*
* Process the query parameters from the current URL into an object. * Process the query parameters from the current URL into an object.
*/ */
var parseQsFromUrl = function() { function parseQsFromUrl() {
var pos = window.location.href.indexOf("?"); var pos = window.location.href.indexOf("?");
if (pos == -1) { if (pos == -1) {
return {}; return {};
@ -174,12 +215,12 @@ var parseQsFromUrl = function() {
result[key] = val; result[key] = val;
}); });
return result; return result;
}; }
/* /*
* Process the cookies and return an object. * Process the cookies and return an object.
*/ */
var parseCookies = function() { function parseCookies() {
var allCookies = document.cookie; var allCookies = document.cookie;
var result = {}; var result = {};
allCookies.split(";").forEach(function(part) { allCookies.split(";").forEach(function(part) {
@ -196,32 +237,32 @@ var parseCookies = function() {
result[key] = val; result[key] = val;
}); });
return result; return result;
}; }
/* /*
* Set a cookie that is valid for 1 hour. * Set a cookie that is valid for 1 hour.
*/ */
var setCookie = function(key, value) { function setCookie(key, value) {
// The maximum age is set in seconds. // The maximum age is set in seconds.
var maxAge = 60 * 60; var maxAge = 60 * 60;
// Set the cookie, this defaults to the current domain and path. // Set the cookie, this defaults to the current domain and path.
document.cookie = key + "=" + encodeURIComponent(value) + ";max-age=" + maxAge + ";sameSite=lax"; document.cookie = key + "=" + encodeURIComponent(value) + ";max-age=" + maxAge + ";sameSite=lax";
}; }
/* /*
* Removes a cookie by key. * Removes a cookie by key.
*/ */
var deleteCookie = function(key) { function deleteCookie(key) {
// Delete a cookie by setting the expiration to 0. (Note that the value // Delete a cookie by setting the expiration to 0. (Note that the value
// doesn't matter.) // doesn't matter.)
document.cookie = key + "=deleted;expires=0"; document.cookie = key + "=deleted;expires=0";
}; }
/* /*
* Submits the login token if one is found in the query parameters. Returns a * Submits the login token if one is found in the query parameters. Returns a
* boolean of whether the login token was found or not. * boolean of whether the login token was found or not.
*/ */
var try_token = function() { function tryTokenLogin() {
// Check if the login token is in the query parameters. // Check if the login token is in the query parameters.
var qs = parseQsFromUrl(); var qs = parseQsFromUrl();
@ -233,18 +274,18 @@ var try_token = function() {
// Retrieve the original query parameters (from before the SSO redirect). // Retrieve the original query parameters (from before the SSO redirect).
// They are stored as JSON in a cookie. // They are stored as JSON in a cookie.
var cookies = parseCookies(); var cookies = parseCookies();
var original_query_params = JSON.parse(cookies[COOKIE_KEY] || "{}") var originalQueryParams = JSON.parse(cookies[COOKIE_KEY] || "{}")
// If the login is successful, delete the cookie. // If the login is successful, delete the cookie.
var callback = function() { function callback() {
deleteCookie(COOKIE_KEY); deleteCookie(COOKIE_KEY);
} }
submitLogin( submitLogin(
"m.login.token", "m.login.token",
{token: loginToken}, {token: loginToken},
original_query_params, originalQueryParams,
callback); callback);
return true; return true;
}; }

View file

@ -31,20 +31,44 @@ form {
margin: 10px 0 0 0; margin: 10px 0 0 0;
} }
/*
* Add some padding to the viewport.
*/
#container {
padding: 10px;
}
/*
* Center all direct children of the main form.
*/
#container > * {
display: block;
margin-left: auto;
margin-right: auto;
text-align: center;
}
/*
* A wrapper around each login flow.
*/
.login_flow { .login_flow {
width: 300px; width: 300px;
text-align: left; text-align: left;
padding: 10px; padding: 10px;
margin-bottom: 40px; margin-bottom: 40px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px; border-radius: 10px;
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15); box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
background-color: #f8f8f8; background-color: #f8f8f8;
border: 1px #ccc solid; border: 1px #ccc solid;
} }
/*
* Used to show error content.
*/
#feedback {
/* Red text. */
color: #ff0000;
/* A little space to not overlap the box-shadow. */
margin-bottom: 20px;
}