Integrate passkey authentication and management

- Added passkey support for secure, passwordless authentication using WebAuthn.
- Implemented passkey management in user profiles, enabling users to add, rename, and delete passkeys.
- Updated authentication forms to support passkey login, including feature detection and login flow.
- Enhanced internationalization files with passkey-related translations for multiple locales.
- Introduced new components and services for handling passkey operations and WebAuthn interactions.
This commit is contained in:
Jeppe Bundgaard
2026-02-23 21:59:01 +01:00
parent af7b02bc79
commit be60ce8fdf
14 changed files with 1382 additions and 36 deletions
+46 -2
View File
@@ -1,12 +1,21 @@
<script setup>
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { parseError, getError } from "@/components/request/HandleGlobalError.vue";
import { parseError, getError, addError } from "@/components/request/HandleGlobalError.vue";
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { isPasskeySupported, authenticateWithPasskey } from "@/services/PasskeyAuthService.js";
const { t } = useI18n();
// Passkey support
const passkeySupported = ref(false);
const isPasskeyLoading = ref(false);
onMounted(() => {
passkeySupported.value = isPasskeySupported();
});
// Login method selection
const loginMethod = ref('phone'); // 'phone' or 'username'
@@ -43,6 +52,23 @@ const login = async () => {
const doNothing = () => {
// Prevent form submission
};
// Passkey login
const loginWithPasskey = async () => {
isPasskeyLoading.value = true;
try {
const result = await authenticateWithPasskey('subuser');
if (result.token) {
localStorage.setItem('token', result.token);
// Reload the page to update the UI
window.location.reload();
}
} catch (e) {
addError('auth', e.message || t('passkeys.login_failed'));
} finally {
isPasskeyLoading.value = false;
}
};
</script>
<template>
@@ -114,6 +140,24 @@ const doNothing = () => {
<LoadButtonWhileAwait :loadFunction="login" class="submit" id="subuser-login-button">
{{ t('auth.login') }}
</LoadButtonWhileAwait>
<!-- Passkey login button -->
<button
v-if="passkeySupported"
type="button"
class="submit passkey-btn"
@click="loginWithPasskey"
:disabled="isPasskeyLoading"
id="subuser-passkey-login-button"
>
<span class="icon" v-if="!isPasskeyLoading">
<i class="fas fa-fingerprint"></i>
</span>
<span class="icon" v-else>
<i class="fas fa-spinner fa-spin"></i>
</span>
<span>{{ t('passkeys.login_with_passkey') }}</span>
</button>
<router-link :to="{name: 'login'}" class="link">{{ t('auth.login_as_customer') }}</router-link>
</form>