Files
UmbraTrans/web/settings.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

80 lines
4.0 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="Your UmbraTrans account settings">
<meta name="referrer" content="same-origin">
<title>Your account · 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">Your account</p>
<h1 id="brand-title">Settings</h1>
<p class="brand-lede">What this account is, and the password that opens it.</p>
</div>
</section>
<section class="auth-card" aria-labelledby="settings-title">
<div class="auth-card-heading">
<span class="status-pill status-pill--info" id="settings-role">Signed in</span>
<h2 id="settings-title">Your account</h2>
<p>Details are resolved by the service for your session and cannot be edited here.</p>
</div>
<dl class="settings-list" id="account-details">
<div><dt>Email address</dt><dd id="account-email">—</dd></div>
<div><dt>Display name</dt><dd id="account-display-name">—</dd></div>
<div><dt>Account name</dt><dd id="account-username">—</dd></div>
<div><dt>Role</dt><dd id="account-role">—</dd></div>
<div><dt>Address confirmed</dt><dd id="account-verified">—</dd></div>
</dl>
<div class="auth-divider"><span>Password</span></div>
<!-- method="post": a form with no method defaults to GET, and a native
fallback submission would put the password-change form 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="password-form" class="stack-form" method="post" novalidate>
<p class="field-hint">You need your current password to change it, so a signed-in session on its own is not enough. Every other device is signed out when you do.</p>
<label for="current-password">Current password</label>
<div class="password-field">
<input id="current-password" name="current_password" type="password" autocomplete="current-password" required>
<button id="toggle-current-password" class="password-toggle" type="button" aria-pressed="false" aria-controls="current-password">Show</button>
</div>
<label for="new-password">New password</label>
<div class="password-field">
<input id="new-password" name="new_password" type="password" autocomplete="new-password" required>
<button id="toggle-new-password" class="password-toggle" type="button" aria-pressed="false" aria-controls="new-password">Show</button>
</div>
<label for="confirm-password">Confirm new password</label>
<input id="confirm-password" name="confirm_password" type="password" autocomplete="new-password" required>
<ul class="policy-list" id="settings-policy">
<li id="policy-length">At least 14 characters.</li>
<li id="policy-match">The two entries must match.</li>
<li id="policy-username">Must not contain your account name.</li>
</ul>
<div id="password-message" class="form-message" role="status" aria-live="polite"></div>
<button id="password-submit" class="button button--primary button--wide" type="submit">Change password <span aria-hidden="true">→</span></button>
<button id="signout" class="button button--ghost button--wide" type="button">Sign out</button>
</form>
</section>
</main>
<script src="assets/api.js" defer></script>
<script src="assets/settings.js" defer></script>
</body>
</html>