{% extends "base.html" %}
{% block title %}Sign in{% endblock %}
{# No topbar on the sign-in page: every link in it needs a session. #}
{% block chrome %}{% endblock %}
{% block content %}
<form class="card" id="login-form">
<h1>Sign in</h1>
{# The OAuth callback redirects here with ?error=google on any failure. The
reason is in the log, not the query string - which of "no such account" and
"bad token" it was is not a stranger's business. #}
{% if request.args.get("error") == "google" %}
<p class="error">That Google sign-in did not work. Try again, or use a password.</p>
{% endif %}
<label>Email
<input type="email" name="email" autocomplete="username" required autofocus>
</label>
<label>Password
<input type="password" name="password" autocomplete="current-password" required>
</label>
<p class="error" id="login-error" hidden></p>
<button type="submit">Sign in</button>
{% if google_enabled() %}
<div class="divider"><span>or</span></div>
{# A link, not a fetch: the OAuth flow is a browser redirect to Google. #}
<a class="button-secondary" href="/auth/google/start">Sign in with Google</a>
{% endif %}
</form>
{% endblock %}
{% block scripts %}
<script>
const form = document.getElementById("login-form");
const error = document.getElementById("login-error");
form.addEventListener("submit", async (e) => {
e.preventDefault();
error.hidden = true;
const button = form.querySelector("button");
button.disabled = true;
try {
const data = new FormData(form);
await api.post("/api/login", {
email: data.get("email"),
password: data.get("password"),
});
// A full navigation, not a fetch: the session cookie is set now, and the
// next page must be rendered by the server with it.
window.location.href = "/";
} catch (err) {
error.textContent = err.message;
error.hidden = false;
button.disabled = false;
}
});
</script>
{% endblock %}