website/islands/PassKeyRegister.tsx

110 lines
2.7 KiB
TypeScript

import { requestApi } from ':src/utils.ts'
import { startRegistration } from '@simplewebauthn/browser'
import { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/types'
import { Button, Input } from 'univoq'
import type {
WebAuthnRegisterFinishPayload,
WebAuthnRegisterStartPayload,
} from '../routes/api/webauthn/register/[step].ts'
function isWebAuthnSupported(): boolean {
return 'credentials' in navigator
}
function RegisterForm({ disabled }: { disabled?: boolean }) {
return (
<form onSubmit={register} method='POST' action=''>
<Input label='Nom de la clé' name='name' required></Input>
<Button
label='Enregistrer une PassKey'
variant='primary'
disabled={disabled}
>
Enregistrer une PassKey
</Button>
</form>
)
}
export default function PassKeyRegister() {
if (!isWebAuthnSupported()) {
return (
<div>
<span>Erreur: WebAuthn n'est pas supporté par votre navigateur</span>
<RegisterForm disabled />
</div>
)
}
return (
<div>
<span>Enregistrer une PassKey pour cet appareil</span>
<RegisterForm />
</div>
)
}
type RegisterFormFields = {
name: string
}
async function register(event: Event) {
if (!(event instanceof SubmitEvent)) return false
event.preventDefault()
const form = event.target as HTMLFormElement
const fields = formJSON<RegisterFormFields>(form)
try {
await webAuthnRegister(fields)
} catch (cause) {
console.error(
new Error('passkey register failed', {
cause,
}),
)
}
}
async function webAuthnRegister(fields: RegisterFormFields) {
if (localStorage.getItem('webauthn-registered')) {
return
}
const registrationOptions = await requestApi<
WebAuthnRegisterStartPayload,
PublicKeyCredentialCreationOptionsJSON
>('webauthn/register/start', 'POST', fields)
// Pass the options to the authenticator and wait for a response
const registration = await startRegistration(registrationOptions)
.catch((error) => {
// Some basic error handling
if (error.name === 'InvalidStateError') {
console.error(
'Authenticator was probably already registered by user',
)
return null
} else {
throw error
}
})
if (registration === null) return
// Verify the registration
try {
await requestApi<
WebAuthnRegisterFinishPayload,
{ verified: boolean }
>('webauthn/register/finish', 'POST', registration)
console.log('Success!')
localStorage.setItem('webauthn-registered', 'true')
} catch (error) {
console.error('Oh no, something went wrong! Response:', error)
}
}
function formJSON<T extends Record<string, unknown>>(form: HTMLFormElement): T {
const formData = new FormData(form)
return Object.fromEntries(formData) as unknown as T
}