[core] Allow users to see passwords if wished

This commit is contained in:
Ferdinand Thiessen 2021-03-31 17:22:55 +02:00
parent 1b1888d4fd
commit c61b5fcc0c
5 changed files with 123 additions and 90 deletions

View File

@ -7,6 +7,7 @@
:placeholder="placeholder" :placeholder="placeholder"
:rules="customRules" :rules="customRules"
:clearable="clearable" :clearable="clearable"
v-bind="attrs"
@clear="dateTime = ''" @clear="dateTime = ''"
> >
<template #append> <template #append>
@ -58,7 +59,7 @@ export default defineComponent({
}, },
}, },
emits: { 'update:modelValue': (date?: Date) => !!date || !date }, emits: { 'update:modelValue': (date?: Date) => !!date || !date },
setup(props, { emit }) { setup(props, { emit, attrs }) {
const customRules = computed(() => [ const customRules = computed(() => [
props.type == 'date' ? stringIsDate : props.type == 'time' ? stringIsTime : stringIsDateTime, props.type == 'date' ? stringIsDate : props.type == 'time' ? stringIsTime : stringIsDateTime,
...props.rules, ...props.rules,
@ -138,12 +139,13 @@ export default defineComponent({
} }
return { return {
attrs,
clearable, clearable,
date,
time,
dateTime,
customRules, customRules,
date,
dateTime,
placeholder, placeholder,
time,
}; };
}, },
}); });

View File

@ -0,0 +1,47 @@
<template>
<q-input v-model="password" v-bind="attrs" :label="label" :type="type">
<template #append><q-icon :name="name" class="cursor-pointer" @click="toggle" /></template
></q-input>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
name: 'PasswordInput',
props: {
modelValue: {
type: String,
required: true,
},
label: {
type: String,
default: '',
},
},
emits: {
'update:modelValue': (value: string) => !!value,
},
setup(props, { emit, attrs }) {
const isPassword = ref(true);
const type = computed(() => (isPassword.value ? 'password' : 'text'));
const name = computed(() => (isPassword.value ? 'visibility_off' : 'visibility'));
const password = computed({
get: () => props.modelValue,
set: (value: string) => emit('update:modelValue', value),
});
function toggle() {
isPassword.value = !isPassword.value;
}
return {
attrs,
isPassword,
name,
password,
toggle,
type,
};
},
});
</script>

View File

@ -6,20 +6,21 @@
</q-toolbar> </q-toolbar>
<q-card-section> <q-card-section>
<q-form ref="LoginForm" class="q-gutter-md" @submit="doLogin"> <q-form class="q-gutter-md" @submit="doLogin">
<q-input <q-input
v-model="userid" v-model="userid"
filled filled
label="Benutzername oder E-Mail" label="Benutzername oder E-Mail"
:rules="rules" autocomplete="username"
:rules="[notEmpty]"
tabindex="1" tabindex="1"
/> />
<q-input <password-input
v-model="password" v-model="password"
filled filled
type="password" label="Passwort"
label="Password" autocomplete="cureent-password"
:rules="rules" :rules="[notEmpty]"
tabindex="2" tabindex="2"
/> />
<div class="row justify-between"> <div class="row justify-between">
@ -54,15 +55,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useMainStore } from 'src/stores';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Loading, Notify } from 'quasar'; import { Loading, Notify } from 'quasar';
import { useMainStore } from 'src/stores';
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
import { setBaseURL, api } from 'boot/axios'; import { setBaseURL, api } from 'boot/axios';
import { notEmpty } from 'src/utils/validators';
import { useUserStore } from 'src/plugins/user/store'; import { useUserStore } from 'src/plugins/user/store';
import PasswordInput from 'src/components/utils/PasswordInput.vue';
export default defineComponent({ export default defineComponent({
name: 'Login', name: 'Login',
components: { PasswordInput },
setup() { setup() {
const mainStore = useMainStore(); const mainStore = useMainStore();
const mainRoute = { name: 'dashboard' }; const mainRoute = { name: 'dashboard' };
@ -71,7 +75,6 @@ export default defineComponent({
/* Stuff for the real login page */ /* Stuff for the real login page */
const userid = ref(''); const userid = ref('');
const password = ref(''); const password = ref('');
const rules = [(val: string) => (val && val.length > 0) || 'Feld darf nicht leer sein!'];
const server = ref<string | undefined>(api.defaults.baseURL); const server = ref<string | undefined>(api.defaults.baseURL);
const visible = ref(false); const visible = ref(false);
@ -138,15 +141,15 @@ export default defineComponent({
} }
return { return {
userid, changeUrl,
password,
doLogin, doLogin,
doReset, doReset,
rules, notEmpty,
server,
changeUrl,
visible,
openServerSettings, openServerSettings,
password,
server,
userid,
visible,
}; };
}, },
}); });

View File

@ -2,44 +2,48 @@
<q-form @submit="save" @reset="reset"> <q-form @submit="save" @reset="reset">
<q-card-section class="fit row justify-start content-center items-center"> <q-card-section class="fit row justify-start content-center items-center">
<q-input <q-input
v-model="user_model.firstname" v-model="userModel.firstname"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="Vorname" label="Vorname"
:rules="[notEmpty]" :rules="[notEmpty]"
autocomplete="given-name"
filled filled
/> />
<q-input <q-input
v-model="user_model.lastname" v-model="userModel.lastname"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="Nachname" label="Nachname"
:rules="[notEmpty]" :rules="[notEmpty]"
autocomplete="family-name"
filled filled
/> />
<q-input <q-input
v-model="user_model.display_name" v-model="userModel.display_name"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="Angezeigter Name" label="Angezeigter Name"
:rules="[notEmpty]" :rules="[notEmpty]"
autocomplete="nickname"
filled filled
/> />
<q-input <q-input
v-model="user_model.mail" v-model="userModel.mail"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="E-Mail" label="E-Mail"
:rules="[isEmail, notEmpty]" :rules="[isEmail, notEmpty]"
autocomplete="email"
filled filled
/> />
<q-input <q-input
v-model="user_model.userid" v-model="userModel.userid"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="Benutzername" label="Benutzername"
:readonly="!newUser" :readonly="!newUser"
:rules="[isUseridUsed, notEmpty]" :rules="[isUIDUsed, notEmpty]"
autocomplete="username"
filled filled
/> />
<q-select <q-select
v-model="user_model.roles" v-model="userModel.roles"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="Rollen" label="Rollen"
filled filled
@ -51,9 +55,10 @@
option-value="name" option-value="name"
/> />
<IsoDateInput <IsoDateInput
v-model="user_model.birthday" v-model="userModel.birthday"
class="col-xs-12 col-sm-6 q-pa-sm" class="col-xs-12 col-sm-6 q-pa-sm"
label="Geburtstag" label="Geburtstag"
autocomplete="bday"
/> />
<q-file <q-file
v-model="avatar" v-model="avatar"
@ -72,31 +77,22 @@
</q-card-section> </q-card-section>
<q-separator v-if="!newUser" /> <q-separator v-if="!newUser" />
<q-card-section v-if="!newUser" class="fit row justify-start content-center items-center"> <q-card-section v-if="!newUser" class="fit row justify-start content-center items-center">
<q-input <PasswordInput
v-if="isCurrentUser" v-if="isCurrentUser"
v-model="password" v-model="password"
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
label="Password"
type="password"
hint="Password muss immer eingetragen werden"
:rules="[notEmpty]" :rules="[notEmpty]"
filled filled
label="Passwort"
autocomplete="current-password"
class="col-xs-12 col-sm-6 q-pa-sm"
hint="Passwort muss immer eingetragen werden"
/> />
<q-input <PasswordInput
v-model="new_password" v-model="newPassword"
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm" filled
label="Neues Password" label="Neues Password"
type="password" autocomplete="new-password"
filled class="col-xs-12 col-sm-6 q-pa-sm"
/>
<q-input
v-model="new_password2"
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
label="Wiederhole neues Password"
type="password"
:disable="new_password.length == 0"
:rules="[samePassword]"
filled
/> />
</q-card-section> </q-card-section>
<q-card-actions align="right"> <q-card-actions align="right">
@ -109,14 +105,16 @@
<script lang="ts"> <script lang="ts">
import { Notify } from 'quasar'; import { Notify } from 'quasar';
import { hasPermission } from 'src/utils/permission'; import { hasPermission } from 'src/utils/permission';
import { notEmpty, isEmail } from 'src/utils/validators';
import IsoDateInput from 'src/components/utils/IsoDateInput.vue'; import IsoDateInput from 'src/components/utils/IsoDateInput.vue';
import PasswordInput from 'src/components/utils/PasswordInput.vue';
import { defineComponent, computed, ref, onBeforeMount, PropType } from 'vue'; import { defineComponent, computed, ref, onBeforeMount, PropType } from 'vue';
import { useUserStore } from '../../store'; import { useUserStore } from '../../store';
import { useMainStore } from 'src/stores'; import { useMainStore } from 'src/stores';
export default defineComponent({ export default defineComponent({
name: 'MainUserSettings', name: 'MainUserSettings',
components: { IsoDateInput }, components: { IsoDateInput, PasswordInput },
props: { props: {
user: { user: {
required: true, required: true,
@ -131,17 +129,19 @@ export default defineComponent({
const userStore = useUserStore(); const userStore = useUserStore();
const mainStore = useMainStore(); const mainStore = useMainStore();
const user_model = ref(props.user);
onBeforeMount(() => { onBeforeMount(() => {
void userStore.getRoles(false); void userStore.getRoles(false);
}); });
const isCurrentUser = computed(() => user_model.value.userid === mainStore.currentUser.userid); const password = ref('');
const newPassword = ref('');
const avatar = ref<string[]>([]);
const userModel = ref(props.user);
const canSetRoles = computed(() => hasPermission('users_set_roles')); const canSetRoles = computed(() => hasPermission('users_set_roles'));
const allRoles = computed(() => userStore.roles.map((role) => role.name));
const isCurrentUser = computed(() => userModel.value.userid === mainStore.currentUser.userid);
const avatar = ref([] as string[]);
function onAvatarRejected() { function onAvatarRejected() {
Notify.create({ Notify.create({
group: false, group: false,
@ -154,20 +154,15 @@ export default defineComponent({
avatar.value = []; avatar.value = [];
} }
const allRoles = computed(() => userStore.roles.map((role) => role.name));
const password = ref('');
const new_password = ref('');
const new_password2 = ref('');
function save() { function save() {
let changed = user_model.value; let changed = userModel.value;
if (typeof changed.birthday === 'string') changed.birthday = new Date(changed.birthday); if (typeof changed.birthday === 'string') changed.birthday = new Date(changed.birthday);
changed = Object.assign(changed, { changed = Object.assign(changed, {
password: password.value, password: password.value,
}); });
if (new_password.value != '') { if (newPassword.value != '') {
changed = Object.assign(changed, { changed = Object.assign(changed, {
new_password: new_password.value, new_password: newPassword.value,
}); });
} }
@ -183,52 +178,32 @@ export default defineComponent({
} }
function reset() { function reset() {
user_model.value = props.user; userModel.value = props.user;
password.value = ''; password.value = '';
new_password.value = ''; newPassword.value = '';
new_password2.value = '';
} }
function samePassword(val: string) { function isUIDUsed(val: string) {
return val == new_password.value || 'Passwörter sind nicht identisch!';
}
function notEmpty(val: string) {
return !!val || 'Feld darf nicht leer sein!';
}
function isEmail(val: string | null) {
return ( return (
!val || /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w\w+)+$/.test(val) || 'E-Mail ist nicht valide.' userStore.users.findIndex((user) => user.userid === val) !== -1 ||
);
}
function isUseridUsed(val: string) {
return (
!userStore.users.find((user: FG.User) => {
return user.userid == val;
}) ||
!props.newUser ||
'Benutzername ist schon vergeben' 'Benutzername ist schon vergeben'
); );
} }
return { return {
avatar,
user_model,
onAvatarRejected,
allRoles, allRoles,
avatar,
canSetRoles, canSetRoles,
password,
new_password,
new_password2,
samePassword,
isCurrentUser, isCurrentUser,
isEmail, isEmail,
isUIDUsed,
newPassword,
notEmpty, notEmpty,
isUseridUsed, onAvatarRejected,
save, password,
reset, reset,
save,
userModel,
}; };
}, },
}); });

View File

@ -15,3 +15,9 @@ export function stringIsTime(val: string) {
export function stringIsDateTime(val: string) { export function stringIsDateTime(val: string) {
return !val || /^\d{4}-\d\d-\d\d \d\d:\d\d$/.test(val) || 'Datum und Zeit ist nicht gültig.'; return !val || /^\d{4}-\d\d-\d\d \d\d:\d\d$/.test(val) || 'Datum und Zeit ist nicht gültig.';
} }
export function isEmail(val: string) {
return (
!val || /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w\w+)+$/.test(val) || 'E-Mail ist nicht gültig.'
);
}