feat: App lock with biometric/PIN

- LockScreen component with PIN pad
- SecuritySettings for setup
- Biometric (WebAuthn) support
- PIN fallback (6 digits)
- Auto-lock after 30s in background
- Lock on app start if enabled
- Settings page integration
This commit is contained in:
2026-03-12 20:54:53 +00:00
parent 21c88be74f
commit e5d09e9c80
26 changed files with 720 additions and 53 deletions

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { api } from '@/api'
import SecuritySettings from '@/components/SecuritySettings.vue'
const authStore = useAuthStore()
@@ -11,6 +12,32 @@ const confirmPassword = ref('')
const loading = ref(false)
const message = ref('')
const error = ref('')
const showSecuritySettings = ref(false)
// App lock status
const lockMethod = ref<string | null>(null)
onMounted(() => {
lockMethod.value = localStorage.getItem('lockMethod')
// Store email for biometric setup
if (authStore.user?.email) {
localStorage.setItem('userEmail', authStore.user.email)
}
})
const lockStatusText = computed(() => {
if (!lockMethod.value || lockMethod.value === 'none') return 'Deaktiviert'
if (lockMethod.value === 'biometric') return 'Fingerabdruck / Face ID'
if (lockMethod.value === 'pin') return 'PIN-Code'
return 'Unbekannt'
})
const lockStatusClass = computed(() => {
if (!lockMethod.value || lockMethod.value === 'none') {
return 'bg-red-100 text-red-700'
}
return 'bg-green-100 text-green-700'
})
async function changePassword() {
if (newPassword.value !== confirmPassword.value) {
@@ -42,6 +69,11 @@ async function changePassword() {
loading.value = false
}
}
function handleSecurityClose() {
showSecuritySettings.value = false
lockMethod.value = localStorage.getItem('lockMethod')
}
</script>
<template>
@@ -50,7 +82,7 @@ async function changePassword() {
<!-- Profile -->
<div class="card">
<h2 class="text-lg font-semibold mb-4">Profil</h2>
<h2 class="text-lg font-semibold mb-4">👤 Profil</h2>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm text-gray-500">Name</label>
@@ -67,9 +99,33 @@ async function changePassword() {
</div>
</div>
<!-- App Security -->
<div class="card">
<h2 class="text-lg font-semibold mb-4">🔐 App-Sicherheit</h2>
<p class="text-gray-600 text-sm mb-4">
Schütze deine App mit Fingerabdruck, Face ID oder PIN.
Bei jedem Öffnen der App musst du dich verifizieren.
</p>
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
<div>
<div class="font-medium">App-Sperre</div>
<div :class="['inline-block px-2 py-1 rounded text-sm mt-1', lockStatusClass]">
{{ lockStatusText }}
</div>
</div>
<button
@click="showSecuritySettings = true"
class="btn btn-primary"
>
Konfigurieren
</button>
</div>
</div>
<!-- Change Password -->
<div class="card">
<h2 class="text-lg font-semibold mb-4">Passwort ändern</h2>
<h2 class="text-lg font-semibold mb-4">🔑 Passwort ändern</h2>
<form @submit.prevent="changePassword" class="space-y-4 max-w-md">
<div>
<label class="block text-sm font-medium mb-1">Aktuelles Passwort</label>
@@ -92,5 +148,11 @@ async function changePassword() {
</button>
</form>
</div>
<!-- Security Settings Modal -->
<SecuritySettings
v-if="showSecuritySettings"
@close="handleSecurityClose"
/>
</div>
</template>