A non-functional draft, saving to allow reverts.

This commit is contained in:
Leo Vasanko
2025-07-03 17:01:41 -06:00
commit 58f7ac61db
13 changed files with 1093 additions and 0 deletions

55
static/app.js Normal file
View File

@@ -0,0 +1,55 @@
const { startRegistration, startAuthentication } = SimpleWebAuthnBrowser
async function register(username) {
// Registration chat
const ws = await aWebSocket('/ws/register')
ws.send(username)
const optionsJSON = JSON.parse(await ws.recv())
if (optionsJSON.error) throw new Error(optionsJSON.error)
ws.send(JSON.stringify(await startRegistration({optionsJSON})))
const result = JSON.parse(await ws.recv())
if (result.error) throw new Error(`Server: ${result.error}`)
}
async function authenticate() {
// Authentication chat
const ws = await aWebSocket('/ws/authenticate')
ws.send('') // Send empty string to trigger authentication
const optionsJSON = JSON.parse(await ws.recv())
if (optionsJSON.error) throw new Error(optionsJSON.error)
ws.send(JSON.stringify(await startAuthentication({optionsJSON})))
const result = JSON.parse(await ws.recv())
if (result.error) throw new Error(`Server: ${result.error}`)
return result
}
(function() {
const regForm = document.getElementById('registrationForm')
const regSubmitBtn = regForm.querySelector('button[type="submit"]')
regForm.addEventListener('submit', ev => {
ev.preventDefault()
regSubmitBtn.disabled = true
const username = (new FormData(regForm)).get('username')
register(username).then(() => {
alert(`Registration successful for ${username}!`)
}).catch(err => {
alert(`Registration failed: ${err.message}`)
}).finally(() => {
regSubmitBtn.disabled = false
})
})
const authForm = document.getElementById('authenticationForm')
const authSubmitBtn = authForm.querySelector('button[type="submit"]')
authForm.addEventListener('submit', ev => {
ev.preventDefault()
authSubmitBtn.disabled = true
authenticate().then(result => {
alert(`Authentication successful! Welcome ${result.username}`)
}).catch(err => {
alert(`Authentication failed: ${err.message}`)
}).finally(() => {
authSubmitBtn.disabled = false
})
})
})()

View File

@@ -0,0 +1,43 @@
class AwaitableWebSocket extends WebSocket {
#received = []
#waiting = []
#err = null
#opened = false
constructor(resolve, reject, url, protocols) {
super(url, protocols)
this.onopen = () => {
this.#opened = true
resolve(this)
}
this.onmessage = e => {
if (this.#waiting.length) this.#waiting.shift().resolve(e.data)
else this.#received.push(e.data)
}
this.onclose = e => {
if (!this.#opened) {
reject(new Error(`WebSocket ${this.url} failed to connect, code ${e.code}`))
return
}
this.#err = e.wasClean
? new Error(`Websocket ${this.url} closed ${e.code}`)
: new Error(`WebSocket ${this.url} closed with error ${e.code}`)
this.#waiting.splice(0).forEach(p => p.reject(this.#err))
}
}
recv() {
// If we have a message already received, return it immediately
if (this.#received.length) return Promise.resolve(this.#received.shift())
// Wait for incoming messages, if we have an error, reject immediately
if (this.#err) return Promise.reject(this.#err)
return new Promise((resolve, reject) => this.#waiting.push({ resolve, reject }))
}
}
// Construct an async WebSocket with await aWebSocket(url)
function aWebSocket(url, protocols) {
return new Promise((resolve, reject) => {
new AwaitableWebSocket(resolve, reject, url, protocols)
})
}

68
static/index.html Normal file
View File

@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<title>WebAuthn Registration Demo</title>
<script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.umd.min.js"></script>
<script src="/static/awaitable-websocket.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.container {
text-align: center;
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
input[type="text"] {
padding: 10px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
margin: 10px;
width: 250px;
}
button {
padding: 12px 24px;
margin: 10px;
font-size: 16px;
cursor: pointer;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h1>WebAuthn Demo</h1>
<div style="margin-bottom: 30px;">
<h2>Register</h2>
<form id="registrationForm">
<input type="text" name="username" placeholder="Username" required>
<br>
<button type="submit">Register Passkey</button>
</form>
</div>
<div>
<h2>Authenticate</h2>
<form id="authenticationForm">
<button type="submit">Authenticate with Passkey</button>
</form>
</div>
</div>
<script src="static/app.js"></script>
</body>
</html>