Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Clean-up the fallback login code #7657

Merged
merged 11 commits into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7657.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Clean-up the login fallback code.
26 changes: 13 additions & 13 deletions synapse/static/client/login/index.html
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
<!doctype html>
<html>
<head>
<title> Login </title>
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
<link rel="stylesheet" href="style.css">
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/login.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title> Login </title>
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
<link rel="stylesheet" href="style.css">
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/login.js"></script>
</head>
<body onload="matrixLogin.onLoad()">
<center>
<br/>
<div id="container">
<h1 id="title"></h1>

<span id="feedback" style="color: #f00"></span>
<span id="feedback"></span>

<div id="loading">
<img src="spinner.gif" />
</div>

<div id="sso_flow" class="login_flow" style="display:none">
<div id="sso_flow" class="login_flow" style="display: none;">
Single-sign on:
<form id="sso_form" action="/_matrix/client/r0/login/sso/redirect" method="get">
<input id="sso_redirect_url" type="hidden" name="redirectUrl" value=""/>
<input type="submit" value="Log in"/>
</form>
</div>

<div id="password_flow" class="login_flow" style="display:none">
<div id="password_flow" class="login_flow" style="display: none;">
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" />
<br/>
<input id="password" size="32" type="password" placeholder="Password"/>
Expand All @@ -38,9 +38,9 @@ <h1 id="title"></h1>
</form>
</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.
</div>
</center>
</div>
</body>
</html>
133 changes: 87 additions & 46 deletions synapse/static/client/login/js/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ window.matrixLogin = {
};

// Titles get updated through the process to give users feedback.
var TITLE_PRE_AUTH = "Log in with one of the following methods";
var TITLE_POST_AUTH = "Logging in...";
const TITLE_PRE_AUTH = "Log in with one of the following methods";
const TITLE_POST_AUTH = "Logging in...";

// 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.
Expand All @@ -20,9 +20,9 @@ var COOKIE_KEY = "synapse_login_fallback_qs";
* login request, e.g. device_id.
* 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);
set_title(TITLE_POST_AUTH);
setTitle(TITLE_POST_AUTH);

// Add the login type.
data.type = type;
Expand All @@ -41,40 +41,58 @@ var submitLogin = function(type, data, extra, callback) {
}
matrixLogin.onLogin(response);
}).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
// 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) {
setFeedbackString(err.responseJSON.error + " (" + err.responseJSON.errcode + ")");
}
else {
setFeedbackString("Request failed: " + err.status);
}
};
}

var setFeedbackString = function(text) {
/*
* Display an error to the user.
*/
function setFeedbackString(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
// and handled after the redirect.
var this_page = window.location.origin + window.location.pathname;
$("#sso_redirect_url").val(this_page);
/*
* (Maybe) Show the login forms.
*
* This actually does a few unrelated functions:
*
* * 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.
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
// for retrieval later.
var qs = parseQsFromUrl();
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();
return;
}
Expand All @@ -87,30 +105,39 @@ var show_login = function(inhibit_redirect) {
$("#password_flow").show();
}

// If neither password or SSO are supported, show an error to the user.
if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsSso) {
$("#no_login_types").show();
}

set_title(TITLE_PRE_AUTH);

$("#loading").hide();
};
}

var show_spinner = function() {
/*
* Hides the forms and shows a loading throbber.
*/
function showSpinner() {
$("#password_flow").hide();
$("#sso_flow").hide();
$("#no_login_types").hide();
$("#loading").show();
};
}

var set_title = function(title) {
/*
* Helper to show the page's main title.
*/
function setTitle(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) {
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];
if ("m.login.sso" === flow.type) {
matrixLogin.serverAcceptsSso = true;
Expand All @@ -126,27 +153,41 @@ var fetch_info = function(cb) {
}).fail(errorFunc);
}

/*
* Called on load to fetch login flows and attempt SSO login (if a token is available).
*/
matrixLogin.onLoad = function() {
fetch_info(function() {
if (!try_token()) {
show_login(false);
fetchLoginFlows(function() {
// (Maybe) attempt logging in via SSO if a token is available.
if (!tryTokenLogin()) {
showLogin(false);
}
});
};

matrixLogin.password_login = function() {
/*
* Submit simple user & password login.
*/
matrixLogin.passwordLogin = function() {
var user = $("#user_id").val();
var pwd = $("#password").val();

setFeedbackString("");

show_spinner();
showSpinner();
submitLogin(
"m.login.password",
{user: user, password: pwd},
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) {
// clobber this function
console.warn("onLogin - This function should be replaced to proceed.");
Expand All @@ -155,7 +196,7 @@ matrixLogin.onLogin = function(response) {
/*
* Process the query parameters from the current URL into an object.
*/
var parseQsFromUrl = function() {
function parseQsFromUrl() {
var pos = window.location.href.indexOf("?");
if (pos == -1) {
return {};
Expand All @@ -174,12 +215,12 @@ var parseQsFromUrl = function() {
result[key] = val;
});
return result;
};
}

/*
* Process the cookies and return an object.
*/
var parseCookies = function() {
function parseCookies() {
var allCookies = document.cookie;
var result = {};
allCookies.split(";").forEach(function(part) {
Expand All @@ -196,32 +237,32 @@ var parseCookies = function() {
result[key] = val;
});
return result;
};
}

/*
* 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.
var maxAge = 60 * 60;
// Set the cookie, this defaults to the current domain and path.
document.cookie = key + "=" + encodeURIComponent(value) + ";max-age=" + maxAge + ";sameSite=lax";
};
}

/*
* 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
// doesn't matter.)
document.cookie = key + "=deleted;expires=0";
};
}

/*
* Submits the login token if one is found in the query parameters. Returns a
* 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.
var qs = parseQsFromUrl();

Expand All @@ -233,18 +274,18 @@ var try_token = function() {
// Retrieve the original query parameters (from before the SSO redirect).
// They are stored as JSON in a cookie.
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.
var callback = function() {
function callback() {
deleteCookie(COOKIE_KEY);
}

submitLogin(
"m.login.token",
{token: loginToken},
original_query_params,
originalQueryParams,
callback);

return true;
};
}
34 changes: 29 additions & 5 deletions synapse/static/client/login/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,44 @@ form {
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 {
width: 300px;
text-align: left;
padding: 10px;
margin-bottom: 40px;

-webkit-border-radius: 10px;
-moz-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);

background-color: #f8f8f8;
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;
}