Register.tsx
Raw
1import { Layout } from "../layout.tsx";
2
3interface RegisterProps {
4 error?: string;
5}
6
7export function Register({ error }: RegisterProps) {
8 return (
9 <Layout user={null} title="Register">
10 <div class="auth-container">
11 <h1 class="page-title">Create account</h1>
12 {error && <p class="form-error">{error}</p>}
13 <form method="POST" action="/register" class="auth-form">
14 <div class="form-group">
15 <label for="username">Username</label>
16 <input
17 id="username"
18 name="username"
19 type="text"
20 required
21 autocomplete="username"
22 pattern="[a-zA-Z0-9_-]+"
23 title="Letters, numbers, hyphens and underscores only"
24 />
25 </div>
26 <div id="passkey-section" style="display:none">
27 <button
28 id="passkey-register-btn"
29 class="btn btn-secondary btn-block"
30 type="button"
31 >
32 Register with passkey
33 </button>
34 <div class="auth-divider">
35 <span>or</span>
36 </div>
37 </div>
38 <div class="form-group">
39 <label for="password">Password</label>
40 <input
41 id="password"
42 name="password"
43 type="password"
44 autocomplete="new-password"
45 minlength="8"
46 />
47 </div>
48 <div class="form-group">
49 <label for="password2">Confirm password</label>
50 <input
51 id="password2"
52 name="password2"
53 type="password"
54 autocomplete="new-password"
55 minlength="8"
56 />
57 </div>
58 <button type="submit" class="btn btn-primary btn-block">
59 Register with password
60 </button>
61 </form>
62 <p class="auth-footer">
63 Already have an account? <a href="/login">Sign in</a>
64 </p>
65 </div>
66 <script type="module">{`
67 import { startRegistration } from '/assets/simplewebauthn-browser.js';
68 const usernameInput = document.getElementById('username');
69 document.getElementById('passkey-section').style.display = 'block';
70 document.getElementById('passkey-register-btn').addEventListener('click', async () => {
71 const username = usernameInput.value.trim();
72 if (!username) { usernameInput.focus(); return; }
73 if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
74 alert('Username may only contain letters, numbers, hyphens, and underscores');
75 return;
76 }
77 try {
78 const createResp = await fetch('/auth/passkey/create-user', {
79 method: 'POST',
80 headers: { 'Content-Type': 'application/json' },
81 body: JSON.stringify({ username }),
82 });
83 if (!createResp.ok) {
84 const err = await createResp.json();
85 alert(err.error ?? 'Failed to create account');
86 return;
87 }
88 await createResp.json();
89 const optsResp = await fetch('/auth/passkey/register/options', { method: 'POST' });
90 const opts = await optsResp.json();
91 const result = await startRegistration({ optionsJSON: opts });
92 const verResp = await fetch('/auth/passkey/register/verify', {
93 method: 'POST',
94 headers: { 'Content-Type': 'application/json' },
95 body: JSON.stringify(result),
96 });
97 if (verResp.ok) {
98 window.location.href = '/';
99 } else {
100 const err = await verResp.json();
101 alert(err.error ?? 'Passkey registration failed');
102 }
103 } catch (e) {
104 alert('Passkey registration failed: ' + e.message);
105 }
106 });
107 `}</script>
108 </Layout>
109 );
110}
111