agilentics / boiler
{% extends "base.html" %}
{% block title %}Set a new password{% endblock %}

{% block chrome %}{% endblock %}

{% block content %}
<form class="card" id="reset-form">
  <h1>Set a new password</h1>

  {# The token from the emailed link, carried through the form rather than
     consumed by the page that rendered it. A mail client that prefetches links
     would otherwise burn it before the person clicked. #}
  <input type="hidden" name="token" value="{{ token }}">

  {% if not token %}
  <p class="error">This link is missing its token. Open the one from the email,
     or <a href="/forgot">ask for a new link</a>.</p>
  {% endif %}

  <label>New password
    <input type="password" name="password" autocomplete="new-password"
           minlength="12" required autofocus>
    <span class="hint">At least 12 characters.</span>
  </label>

  <p class="error" id="reset-error" hidden></p>
  <button type="submit" id="reset-submit" {% if not token %}disabled{% endif %}>
    Set password
  </button>
</form>

<div class="card" id="reset-done" hidden>
  <h1>Password set</h1>
  <p>You can sign in with it now.</p>
  {# No session is started here on purpose: signing in re-runs every gate,
     including the second factor if one survived. See api_password_reset. #}
  <p id="passkey-note" class="note" hidden>
    Any passkeys on this account were removed, because a reset is how somebody
    gets back in when the passkey itself is what they lost. Enrol a new one from
    <strong>Security</strong> after you sign in.
  </p>
  <p><a class="button-secondary" href="/login">Sign in</a></p>
</div>
{% endblock %}

{% block scripts %}
<script>
  const form = document.getElementById("reset-form");
  const error = document.getElementById("reset-error");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();
    error.hidden = true;
    const button = document.getElementById("reset-submit");
    button.disabled = true;
    const data = new FormData(form);
    try {
      await api.post("/api/password/reset", {
        token: data.get("token"),
        password: data.get("password"),
      });
      form.hidden = true;
      // Shown unconditionally: the endpoint deliberately does not report whether
      // the account had a passkey, because that is a fact about an account the
      // caller has not signed into yet.
      document.getElementById("passkey-note").hidden = false;
      document.getElementById("reset-done").hidden = false;
    } catch (err) {
      error.textContent = err.message;
      error.hidden = false;
      button.disabled = false;
    }
  });
</script>
{% endblock %}