98 lines
2.5 KiB
TypeScript
98 lines
2.5 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=''>
|
|
<Input
|
|
label='Email'
|
|
name='email'
|
|
autoComplete='email'
|
|
type='email'
|
|
required
|
|
/>
|
|
<Button label='Connection' variant='primary'>Connection</Button>
|
|
</form>
|
|
)
|
|
}
|
|
|
|
type LoginFormFields = {
|
|
email: string
|
|
}
|
|
|
|
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 {
|
|
// 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', { ...fields, 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
|
|
}
|