feat: support change-password and admin must_change_password
This commit is contained in:
@@ -35,6 +35,14 @@ export const useAuth = () => {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
console.log('[Auth] User synced successfully')
|
console.log('[Auth] User synced successfully')
|
||||||
user.value = res.user
|
user.value = res.user
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
if (res.user.must_change_password && route.path !== '/auth/change-password') {
|
||||||
|
console.log('[Auth] User must change password, redirecting...')
|
||||||
|
await navigateTo('/auth/change-password')
|
||||||
|
} else {
|
||||||
|
await navigateTo('/dashboard')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Auth] Sync failed, logging out:', error)
|
console.error('[Auth] Sync failed, logging out:', error)
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
middleware: 'auth'
|
||||||
|
})
|
||||||
|
|
||||||
|
useHead({ title: 'Cambio Password - Visione' })
|
||||||
|
|
||||||
|
const { changePassword, user, syncUser } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
current_password: '', // FIX: rinominato da old_password
|
||||||
|
new_password: '',
|
||||||
|
confirm_password: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const errorMessage = ref('')
|
||||||
|
|
||||||
|
// FIX: Stati separati per la visibilità di ogni campo
|
||||||
|
const showCurrent = ref(false)
|
||||||
|
const showNew = ref(false)
|
||||||
|
const showConfirm = ref(false)
|
||||||
|
|
||||||
|
const canSubmit = computed(() => {
|
||||||
|
return (
|
||||||
|
formData.current_password.length > 0 &&
|
||||||
|
formData.new_password.length >= 8 &&
|
||||||
|
formData.new_password === formData.confirm_password
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!canSubmit.value) return
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
// FIX: Payload con current_password come richiesto dall'API
|
||||||
|
const res = await changePassword({
|
||||||
|
current_password: formData.current_password,
|
||||||
|
new_password: formData.new_password
|
||||||
|
} as any) // Cast temporaneo se l'interfaccia types/api non è ancora aggiornata
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
await syncUser()
|
||||||
|
await router.push('/dashboard')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
errorMessage.value = error.data?.message || 'Errore. Verifica la password attuale.'
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container my-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-8 col-lg-6">
|
||||||
|
|
||||||
|
<div v-if="user?.must_change_password" class="callout callout-highlight callout-warning mb-4">
|
||||||
|
<div class="callout-title d-flex align-items-center">
|
||||||
|
<BIIcon name="it-warning-circle" size="sm" color="warning" class="me-2" />
|
||||||
|
Azione Richiesta
|
||||||
|
</div>
|
||||||
|
<p>Per motivi di sicurezza, imposta una nuova password per il tuo account.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-bg shadow-sm">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h3 class="card-title h4 mb-4">Cambia la tua password</h3>
|
||||||
|
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
|
||||||
|
<!-- 1. PASSWORD ATTUALE -->
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text"><BIIcon name="it-lock" size="sm" /></div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
:type="showCurrent ? 'text' : 'password'"
|
||||||
|
class="form-control"
|
||||||
|
id="current_password"
|
||||||
|
v-model="formData.current_password"
|
||||||
|
>
|
||||||
|
<!-- FIX: Aggiunta classe 'active' dinamica per evitare sovrapposizione placeholder -->
|
||||||
|
<label for="current_password" :class="{ 'active': formData.current_password }">Password attuale</label>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" @click="showCurrent = !showCurrent">
|
||||||
|
<BIIcon :name="showCurrent ? 'it-password-visible' : 'it-password-invisible'" size="sm" color="white" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<!-- 2. NUOVA PASSWORD -->
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text"><BIIcon name="it-lock" size="sm" /></div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
:type="showNew ? 'text' : 'password'"
|
||||||
|
class="form-control"
|
||||||
|
id="new_password"
|
||||||
|
v-model="formData.new_password"
|
||||||
|
>
|
||||||
|
<label for="new_password" :class="{ 'active': formData.new_password }">Nuova password</label>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" @click="showNew = !showNew">
|
||||||
|
<BIIcon :name="showNew ? 'it-password-visible' : 'it-password-invisible'" size="sm" color="white" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">Minimo 8 caratteri.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 3. CONFERMA PASSWORD -->
|
||||||
|
<div class="form-group mb-4">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text"><BIIcon name="it-lock" size="sm" /></div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
:type="showConfirm ? 'text' : 'password'"
|
||||||
|
class="form-control"
|
||||||
|
:class="{ 'is-invalid': formData.confirm_password && formData.confirm_password !== formData.new_password }"
|
||||||
|
id="confirm_password"
|
||||||
|
v-model="formData.confirm_password"
|
||||||
|
>
|
||||||
|
<label for="confirm_password" :class="{ 'active': formData.confirm_password }">Conferma nuova password</label>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" @click="showConfirm = !showConfirm">
|
||||||
|
<BIIcon :name="showConfirm ? 'it-password-visible' : 'it-password-invisible'" size="sm" color="white" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="errorMessage" class="alert alert-danger mb-4" role="alert">
|
||||||
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary w-100 py-2"
|
||||||
|
:disabled="!canSubmit || isLoading"
|
||||||
|
>
|
||||||
|
<span v-if="isLoading" class="spinner-border spinner-border-sm me-2"></span>
|
||||||
|
<span v-else>Aggiorna Password</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Correzione per i bottoni occhio in Bootstrap Italia */
|
||||||
|
.input-group-append .btn {
|
||||||
|
z-index: 4;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stile per gestire correttamente le label di Bootstrap Italia che si sovrappongono */
|
||||||
|
.form-group label.active {
|
||||||
|
transform: translateY(-140%) scale(0.9);
|
||||||
|
color: #006f9d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus + label {
|
||||||
|
color: #006f9d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -55,7 +55,6 @@ const handlePasswordLogin = async () => {
|
|||||||
try {
|
try {
|
||||||
const res = await loginWithPassword({ email: email.value, password: password.value })
|
const res = await loginWithPassword({ email: email.value, password: password.value })
|
||||||
await handleLoginSuccess(res.token)
|
await handleLoginSuccess(res.token)
|
||||||
router.push('/dashboard')
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
errorMessage.value = 'Credenziali non valide.'
|
errorMessage.value = 'Credenziali non valide.'
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user