Skip to content

Commit

Permalink
Merge pull request #724 from webitel/fix/user-password-input
Browse files Browse the repository at this point in the history
fix: password regex input [WTEL-4784, WTEL-4785, WTEL-4786]
  • Loading branch information
dlohvinov authored Jul 22, 2024
2 parents 2c0afaf + 44fad4c commit cb54edb
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 128 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@vuelidate/validators": "^2.0.0",
"@vueuse/core": "^10.3.0",
"@webitel/flow-ui-sdk": "^0.1.14",
"@webitel/ui-sdk": "^24.6.58",
"@webitel/ui-sdk": "^24.6.59",
"axios": "^1.6.8",
"clipboard-copy": "^4.0.1",
"cron-validator": "^1.3.1",
Expand Down
1 change: 1 addition & 0 deletions src/app/components/utils/generate-password-input.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div class="generate-password-input">
<wt-input
v-bind="$attrs"
:disabled="disabled"
:label="$t('objects.password')"
:label-props="{ hint: $t('objects.directory.passwordInfo'), hintPosition: 'right' }"
Expand Down
92 changes: 92 additions & 0 deletions src/app/components/utils/user-password-input.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<template>
<generate-password-input
:v="v$.model"
:value="model"
class="user-password-input"
v-bind="attrs"
@input="model = $event"
/>
</template>

<script setup>
import { useVuelidate } from '@vuelidate/core';
import { helpers, required } from '@vuelidate/validators';
import { computed, ref, useAttrs } from 'vue';
import { useRoute } from 'vue-router';
import { EngineSystemSettingName } from 'webitel-sdk';
import ConfigurationAPI from '../../../modules/system/modules/configuration/api/configuration.js';
import GeneratePasswordInput from './generate-password-input.vue';
const model = defineModel({
type: String,
default: '',
});
const attrs = useAttrs();
const route = useRoute();
const vRegex = ref(false);
const vErrorText = ref(false);
// bad check: implicit dependency :(
const isNew = computed(() => {
// settings page allows empty value, because it's password edit
return route.fullPath.includes('new');
});
const v$ = useVuelidate(
computed(() => {
const vRegexRule = (v) => (vRegex.value ? vRegex.value.test(v) : false);
const regex = helpers.withParams(
{ regex: vRegex.value },
vErrorText.value ? helpers.withMessage(vErrorText.value, vRegexRule) : vRegexRule,
);
if (isNew.value) {
return {
model: {
required,
regex,
},
};
}
return {
model: model.value ? { regex } : {},
};
}),
{ model },
{
$autoDirty: true,
},
);
const loadV = async () => {
const configurations = await ConfigurationAPI.getList({
name: [EngineSystemSettingName.PasswordRegExp, EngineSystemSettingName.PasswordValidationText],
});
const regex = configurations.items.find(
({ name }) => name === EngineSystemSettingName.PasswordRegExp,
)?.value;
if (!regex) return;
const errorText = configurations.items.find(
({ name }) => name === EngineSystemSettingName.PasswordValidationText,
)?.value;
vRegex.value = new RegExp(regex);
vErrorText.value = errorText;
};
loadV();
</script>
<style lang="scss" scoped>
.user-password-input {
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export default {
},

methods: {
checkValidations(validatedInstance = 'itemInstance') {
checkValidations() {
const v = this.v$ ? this.v$ : this.v;
v[validatedInstance].$touch();
v.$touch();
// if its still pending or an error is returned do not submit
return v[validatedInstance].$pending || v[validatedInstance].$error;
return v.$pending || v.$error;
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@
@input="setItemProp({ prop: 'email', value: $event })"
/>

<password-input
<user-password-input
:disabled="disableUserInput"
:v="v.itemInstance.password"
:value="itemInstance.password"
:model-value="itemInstance.password"
required
@input="setItemProp({ prop: 'password', value: $event })"
@update:model-value="setItemProp({ prop: 'password', value: $event })"
/>

<qrcode
Expand All @@ -55,13 +54,13 @@

<script>
import { mapGetters } from 'vuex';
import PasswordInput from '../../../../../app/components/utils/generate-password-input.vue';
import UserPasswordInput from '../../../../../app/components/utils/user-password-input.vue';
import openedTabComponentMixin from '../../../../../app/mixins/objectPagesMixins/openedObjectTabMixin/openedTabComponentMixin';
import Qrcode from './_internals/qrcode-two-factor-auth.vue';
export default {
name: 'OpenedUserGeneral',
components: { PasswordInput, Qrcode },
components: { UserPasswordInput, Qrcode },
mixins: [openedTabComponentMixin],
computed: {
...mapGetters('directory/users', {
Expand Down
87 changes: 41 additions & 46 deletions src/modules/directory/modules/users/components/opened-user.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@

<script>
import { useVuelidate } from '@vuelidate/core';
import { helpers, required, requiredIf, requiredUnless } from '@vuelidate/validators';
import { helpers, required, requiredIf } from '@vuelidate/validators';
import getNamespacedState from '@webitel/ui-sdk/src/store/helpers/getNamespacedState';
import { computed } from 'vue';
import { useStore } from 'vuex';
import openedObjectMixin from '../../../../../app/mixins/objectPagesMixins/openedObjectMixin/openedObjectMixin';
import { isRegExpMatched } from '../../../../../app/utils/validators.js';
import Logs from '../modules/logs/components/opened-user-logs.vue';
import LogsFilters from '../modules/logs/modules/filters/components/opened-user-logs-filters.vue';
import Tokens from '../modules/tokens/components/opened-user-token.vue';
Expand All @@ -57,8 +59,8 @@ import General from './opened-user-general.vue';
import License from './opened-user-license.vue';
import Roles from './opened-user-roles.vue';
import Variables from './opened-user-variables.vue';
import { EngineSystemSettingName } from 'webitel-sdk';
import ConfigurationAPI from '../../../../system/modules/configuration/api/configuration.js';
const namespace = 'directory/users';
export default {
name: 'OpenedUser',
Expand All @@ -74,35 +76,45 @@ export default {
},
mixins: [openedObjectMixin],
setup: () => ({
v$: useVuelidate(),
}),
setup: () => {
const store = useStore();
const itemInstance = computed(() => getNamespacedState(store.state, namespace).itemInstance);
/** useVuelidate collects nested validations,
* so that it collects validation from nested user-password-input.vue */
const v$ = useVuelidate(
computed(() => ({
itemInstance: {
username: { required },
/** see comment above */
// password: {
// required: requiredUnless((value, item) => !!item.id),
// },
variables: {
$each: helpers.forEach({
key: {
required: requiredIf((value, item) => !!item.value),
},
value: {
required: requiredIf((value, item) => !!item.key),
},
}),
},
},
})),
{ itemInstance },
{ $autoDirty: true },
);
return { v$ };
},
data: () => ({
namespace: 'directory/users',
namespace,
passwordRegExp: '',
validationText: '',
}),
validations() {
return {
itemInstance: {
username: { required },
password: {
required: requiredUnless((value, item) => !!item.id),
isRegExpMatched: isRegExpMatched(this.passwordRegExp, this.validationText)
},
variables: {
$each: helpers.forEach({
key: {
required: requiredIf((value, item) => !!item.value),
},
value: {
required: requiredIf((value, item) => !!item.key),
},
}),
},
},
};
},
computed: {
path() {
Expand Down Expand Up @@ -160,23 +172,6 @@ export default {
return tabs;
},
},
methods: {
async checkPasswordRegExp() {
const PasswordRegExp = await ConfigurationAPI.getList({ name: EngineSystemSettingName.PasswordRegExp });
const exportSettingsValue = PasswordRegExp.items[0]?.value;
this.passwordRegExp = new RegExp(exportSettingsValue);
},
async checkPasswordValidationText() {
const PasswordRegExp = await ConfigurationAPI.getList({ name: EngineSystemSettingName.PasswordValidationText });
const exportSettingsValue = PasswordRegExp.items[0]?.value;
this.validationText = new RegExp(exportSettingsValue);
},
},
async mounted() {
await this.checkPasswordRegExp();
await this.checkPasswordValidationText();
}
};
</script>

Expand Down
85 changes: 85 additions & 0 deletions src/modules/settings/components/change-password.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<template>
<article class="change-password">
<header class="content-header">
<h3 class="content-title">
{{ $t('settings.changePassword') }}
</h3>
</header>
<form @submit="changePassword">
<user-password-input
:model-value="newPassword"
:label="$t('auth.password')"
@update:model-value="newPassword = $event"
/>
<wt-input
v-model="confirmNewPassword"
:label="$t('auth.confirmPassword')"
:v="v$.confirmNewPassword"
type="password"
/>
<wt-button
:disabled="v$.$invalid"
:loading="isPasswordPatching"
type="submit"
@click.prevent="changePassword"
>
{{ $t('objects.save') }}
</wt-button>
</form>
</article>
</template>

<script setup>
import { useVuelidate } from '@vuelidate/core';
import { required, sameAs } from '@vuelidate/validators';
import getNamespacedState from '@webitel/ui-sdk/src/store/helpers/getNamespacedState';
import { computed, inject, ref } from 'vue';
import { useStore } from 'vuex';
import UserPasswordInput from '../../../app/components/utils/user-password-input.vue';
import { changePassword as requestChangePassword } from '../api/settings';
const $eventBus = inject('$eventBus');
const store = useStore();
const userId = computed(() => getNamespacedState(store.state, 'userinfo').userId);
const isPasswordPatching = ref(false);
const newPassword = ref('');
const confirmNewPassword = ref('');
const v$ = useVuelidate(
computed(() => ({
confirmNewPassword: {
sameAs: sameAs(newPassword),
},
})),
{ newPassword, confirmNewPassword },
{ $autoDirty: true },
);
async function changePassword() {
try {
isPasswordPatching.value = true;
const changes = {
password: newPassword.value,
};
await requestChangePassword({
id: userId.value,
changes,
});
$eventBus.$emit('notification', {
type: 'success',
text: 'Password is successfully updated!',
});
} finally {
isPasswordPatching.value = false;
}
}
</script>

<style lang="scss" scoped>
.change-password {
}
</style>
Loading

0 comments on commit cb54edb

Please sign in to comment.