Skip to content

Commit

Permalink
Add login with passkey (#42)
Browse files Browse the repository at this point in the history
Added Login with passkey button
  • Loading branch information
vokomarov authored Apr 14, 2024
2 parents 8105169 + 40d0bab commit 9006152
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 4 deletions.
95 changes: 91 additions & 4 deletions components/Auth/Login.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
<script setup lang="ts">
import type { Form, FormSubmitEvent } from '#ui/types'
import type { AuthenticationResponseJSON } from '@simplewebauthn/types'
import { useReCaptcha } from 'vue-recaptcha-v3'
import { onMounted, useI18n, useLocalePath, useRuntimeConfig } from '#imports'
import { startAuthentication, browserSupportsWebAuthn } from '@simplewebauthn/browser'
import { onMounted, ref, useI18n, useLocalePath, useRuntimeConfig } from '#imports'
import { useLoader } from '@/shared/Loader'
import { login, type LoginRequestInterface, type LoginResponseInterface } from '@/api/login'
import { useValidationMessager } from '@/shared/ValidatorMessager'
import { profileGet } from '@/api/profile'
import { useAuthStore } from '@/store/auth'
import { googleAuthProvider } from '@/api/authProvider'
import { passkeyInit, type PasskeyInitResponseInterface, passkeyLogin } from '@/api/passkey'
const { t } = useI18n()
const loader = useLoader()
const passkeyLoader = useLoader()
const messager = useValidationMessager()
const config = useRuntimeConfig()
const recaptchaInstance = useReCaptcha()
const store = useAuthStore()
const localePath = useLocalePath()
const passkeysSupported = ref<boolean>(false)
const request = reactive<LoginRequestInterface>({
email: '',
Expand Down Expand Up @@ -128,6 +133,7 @@ function onSuccess(response: LoginResponseInterface) {
onMounted(() => {
initGoogleAuthButton()
passkeysSupported.value = browserSupportsWebAuthn()
})
function initGoogleAuthButton() {
Expand All @@ -153,6 +159,73 @@ function initGoogleAuthButton() {
text: 'signin_with'
})
}
async function loginWithPasskey() {
if (!passkeysSupported.value) {
console.info('Passkeys: not supported')
return
}
form?.value?.clear()
passkeyLoader.setLoading()
messager.resetMessage()
let challenge: string | undefined
try {
challenge = await recaptcha()
if (challenge === undefined) {
throw new Error('empty challenge')
}
} catch (error) {
console.log('Captcha error: ', error)
messager.setMessage(t('error.captcha'))
passkeyLoader.setLoaded()
return
}
let initResponse: PasskeyInitResponseInterface
try {
initResponse = await passkeyInit(challenge)
} catch (error) {
passkeyLoader.setLoaded()
messager.dispatchError(error)
return
}
let authResponse: AuthenticationResponseJSON
try {
authResponse = await startAuthentication(initResponse)
} catch (error) {
passkeyLoader.setLoaded()
messager.dispatchError(error)
return
}
let loginResponse: LoginResponseInterface
try {
loginResponse = await passkeyLogin(initResponse.challenge, authResponse, challenge)
} catch (error) {
passkeyLoader.setLoaded()
messager.dispatchError(error)
return
}
try {
const profileResponse = await profileGet()
store.login(profileResponse.data)
} catch (error) {
passkeyLoader.setLoaded()
messager.dispatchError(error)
return
}
onSuccess(loginResponse)
}
</script>

<template>
Expand All @@ -163,7 +236,7 @@ function initGoogleAuthButton() {
size="xl"
:label="$t('signIn.email')"
name="email"
:disabled="loader.isLoading()"
:disabled="loader.isLoading() || passkeyLoader.isLoading()"
:ui="{label:{wrapper: 'flex content-center items-center justify-between mb-4'}}"
:error="messager.validationMessage('email')"
@change="messager.resetValidationMessage('email')"
Expand All @@ -176,7 +249,7 @@ function initGoogleAuthButton() {
size="xl"
:label="$t('signIn.password')"
name="password"
:disabled="loader.isLoading()"
:disabled="loader.isLoading() || passkeyLoader.isLoading()"
:ui="{label:{wrapper: 'flex content-center items-center justify-between mb-4'}}"
:error="messager.validationMessage('password')"
@change="messager.resetValidationMessage('password')"
Expand All @@ -203,13 +276,27 @@ function initGoogleAuthButton() {
size="lg"
class="mb-6"
:loading="loader.isLoading()"
:disabled="loader.isLoading()"
:disabled="loader.isLoading() || passkeyLoader.isLoading()"
>
{{ $t('signIn.login') }}
</UButton>

<UDivider :label="$t('signIn.or')" class="mb-6" />

<UButton
v-if="passkeysSupported"
type="button"
block
size="lg"
class="mb-6"
variant="outline"
:loading="passkeyLoader.isLoading()"
:disabled="!passkeysSupported || passkeyLoader.isLoading()"
@click="loginWithPasskey"
>
{{ $t('signIn.loginWithPasskey') }}
</UButton>

<div id="login-google-button" class="mb-6" />

<ULink :to="localePath('/register')" class="link">
Expand Down
1 change: 1 addition & 0 deletions lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default {
email: 'Email',
password: 'Password',
login: 'Login',
loginWithPasskey: 'Login with Passkey',
forgotPassword: 'Forgot Your Password?',
dontHaveAccount: 'Don\'t have an account?',
or: 'OR'
Expand Down
1 change: 1 addition & 0 deletions lang/uk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default {
email: 'Email',
password: 'Пароль',
login: 'Увійти',
loginWithPasskey: 'Увійти Ключем Доступу',
forgotPassword: 'Забули пароль?',
dontHaveAccount: 'Досі не маєте аккаунту?',
or: 'АБО'
Expand Down

0 comments on commit 9006152

Please sign in to comment.