Files
UmbraTrans/web/setup.html
umbra audio 3419485aa7 Fix a syntax error I introduced, which put a password in a URL
The commit immediately before this one - the password-toggle fix - deleted a closing
brace and did not replace it. app.js did not parse. The browser reported it in a console
nobody was watching, and every script-driven behaviour on the sign-in page stopped.

The consequence was not a cosmetic one. The login form's submit handler, which is the
only thing calling preventDefault(), never ran, so the browser performed a native form
submission. Neither form declared a method, and a form with no method attribute submits
as GET. Both password fields went into the query string:

    GET /?email=umbra%40bdmurphy.me&password=...

The server logged it in plain text, three times, before it was noticed. It would also
have sat in browser history and in any proxy access log between here and the public
internet.

Two fixes, the second of which is the one that matters:

- The toggle fix is reapplied correctly to the last known-good version of app.js, with
  nothing else in the file touched - the diff is four lines against thirteen and two
  against one.
- **Every form on every page now declares method=post.** The script still intercepts
  and submits via fetch; this decides what happens when the script does not run, which is
  precisely the case that failed. Five more forms carried the same latent defect,
  including password reset and email verification.

test_form_safety.py checks both, and was confirmed to fail when app.js is broken the
same way. It uses esprima rather than a hand-written brace counter, which gave two false
readings during this work - one of them 'balanced' for a file that did not parse at all.
A check that can be wrong about a syntax error is worse than no check.

The account password should be treated as exposed and changed. So should any password
entered into that form while the script was broken.
2026-10-03 20:06:05 -07:00

85 lines
4.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Create the first UmbraTrans administrator">
<meta name="referrer" content="same-origin">
<title>Set up UmbraTrans</title>
<link rel="icon" type="image/png" href="assets/umbratrans-emblem.png">
<link rel="stylesheet" href="assets/styles.css">
</head>
<body class="auth-page">
<main class="auth-shell">
<section class="auth-brand" aria-labelledby="brand-title">
<a class="brand-lockup" href="index.html" aria-label="UmbraTrans home">
<img class="brand-image" src="assets/umbratrans-emblem.png" alt="" aria-hidden="true">
<span>UmbraTrans</span>
</a>
<div class="brand-copy">
<p class="eyebrow">First run</p>
<h1 id="brand-title">Create the first administrator.</h1>
<p class="brand-lede">This deployment has no administrator yet. The account you create here is the one that can then create organizations, enroll accounts, and audit everything that follows.</p>
</div>
</section>
<section class="auth-card" aria-labelledby="setup-title">
<div id="setup-unavailable" hidden>
<div class="auth-card-heading">
<span class="status-pill status-pill--success">Already set up</span>
<h2 id="setup-title">This deployment is configured</h2>
<p>A platform administrator already exists, so first-run setup is closed. If you need another account, sign in to the control panel and create it there.</p>
</div>
<a class="admin-link" href="index.html"><span>Back to sign in</span><span aria-hidden="true">↗</span></a>
</div>
<div id="setup-form-view" hidden>
<div class="auth-card-heading">
<span class="status-pill status-pill--warning">Action required</span>
<h2 id="setup-title">First-run setup</h2>
<p id="setup-intro">Paste the single-use setup code from the service log, then choose the administrator's email address and password.</p>
</div>
<!-- method="post": a form with no method defaults to GET, and a native
fallback submission would put first-administrator setup into the query string and into
the server log. No action: the script submits via fetch; this only
decides what happens when the script does not run. -->
<form id="setup-form" class="stack-form" method="post" novalidate>
<label for="setup-token">Setup code</label>
<input id="setup-token" name="token" type="text" autocomplete="off" autocapitalize="none" autocorrect="off" spellcheck="false" required>
<p class="field-hint">Written to the service log when the service started. Shown once, and not stored.</p>
<label for="setup-email">Email address</label>
<input id="setup-email" name="email" type="email" inputmode="email" autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false" required>
<p class="field-hint">This is the address the administrator signs in with. Other accounts are created for their own addresses.</p>
<label for="setup-display-name">Display name</label>
<input id="setup-display-name" name="display_name" type="text" autocomplete="name" maxlength="120" required>
<label for="setup-username">Account name <span class="field-optional">optional</span></label>
<input id="setup-username" name="username" type="text" autocomplete="off" autocapitalize="none" spellcheck="false" maxlength="64">
<p class="field-hint">A display handle. Sign-in uses the email address, not this.</p>
<label for="setup-password">Password</label>
<div class="password-field">
<input id="setup-password" name="password" type="password" autocomplete="new-password" required>
<button id="toggle-setup-password" class="password-toggle" type="button" aria-pressed="false" aria-controls="setup-password">Show</button>
</div>
<ul class="policy-list" id="setup-policy">
<li id="setup-policy-length">At least 14 characters.</li>
<li id="setup-policy-username">Must not contain your account name.</li>
</ul>
<div id="setup-message" class="form-message" role="status" aria-live="polite"></div>
<button id="setup-submit" class="button button--primary button--wide" type="submit">Create administrator <span aria-hidden="true">→</span></button>
</form>
</div>
<p class="auth-footnote">The setup code authorises exactly one account and is discarded as soon as it is used, whether or not that account is created. Failed attempts are counted.</p>
</section>
</main>
<script src="assets/api.js" defer></script>
<script src="assets/setup.js" defer></script>
</body>
</html>