MatrixSynapse/synapse/static/client/login/js/login.js

292 lines
8.1 KiB
JavaScript

window.matrixLogin = {
endpoint: location.origin + "/_matrix/client/r0/login",
serverAcceptsPassword: false,
serverAcceptsSso: false,
};
// Titles get updated through the process to give users feedback.
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.
const COOKIE_KEY = "synapse_login_fallback_qs";
/*
* Submit a login request.
*
* type: The login type as a string (e.g. "m.login.foo").
* data: An object of data specific to the login type.
* extra: (Optional) An object to search for extra information to send with the
* login request, e.g. device_id.
* callback: (Optional) Function to call on successful login.
*/
function submitLogin(type, data, extra, callback) {
console.log("Logging in with " + type);
setTitle(TITLE_POST_AUTH);
// Add the login type.
data.type = type;
// Add the device information, if it was provided.
if (extra.device_id) {
data.device_id = extra.device_id;
}
if (extra.initial_device_display_name) {
data.initial_device_display_name = extra.initial_device_display_name;
}
$.post(matrixLogin.endpoint, JSON.stringify(data), function(response) {
if (callback) {
callback();
}
matrixLogin.onLogin(response);
}).fail(errorFunc);
}
/*
* 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.
showLogin(true);
if (err.responseJSON && err.responseJSON.error) {
setFeedbackString(err.responseJSON.error + " (" + err.responseJSON.errcode + ")");
}
else {
setFeedbackString("Request failed: " + err.status);
}
}
/*
* Display an error to the user.
*/
function setFeedbackString(text) {
$("#feedback").text(text);
}
/*
* (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 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 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;
}
// Otherwise, show the SSO form
$("#sso_flow").show();
}
if (matrixLogin.serverAcceptsPassword) {
$("#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();
}
$("#loading").hide();
}
/*
* Hides the forms and shows a loading throbber.
*/
function showSpinner() {
$("#password_flow").hide();
$("#sso_flow").hide();
$("#no_login_types").hide();
$("#loading").show();
}
/*
* Helper to show the page's main title.
*/
function setTitle(title) {
$("#title").text(title);
}
/*
* Query the login endpoint for the homeserver's supported flows.
*
* This populates matrixLogin.serverAccepts* variables.
*/
function fetchLoginFlows(cb) {
$.get(matrixLogin.endpoint, function(response) {
for (var i = 0; i < response.flows.length; i++) {
var flow = response.flows[i];
if ("m.login.sso" === flow.type) {
matrixLogin.serverAcceptsSso = true;
console.log("Server accepts SSO");
}
if ("m.login.password" === flow.type) {
matrixLogin.serverAcceptsPassword = true;
console.log("Server accepts password");
}
}
cb();
}).fail(errorFunc);
}
/*
* Called on load to fetch login flows and attempt SSO login (if a token is available).
*/
matrixLogin.onLoad = function() {
fetchLoginFlows(function() {
// (Maybe) attempt logging in via SSO if a token is available.
if (!tryTokenLogin()) {
showLogin(false);
}
});
};
/*
* Submit simple user & password login.
*/
matrixLogin.passwordLogin = function() {
var user = $("#user_id").val();
var pwd = $("#password").val();
setFeedbackString("");
showSpinner();
submitLogin(
"m.login.password",
{user: user, password: pwd},
parseQsFromUrl());
};
/*
* The onLogin function gets called after a successful 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.");
};
/*
* Process the query parameters from the current URL into an object.
*/
function parseQsFromUrl() {
var pos = window.location.href.indexOf("?");
if (pos == -1) {
return {};
}
var query = window.location.href.substr(pos + 1);
var result = {};
query.split("&").forEach(function(part) {
var item = part.split("=");
var key = item[0];
var val = item[1];
if (val) {
val = decodeURIComponent(val);
}
result[key] = val;
});
return result;
}
/*
* Process the cookies and return an object.
*/
function parseCookies() {
var allCookies = document.cookie;
var result = {};
allCookies.split(";").forEach(function(part) {
var item = part.split("=");
// Cookies might have arbitrary whitespace between them.
var key = item[0].trim();
// You can end up with a broken cookie that doesn't have an equals sign
// in it. Set to an empty value.
var val = (item[1] || "").trim();
// Values might be URI encoded.
if (val) {
val = decodeURIComponent(val);
}
result[key] = val;
});
return result;
}
/*
* Set a cookie that is valid for 1 hour.
*/
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.
*/
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.
*/
function tryTokenLogin() {
// Check if the login token is in the query parameters.
var qs = parseQsFromUrl();
var loginToken = qs.loginToken;
if (!loginToken) {
return false;
}
// Retrieve the original query parameters (from before the SSO redirect).
// They are stored as JSON in a cookie.
var cookies = parseCookies();
var originalQueryParams = JSON.parse(cookies[COOKIE_KEY] || "{}")
// If the login is successful, delete the cookie.
function callback() {
deleteCookie(COOKIE_KEY);
}
submitLogin(
"m.login.token",
{token: loginToken},
originalQueryParams,
callback);
return true;
}