Skip to content

Commit

Permalink
feat: added remote password policy for password forms
Browse files Browse the repository at this point in the history
  • Loading branch information
Tbaile committed Feb 21, 2024
1 parent 3e933a9 commit ecbd751
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 180 deletions.
114 changes: 51 additions & 63 deletions src/components/CreateUserDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import type { BaseResponse } from '@/lib/axiosHelpers'
import { MessageBag } from '@/lib/validation'
import { useI18n } from 'vue-i18n'
import { useGroups } from '@/composables/useGroups'
import { usePasswordPolicy } from '@/composables/usePasswordPolicy'
import PasswordRequirementList from '@/components/PasswordRequirementList.vue'
interface ErrorResponse extends BaseResponse {
error: Array<{ error: string; field: string; parameter: string; value: string }>
Expand All @@ -23,6 +25,18 @@ interface ErrorResponse extends BaseResponse {
const { t } = useI18n()
const { data: remoteGroups, loading: groupsLoading, error: groupsError } = useGroups()
const {
validatePassword,
passwordPolicyLoading,
passwordPolicyError,
minimumLength,
minimumLowercaseCharacters,
minimumNumberCharacters,
minimumSpecialCharacters,
minimumUppercaseCharacters,
complexityCheck,
strengthEnforced
} = usePasswordPolicy()
const props = defineProps<{
show: boolean
Expand All @@ -37,12 +51,6 @@ const password = ref('')
const confirmPassword = ref('')
const groups = ref<Array<NeComboboxOption>>([])
const minimumPasswordLength = 8
const minimumUppercaseCharacters = 1
const minimumLowercaseCharacters = 1
const minimumNumberCharacters = 1
const minimumSpecialCharacters = 1
const validationErrors = ref(new MessageBag())
const loading = ref(false)
const error = ref<Error>()
Expand Down Expand Up @@ -78,39 +86,9 @@ function handleCancel() {
function validate(): boolean {
validationErrors.value.clear()
if (password.value != confirmPassword.value) {
validationErrors.value.append('confirm_password', t('account_settings.passwords_mismatch'))
}
if (password.value.length < minimumPasswordLength) {
validationErrors.value.append(
'password',
t('account_settings.password_length', minimumPasswordLength)
)
}
if (!password.value.match(`(?=(?:.*[A-Z]){${minimumUppercaseCharacters},})`)) {
validationErrors.value.append(
'password',
t('account_settings.password_uppercase', minimumUppercaseCharacters)
)
}
if (!password.value.match(`(?=(?:.*[a-z]){${minimumLowercaseCharacters},})`)) {
validationErrors.value.append(
'password',
t('account_settings.password_lowercase', minimumLowercaseCharacters)
)
}
if (!password.value.match(`(?=(?:.*[0-9]){${minimumNumberCharacters},})`)) {
validationErrors.value.append(
'password',
t('account_settings.password_number', minimumNumberCharacters)
)
}
if (!password.value.match(`(?=(?:.*[^A-Za-z0-9]){${minimumSpecialCharacters},})`)) {
validationErrors.value.append(
'password',
t('account_settings.password_special', minimumSpecialCharacters)
)
}
validatePassword(password.value, confirmPassword.value).forEach((item, key) => {
validationErrors.value.set(key, item)
})
return validationErrors.value.size < 1
}
Expand Down Expand Up @@ -197,31 +175,41 @@ function submit() {
multiple
name="users"
/>
<NeTextInput
v-model="password"
:disabled="loading"
:invalid-message="validationErrors.getFirstMessage('password')"
:label="$t('user_manager.user_password')"
autocomplete="new-password"
is-password
required
/>
<ul class="description-text ml-6 list-disc">
<li>{{ $t('account_settings.minimum_characters', minimumPasswordLength) }}</li>
<li>{{ $t('account_settings.minimum_uppercase', minimumUppercaseCharacters) }}</li>
<li>{{ $t('account_settings.minimum_lowercase', minimumLowercaseCharacters) }}</li>
<li>{{ $t('account_settings.minimum_number', minimumNumberCharacters) }}</li>
<li>{{ $t('account_settings.minimum_special', minimumSpecialCharacters) }}</li>
</ul>
<NeTextInput
v-model="confirmPassword"
:disabled="loading"
:invalid-message="validationErrors.getFirstMessage('confirm_password')"
:label="$t('user_manager.user_confirm_password')"
autocomplete="new-password"
is-password
required
<NeSkeleton v-if="passwordPolicyLoading" :lines="2" />
<NeInlineNotification
v-else-if="passwordPolicyError"
:title="t('account_settings.password_policy_error')"
kind="error"
/>
<template v-else>
<NeTextInput
v-model="password"
:disabled="loading"
:invalid-message="validationErrors.getFirstMessage('password')"
:label="$t('user_manager.user_password')"
autocomplete="new-password"
is-password
required
/>
<PasswordRequirementList
:strength-enforced="strengthEnforced"
:complexity-check="complexityCheck"
:minimum-password-length="minimumLength"
:minimum-uppercase-characters="minimumUppercaseCharacters"
:minimum-lowercase-characters="minimumLowercaseCharacters"
:minimum-number-characters="minimumNumberCharacters"
:minimum-special-characters="minimumSpecialCharacters"
/>
<NeTextInput
v-model="confirmPassword"
:disabled="loading"
:invalid-message="validationErrors.getFirstMessage('confirm_password')"
:label="$t('user_manager.user_confirm_password')"
autocomplete="new-password"
is-password
required
/>
</template>
</form>
</template>
<template #footer>
Expand Down
118 changes: 53 additions & 65 deletions src/components/EditUserPasswordDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,25 @@ import { MessageBag } from '@/lib/validation'
import { useI18n } from 'vue-i18n'
import axios from 'axios'
import type { BaseResponse } from '@/lib/axiosHelpers'
import { NeInlineNotification } from '@nethesis/vue-components'
const minimumPasswordLength = 8
const minimumUppercaseCharacters = 1
const minimumLowercaseCharacters = 1
const minimumNumberCharacters = 1
const minimumSpecialCharacters = 1
import { NeInlineNotification, NeSkeleton } from '@nethesis/vue-components'
import PasswordRequirementList from '@/components/PasswordRequirementList.vue'
import { usePasswordPolicy } from '@/composables/usePasswordPolicy'
const { t } = useI18n()
const {
validatePassword,
passwordPolicyLoading,
passwordPolicyError,
minimumLength,
minimumLowercaseCharacters,
minimumNumberCharacters,
minimumSpecialCharacters,
minimumUppercaseCharacters,
complexityCheck,
strengthEnforced
} = usePasswordPolicy()
const props = defineProps<{
user?: User
}>()
Expand Down Expand Up @@ -50,41 +59,10 @@ function handleCancel() {
}
function validate(): boolean {
// TODO: merge password logic between components
validationErrors.value.clear()
if (password.value != confirmPassword.value) {
validationErrors.value.append('confirm_password', t('account_settings.passwords_mismatch'))
}
if (password.value.length < minimumPasswordLength) {
validationErrors.value.append(
'password',
t('account_settings.password_length', minimumPasswordLength)
)
}
if (!password.value.match(`(?=(?:.*[A-Z]){${minimumUppercaseCharacters},})`)) {
validationErrors.value.append(
'password',
t('account_settings.password_uppercase', minimumUppercaseCharacters)
)
}
if (!password.value.match(`(?=(?:.*[a-z]){${minimumLowercaseCharacters},})`)) {
validationErrors.value.append(
'password',
t('account_settings.password_lowercase', minimumLowercaseCharacters)
)
}
if (!password.value.match(`(?=(?:.*[0-9]){${minimumNumberCharacters},})`)) {
validationErrors.value.append(
'password',
t('account_settings.password_number', minimumNumberCharacters)
)
}
if (!password.value.match(`(?=(?:.*[^A-Za-z0-9]){${minimumSpecialCharacters},})`)) {
validationErrors.value.append(
'password',
t('account_settings.password_special', minimumSpecialCharacters)
)
}
validatePassword(password.value, confirmPassword.value, 'password').forEach((item, key) => {
validationErrors.value.set(key, item)
})
return validationErrors.value.size < 1
}
Expand Down Expand Up @@ -146,31 +124,41 @@ function submit() {
:label="$t('user_manager.user_username')"
disabled
/>
<NeTextInput
v-model="password"
:disabled="loading"
:invalid-message="validationErrors.getFirstMessage('password')"
:label="$t('user_manager.user_password')"
autocomplete="new-password"
is-password
required
/>
<ul class="description-text ml-6 list-disc">
<li>{{ t('account_settings.minimum_characters', minimumPasswordLength) }}</li>
<li>{{ t('account_settings.minimum_uppercase', minimumUppercaseCharacters) }}</li>
<li>{{ t('account_settings.minimum_lowercase', minimumLowercaseCharacters) }}</li>
<li>{{ t('account_settings.minimum_number', minimumNumberCharacters) }}</li>
<li>{{ t('account_settings.minimum_special', minimumSpecialCharacters) }}</li>
</ul>
<NeTextInput
v-model="confirmPassword"
:disabled="loading"
:invalid-message="validationErrors.getFirstMessage('confirm_password')"
:label="t('user_manager.user_confirm_password')"
autocomplete="new-password"
is-password
required
<NeSkeleton v-if="passwordPolicyLoading" :lines="2" />
<NeInlineNotification
v-else-if="passwordPolicyError"
:title="t('account_settings.password_policy_error')"
kind="error"
/>
<template v-else>
<NeTextInput
v-model="password"
:disabled="loading"
:invalid-message="validationErrors.getFirstMessage('password')"
:label="$t('user_manager.user_password')"
autocomplete="new-password"
is-password
required
/>
<PasswordRequirementList
:strength-enforced="strengthEnforced"
:complexity-check="complexityCheck"
:minimum-password-length="minimumLength"
:minimum-uppercase-characters="minimumUppercaseCharacters"
:minimum-lowercase-characters="minimumLowercaseCharacters"
:minimum-number-characters="minimumNumberCharacters"
:minimum-special-characters="minimumSpecialCharacters"
/>
<NeTextInput
v-model="confirmPassword"
:disabled="loading"
:invalid-message="validationErrors.getFirstMessage('confirm_password')"
:label="t('user_manager.user_confirm_password')"
autocomplete="new-password"
is-password
required
/>
</template>
</form>
</template>
<template #footer>
Expand Down
27 changes: 27 additions & 0 deletions src/components/PasswordRequirementList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
defineProps<{
strengthEnforced: boolean
complexityCheck: boolean
minimumPasswordLength: number
minimumUppercaseCharacters: number
minimumLowercaseCharacters: number
minimumNumberCharacters: number
minimumSpecialCharacters: number
}>()
</script>

<template>
<ul v-if="strengthEnforced" class="description-text ml-6 list-disc">
<li>{{ t('account_settings.minimum_characters', minimumPasswordLength) }}</li>
<template v-if="complexityCheck">
<li>{{ t('account_settings.minimum_uppercase', minimumUppercaseCharacters) }}</li>
<li>{{ t('account_settings.minimum_lowercase', minimumLowercaseCharacters) }}</li>
<li>{{ t('account_settings.minimum_number', minimumNumberCharacters) }}</li>
<li>{{ t('account_settings.minimum_special', minimumSpecialCharacters) }}</li>
</template>
</ul>
</template>
Loading

0 comments on commit ecbd751

Please sign in to comment.