website/islands/LoginForm.tsx

115 lines
2.8 KiB
TypeScript

import { startAuthentication } from '@simplewebauthn/browser'
import { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types'
import { Button, Input } from 'univoq'
import type {
WebAuthnLoginFinishPayload,
WebAuthnLoginStartPayload,
} from '../routes/api/webauthn/login/[step].ts'
import { requestApi } from '../src/utils.ts'
export default function LoginForm() {
return (
<form
onSubmit={connect}
method='POST'
action=''
className='island__login_form__form'
>
<Input
label='Email'
name='email'
autoComplete='email'
type='email'
required
/>
<Input
label='Utiliser une Passkey si disponible'
name='passkey'
type='checkbox'
checked
/>
<Button label='Me connecter' variant='primary'>Me connecter</Button>
</form>
)
}
type LoginFormFields = {
email: string
passkey: 'on' | undefined
}
async function connect(event: Event) {
if (!(event instanceof SubmitEvent)) return false
event.preventDefault()
const form = event.target as HTMLFormElement
const fields = formJSON<LoginFormFields>(form)
try {
// User disable passkey
if (fields.passkey !== 'on') {
throw new Error('User refused passkey')
}
// Try PassKey connection
if (!isWebAuthnSupported()) {
throw new Error('WebAuthn is not supported by your browser')
}
await webAuthnLogin(fields)
// Reload UI
location.reload()
} catch (error) {
// Check is user stop passkey connection
if (error instanceof Error && error.name === 'NotAllowedError') {
form.reset()
return
}
// Else use magic link
await magicLinkLogin(fields)
// Reload UI
location.reload()
}
form.reset()
}
async function magicLinkLogin(fields: LoginFormFields) {
await requestApi('magiclink', 'POST', fields)
console.log('Un lien de connection vous a été envoyé par mail !')
}
async function webAuthnLogin(fields: LoginFormFields) {
// Send WebAuthn authentication options
const authenticationOptions = await requestApi<
WebAuthnLoginStartPayload,
PublicKeyCredentialRequestOptionsJSON
>('webauthn/login/start', 'POST', fields)
// Pass the options to the authenticator and wait for a response
const authentication = await startAuthentication(authenticationOptions)
// Verify authentication
const verification = await requestApi<
WebAuthnLoginFinishPayload,
{ verified: boolean }
>('webauthn/login/finish', 'POST', authentication)
// Show UI appropriate for the `verified` status
if (verification.verified) {
console.log('Success!')
} else {
console.error('PassKey was not verified!')
}
}
function isWebAuthnSupported(): boolean {
return 'credentials' in navigator
}
function formJSON<T extends Record<string, unknown>>(form: HTMLFormElement): T {
const formData = new FormData(form)
return Object.fromEntries(formData) as unknown as T
}