feat(auth): harden authentication and add configurable two-factor support

This commit is contained in:
2026-07-31 03:12:46 +02:00
parent 235d5f646c
commit e47243f7dc
51 changed files with 2904 additions and 1359 deletions

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { useForm } from '@inertiajs/vue3'
import { computed } from 'vue'
import RecoveryCodesPanel from '@/components/auth/RecoveryCodesPanel.vue'
import { useAuth } from '@/composables/useAuth'
import DashboardLayout from '@/layouts/DashboardLayout.vue'
@@ -235,19 +236,24 @@ function recoveryCodesAction(regenerate: boolean) {
</p>
</div>
<UFormField
:label="twoFactor.requiresPassword ? 'Current password' : 'Authentication or recovery code'"
:name="twoFactor.requiresPassword ? 'password' : 'code'"
:error="recoveryForm.errors.password || recoveryForm.errors.code"
v-if="twoFactor.requiresPassword"
label="Current password"
name="password"
:error="recoveryForm.errors.password"
>
<UInput
v-if="twoFactor.requiresPassword"
v-model="recoveryForm.password"
type="password"
autocomplete="current-password"
class="w-full"
/>
</UFormField>
<UFormField
label="Authentication or recovery code"
name="code"
:error="recoveryForm.errors.code"
>
<UInput
v-else
v-model="recoveryForm.code"
autocomplete="one-time-code"
class="w-full"
@@ -272,7 +278,16 @@ function recoveryCodesAction(regenerate: boolean) {
</div>
</div>
<form class="space-y-4 border-t pt-6" @submit.prevent="disable">
<UAlert
v-if="twoFactorRequired"
color="neutral"
variant="subtle"
icon="i-lucide-lock-keyhole"
title="Two-factor authentication is required by your administrator."
description="It cannot be disabled while the mandatory security policy is active."
/>
<form v-else class="space-y-4 border-t pt-6" @submit.prevent="disable">
<div>
<h3 class="font-medium text-error">
Disable two-factor authentication
@@ -282,19 +297,24 @@ function recoveryCodesAction(regenerate: boolean) {
</p>
</div>
<UFormField
:label="twoFactor.requiresPassword ? 'Current password' : 'Authentication or recovery code'"
:name="twoFactor.requiresPassword ? 'password' : 'code'"
:error="disableForm.errors.password || disableForm.errors.code"
v-if="twoFactor.requiresPassword"
label="Current password"
name="password"
:error="disableForm.errors.password"
>
<UInput
v-if="twoFactor.requiresPassword"
v-model="disableForm.password"
type="password"
autocomplete="current-password"
class="w-full"
/>
</UFormField>
<UFormField
label="Authentication or recovery code"
name="code"
:error="disableForm.errors.code"
>
<UInput
v-else
v-model="disableForm.code"
autocomplete="one-time-code"
class="w-full"
@@ -318,13 +338,7 @@ function recoveryCodesAction(regenerate: boolean) {
</p>
</div>
</template>
<div class="grid gap-2 sm:grid-cols-2">
<code
v-for="code in recoveryCodes"
:key="code"
class="rounded bg-elevated px-3 py-2 text-sm"
>{{ code }}</code>
</div>
<RecoveryCodesPanel :codes="recoveryCodes" />
</UCard>
</div>
</template>