ChangePwdDialog.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <template lang="pug">
  2. q-dialog(ref='dialogRef', @hide='onDialogHide')
  3. q-card(style='min-width: 650px;')
  4. q-card-section.card-header
  5. q-icon(name='img:/_assets/icons/fluent-password-reset.svg', left, size='sm')
  6. span {{t(`admin.users.changePassword`)}}
  7. q-form.q-py-sm(ref='changeUserPwdForm', @submit='save')
  8. q-item
  9. blueprint-icon(icon='lock')
  10. q-item-section
  11. q-input(
  12. outlined
  13. v-model='state.currentPassword'
  14. dense
  15. :rules='currentPasswordValidation'
  16. hide-bottom-space
  17. :label='t(`auth.changePwd.currentPassword`)'
  18. :aria-label='t(`auth.changePwd.currentPassword`)'
  19. lazy-rules='ondemand'
  20. autofocus
  21. )
  22. q-item
  23. blueprint-icon(icon='password')
  24. q-item-section
  25. q-input(
  26. outlined
  27. v-model='state.newPassword'
  28. dense
  29. :rules='newPasswordValidation'
  30. hide-bottom-space
  31. :label='t(`auth.changePwd.newPassword`)'
  32. :aria-label='t(`auth.changePwd.newPassword`)'
  33. lazy-rules='ondemand'
  34. autofocus
  35. )
  36. template(#append)
  37. .flex.items-center
  38. q-badge(
  39. :color='passwordStrength.color'
  40. :label='passwordStrength.label'
  41. )
  42. q-separator.q-mx-sm(vertical)
  43. q-btn(
  44. flat
  45. dense
  46. padding='none xs'
  47. color='brown'
  48. @click='randomizePassword'
  49. )
  50. q-icon(name='las la-dice-d6')
  51. .q-pl-xs.text-caption: strong Generate
  52. q-item
  53. blueprint-icon(icon='good-pincode')
  54. q-item-section
  55. q-input(
  56. outlined
  57. v-model='state.verifyPassword'
  58. dense
  59. :rules='verifyPasswordValidation'
  60. hide-bottom-space
  61. :label='t(`auth.changePwd.newPasswordVerify`)'
  62. :aria-label='t(`auth.changePwd.newPasswordVerify`)'
  63. lazy-rules='ondemand'
  64. autofocus
  65. )
  66. q-card-actions.card-actions
  67. q-space
  68. q-btn.acrylic-btn(
  69. flat
  70. :label='t(`common.actions.cancel`)'
  71. color='grey'
  72. padding='xs md'
  73. @click='onDialogCancel'
  74. )
  75. q-btn(
  76. unelevated
  77. :label='t(`common.actions.update`)'
  78. color='primary'
  79. padding='xs md'
  80. @click='save'
  81. :loading='state.isLoading'
  82. )
  83. </template>
  84. <script setup>
  85. import gql from 'graphql-tag'
  86. import zxcvbn from 'zxcvbn'
  87. import { sampleSize } from 'lodash-es'
  88. import { useI18n } from 'vue-i18n'
  89. import { useDialogPluginComponent, useQuasar } from 'quasar'
  90. import { computed, reactive, ref } from 'vue'
  91. import { useSiteStore } from 'src/stores/site'
  92. // PROPS
  93. const props = defineProps({
  94. strategyId: {
  95. type: String,
  96. required: true
  97. }
  98. })
  99. // EMITS
  100. defineEmits([
  101. ...useDialogPluginComponent.emits
  102. ])
  103. // QUASAR
  104. const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
  105. const $q = useQuasar()
  106. // STORES
  107. const siteStore = useSiteStore()
  108. // I18N
  109. const { t } = useI18n()
  110. // DATA
  111. const state = reactive({
  112. currentPassword: '',
  113. newPassword: '',
  114. verifyPassword: '',
  115. isLoading: false
  116. })
  117. // REFS
  118. const changeUserPwdForm = ref(null)
  119. // COMPUTED
  120. const passwordStrength = computed(() => {
  121. if (state.newPassword.length < 8) {
  122. return {
  123. color: 'negative',
  124. label: t('admin.users.pwdStrengthWeak')
  125. }
  126. } else {
  127. switch (zxcvbn(state.newPassword).score) {
  128. case 1:
  129. return {
  130. color: 'deep-orange-7',
  131. label: t('admin.users.pwdStrengthPoor')
  132. }
  133. case 2:
  134. return {
  135. color: 'purple-7',
  136. label: t('admin.users.pwdStrengthMedium')
  137. }
  138. case 3:
  139. return {
  140. color: 'blue-7',
  141. label: t('admin.users.pwdStrengthGood')
  142. }
  143. case 4:
  144. return {
  145. color: 'green-7',
  146. label: t('admin.users.pwdStrengthStrong')
  147. }
  148. default:
  149. return {
  150. color: 'negative',
  151. label: t('admin.users.pwdStrengthWeak')
  152. }
  153. }
  154. }
  155. })
  156. // VALIDATION RULES
  157. const currentPasswordValidation = [
  158. val => val.length > 0 || t('auth.errors.missingPassword')
  159. ]
  160. const newPasswordValidation = [
  161. val => val.length > 0 || t('auth.errors.missingPassword'),
  162. val => val.length >= 8 || t('auth.errors.passwordTooShort')
  163. ]
  164. const verifyPasswordValidation = [
  165. val => val.length > 0 || t('auth.errors.missingVerifyPassword'),
  166. val => val === state.newPassword || t('auth.errors.passwordsNotMatch')
  167. ]
  168. // METHODS
  169. function randomizePassword () {
  170. const pwdChars = 'abcdefghkmnpqrstuvwxyzABCDEFHJKLMNPQRSTUVWXYZ23456789_*=?#!()+'
  171. state.newPassword = sampleSize(pwdChars, 16).join('')
  172. }
  173. async function save () {
  174. state.isLoading = true
  175. try {
  176. const isFormValid = await changeUserPwdForm.value.validate(true)
  177. if (!isFormValid) {
  178. throw new Error(t('auth.errors.fields'))
  179. }
  180. const resp = await APOLLO_CLIENT.mutate({
  181. mutation: gql`
  182. mutation changePwd (
  183. $currentPassword: String
  184. $newPassword: String!
  185. $strategyId: UUID!
  186. $siteId: UUID!
  187. ) {
  188. changePassword (
  189. currentPassword: $currentPassword
  190. newPassword: $newPassword
  191. strategyId: $strategyId
  192. siteId: $siteId
  193. ) {
  194. operation {
  195. succeeded
  196. message
  197. }
  198. }
  199. }
  200. `,
  201. variables: {
  202. currentPassword: state.currentPassword,
  203. newPassword: state.newPassword,
  204. strategyId: props.strategyId,
  205. siteId: siteStore.id
  206. }
  207. })
  208. if (resp?.data?.changePassword?.operation?.succeeded) {
  209. $q.notify({
  210. type: 'positive',
  211. message: t('auth.changePwd.success')
  212. })
  213. onDialogOK()
  214. } else {
  215. throw new Error(resp?.data?.changePassword?.operation?.message || 'An unexpected error occured.')
  216. }
  217. } catch (err) {
  218. $q.notify({
  219. type: 'negative',
  220. message: err.message
  221. })
  222. }
  223. state.isLoading = false
  224. }
  225. </script>