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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user