Refactor password update functionality

This commit is contained in:
Douglas Barone 2023-12-18 15:09:36 -04:00
parent e6381d30c4
commit e4dc910865
5 changed files with 87 additions and 30 deletions

View File

@ -39,18 +39,18 @@ async function getUserDN(username: string): Promise<string> {
export async function updatePassword({
username,
password,
currentPassword,
newPassword
}: {
username: string
password: string
currentPassword: string
newPassword: string
}): Promise<'SUCCESS' | 'FAIL'> {
try {
const userDN = await getUserDN(username)
// Check if user can bind with current password
await ldapClient.bind(userDN, password)
await ldapClient.bind(userDN, currentPassword)
await ldapClient.unbind()
// Bind with admin user to change password
@ -68,7 +68,7 @@ export async function updatePassword({
// operation: 'delete',
// modification: new Attribute({
// type: 'unicodePwd',
// values: [encodePassword(password)]
// values: [encodePassword(currentPassword)]
// })
// }),

View File

@ -16,15 +16,16 @@ export const appRouter = router({
updatePassword: input(
z.object({
username: z.string(),
password: z.string(),
currentPassword: z.string(),
newPassword: z.string().min(8)
})
).mutation(async ({ input }) => {
const { username, password, newPassword } = input
const { username, currentPassword, newPassword } = input
try {
await updatePassword({
username,
password,
currentPassword,
newPassword
})

View File

@ -1,6 +1,6 @@
<template>
<v-form @submit.prevent="submit">
<v-card :elevation="2">
<v-form ref="form" @submit.prevent="submit" validate-on="input">
<v-card :elevation="2" :loading="loading" :disabled="loading">
<v-card-title class="mb-6 pa-6 text-center">
<span class="headline font-weight-light text-h4">Trocar senha</span>
</v-card-title>
@ -13,7 +13,7 @@
autocomplete="username"
hint="SIAPE para servidores, CPF para alunos."
prepend-inner-icon="mdi-account"
:rules="[v => !!v || 'Campo obrigatório']"
:rules="[v => !!v || 'O usuário é obrigatório']"
required
density="compact"
/>
@ -27,7 +27,7 @@
prepend-inner-icon="mdi-form-textbox-password"
:append-inner-icon="showCurrent ? 'mdi-eye' : 'mdi-eye-off'"
@click:append-inner="showCurrent = !showCurrent"
:rules="[v => !!v || 'Campo obrigatório']"
:rules="[v => !!v || 'A senha atual é obrigatória']"
required
density="compact"
/>
@ -42,7 +42,7 @@
prepend-inner-icon="mdi-lock-check"
:append-inner-icon="showNew ? 'mdi-eye' : 'mdi-eye-off'"
@click:append-inner="showNew = !showNew"
:rules="[v => !!v || 'Campo obrigatório']"
:rules="newPasswordRules"
required
density="compact"
/>
@ -56,7 +56,7 @@
:append-inner-icon="showConfirm ? 'mdi-eye' : 'mdi-eye-off'"
@click:append-inner="showConfirm = !showConfirm"
:rules="[
v => !!v || 'Campo obrigatório',
v => !!v || 'A confirmação da senha é obrigatória',
v => v === newPassword || 'As senhas não coincidem'
]"
required
@ -66,19 +66,31 @@
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn type="submit" color="primary">Trocar senha</v-btn>
<v-btn
type="submit"
color="primary"
variant="flat"
size="large"
:disabled="!valid || loading"
>
Trocar senha
</v-btn>
</v-card-actions>
</v-card>
</v-form>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ref, computed } from 'vue'
import PasswordChecker from './PasswordChecker.vue'
const emit = defineEmits<{
submit: [{ username: string; currentPassword: string; newPassword: string }]
}>()
defineProps<{
loading: boolean
}>()
const username = ref('')
const password = ref('')
const newPassword = ref('')
@ -88,11 +100,32 @@ const showCurrent = ref(false)
const showNew = ref(false)
const showConfirm = ref(false)
const submit = () => {
emit('submit', {
username: username.value,
currentPassword: password.value,
newPassword: newPassword.value
})
// use form refs to validate
const form = ref<HTMLFormElement | null>(null)
const valid = computed(() => {
return form.value?.isValid || false
})
const newPasswordRules = [
(v: string) => !!v || 'A nova senha é obrigatória',
(v: string) =>
/[a-z]/.test(v) || 'A nova senha deve ter pelo menos uma letra minúscula',
(v: string) =>
/[A-Z]/.test(v) || 'A nova senha deve ter pelo menos uma letra maiúscula',
(v: string) =>
v.length >= 8 || 'A nova senha deve ter pelo menos 8 caracteres',
(v: string) =>
/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/.test(v) ||
'A nova senha deve ter pelo menos um caractere especial'
]
async function submit() {
if (await form.value?.validate())
emit('submit', {
username: username.value,
currentPassword: password.value,
newPassword: newPassword.value
})
}
</script>

View File

@ -1,15 +1,15 @@
<template>
<div>
<div class="font-weight-light mb-2">Regras para a nova senha:</div>
<v-alert
v-for="alert in alerts"
:key="alert.text"
:type="alertType(password, alert.rule)"
class="mb-1"
class="mb-2 rule"
variant="tonal"
density="compact"
>
{{ alert.text }}
</v-alert>
:text="alert.text"
/>
</div>
</template>
@ -44,3 +44,9 @@ const alerts = [
}
]
</script>
<style scoped>
.rule {
transition: all 0.2s ease-in-out;
}
</style>

View File

@ -1,19 +1,23 @@
<template>
<v-container>
<v-row>
<v-col cols="8" offset="2">
<v-row justify="center">
<v-col xl="5" lg="6" md="7" sm="10">
<logo class="mx-auto my-3" :style="{ maxWidth: '128px' }" />
<main-form @submit="submit" />
<main-form @submit="handleSubmit" :loading="loading" />
</v-col>
</v-row>
</v-container>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import Logo from '../components/Logo.vue'
import MainForm from '../components/MainForm.vue'
import { trpc } from '../trpc'
function submit({
const loading = ref(false)
async function handleSubmit({
username,
currentPassword,
newPassword
@ -22,6 +26,19 @@ function submit({
currentPassword: string
newPassword: string
}) {
console.log('submit', { username, currentPassword, newPassword })
try {
loading.value = true
await trpc.updatePassword.mutate({
username,
currentPassword,
newPassword
})
} catch (error) {
console.error(error)
} finally {
setTimeout(() => {
loading.value = false
}, 1000)
}
}
</script>