|
| 1 | +window.matrixLogin = { |
| 2 | + endpoint: location.origin + "/_matrix/client/v3/login", |
| 3 | + serverAcceptsPassword: false, |
| 4 | + serverAcceptsSso: false, |
| 5 | +}; |
| 6 | + |
| 7 | +// Titles get updated through the process to give users feedback. |
| 8 | +const TITLE_PRE_AUTH = "Log in with one of the following methods"; |
| 9 | +const TITLE_POST_AUTH = "Logging in..."; |
| 10 | + |
| 11 | +// The cookie used to store the original query parameters when using SSO. |
| 12 | +const COOKIE_KEY = "dendrite_login_fallback_qs"; |
| 13 | + |
| 14 | +/* |
| 15 | + * Submit a login request. |
| 16 | + * |
| 17 | + * type: The login type as a string (e.g. "m.login.foo"). |
| 18 | + * data: An object of data specific to the login type. |
| 19 | + * extra: (Optional) An object to search for extra information to send with the |
| 20 | + * login request, e.g. device_id. |
| 21 | + * callback: (Optional) Function to call on successful login. |
| 22 | + */ |
| 23 | +function submitLogin(type, data, extra, callback) { |
| 24 | + console.log("Logging in with " + type); |
| 25 | + setTitle(TITLE_POST_AUTH); |
| 26 | + |
| 27 | + // Add the login type. |
| 28 | + data.type = type; |
| 29 | + |
| 30 | + // Add the device information, if it was provided. |
| 31 | + if (extra.device_id) { |
| 32 | + data.device_id = extra.device_id; |
| 33 | + } |
| 34 | + if (extra.initial_device_display_name) { |
| 35 | + data.initial_device_display_name = extra.initial_device_display_name; |
| 36 | + } |
| 37 | + |
| 38 | + $.post(matrixLogin.endpoint, JSON.stringify(data), function(response) { |
| 39 | + if (callback) { |
| 40 | + callback(); |
| 41 | + } |
| 42 | + matrixLogin.onLogin(response); |
| 43 | + }).fail(errorFunc); |
| 44 | +} |
| 45 | + |
| 46 | +/* |
| 47 | + * Display an error to the user and show the login form again. |
| 48 | + */ |
| 49 | +function errorFunc(err) { |
| 50 | + // We want to show the error to the user rather than redirecting immediately to the |
| 51 | + // SSO portal (if SSO is the only login option), so we inhibit the redirect. |
| 52 | + showLogin(true); |
| 53 | + |
| 54 | + if (err.responseJSON && err.responseJSON.error) { |
| 55 | + setFeedbackString(err.responseJSON.error + " (" + err.responseJSON.errcode + ")"); |
| 56 | + } |
| 57 | + else { |
| 58 | + setFeedbackString("Request failed: " + err.status); |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +/* |
| 63 | + * Display an error to the user. |
| 64 | + */ |
| 65 | +function setFeedbackString(text) { |
| 66 | + $("#feedback").text(text); |
| 67 | +} |
| 68 | + |
| 69 | +/* |
| 70 | + * (Maybe) Show the login forms. |
| 71 | + * |
| 72 | + * This actually does a few unrelated functions: |
| 73 | + * |
| 74 | + * * Configures the SSO redirect URL to come back to this page. |
| 75 | + * * Configures and shows the SSO form, if the server supports SSO. |
| 76 | + * * Otherwise, shows the password form. |
| 77 | + */ |
| 78 | +function showLogin(inhibitRedirect) { |
| 79 | + setTitle(TITLE_PRE_AUTH); |
| 80 | + |
| 81 | + // If inhibitRedirect is false, and SSO is the only supported login method, |
| 82 | + // we can redirect straight to the SSO page. |
| 83 | + if (matrixLogin.serverAcceptsSso) { |
| 84 | + // Set the redirect to come back to this page, a login token will get |
| 85 | + // added as a query parameter and handled after the redirect. |
| 86 | + $("#sso_redirect_url").val(window.location.origin + window.location.pathname); |
| 87 | + |
| 88 | + // Before submitting SSO, set the current query parameters into a cookie |
| 89 | + // for retrieval later. |
| 90 | + var qs = parseQsFromUrl(); |
| 91 | + setCookie(COOKIE_KEY, JSON.stringify(qs)); |
| 92 | + |
| 93 | + // If password is not supported and redirects are allowed, then submit |
| 94 | + // the form (redirecting to the SSO provider). |
| 95 | + if (!inhibitRedirect && !matrixLogin.serverAcceptsPassword) { |
| 96 | + $("#sso_form").submit(); |
| 97 | + return; |
| 98 | + } |
| 99 | + |
| 100 | + // Otherwise, show the SSO form |
| 101 | + $("#sso_flow").show(); |
| 102 | + } |
| 103 | + |
| 104 | + if (matrixLogin.serverAcceptsPassword) { |
| 105 | + $("#password_flow").show(); |
| 106 | + } |
| 107 | + |
| 108 | + // If neither password or SSO are supported, show an error to the user. |
| 109 | + if (!matrixLogin.serverAcceptsPassword && !matrixLogin.serverAcceptsSso) { |
| 110 | + $("#no_login_types").show(); |
| 111 | + } |
| 112 | + |
| 113 | + $("#loading").hide(); |
| 114 | +} |
| 115 | + |
| 116 | +/* |
| 117 | + * Hides the forms and shows a loading throbber. |
| 118 | + */ |
| 119 | +function showSpinner() { |
| 120 | + $("#password_flow").hide(); |
| 121 | + $("#sso_flow").hide(); |
| 122 | + $("#no_login_types").hide(); |
| 123 | + $("#loading").show(); |
| 124 | +} |
| 125 | + |
| 126 | +/* |
| 127 | + * Helper to show the page's main title. |
| 128 | + */ |
| 129 | +function setTitle(title) { |
| 130 | + $("#title").text(title); |
| 131 | +} |
| 132 | + |
| 133 | +/* |
| 134 | + * Query the login endpoint for the homeserver's supported flows. |
| 135 | + * |
| 136 | + * This populates matrixLogin.serverAccepts* variables. |
| 137 | + */ |
| 138 | +function fetchLoginFlows(cb) { |
| 139 | + $.get(matrixLogin.endpoint, function(response) { |
| 140 | + for (var i = 0; i < response.flows.length; i++) { |
| 141 | + var flow = response.flows[i]; |
| 142 | + if ("m.login.sso" === flow.type) { |
| 143 | + matrixLogin.serverAcceptsSso = true; |
| 144 | + console.log("Server accepts SSO"); |
| 145 | + } |
| 146 | + if ("m.login.password" === flow.type) { |
| 147 | + matrixLogin.serverAcceptsPassword = true; |
| 148 | + console.log("Server accepts password"); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + cb(); |
| 153 | + }).fail(errorFunc); |
| 154 | +} |
| 155 | + |
| 156 | +/* |
| 157 | + * Called on load to fetch login flows and attempt SSO login (if a token is available). |
| 158 | + */ |
| 159 | +matrixLogin.onLoad = function() { |
| 160 | + fetchLoginFlows(function() { |
| 161 | + // (Maybe) attempt logging in via SSO if a token is available. |
| 162 | + if (!tryTokenLogin()) { |
| 163 | + showLogin(false); |
| 164 | + } |
| 165 | + }); |
| 166 | +}; |
| 167 | + |
| 168 | +/* |
| 169 | + * Submit simple user & password login. |
| 170 | + */ |
| 171 | +matrixLogin.passwordLogin = function() { |
| 172 | + var user = $("#user_id").val(); |
| 173 | + var pwd = $("#password").val(); |
| 174 | + |
| 175 | + setFeedbackString(""); |
| 176 | + |
| 177 | + showSpinner(); |
| 178 | + submitLogin( |
| 179 | + "m.login.password", |
| 180 | + {user: user, password: pwd}, |
| 181 | + parseQsFromUrl()); |
| 182 | +}; |
| 183 | + |
| 184 | +/* |
| 185 | + * The onLogin function gets called after a successful login. |
| 186 | + * |
| 187 | + * It is expected that implementations override this to be notified when the |
| 188 | + * login is complete. The response to the login call is provided as the single |
| 189 | + * parameter. |
| 190 | + */ |
| 191 | +matrixLogin.onLogin = function(response) { |
| 192 | + // clobber this function |
| 193 | + console.warn("onLogin - This function should be replaced to proceed."); |
| 194 | +}; |
| 195 | + |
| 196 | +/* |
| 197 | + * Process the query parameters from the current URL into an object. |
| 198 | + */ |
| 199 | +function parseQsFromUrl() { |
| 200 | + var pos = window.location.href.indexOf("?"); |
| 201 | + if (pos == -1) { |
| 202 | + return {}; |
| 203 | + } |
| 204 | + var query = window.location.href.substr(pos + 1); |
| 205 | + |
| 206 | + var result = {}; |
| 207 | + query.split("&").forEach(function(part) { |
| 208 | + var item = part.split("="); |
| 209 | + var key = item[0]; |
| 210 | + var val = item[1]; |
| 211 | + |
| 212 | + if (val) { |
| 213 | + val = decodeURIComponent(val); |
| 214 | + } |
| 215 | + result[key] = val; |
| 216 | + }); |
| 217 | + return result; |
| 218 | +} |
| 219 | + |
| 220 | +/* |
| 221 | + * Process the cookies and return an object. |
| 222 | + */ |
| 223 | +function parseCookies() { |
| 224 | + var allCookies = document.cookie; |
| 225 | + var result = {}; |
| 226 | + allCookies.split(";").forEach(function(part) { |
| 227 | + var item = part.split("="); |
| 228 | + // Cookies might have arbitrary whitespace between them. |
| 229 | + var key = item[0].trim(); |
| 230 | + // You can end up with a broken cookie that doesn't have an equals sign |
| 231 | + // in it. Set to an empty value. |
| 232 | + var val = (item[1] || "").trim(); |
| 233 | + // Values might be URI encoded. |
| 234 | + if (val) { |
| 235 | + val = decodeURIComponent(val); |
| 236 | + } |
| 237 | + result[key] = val; |
| 238 | + }); |
| 239 | + return result; |
| 240 | +} |
| 241 | + |
| 242 | +/* |
| 243 | + * Set a cookie that is valid for 1 hour. |
| 244 | + */ |
| 245 | +function setCookie(key, value) { |
| 246 | + // The maximum age is set in seconds. |
| 247 | + var maxAge = 60 * 60; |
| 248 | + // Set the cookie, this defaults to the current domain and path. |
| 249 | + document.cookie = key + "=" + encodeURIComponent(value) + ";max-age=" + maxAge + ";sameSite=lax"; |
| 250 | +} |
| 251 | + |
| 252 | +/* |
| 253 | + * Removes a cookie by key. |
| 254 | + */ |
| 255 | +function deleteCookie(key) { |
| 256 | + // Delete a cookie by setting the expiration to 0. (Note that the value |
| 257 | + // doesn't matter.) |
| 258 | + document.cookie = key + "=deleted;expires=0"; |
| 259 | +} |
| 260 | + |
| 261 | +/* |
| 262 | + * Submits the login token if one is found in the query parameters. Returns a |
| 263 | + * boolean of whether the login token was found or not. |
| 264 | + */ |
| 265 | +function tryTokenLogin() { |
| 266 | + // Check if the login token is in the query parameters. |
| 267 | + var qs = parseQsFromUrl(); |
| 268 | + |
| 269 | + var loginToken = qs.loginToken; |
| 270 | + if (!loginToken) { |
| 271 | + return false; |
| 272 | + } |
| 273 | + |
| 274 | + // Retrieve the original query parameters (from before the SSO redirect). |
| 275 | + // They are stored as JSON in a cookie. |
| 276 | + var cookies = parseCookies(); |
| 277 | + var originalQueryParams = JSON.parse(cookies[COOKIE_KEY] || "{}") |
| 278 | + |
| 279 | + // If the login is successful, delete the cookie. |
| 280 | + function callback() { |
| 281 | + deleteCookie(COOKIE_KEY); |
| 282 | + } |
| 283 | + |
| 284 | + submitLogin( |
| 285 | + "m.login.token", |
| 286 | + {token: loginToken}, |
| 287 | + originalQueryParams, |
| 288 | + callback); |
| 289 | + |
| 290 | + return true; |
| 291 | +} |
0 commit comments