- Remove dead CSS color property in .tab-mobile - Fix design token violations (text-red-500, fallback colors) - Add disconnectedCallback to chip-scroll, image-upload, searchable-select - Replace innerHTML with safe DOM APIs in world-map screen reader table - Add credentials: same-origin to WebAuthn fetch calls - Move page-specific scripts (donut-chart, location) out of base.html - Add client-side image resizing (1920px max dimension) - Resize app-icon-512.png from 2048x2048 to 512x512 - Document Datastar unsafe-eval CSP requirement - Add static asset serving tests (16 routes) - Add e2e tests for world-map, donut-chart, chip-scroll presence - Add cache-busting query params to all static asset URLs
124 lines
3.9 KiB
HTML
124 lines
3.9 KiB
HTML
{% extends "base.html" %} {% import "partials/icons.html" as icons %}
|
|
{% block title %}Brewlog · Register{% endblock %}
|
|
{% block head %}
|
|
<script
|
|
defer
|
|
src="/static/js/webauthn.js?v={{ version_info.commit }}"
|
|
></script>
|
|
{% endblock %}
|
|
{% block content %}
|
|
<div class="mx-auto max-w-md">
|
|
<div class="rounded-lg border bg-surface p-6">
|
|
<h1 class="text-2xl font-semibold text-accent">Register</h1>
|
|
<p class="mt-2 text-sm text-text-secondary">
|
|
Create an account by registering a passkey. This will be used to sign in
|
|
going forward.
|
|
</p>
|
|
|
|
<div
|
|
id="register-error"
|
|
class="mt-4 hidden rounded-md bg-error-bg border border-error-border p-3 text-sm text-error-text"
|
|
role="alert"
|
|
></div>
|
|
|
|
<div
|
|
id="register-unsupported"
|
|
class="mt-4 hidden rounded-md bg-warning-bg border border-warning-border p-3 text-sm text-warning-text"
|
|
>
|
|
This browser does not support passkeys. Please use a modern browser
|
|
(Chrome, Firefox, Safari, or Edge).
|
|
</div>
|
|
|
|
<div
|
|
id="register-form"
|
|
class="mt-6 flex flex-col gap-4"
|
|
data-token="{{ token }}"
|
|
>
|
|
<label class="flex flex-col gap-1 text-sm">
|
|
<span class="text-text">Display Name</span>
|
|
<input
|
|
type="text"
|
|
id="display-name"
|
|
required
|
|
aria-required="true"
|
|
autofocus
|
|
class="input-field"
|
|
placeholder="Display name"
|
|
/>
|
|
</label>
|
|
|
|
<label class="flex flex-col gap-1 text-sm">
|
|
<span class="text-text">Passkey Name</span>
|
|
<input
|
|
type="text"
|
|
id="passkey-name"
|
|
required
|
|
aria-required="true"
|
|
class="input-field"
|
|
placeholder="e.g. MacBook Touch ID, iPhone"
|
|
/>
|
|
</label>
|
|
|
|
<button
|
|
id="register-button"
|
|
type="button"
|
|
onclick="handleRegister()"
|
|
class="mt-2 inline-flex w-full items-center justify-center gap-2 rounded-md bg-accent px-4 py-3 text-sm font-semibold text-accent-text transition hover:bg-accent-hover disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{{ icons::key("h-4 w-4") }} Register Passkey
|
|
</button>
|
|
|
|
<div
|
|
id="register-loading"
|
|
class="mt-2 hidden text-center text-sm text-text-muted"
|
|
>
|
|
<p>Follow the prompts from the browser or device...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const handleRegister = async () => {
|
|
const token = document.getElementById("register-form").dataset.token;
|
|
const button = document.getElementById("register-button");
|
|
const errorDiv = document.getElementById("register-error");
|
|
const loadingDiv = document.getElementById("register-loading");
|
|
|
|
const displayName = document.getElementById("display-name").value.trim();
|
|
if (!displayName) {
|
|
errorDiv.textContent = "Please enter a display name.";
|
|
errorDiv.classList.remove("hidden");
|
|
return;
|
|
}
|
|
|
|
const passkeyName = document.getElementById("passkey-name").value.trim();
|
|
if (!passkeyName) {
|
|
errorDiv.textContent = "Please enter a name for this passkey.";
|
|
errorDiv.classList.remove("hidden");
|
|
return;
|
|
}
|
|
|
|
errorDiv.classList.add("hidden");
|
|
loadingDiv.classList.remove("hidden");
|
|
button.disabled = true;
|
|
|
|
try {
|
|
await startPasskeyRegistration(token, displayName, passkeyName);
|
|
window.location.href = "/";
|
|
} catch (err) {
|
|
errorDiv.textContent = err.message;
|
|
errorDiv.classList.remove("hidden");
|
|
loadingDiv.classList.add("hidden");
|
|
button.disabled = false;
|
|
}
|
|
};
|
|
|
|
if (!window.PublicKeyCredential) {
|
|
document.getElementById("register-button").disabled = true;
|
|
document
|
|
.getElementById("register-unsupported")
|
|
.classList.remove("hidden");
|
|
}
|
|
</script>
|
|
{% endblock %}
|