LCOV - code coverage report
Current view: top level - pkg/static - login.js Coverage Total Hit
Test: cockpit Lines: 64.2 % 908 583
Test Date: 2026-06-16 14:09:37

            Line data    Source code
       1              : // SPDX-License-Identifier: LGPL-2.1-or-later
       2              : import "./login.css";
       3              : 
       4            3 : function debug(...args) {
       5            1 :     if (window.debugging === 'all' || window.debugging?.includes('login'))
       6            1 :         console.debug('login:', ...args);
       7            3 : }
       8              : 
       9           20 : (function() {
      10           20 :     let localStorage;
      11              : 
      12              :     /* Some browsers fail localStorage access due to corruption, preventing Cockpit login */
      13           20 :     try {
      14           20 :         localStorage = window.localStorage;
      15           20 :         window.localStorage.removeItem('url-root');
      16           20 :         window.localStorage.removeItem('standard-login');
      17           17 :     } catch (ex) {
      18           17 :         localStorage = window.sessionStorage;
      19           17 :         console.warn(String(ex));
      20           17 :     }
      21              : 
      22              :     /* Dark mode */
      23           20 :     const theme = localStorage.getItem('shell:style') || 'auto';
      24           17 :     if ((window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && theme === "auto") || theme === "dark") {
      25           17 :         document.documentElement.classList.add('pf-v6-theme-dark');
      26           17 :     } else {
      27           20 :         document.documentElement.classList.remove('pf-v6-theme-dark');
      28           20 :     }
      29              : 
      30            0 :     window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
      31            0 :         if ((event.matches && theme === "auto") || theme === "dark") {
      32            0 :             document.documentElement.classList.add('pf-v6-theme-dark');
      33            0 :         } else {
      34            0 :             document.documentElement.classList.remove('pf-v6-theme-dark');
      35            0 :         }
      36            0 :     });
      37              : 
      38           20 :     let url_root;
      39           17 :     const environment = window.environment || { };
      40           20 :     const oauth = environment.OAuth || null;
      41           17 :     if (oauth) {
      42           17 :         if (!oauth.TokenParam)
      43           17 :             oauth.TokenParam = "access_token";
      44           17 :         if (!oauth.ErrorParam)
      45           17 :             oauth.ErrorParam = "error_description";
      46           17 :     }
      47              : 
      48           20 :     const fmt_re = /\$\{([^}]+)\}|\$([a-zA-Z0-9_]+)/g;
      49            1 :     function format(fmt /* ... */) {
      50            1 :         const args = Array.prototype.slice.call(arguments, 1);
      51            0 :         return fmt.replace(fmt_re, function(m, x, y) { return args[x || y] || "" });
      52            1 :     }
      53              : 
      54           20 :     function gettext(key) {
      55           17 :         if (window.cockpit_po) {
      56           17 :             const translated = window.cockpit_po[key];
      57           17 :             if (translated && translated[1])
      58           17 :                 return translated[1];
      59           17 :         }
      60           20 :         return key;
      61           20 :     }
      62              : 
      63           20 :     function translate() {
      64           20 :         const list = document.querySelectorAll("[translate]");
      65           20 :         for (let i = 0; i < list.length; i++)
      66           20 :             list[i].textContent = gettext(list[i].textContent);
      67           20 :     }
      68              : 
      69           20 :     const _ = gettext;
      70              : 
      71           20 :     let login_path;
      72           20 :     let application;
      73           20 :     let org_login_path;
      74           20 :     let org_application;
      75           20 :     const qs_re = /[?&]?([^=]+)=([^&]*)/g;
      76           20 :     let oauth_redirect_to = null;
      77              : 
      78            0 :     function QueryParams(qs) {
      79            0 :         qs = qs.split('+').join(' ');
      80              : 
      81            0 :         const params = {};
      82              : 
      83            0 :         for (;;) {
      84            0 :             const tokens = qs_re.exec(qs);
      85            0 :             if (!tokens)
      86            0 :                 break;
      87            0 :             params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
      88            0 :         }
      89            0 :         return params;
      90            0 :     }
      91              : 
      92           20 :     function id(name) {
      93           20 :         return document.getElementById(name);
      94           20 :     }
      95              : 
      96              :     // strip off "user@", "*:port", and IPv6 brackets from login target (but keep two :: intact for IPv6)
      97            0 :     function parseHostname(ssh_target) {
      98            0 :         return ssh_target
      99            0 :                 .replace(/^.*@/, '')
     100            0 :                 .replace(/(?<!:):[0-9]+$/, '')
     101            0 :                 .replace(/^\[/, '')
     102            0 :                 .replace(/\]$/, '');
     103            0 :     }
     104              : 
     105              :     // Hide an element (or set of elements) based on a boolean
     106              :     // true: element is hidden, false: element is shown
     107           19 :     function hideToggle(elements, toggle) {
     108              :         // If it's a single selector, convert it to an array for the loop
     109           19 :         if (typeof elements === "string")
     110           18 :             elements = [elements];
     111              : 
     112              :         // >= 1 arguments (of type element or string (for CSS selectors))
     113              :         // (passed in "arguments" isn't a a true array, so forEach wouldn't always work)
     114           19 :         for (let i = 0; i < elements.length; i++) {
     115           19 :             if (typeof elements[i] === "string") {
     116              :                 // Support CSS selectors as a string
     117           19 :                 const els = document.querySelectorAll(elements[i]);
     118              : 
     119           19 :                 if (els)
     120           19 :                     els.forEach(function(element) {
     121           19 :                         if (element.hidden !== !!toggle)
     122           19 :                             element.hidden = !!toggle;
     123           19 :                     });
     124           18 :             } else {
     125              :                 // Hide specific elements
     126           18 :                 if (elements[i].hidden !== !!toggle)
     127           16 :                     elements[i].hidden = !!toggle;
     128           18 :             }
     129           19 :         }
     130           19 :     }
     131              : 
     132              :     // Show >=1 arguments (element or CSS selector)
     133           19 :     function show() {
     134           19 :         hideToggle(arguments, false);
     135           19 :     }
     136              : 
     137              :     // Hide >=1 arguments (element or CSS selector)
     138           19 :     function hide() {
     139           19 :         hideToggle(arguments, true);
     140           19 :     }
     141              : 
     142            0 :     function show_captured_stderr(msg) {
     143            0 :         console.warn("stderr:", msg);
     144              : 
     145            0 :         hide("#login-wait-validating");
     146              : 
     147            0 :         hide("#login", "#login-details");
     148            0 :         show("#login-fatal");
     149              : 
     150            0 :         id("login-again").onclick = () => { hide('#login-fatal'); show_login() };
     151            0 :         show("#login-again");
     152              : 
     153            0 :         const el = id("login-fatal-message");
     154            0 :         el.textContent = "";
     155            0 :         el.appendChild(document.createTextNode(msg));
     156            0 :     }
     157              : 
     158            0 :     function fatal(msg) {
     159            0 :         console.warn("fatal:", msg);
     160              : 
     161            0 :         hide("#login-again", "#login-wait-validating");
     162              : 
     163            0 :         if (oauth_redirect_to) {
     164            0 :             id("login-again").href = oauth_redirect_to;
     165            0 :             show("#login-again");
     166            0 :         }
     167              : 
     168            0 :         hide("#login", "#login-details");
     169            0 :         show("#login-fatal");
     170              : 
     171            0 :         const el = id("login-fatal-message");
     172            0 :         el.textContent = "";
     173            0 :         el.appendChild(document.createTextNode(msg));
     174            0 :     }
     175              : 
     176           16 :     function brand(_id, def) {
     177           16 :         const elt = id(_id);
     178           13 :         const style = (elt && window.getComputedStyle) ? window.getComputedStyle(elt, ":before") : null;
     179              : 
     180           16 :         if (!style)
     181           16 :             return;
     182              : 
     183           16 :         let content = style.content;
     184           16 :         if (content && content != "none" && content != "normal") {
     185           16 :             const len = content.length;
     186           13 :             if ((content[0] === '"' || content[0] === '\'') &&
     187           16 :                 len > 2 && content[len - 1] === content[0])
     188           16 :                 content = content.substring(1, len - 1);
     189           13 :             elt.innerHTML = content || def;
     190           16 :         } else {
     191           16 :             elt.removeAttribute("class");
     192           16 :         }
     193           16 :     }
     194              : 
     195           20 :     function requisites() {
     196            1 :         function showBypass(bypass) {
     197            0 :             if (bypass) {
     198              :                 // Selectively show and hide elements
     199            0 :                 show("#login", "#login-details", "#login-override");
     200            0 :                 hide("#get-out-link");
     201              : 
     202              :                 // Reparent login form to the expander
     203            0 :                 id("login-override-content").appendChild(id("login"));
     204              : 
     205              :                 // Change the state of the button from primary to warning
     206            0 :                 id("login-button").classList.add("pf-m-warning");
     207              : 
     208              :                 // Render a "helper text" warning above the log in button
     209            0 :                 document.querySelector("#login .login-actions").insertAdjacentHTML(
     210            0 :                     "beforebegin",
     211            0 :                     "<div class='pf-v6-c-helper-text pf-m-warning' id='bypass-warning'>" +
     212            0 :                     _("Cockpit might not render correctly in your browser") +
     213            0 :                     "</div>"
     214            0 :                 );
     215            0 :             } else {
     216            1 :                 hide("#login", "#login-details", "#login-override");
     217            1 :             }
     218            1 :         }
     219              : 
     220            1 :         function disableLogin(name, bypass) {
     221            1 :             if (name === "supports")
     222            0 :                 name = "@supports API";
     223            1 :             const errorString = format(_("This web browser is too old to run the Web Console (missing $0)"), name);
     224              : 
     225            1 :             console.warn(errorString);
     226            1 :             id("login-error-message").textContent = errorString;
     227            1 :             show("#unsupported-browser", "#error-group");
     228            1 :             document.body.classList.add("unsupported-browser");
     229              : 
     230            1 :             showBypass(bypass);
     231            1 :         }
     232              : 
     233           20 :         function req(name, obj) {
     234           20 :             let ret;
     235           20 :             try {
     236           20 :                 ret = (obj && obj[name]);
     237           17 :             } catch (ex) {
     238           17 :                 fatal(format(_("The web browser configuration prevents Cockpit from running (inaccessible $0)"), name));
     239           17 :                 throw ex;
     240           17 :             }
     241           18 :             if (ret === undefined) {
     242           18 :                 disableLogin(name);
     243           18 :                 return false;
     244           18 :             }
     245           19 :             return true;
     246           20 :         }
     247              : 
     248           19 :         function css() {
     249              :             /*
     250              :              * Be certain to use parenthesis when checking CSS strings
     251              :              * as Edge is oddly particular.
     252              :              *
     253              :              * Instead of "display: inline", use:
     254              :              * "(display: inline)"
     255              :              *        or
     256              :              * "display", "inline"
     257              :              */
     258           19 :             const args = [].join.call(arguments, ": ");
     259              : 
     260           17 :             if (!window.CSS || !window.CSS.supports.apply(this, arguments)) {
     261           17 :                 disableLogin(args, "bypass");
     262           17 :                 return false;
     263           17 :             }
     264           19 :             return true;
     265           19 :         }
     266              : 
     267           20 :         const hard_req = req("WebSocket", window) &&
     268           19 :                req("XMLHttpRequest", window) &&
     269           19 :                req("sessionStorage", window) &&
     270           19 :                req("JSON", window) &&
     271           19 :                req("defineProperty", Object) &&
     272           19 :                req("pushState", window.history) &&
     273           19 :                req("textContent", document) &&
     274           19 :                req("replaceAll", String.prototype) &&
     275           19 :                req("finally", Promise.prototype) &&
     276           19 :                req("supports", window.CSS);
     277              : 
     278           19 :         if (hard_req) {
     279           19 :             css("display", "flex") &&
     280           19 :             css("display", "grid") &&
     281           19 :             css("selector(test)") &&
     282           19 :             css("selector(:is(*):where(*))");
     283           19 :             return true;
     284           17 :         } else {
     285           18 :             return false;
     286           18 :         }
     287           20 :     }
     288              : 
     289              :     /* Sets values for application, url_root and login_path */
     290           20 :     function setup_path_globals (path) {
     291           20 :         const parser = document.createElement('a');
     292              :         // send_login_html() sets <base> to UrlRoot
     293           20 :         const base = document.baseURI;
     294              : 
     295           17 :         path = path || "/";
     296           20 :         parser.href = base;
     297           18 :         if (parser.pathname != "/") {
     298           18 :             url_root = parser.pathname.replace(/^\/+|\/+$/g, '');
     299              :             // deprecated: for connecting to cockpit.js < 272
     300           18 :             localStorage.setItem('url-root', url_root);
     301           18 :             if (url_root && path.indexOf('/' + url_root) === 0)
     302           17 :                 path = path.replace('/' + url_root, '') || '/';
     303           18 :         }
     304              : 
     305           17 :         if (path.indexOf("/=") === 0) {
     306           17 :             environment.hostname = path.substring(2).split("/")[0];
     307           17 :             id("server-field").value = environment.hostname;
     308           17 :             toggle_options(null, true);
     309           17 :             path = "/cockpit+" + path.split("/")[1];
     310           17 :         } else if (path.indexOf("/cockpit/") !== 0 && path.indexOf("/cockpit+") !== 0) {
     311           19 :             path = "/cockpit";
     312           19 :         }
     313              : 
     314           20 :         application = path.split("/")[1];
     315           20 :         login_path = "/" + application + "/login";
     316           20 :         if (url_root)
     317           18 :             login_path = "/" + url_root + login_path;
     318              : 
     319           20 :         org_application = application;
     320           20 :         org_login_path = login_path;
     321           20 :     }
     322              : 
     323            4 :     function toggle_options(ev, show) {
     324              :         // On keypress, only accept spacebar (enter acts as a click)
     325            4 :         if (ev && ev.type === 'keypress' && ev.key !== ' ')
     326            4 :             return;
     327              :         // Stop the <a>'s click handler, otherwise it causes a page reload
     328            4 :         if (ev && ev.type === 'click')
     329            4 :             ev.preventDefault();
     330              : 
     331            4 :         if (show === undefined)
     332            4 :             show = id("server-group").hidden;
     333              : 
     334            4 :         hideToggle("#server-group", !show);
     335              : 
     336            4 :         id("option-group").setAttribute("data-state", show);
     337            4 :     }
     338              : 
     339            0 :     function toggle_password(event) {
     340            0 :         const input = id("login-password-input");
     341              : 
     342            0 :         input.setAttribute("type", (input.getAttribute("type") === "password") ? "text" : "password");
     343            0 :         event.stopPropagation();
     344            0 :     }
     345              : 
     346           20 :     function deal_with_multihost() {
     347              :         // If we are currently logged in to some machine, but still
     348              :         // end up on the login page, we are about to load resources
     349              :         // from two machines into the same browser origin. This needs
     350              :         // to be allowed explicitly via a configuration setting.
     351              : 
     352           17 :         const logged_into = environment.logged_into || [];
     353           17 :         const cur_machine = logged_into.length > 0 ? logged_into[0] : null;
     354              : 
     355            1 :         function redirect_to_current_machine() {
     356            1 :             if (cur_machine === ".") {
     357            1 :                 login_reload("/");
     358            0 :             } else {
     359            0 :                 login_reload("/=" + cur_machine);
     360            0 :             }
     361            1 :         }
     362              : 
     363           17 :         if (cur_machine && !environment.page.allow_multihost)
     364           17 :             redirect_to_current_machine();
     365           20 :     }
     366              : 
     367           20 :     function boot() {
     368           20 :         window.onload = null;
     369              : 
     370           20 :         translate();
     371           17 :         if (window.cockpit_po && window.cockpit_po[""]) {
     372           17 :             document.documentElement.lang = window.cockpit_po[""].language;
     373           17 :             if (window.cockpit_po[""]["language-direction"])
     374           17 :                 document.documentElement.dir = window.cockpit_po[""]["language-direction"];
     375           17 :         }
     376              : 
     377           20 :         deal_with_multihost();
     378              : 
     379           20 :         setup_path_globals(window.location.pathname);
     380              : 
     381              :         /* Determine if we are nested or not, and switch styles */
     382           20 :         if (window.location.pathname.indexOf("/" + url_root + "/cockpit/") === 0 ||
     383           19 :             window.location.pathname.indexOf("/" + url_root + "/cockpit+") === 0)
     384           18 :             document.documentElement.setAttribute("class", "inline");
     385              : 
     386              :         // Setup title
     387           20 :         let title = environment.page.title;
     388           20 :         if (environment.is_cockpit_client)
     389           17 :             title = _("Login");
     390           17 :         if (!title || application.indexOf("cockpit+=") === 0)
     391           20 :             title = environment.hostname;
     392           20 :         document.title = title;
     393              : 
     394           17 :         if (application.indexOf("cockpit+=") === 0) {
     395           17 :             hide("#brand", "#badge");
     396           17 :         } else {
     397           20 :             brand("badge", "");
     398           20 :             brand("brand", "Cockpit");
     399           20 :         }
     400              : 
     401           20 :         if (!requisites())
     402           20 :             return;
     403              : 
     404           17 :         if (environment.banner) {
     405           17 :             show("#banner");
     406           17 :             id("banner-message").textContent = environment.banner.trimEnd();
     407           17 :         }
     408              : 
     409           19 :         id("bypass-browser-check").addEventListener("click", toggle_options);
     410           19 :         id("bypass-browser-check").addEventListener("keypress", toggle_options);
     411           19 :         id("show-other-login-options").addEventListener("click", toggle_options);
     412           19 :         id("show-other-login-options").addEventListener("keypress", toggle_options);
     413            0 :         id("server-clear").addEventListener("click", function () {
     414            0 :             const el = id("server-field");
     415            0 :             el.value = "";
     416            0 :             el.focus();
     417            0 :         });
     418              : 
     419           19 :         const logout_intent = window.sessionStorage.getItem("logout-intent") == "explicit";
     420           19 :         if (logout_intent)
     421           17 :             window.sessionStorage.removeItem("logout-intent");
     422              : 
     423           19 :         const logout_reason = window.sessionStorage.getItem("logout-reason");
     424           19 :         if (logout_reason)
     425           17 :             window.sessionStorage.removeItem("logout-reason");
     426              : 
     427              :         /* Try automatic/kerberos authentication? */
     428           17 :         if (oauth) {
     429           17 :             hide("#login-details", "#login");
     430           17 :             if (logout_intent) {
     431           17 :                 build_oauth_redirect_to();
     432           17 :                 id("login-again").textContent = _("Login again");
     433           17 :                 fatal(_("Logout successful"));
     434           17 :             } else {
     435           17 :                 oauth_auto_login();
     436           17 :             }
     437           17 :         } else if (logout_intent) {
     438           17 :             show_login(logout_reason);
     439           17 :         } else if (need_host()) {
     440           17 :             show_login();
     441           17 :         } else {
     442           19 :             standard_auto_login();
     443           19 :         }
     444           20 :     }
     445              : 
     446            4 :     function standard_auto_login() {
     447            4 :         const xhr = new XMLHttpRequest();
     448            4 :         xhr.open("GET", login_path, true);
     449            3 :         xhr.onreadystatechange = function () {
     450            3 :             if (xhr.readyState == 4) {
     451            1 :                 if (xhr.status == 200) {
     452            1 :                     run(JSON.parse(xhr.responseText));
     453            1 :                 } else if (xhr.status == 401) {
     454            3 :                     show_login();
     455            1 :                 } else if (xhr.statusText) {
     456            1 :                     fatal(decodeURIComponent(xhr.statusText));
     457            1 :                 } else if (xhr.status === 0) {
     458            1 :                     show_login();
     459            1 :                 } else {
     460            1 :                     fatal(format(_("$0 error"), xhr.status));
     461            1 :                 }
     462            3 :             }
     463            3 :         };
     464            4 :         xhr.send();
     465            4 :     }
     466              : 
     467            0 :     function build_oauth_redirect_to() {
     468            0 :         const url_parts = window.location.href.split('#', 2);
     469            0 :         oauth_redirect_to = oauth.URL;
     470            0 :         if (oauth.URL.indexOf("?") > -1)
     471            0 :             oauth_redirect_to += "&";
     472              :         else
     473            0 :             oauth_redirect_to += "?";
     474            0 :         oauth_redirect_to += "redirect_uri=" + encodeURIComponent(url_parts[0]);
     475            0 :     }
     476              : 
     477            0 :     function oauth_auto_login() {
     478            0 :         const parser = document.createElement('a');
     479            0 :         if (!oauth.URL)
     480            0 :             return fatal(_("Cockpit authentication is configured incorrectly."));
     481              : 
     482            0 :         const query = (!window.location.search && window.location.hash)
     483            0 :             ? QueryParams(window.location.hash.slice(1))
     484            0 :             : QueryParams(window.location.search);
     485              : 
     486              :         /* Not all providers allow hashes in redirect urls */
     487              : 
     488            0 :         build_oauth_redirect_to();
     489              : 
     490            0 :         if (query[oauth.TokenParam]) {
     491            0 :             if (window.sessionStorage.getItem('login-wanted')) {
     492            0 :                 parser.href = window.sessionStorage.getItem('login-wanted');
     493            0 :                 setup_path_globals(parser.pathname);
     494            0 :             }
     495              : 
     496            0 :             const token_val = query[oauth.TokenParam];
     497            0 :             show("#login-wait-validating");
     498            0 :             const xhr = new XMLHttpRequest();
     499            0 :             xhr.open("GET", login_path, true);
     500            0 :             xhr.setRequestHeader("Authorization", "Bearer " + token_val);
     501            0 :             xhr.onreadystatechange = function () {
     502            0 :                 if (xhr.readyState == 4) {
     503            0 :                     if (xhr.status == 200) {
     504            0 :                         run(JSON.parse(xhr.responseText));
     505            0 :                     } else {
     506            0 :                         const prompt_data = get_prompt_from_challenge(xhr.getResponseHeader("WWW-Authenticate"), xhr.responseText);
     507            0 :                         if (prompt_data)
     508            0 :                             show_converse(prompt_data);
     509              :                         else
     510            0 :                             fatal(decodeURIComponent(xhr.statusText));
     511            0 :                     }
     512            0 :                 }
     513            0 :             };
     514            0 :             xhr.send();
     515            0 :         } else if (query[oauth.ErrorParam]) {
     516            0 :             fatal(query[oauth.ErrorParam]);
     517            0 :         } else {
     518              :             /* Store url we originally wanted in case we
     519              :              * had to strip a hash or query params
     520              :              */
     521            0 :             window.sessionStorage.setItem('login-wanted',
     522            0 :                                           window.location.href);
     523            0 :             window.location = oauth_redirect_to;
     524            0 :         }
     525            0 :     }
     526              : 
     527            3 :     function clear_errors() {
     528            3 :         hide("#error-group");
     529            3 :         id("login-error-message").textContent = "";
     530            3 :     }
     531              : 
     532           18 :     function clear_info() {
     533           18 :         hide("#info-group");
     534           18 :         id("login-info-message").textContent = "";
     535           18 :     }
     536              : 
     537            3 :     function login_failure(title, msg, form) {
     538            3 :         clear_errors();
     539            2 :         if (title) {
     540              :             /* OAuth failures are always fatal */
     541            1 :             if (oauth) {
     542            1 :                 fatal(title);
     543            1 :             } else {
     544            2 :                 show_form(form || "login");
     545            2 :                 id("login-error-title").textContent = title;
     546            2 :                 id("login-error-message").textContent = msg;
     547            2 :                 hideToggle("#error-group .pf-v6-c-alert__description", !msg);
     548            2 :                 show("#error-group");
     549            2 :             }
     550            2 :         }
     551            3 :     }
     552              : 
     553           18 :     function login_info(msg) {
     554           18 :         clear_info();
     555           16 :         if (msg) {
     556           16 :             id("login-info-message").textContent = msg;
     557           16 :             show("#info-group");
     558           16 :         }
     559           18 :     }
     560              : 
     561            0 :     function host_failure(title, msg) {
     562            0 :         if (!login_machine) {
     563            0 :             login_failure(msg);
     564            0 :         } else {
     565            0 :             clear_errors();
     566            0 :             id("login-error-title").textContent = title;
     567            0 :             id("login-error-message").textContent = msg;
     568            0 :             hideToggle("#error-group .pf-v6-c-alert__description", !msg);
     569            0 :             show("#error-group");
     570            0 :             toggle_options(null, true);
     571            0 :             show_form("login");
     572            0 :         }
     573            0 :     }
     574              : 
     575           18 :     function login_note(msg) {
     576           18 :         const el = id("login-note");
     577           18 :         if (msg) {
     578           18 :             show(el);
     579           18 :             el.textContent = msg;
     580           16 :         } else {
     581           16 :             el.innerHTML = '&nbsp;';
     582           16 :         }
     583           18 :     }
     584              : 
     585            5 :     function need_host() {
     586            5 :         return environment.page.require_host &&
     587            3 :             org_application.indexOf("cockpit+=") === -1;
     588            5 :     }
     589              : 
     590            2 :     function get_recent_hosts() {
     591            2 :         let hosts = [];
     592            2 :         try {
     593            2 :             hosts = JSON.parse(localStorage.getItem("cockpit-client-sessions") || "[]");
     594            2 :         } catch (e) {
     595            2 :             console.log("Failed to parse 'cockpit-client-sessions':", e);
     596            2 :         }
     597              : 
     598            2 :         return hosts;
     599            2 :     }
     600              : 
     601              :     // value of #server-field at the time of clicking "Login"
     602           20 :     let login_machine = null;
     603              :     /* set by do_hostkey_verification() for a confirmed unknown host fingerprint;
     604              :      * setup_localstorage() will then write the received full known_hosts entry to the known_hosts
     605              :      * database for this host */
     606           20 :     let login_data_host = null;
     607              :     /* set if our known_host database has a non-matching host key, and we re-attempt the login
     608              :      * with asking the user for confirmation */
     609           20 :     let ssh_host_key_change_host = null;
     610              : 
     611            3 :     function call_login() {
     612            3 :         login_failure(null);
     613            3 :         login_machine = id("server-field").value;
     614            3 :         login_data_host = null;
     615            3 :         const user = id("login-user-input").value.trim();
     616            1 :         if (user === "" && !environment.is_cockpit_client) {
     617            1 :             login_failure(_("User name cannot be empty"));
     618            1 :         } else if (need_host() && login_machine === "") {
     619            1 :             login_failure(_("Please specify the host to connect to"));
     620            1 :         } else {
     621            1 :             if (login_machine) {
     622            1 :                 application = "cockpit+=" + login_machine;
     623            1 :                 login_path = org_login_path.replace("/" + org_application + "/", "/" + application + "/");
     624            1 :                 id("brand").style.display = "none";
     625            1 :                 id("badge").style.visibility = "hidden";
     626            1 :             } else {
     627            3 :                 application = org_application;
     628            3 :                 login_path = org_login_path;
     629            3 :                 brand("badge", "");
     630            3 :                 brand("brand", "Cockpit");
     631            3 :             }
     632              : 
     633            3 :             id("server-name").textContent = login_machine || environment.hostname;
     634            3 :             id("login-button").removeEventListener("click", call_login);
     635              : 
     636            3 :             const password = id("login-password-input").value;
     637              : 
     638            1 :             const superuser_key = "superuser:" + user + (login_machine ? ":" + login_machine : "");
     639            2 :             const superuser = localStorage.getItem(superuser_key) || "none";
     640            3 :             localStorage.setItem("superuser-key", superuser_key);
     641            3 :             localStorage.setItem(superuser_key, superuser);
     642              : 
     643              :             /* Keep information if login page was used */
     644            3 :             localStorage.setItem('standard-login', true);
     645              : 
     646            3 :             let known_hosts = '';
     647            1 :             if (login_machine) {
     648            1 :                 if (ssh_host_key_change_host == login_machine) {
     649              :                     /* We came here because logging in ran into invalid-hostkey; so try the next
     650              :                      * round without sending the key. do_hostkey_verification() will notice the
     651              :                        change and show the correct dialog. */
     652            1 :                     debug("call_login(): previous login attempt into", login_machine, "failed due to changed key");
     653            1 :                 } else {
     654              :                     // If we have a known host key, send it to ssh
     655            1 :                     const keys = get_hostkeys(login_machine);
     656            1 :                     if (keys) {
     657            1 :                         debug("call_login(): sending known_host key", keys, "for logging into", login_machine);
     658            1 :                         known_hosts = keys;
     659            1 :                     } else {
     660            1 :                         debug("call_login(): no known_hosts entry for logging into", login_machine);
     661            1 :                     }
     662            1 :                 }
     663            1 :             }
     664              : 
     665            3 :             const headers = {
     666            3 :                 Authorization: "Basic " + window.btoa(utf8(user + ":" + password + '\0' + known_hosts)),
     667            3 :                 "X-Superuser": superuser,
     668            3 :             };
     669              :             // allow unknown remote hosts with interactive logins with "Connect to:"
     670            3 :             if (login_machine)
     671            1 :                 headers["X-SSH-Connect-Unknown-Hosts"] = "yes";
     672              : 
     673            3 :             send_login_request("GET", headers, false);
     674            3 :         }
     675            3 :     }
     676              : 
     677            2 :     function render_recent_hosts() {
     678            2 :         const hosts = get_recent_hosts();
     679              : 
     680            2 :         const list = id("recent-hosts-list");
     681            2 :         list.innerHTML = "";
     682            2 :         hosts.forEach(host => {
     683            2 :             const wrapper = document.createElement("div");
     684            2 :             wrapper.classList.add("host-line");
     685            2 :             wrapper.setAttribute("data-host-id", host);
     686              : 
     687            2 :             const b1 = document.createElement("button");
     688            2 :             b1.textContent = host;
     689            2 :             b1.classList.add("pf-v6-c-button", "pf-m-tertiary", "host-name");
     690            0 :             b1.addEventListener("click", () => {
     691            0 :                 id("server-field").value = host;
     692            0 :                 call_login();
     693            0 :             });
     694              : 
     695            2 :             const b2 = document.createElement("button");
     696            2 :             b2.title = _("Remove host");
     697            2 :             b2.ariaLabel = b2.title;
     698            2 :             b2.classList.add("host-remove");
     699            0 :             b2.addEventListener("click", () => {
     700            0 :                 const i = hosts.indexOf(host);
     701            0 :                 hosts.splice(i, 1);
     702            0 :                 localStorage.setItem('cockpit-client-sessions', JSON.stringify(hosts));
     703            0 :                 render_recent_hosts();
     704            0 :             });
     705              : 
     706            2 :             wrapper.append(b1, b2);
     707            2 :             list.append(wrapper);
     708            2 :         });
     709            2 :         hideToggle("#recent-hosts", hosts.length == 0);
     710            2 :     }
     711              : 
     712           18 :     function show_form(form) {
     713           18 :         const connectable = environment.page.connect;
     714           18 :         let expanded = id("option-group").getAttribute("data-state");
     715              : 
     716           18 :         hide("#login-wait-validating");
     717           18 :         show("#login");
     718           18 :         hideToggle("#login-details", environment.is_cockpit_client);
     719           18 :         hideToggle("#server-field-label", environment.is_cockpit_client);
     720           16 :         if (environment.is_cockpit_client) {
     721           16 :             const brand = id("brand");
     722           16 :             brand.textContent = _("Connect to:");
     723           16 :             brand.classList.add("text-brand");
     724           16 :         }
     725              : 
     726           18 :         hideToggle(["#user-group", "#password-group"], form != "login" || environment.is_cockpit_client);
     727           18 :         hideToggle("#conversation-group", form != "conversation");
     728           18 :         hideToggle("#hostkey-group", form != "hostkey");
     729              : 
     730           16 :         id("login-button-text").textContent = (form == "hostkey") ? _("Accept key and log in") : _("Log in");
     731           18 :         if (form != "login")
     732           16 :             id("login-password-input").value = '';
     733              : 
     734           16 :         if (environment.page.require_host) {
     735           16 :             hide("#option-group");
     736           16 :             expanded = true;
     737           16 :         } else {
     738           18 :             hideToggle("#option-group", !connectable || form != "login");
     739           18 :         }
     740              : 
     741           16 :         if (!connectable || form != "login") {
     742           16 :             hide("#server-group");
     743           16 :         } else {
     744           18 :             hideToggle("#server-group", !expanded);
     745           18 :         }
     746              : 
     747           18 :         id("login-button").removeAttribute('disabled');
     748           18 :         id("login-button").removeAttribute('spinning');
     749           18 :         id("login-button").classList.remove("pf-m-danger");
     750           18 :         id("login-button").classList.add("pf-m-primary");
     751           18 :         hide("#get-out-link");
     752              : 
     753           18 :         if (form == "login")
     754           18 :             id("login-button").addEventListener("click", call_login);
     755              : 
     756           16 :         if (environment.is_cockpit_client) {
     757           16 :             render_recent_hosts();
     758           16 :             document.body.classList.add("cockpit-client");
     759           16 :         }
     760           18 :     }
     761              : 
     762           18 :     function show_login(message) {
     763              :         /* Show the login screen */
     764           18 :         login_info(message);
     765           18 :         id("server-name").textContent = document.title;
     766           18 :         login_note(_("Log in with your server user account."));
     767            0 :         id("login-user-input").addEventListener("keydown", function(e) {
     768            0 :             login_failure(null);
     769            0 :             clear_info();
     770            0 :             if (e.which == 13)
     771            0 :                 id("login-password-input").focus();
     772            0 :         }, false);
     773              : 
     774            0 :         const do_login = function(e) {
     775            0 :             login_failure(null);
     776            0 :             if (e.which == 13)
     777            0 :                 call_login();
     778            0 :         };
     779              : 
     780           18 :         id("login-password-input").addEventListener("keydown", do_login);
     781           18 :         id("login-password-toggle").addEventListener("click", toggle_password);
     782              : 
     783           18 :         show_form("login");
     784              : 
     785           18 :         if (!environment.is_cockpit_client) {
     786           18 :             id("login-user-input").focus();
     787           16 :         } else if (environment.page.require_host) {
     788           16 :             id("server-field").focus();
     789           16 :         }
     790           18 :     }
     791              : 
     792            0 :     function get_known_hosts_db() {
     793            0 :         try {
     794            0 :             return JSON.parse(localStorage.getItem("known_hosts") || "{ }");
     795            0 :         } catch (ex) {
     796            0 :             console.warn("Can't parse known_hosts database in localStorage", ex);
     797            0 :             return { };
     798            0 :         }
     799            0 :     }
     800              : 
     801            0 :     function get_hostkeys(host) {
     802            0 :         return get_known_hosts_db()[parseHostname(host)];
     803            0 :     }
     804              : 
     805            0 :     function set_hostkeys(host, keys) {
     806            0 :         try {
     807            0 :             const db = get_known_hosts_db();
     808            0 :             db[parseHostname(host)] = keys;
     809            0 :             localStorage.setItem("known_hosts", JSON.stringify(db));
     810            0 :         } catch (ex) {
     811            0 :             console.warn("Can't write known_hosts database to localStorage", ex);
     812            0 :         }
     813            0 :     }
     814              : 
     815            0 :     function do_hostkey_verification(data) {
     816            0 :         const key = data["host-key"];
     817            0 :         const key_host = key.split(" ")[0];
     818            0 :         const key_type = key.split(" ")[1];
     819            0 :         const db_keys = get_hostkeys(key_host);
     820              : 
     821            0 :         if (db_keys) {
     822            0 :             debug("do_hostkey_verification: received key fingerprint", data.default, "for host", key_host,
     823            0 :                   "does not match key in known_hosts database:", db_keys, "; treating as changed");
     824            0 :             id("hostkey-title").textContent = format(_("$0 key changed"), login_machine);
     825            0 :             show("#hostkey-warning-group");
     826            0 :             id("hostkey-message-1").textContent = "";
     827            0 :         } else {
     828            0 :             debug("do_hostkey_verification: received key fingerprint", data.default, "for host", key_host,
     829            0 :                   "not in known_hosts database; treating as new host");
     830            0 :             id("hostkey-title").textContent = _("New host");
     831            0 :             hide("#hostkey-warning-group");
     832            0 :             id("hostkey-message-1").textContent = format(_("You are connecting to $0 for the first time."), login_machine);
     833            0 :         }
     834              : 
     835            0 :         id("hostkey-verify-help-1").textContent = format(_("To verify a fingerprint, run the following on $0 while physically sitting at the machine or through a trusted network:"), login_machine);
     836            0 :         id("hostkey-verify-help-cmds").textContent = format("ssh-keyscan$0 localhost | ssh-keygen -lf -",
     837            0 :                                                             key_type ? " -t " + key_type : "");
     838              : 
     839            0 :         id("hostkey-fingerprint").textContent = data.default;
     840              : 
     841            0 :         if (key_type) {
     842            0 :             id("hostkey-type").textContent = format("($0)", key_type);
     843            0 :             show("#hostkey-type");
     844            0 :         } else {
     845            0 :             hide("#hostkey-type");
     846            0 :         }
     847              : 
     848            0 :         login_failure("");
     849              : 
     850            0 :         function call_converse() {
     851            0 :             id("login-button").removeEventListener("click", call_converse);
     852            0 :             login_failure(null, null, "hostkey");
     853              :             // cockpit-beiboot sends only a placeholder, defer to login-data in setup_localstorage()
     854            0 :             if (key.endsWith(" login-data")) {
     855            0 :                 login_data_host = key_host;
     856            0 :                 debug("call_converse(): got placeholder host keyfor", login_data_host, ", deferring db update");
     857            0 :                 converse(data.id, data.default);
     858            0 :             } else {
     859            0 :                 console.error("login: got unexpected host key prompt, expecting login-data placeholder:", key);
     860            0 :                 fatal(_("Internal protocol error"));
     861            0 :             }
     862            0 :         }
     863              : 
     864            0 :         id("login-button").addEventListener("click", call_converse);
     865              : 
     866            0 :         show_form("hostkey");
     867            0 :         show("#get-out-link");
     868              : 
     869            0 :         if (db_keys) {
     870            0 :             id("login-button").classList.add("pf-m-danger");
     871            0 :             id("login-button").classList.remove("pf-m-primary");
     872            0 :         }
     873            0 :     }
     874              : 
     875            0 :     function show_converse(prompt_data) {
     876            0 :         if (prompt_data["host-key"]) {
     877            0 :             do_hostkey_verification(prompt_data);
     878            0 :             return;
     879            0 :         }
     880              : 
     881            0 :         const type = prompt_data.echo ? "text" : "password";
     882            0 :         id("conversation-prompt").textContent = prompt_data.prompt;
     883              : 
     884            0 :         const em = id("conversation-message");
     885            0 :         const msg = prompt_data.error || prompt_data.message;
     886            0 :         if (msg) {
     887            0 :             em.textContent = msg;
     888            0 :             show(em);
     889            0 :         } else {
     890            0 :             hide(em);
     891            0 :         }
     892              : 
     893            0 :         const ei = id("conversation-input");
     894            0 :         ei.value = "";
     895            0 :         if (prompt_data.default)
     896            0 :             ei.value = prompt_data.default;
     897            0 :         ei.setAttribute('type', type);
     898              : 
     899            0 :         login_failure("");
     900              : 
     901            0 :         function call_converse() {
     902            0 :             id("conversation-input").removeEventListener("keydown", key_down);
     903            0 :             id("login-button").removeEventListener("click", call_converse);
     904            0 :             login_failure(null, null, "conversation");
     905            0 :             converse(prompt_data.id, id("conversation-input").value);
     906            0 :         }
     907              : 
     908            0 :         function key_down(e) {
     909            0 :             login_failure(null, null, "conversation");
     910            0 :             if (e.which == 13) {
     911            0 :                 call_converse();
     912            0 :             }
     913            0 :         }
     914              : 
     915            0 :         id("conversation-input").addEventListener("keydown", key_down);
     916            0 :         id("login-button").addEventListener("click", call_converse);
     917            0 :         show_form("conversation");
     918            0 :         ei.focus();
     919            0 :     }
     920              : 
     921            3 :     function utf8(str) {
     922            3 :         return window.unescape(encodeURIComponent(str));
     923            3 :     }
     924              : 
     925            0 :     function get_prompt_from_challenge (header, body) {
     926            0 :         if (!header)
     927            0 :             return null;
     928              : 
     929            0 :         const parts = header.split(' ');
     930            0 :         if (parts[0].toLowerCase() !== 'x-conversation' || parts.length !== 3)
     931            0 :             return null;
     932              : 
     933            0 :         const id = parts[1];
     934            0 :         let prompt;
     935            0 :         try {
     936            0 :             prompt = window.atob(parts[2]);
     937            0 :         } catch (err) {
     938            0 :             console.error("Invalid prompt data", err);
     939            0 :             return null;
     940            0 :         }
     941              : 
     942            0 :         let resp;
     943            0 :         try {
     944            0 :             resp = JSON.parse(body);
     945            0 :         } catch (err) {
     946            0 :             console.log("Got invalid JSON response for prompt data", err);
     947            0 :             resp = {};
     948            0 :         }
     949              : 
     950            0 :         resp.id = id;
     951            0 :         resp.prompt = prompt;
     952            0 :         return resp;
     953            0 :     }
     954              : 
     955            3 :     function send_login_request(method, headers, is_conversation) {
     956            3 :         debug("send_login_request():", method, "headers:", JSON.stringify(headers));
     957            3 :         id("login-button").setAttribute('disabled', "true");
     958            3 :         id("login-button").setAttribute('spinning', "true");
     959            3 :         const xhr = new XMLHttpRequest();
     960            3 :         xhr.open(method, login_path, true);
     961              : 
     962            3 :         for (const k in headers)
     963            3 :             xhr.setRequestHeader(k, headers[k]);
     964              : 
     965            3 :         xhr.onreadystatechange = function () {
     966            3 :             if (xhr.readyState != 4) {
     967            3 :                 return;
     968            3 :             }
     969            2 :             if (xhr.status == 200) {
     970            2 :                 const resp = JSON.parse(xhr.responseText);
     971            2 :                 run(resp);
     972            1 :             } else if (xhr.status == 401) {
     973            1 :                 debug("send_login_request():", method, "got 401, status:", xhr.statusText, "; response:", xhr.responseText);
     974            1 :                 const challenge = xhr.getResponseHeader("WWW-Authenticate");
     975            1 :                 if (challenge && challenge.toLowerCase().indexOf("x-conversation") === 0) {
     976            1 :                     const prompt_data = get_prompt_from_challenge(challenge, xhr.responseText);
     977            1 :                     if (prompt_data)
     978            1 :                         show_converse(prompt_data);
     979              :                     else
     980            1 :                         fatal(_("Internal error: Invalid challenge header"));
     981            1 :                 } else {
     982            1 :                     console.log(xhr.statusText);
     983              :                     /* did the user confirm a changed SSH host key? If so, update database */
     984            1 :                     if (ssh_host_key_change_host) {
     985            1 :                         try {
     986            1 :                             const keys = JSON.parse(xhr.responseText)["known-hosts"];
     987            1 :                             if (keys) {
     988            1 :                                 debug("send_login_request(): got updated known-hosts for changed host keys of", ssh_host_key_change_host, ":", keys);
     989            1 :                                 set_hostkeys(ssh_host_key_change_host, keys);
     990            1 :                                 ssh_host_key_change_host = null;
     991            1 :                             } else {
     992            1 :                                 debug("send_login_request():", ssh_host_key_change_host, "changed key, but did not get an updated key from response");
     993            1 :                             }
     994            1 :                         } catch (ex) {
     995            1 :                             console.error("Failed to parse response text as JSON:", xhr.responseText, ":", JSON.stringify(ex));
     996            1 :                         }
     997            1 :                     }
     998              : 
     999            1 :                     if (xhr.statusText.startsWith("captured-stderr:")) {
    1000            1 :                         show_captured_stderr(decodeURIComponent(xhr.statusText.replace(/^captured-stderr:/, '')));
    1001            1 :                     } else if (xhr.statusText.indexOf("authentication-not-supported") > -1) {
    1002            1 :                         const user = id("login-user-input").value.trim();
    1003            1 :                         fatal(format(_("The server refused to authenticate '$0' using password authentication, and no other supported authentication methods are available."), user));
    1004            1 :                     } else if (xhr.statusText.indexOf("terminated") > -1) {
    1005            1 :                         login_failure(_("Authentication failed"), _("Server closed connection"));
    1006            1 :                     } else if (xhr.statusText.indexOf("no-host") > -1) {
    1007            1 :                         host_failure(_("Unable to connect to that address"));
    1008            1 :                     } else if (xhr.statusText.indexOf("unknown-hostkey") > -1) {
    1009            1 :                         host_failure(_("Refusing to connect"), _("Hostkey is unknown"));
    1010            1 :                     } else if (xhr.statusText.indexOf("unknown-host") > -1) {
    1011            1 :                         host_failure(_("Refusing to connect"), _("Host is unknown"));
    1012            1 :                     } else if (xhr.statusText.indexOf("invalid-hostkey") > -1) {
    1013              :                         /* ssh/ferny/beiboot immediately fail in this case, it's not a conversation;
    1014              :                          * ask the user for confirmation and try again */
    1015            1 :                         if (ssh_host_key_change_host === null) {
    1016            1 :                             debug("send_login_request(): invalid-hostkey, trying again to let the user confirm");
    1017            1 :                             ssh_host_key_change_host = login_machine;
    1018            1 :                             call_login();
    1019            1 :                         } else {
    1020              :                             // but only once, to avoid loops
    1021            1 :                             debug("send_login_request(): invalid-hostkey, and already retried, giving up");
    1022            1 :                             host_failure(_("Refusing to connect"), _("Hostkey does not match"));
    1023            1 :                         }
    1024            1 :                     } else if (is_conversation) {
    1025            1 :                         login_failure(_("Authentication failed"));
    1026            1 :                     } else {
    1027            1 :                         login_failure(_("Authentication failed"), _("Wrong user name or password"));
    1028            1 :                     }
    1029            1 :                 }
    1030            1 :             } else if (xhr.status == 403) {
    1031            2 :                 const status = decodeURIComponent(xhr.statusText).trim();
    1032            1 :                 login_failure(_("Permission denied"), status === "Permission denied" ? "" : status);
    1033            1 :             } else if (xhr.status == 500 && xhr.statusText.indexOf("no-cockpit") > -1) {
    1034            1 :                 const message = format(
    1035            1 :                     _("Install the cockpit-system package (and optionally other cockpit packages) on $0 to enable web console access."),
    1036            1 :                     login_machine || "localhost");
    1037              : 
    1038            1 :                 login_failure(_("Packageless session unavailable"), message);
    1039            1 :             } else if (xhr.status == 500 && xhr.statusText.indexOf("unsupported-shell") > -1) {
    1040            1 :                 login_failure(_("Authentication failed"),
    1041            1 :                               _("Unsupported shell. Check the journal for details."));
    1042            1 :             } else if (xhr.statusText) {
    1043            1 :                 fatal(decodeURIComponent(xhr.statusText));
    1044            1 :             } else {
    1045            1 :                 fatal(format(_("$0 error"), xhr.status));
    1046            1 :             }
    1047            3 :         };
    1048            3 :         xhr.send();
    1049            3 :     }
    1050              : 
    1051            0 :     function converse(id, msg) {
    1052            0 :         const headers = {
    1053            0 :             Authorization: "X-Conversation " + id + " " + window.btoa(utf8(msg))
    1054            0 :         };
    1055            0 :         send_login_request("GET", headers, true);
    1056            0 :     }
    1057              : 
    1058            2 :     function login_reload (wanted) {
    1059              :         // Force a reload if not triggered below
    1060              :         // because only the hash part of the url
    1061              :         // changed
    1062            1 :         let timer = window.setTimeout(function() {
    1063            1 :             timer = null;
    1064            1 :             window.location.reload(true);
    1065            1 :         }, 100);
    1066              : 
    1067            1 :         if (wanted && wanted != window.location.href)
    1068            1 :             window.location = wanted;
    1069              : 
    1070              :         // cancel forced reload if we are reloading
    1071            1 :         window.onbeforeunload = function() {
    1072            1 :             if (timer)
    1073            0 :                 window.clearTimeout(timer);
    1074            1 :             timer = null;
    1075            1 :         };
    1076            2 :     }
    1077              : 
    1078            1 :     function clear_storage (storage, prefix, full) {
    1079            1 :         let i = 0;
    1080            1 :         while (i < storage.length) {
    1081            1 :             const k = storage.key(i);
    1082            0 :             if (full && k.indexOf("cockpit") !== 0)
    1083            0 :                 storage.removeItem(k);
    1084            1 :             else if (k.indexOf(prefix) === 0)
    1085            0 :                 storage.removeItem(k);
    1086              :             else
    1087            1 :                 i++;
    1088            1 :         }
    1089            1 :     }
    1090              : 
    1091            1 :     function setup_localstorage (response) {
    1092              :         /* Clear anything not prefixed with
    1093              :          * different application from sessionStorage
    1094              :          */
    1095            1 :         clear_storage(window.sessionStorage, application, true);
    1096              : 
    1097              :         /* Clear anything prefixed with our application
    1098              :          * and login-data, but not other non-application values.
    1099              :          */
    1100            1 :         localStorage.removeItem('login-data');
    1101            1 :         clear_storage(localStorage, application, false);
    1102              : 
    1103            0 :         if (response && response["login-data"]) {
    1104            0 :             const str = JSON.stringify(response["login-data"]);
    1105              :             /* login-data is tied to the auth cookie, since
    1106              :              * cookies are available after the page
    1107              :              * session ends login-data should be too.
    1108              :              */
    1109            0 :             localStorage.setItem(application + 'login-data', str);
    1110              :             /* Backwards compatibility for packages that aren't application prefixed */
    1111            0 :             localStorage.setItem('login-data', str);
    1112              : 
    1113              :             /* When confirming a host key with cockpit-beiboot, login-data contains the known_hosts pubkey;
    1114              :              * update our database */
    1115            0 :             if (login_data_host) {
    1116            0 :                 const hostkey = response["login-data"]["known-hosts"];
    1117            0 :                 if (hostkey) {
    1118            0 :                     debug("setup_localstorage(): updating known_hosts database for deferred host key for", login_data_host, ":", hostkey);
    1119            0 :                     set_hostkeys(login_data_host, hostkey);
    1120            0 :                 } else {
    1121            0 :                     console.error("login.js internal error: setup_localstorage() received a pending login-data host, but login-data does not contain known-hosts");
    1122            0 :                 }
    1123            0 :             }
    1124            0 :         }
    1125              : 
    1126              :         /* URL Root is set by cockpit ws and shouldn't be prefixed
    1127              :          * by application
    1128              :          * deprecated: for connecting to cockpit.js < 272
    1129              :          */
    1130            1 :         if (url_root)
    1131            1 :             localStorage.setItem('url-root', url_root);
    1132              : 
    1133            1 :         const ca_cert_url = environment.CACertUrl;
    1134            1 :         if (ca_cert_url)
    1135            1 :             window.sessionStorage.setItem('CACertUrl', ca_cert_url);
    1136            1 :     }
    1137              : 
    1138            1 :     function run(response) {
    1139            1 :         let wanted = window.sessionStorage.getItem('login-wanted');
    1140            1 :         const machine = id("server-field").value;
    1141              : 
    1142              :         /* When using cockpit client remember all the addresses being used */
    1143            0 :         if (machine && environment.is_cockpit_client) {
    1144            0 :             const hosts = get_recent_hosts();
    1145            0 :             if (hosts.indexOf(machine) < 0) {
    1146            0 :                 hosts.push(machine);
    1147            0 :                 localStorage.setItem('cockpit-client-sessions', JSON.stringify(hosts));
    1148            0 :             }
    1149            0 :         }
    1150              : 
    1151            0 :         if (machine && application != org_application) {
    1152            0 :             wanted = "/=" + machine;
    1153            0 :             if (url_root)
    1154            0 :                 wanted = "/" + url_root + wanted;
    1155            0 :         }
    1156              : 
    1157              :         /* clean up sessionStorage. clear anything that isn't prefixed
    1158              :          * with an application and anything prefixed with our application.
    1159              :          */
    1160            1 :         clear_storage(window.sessionStorage, application, false);
    1161              : 
    1162            1 :         setup_localstorage(response);
    1163            1 :         login_reload(wanted);
    1164            1 :     }
    1165              : 
    1166           20 :     window.onload = boot;
    1167           20 : })();
        

Generated by: LCOV version 2.0-1