feat: MFA frontend with auth page restyling, challenge screen, and setup wizard
- Restyle organizer auth pages: Dutch text, remove placeholder social login - Restyle portal auth pages to Vuexy v1 centered card pattern with decorative shapes - MFA challenge card component with VOtpInput, method tabs, backup code input, trusted device checkbox, and session countdown timer - Login pages handle mfa_required response with device fingerprint header - Security settings page with TOTP setup (QR code), email setup, disable MFA, backup codes regeneration, and trusted devices management - Portal profile page includes MFA security section - Admin user detail page shows MFA status with reset button - MFA enforcement route guard redirects to security settings when required - Device fingerprint utility for trusted device identification - MFA types, composables with TanStack Query for both apps Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
useUpdateAdminUser,
|
||||
useStartImpersonation,
|
||||
} from '@/composables/api/useAdmin'
|
||||
import { useAdminResetMfa } from '@/composables/api/useMfa'
|
||||
import { useImpersonationStore } from '@/stores/useImpersonationStore'
|
||||
import type { AdminUser, UpdateAdminUserPayload } from '@/types/admin'
|
||||
|
||||
@@ -86,6 +87,21 @@ function confirmImpersonate() {
|
||||
})
|
||||
}
|
||||
|
||||
// MFA Reset
|
||||
const isMfaResetDialogOpen = ref(false)
|
||||
const { mutate: resetMfa, isPending: isResettingMfa } = useAdminResetMfa()
|
||||
const showMfaResetSuccess = ref(false)
|
||||
|
||||
function confirmMfaReset() {
|
||||
resetMfa(userId.value, {
|
||||
onSuccess: () => {
|
||||
isMfaResetDialogOpen.value = false
|
||||
showMfaResetSuccess.value = true
|
||||
refetch()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString('nl-NL', {
|
||||
day: '2-digit',
|
||||
@@ -253,6 +269,30 @@ function getInitials(name: string): string {
|
||||
{{ user.email_verified_at ? formatDate(user.email_verified_at) : 'Niet geverifieerd' }}
|
||||
</p>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<p class="text-body-2 text-disabled mb-1">
|
||||
Tweestapsverificatie
|
||||
</p>
|
||||
<p class="text-body-1">
|
||||
<VChip
|
||||
:color="user.mfa_enabled ? 'success' : 'default'"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ user.mfa_enabled ? 'Ingeschakeld' : 'Uitgeschakeld' }}
|
||||
</VChip>
|
||||
<VBtn
|
||||
v-if="user.mfa_enabled"
|
||||
variant="tonal"
|
||||
color="error"
|
||||
size="small"
|
||||
class="ms-2"
|
||||
@click="isMfaResetDialogOpen = true"
|
||||
>
|
||||
MFA resetten
|
||||
</VBtn>
|
||||
</p>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -404,6 +444,41 @@ function getInitials(name: string): string {
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- MFA Reset Dialog -->
|
||||
<VDialog
|
||||
v-model="isMfaResetDialogOpen"
|
||||
max-width="460"
|
||||
>
|
||||
<VCard title="MFA resetten">
|
||||
<VCardText>
|
||||
<VAlert
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
Weet je zeker dat je de tweestapsverificatie van <strong>{{ user?.full_name }}</strong> wilt uitschakelen?
|
||||
De gebruiker moet MFA opnieuw instellen bij de volgende login.
|
||||
</VAlert>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
@click="isMfaResetDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="error"
|
||||
:loading="isResettingMfa"
|
||||
@click="confirmMfaReset"
|
||||
>
|
||||
MFA resetten
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Success snackbar -->
|
||||
<VSnackbar
|
||||
v-model="showEditSuccess"
|
||||
@@ -412,5 +487,13 @@ function getInitials(name: string): string {
|
||||
>
|
||||
Gebruiker bijgewerkt
|
||||
</VSnackbar>
|
||||
|
||||
<VSnackbar
|
||||
v-model="showMfaResetSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
MFA is gereset voor deze gebruiker
|
||||
</VSnackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user