add posibility user can change own password close #16
This commit is contained in:
85
src/vueLib/login/ChangePassword.vue
Normal file
85
src/vueLib/login/ChangePassword.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<DialogFrame
|
||||
ref="dialog"
|
||||
:header-title="$t('changePassword') + ' ' + $t('user') + ' ' + localUser.user"
|
||||
:height="510"
|
||||
:width="500"
|
||||
:inner-padding="48"
|
||||
>
|
||||
<q-form ref="form">
|
||||
<div class="row justify-center q-gutter-md">
|
||||
<q-input
|
||||
class="col-6 required"
|
||||
filled
|
||||
autocomplete="current-password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
:label="$t('currentPassword')"
|
||||
v-model="localUser.password"
|
||||
:rules="[(val) => !!val || $t('currentPassword') + ' ' + $t('isRequired')]"
|
||||
>
|
||||
<template #append>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
:icon="showPassword ? 'visibility_off' : 'visibility'"
|
||||
@mousedown.prevent="showPassword = true"
|
||||
@mouseup.prevent="showPassword = false"
|
||||
@mouseleave.prevent="showPassword = false"
|
||||
@touchstart.prevent="showPassword = true"
|
||||
@touchend.prevent="showPassword = false"
|
||||
@touchcancel.prevent="showPassword = false"
|
||||
></q-btn>
|
||||
</template>
|
||||
</q-input>
|
||||
<div class="col-6">
|
||||
<EnterNewPassword v-model:password="localUser.newPassword!" />
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
<div class="row justify-center">
|
||||
<q-btn class="q-ma-md" color="primary" no-caps @click="save">{{ $t('save') }}</q-btn>
|
||||
</div>
|
||||
</DialogFrame>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import DialogFrame from '../dialog/DialogFrame.vue';
|
||||
import type { User } from 'src/vueLib/models/users';
|
||||
import { i18n } from 'src/boot/lang';
|
||||
import { useNotify } from '../general/useNotify';
|
||||
import { validateQForm } from '../utils/validation';
|
||||
import EnterNewPassword from './EnterNewPassword.vue';
|
||||
|
||||
const { NotifyResponse } = useNotify();
|
||||
const dialog = ref();
|
||||
const showPassword = ref(false);
|
||||
const form = ref();
|
||||
const localUser = ref<User>({
|
||||
user: '',
|
||||
email: '',
|
||||
});
|
||||
|
||||
const open = (user: User) => {
|
||||
localUser.value = user;
|
||||
localUser.value.password = '';
|
||||
localUser.value.newPassword = '';
|
||||
dialog.value?.open();
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
dialog.value?.close();
|
||||
};
|
||||
|
||||
const emit = defineEmits(['update:password']);
|
||||
|
||||
async function save() {
|
||||
if (!(await validateQForm(form.value))) {
|
||||
NotifyResponse(i18n.global.t('notAllRequiredFieldsFilled'), 'error');
|
||||
return;
|
||||
}
|
||||
emit('update:password', localUser.value);
|
||||
}
|
||||
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
129
src/vueLib/login/EnterNewPassword.vue
Normal file
129
src/vueLib/login/EnterNewPassword.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-input
|
||||
class="col-5 required"
|
||||
:label="$t('password')"
|
||||
autocomplete="new-password"
|
||||
filled
|
||||
:lazy-rules="false"
|
||||
:rules="[validatePassword]"
|
||||
v-model="password"
|
||||
@update:model-value="checkStrength"
|
||||
:type="showPassword1 ? 'text' : 'password'"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
:icon="showPassword1 ? 'visibility_off' : 'visibility'"
|
||||
@mousedown.prevent="showPassword1 = true"
|
||||
@mouseup.prevent="showPassword1 = false"
|
||||
@mouseleave.prevent="showPassword1 = false"
|
||||
@touchstart.prevent="showPassword1 = true"
|
||||
@touchend.prevent="showPassword1 = false"
|
||||
@touchcancel.prevent="showPassword1 = false"
|
||||
></q-btn>
|
||||
<q-icon :name="strengthIcon" :color="strengthColor"></q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="q-mt-md q-px-xl">
|
||||
<q-linear-progress :value="strengthValue" :color="strengthColor" size="8px" rounded />
|
||||
<div class="text-caption text-center q-mt-xs">
|
||||
{{ strengthLabel }}
|
||||
</div>
|
||||
</div>
|
||||
<q-input
|
||||
ref="pwdForm2"
|
||||
class="col-5 required"
|
||||
:label="$t('password')"
|
||||
autocomplete="new-password"
|
||||
filled
|
||||
:type="showPassword2 ? 'text' : 'password'"
|
||||
:rules="[checkSamePassword]"
|
||||
v-model="passwordCheck"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
:icon="showPassword2 ? 'visibility_off' : 'visibility'"
|
||||
@mousedown.prevent="showPassword2 = true"
|
||||
@mouseup.prevent="showPassword2 = false"
|
||||
@mouseleave.prevent="showPassword2 = false"
|
||||
@touchstart.prevent="showPassword2 = true"
|
||||
@touchend.prevent="showPassword2 = false"
|
||||
@touchcancel.prevent="showPassword2 = false"
|
||||
></q-btn>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import zxcvbn from 'zxcvbn';
|
||||
import { i18n } from 'src/boot/lang';
|
||||
|
||||
const props = defineProps({
|
||||
password: { type: String },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:password']);
|
||||
|
||||
const pwdForm2 = ref();
|
||||
const showPassword1 = ref(false);
|
||||
const showPassword2 = ref(false);
|
||||
const passwordCheck = ref('');
|
||||
const strengthValue = ref(0);
|
||||
const strengthLabel = ref('Enter a password');
|
||||
const strengthColor = ref('grey');
|
||||
const strengthIcon = ref('lock');
|
||||
|
||||
function checkStrength() {
|
||||
const result = zxcvbn(password.value || '');
|
||||
strengthValue.value = (result.score + 1) / 5;
|
||||
const levels = [
|
||||
i18n.global.t('veryWeak'),
|
||||
i18n.global.t('weak'),
|
||||
i18n.global.t('fair'),
|
||||
i18n.global.t('good'),
|
||||
i18n.global.t('strong'),
|
||||
];
|
||||
const colors = ['red', 'orange', 'yellow', 'light-green', 'green'];
|
||||
const icon = ['lock', 'warning', 'error_outline', 'check_circle_outline', 'verified_user'];
|
||||
|
||||
strengthLabel.value = levels[result.score] || i18n.global.t('veryWeak');
|
||||
strengthColor.value = colors[result.score] || 'grey';
|
||||
strengthIcon.value = icon[result.score] || 'lock';
|
||||
}
|
||||
|
||||
function validatePassword(): string | boolean {
|
||||
pwdForm2.value?.validate();
|
||||
|
||||
if (!password.value) return i18n.global.t('passwordIsRequired');
|
||||
|
||||
if (password.value.length < 8) {
|
||||
return i18n.global.t('passwordTooShort');
|
||||
} else if (!/[A-Z]/.test(password.value)) {
|
||||
return i18n.global.t('passwordNeedsUppercase');
|
||||
} else if (!/[a-z]/.test(password.value)) {
|
||||
return i18n.global.t('passwordNeedsLowercase');
|
||||
} else if (!/[0-9]/.test(password.value)) {
|
||||
return i18n.global.t('passwordNeedsNumber');
|
||||
} else if (!/[!@#$%^&*(),.?":{}|<>]/.test(password.value)) {
|
||||
return i18n.global.t('passwordNeedsSpecial');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkSamePassword(): string | boolean {
|
||||
if (password.value === passwordCheck.value) return true;
|
||||
return i18n.global.t('passwordDoNotMatch');
|
||||
}
|
||||
|
||||
const password = computed({
|
||||
get: () => props.password,
|
||||
set: (v) => emit('update:password', v),
|
||||
});
|
||||
</script>
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { Role } from './roles';
|
||||
import type { Settings } from './settings';
|
||||
|
||||
export interface User {
|
||||
id?: number;
|
||||
user: string;
|
||||
email: string;
|
||||
role: string;
|
||||
role?: Role;
|
||||
expiration?: string;
|
||||
password?: string;
|
||||
newPassword?: string;
|
||||
settings?: Settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ export function useUserTable() {
|
||||
field: 'email',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'newPassword',
|
||||
align: 'left' as const,
|
||||
label: '',
|
||||
field: 'newPassword',
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
align: 'left' as const,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table
|
||||
flat
|
||||
bordered
|
||||
@@ -19,6 +18,7 @@
|
||||
:selection="selectOption ? 'multiple' : 'none'"
|
||||
v-model:selected="selected"
|
||||
binary-state-sort
|
||||
dense
|
||||
class="bigger-table-text"
|
||||
>
|
||||
<template v-slot:top-left>
|
||||
@@ -59,13 +59,15 @@
|
||||
{{ $t('selected') }}: {{ selected.length }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- top right of table-->
|
||||
|
||||
<template v-slot:top-right>
|
||||
<q-input filled dense debounce="300" v-model="filter" :placeholder="$t('search')">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
<SearchableInput v-model="filter" :placeholder="$t('search')" />
|
||||
</template>
|
||||
|
||||
<!-- table body content-->
|
||||
|
||||
<template v-slot:body-cell="props">
|
||||
<q-td
|
||||
:props="props"
|
||||
@@ -83,6 +85,19 @@
|
||||
{{ props.value }}
|
||||
</q-td>
|
||||
</template>
|
||||
<template v-slot:body-cell-newPassword="props">
|
||||
<q-td :props="props">
|
||||
<q-btn
|
||||
v-if="autorized(props.row) && currentUser.id === props.row.id"
|
||||
flat
|
||||
dense
|
||||
no-caps
|
||||
color="primary"
|
||||
:label="$t('changePassword')"
|
||||
@click="openPwdDialog(props.row)"
|
||||
></q-btn>
|
||||
</q-td>
|
||||
</template>
|
||||
<template v-slot:body-cell-role="props">
|
||||
<q-td :props="props">
|
||||
<q-select
|
||||
@@ -90,6 +105,7 @@
|
||||
dense
|
||||
v-model="props.row.role"
|
||||
:options="localRoles"
|
||||
option-label="role"
|
||||
@update:model-value="updateUser(props.row)"
|
||||
></q-select>
|
||||
</q-td>
|
||||
@@ -127,7 +143,6 @@
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
<EditOneDialog
|
||||
ref="editOneDialog"
|
||||
query-id
|
||||
@@ -145,6 +160,7 @@
|
||||
button-ok-color="red"
|
||||
v-on:update-confirm="(val) => removeUser(...val)"
|
||||
></OkDialog>
|
||||
<ChangePassword ref="changePwdDialog" v-on:update:password="changePassword" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -159,6 +175,8 @@ import { useUserTable } from './UserTable';
|
||||
import { roles, useRoleTable } from '../roles/RoleTable';
|
||||
import { i18n } from 'src/boot/lang';
|
||||
import { useUserStore } from 'src/vueLib/login/userStore';
|
||||
import ChangePassword from 'src/vueLib/login/ChangePassword.vue';
|
||||
import SearchableInput from '../components/SearchableInput.vue';
|
||||
|
||||
const { NotifyResponse } = useNotify();
|
||||
const editOneDialog = ref();
|
||||
@@ -176,6 +194,7 @@ const currentUser = ref();
|
||||
const { users, pagination, loading, columns, updateUsers } = useUserTable();
|
||||
const { updateRoles } = useRoleTable();
|
||||
const user = useUserStore();
|
||||
const changePwdDialog = ref();
|
||||
|
||||
//load on mounting page
|
||||
onMounted(() => {
|
||||
@@ -201,6 +220,26 @@ function openAllValueDialog(user: User | null) {
|
||||
editAllDialog.value?.open(user);
|
||||
}
|
||||
|
||||
//opens password change dialog
|
||||
function openPwdDialog(user: User) {
|
||||
changePwdDialog.value.open(user);
|
||||
}
|
||||
|
||||
//change password api request
|
||||
async function changePassword(user: User) {
|
||||
console.log(8, user);
|
||||
if (user.password == user.newPassword) {
|
||||
NotifyResponse(i18n.global.t('samePasswordEntered'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
await appApi
|
||||
.post('/users/new/password', user)
|
||||
.then((resp) => console.log(67, resp))
|
||||
.catch((err) => console.error(err));
|
||||
changePwdDialog.value.close();
|
||||
}
|
||||
|
||||
//opens remove dialog
|
||||
function openRemoveDialog(...users: Users) {
|
||||
if (users.length === 1) {
|
||||
@@ -245,20 +284,6 @@ function updateUser(user: User) {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes blink-yellow {
|
||||
0%,
|
||||
100% {
|
||||
background-color: yellow;
|
||||
}
|
||||
50% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.blink-yellow {
|
||||
animation: blink-yellow 1.5s step-start 6 !important;
|
||||
}
|
||||
|
||||
.bigger-table-text .q-table__middle td {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user