|
| 1 | +<template> |
| 2 | + <div> |
| 3 | + <div class="col-12"> |
| 4 | + <q-input @update:model-value="checkPassword($event,'pass')" :label-color="pwdColor" v-model="newPassword" |
| 5 | + label="Nouveau mot de passe" :type="typePasswordProp"> |
| 6 | + <template v-slot:append> |
| 7 | + <q-icon name="mdi-eye" @click="togglePassword" style="cursor: pointer;"/> |
| 8 | + </template> |
| 9 | + |
| 10 | + </q-input> |
| 11 | + <q-input @update:model-value="checkPassword($event,'confirm')" v-model="confirmNewPassword" :label-color="confirmColor" |
| 12 | + label="Confirmation du nouveau mot de passe" :type="typeConfirmProp"> |
| 13 | + <template v-slot:append> |
| 14 | + <q-icon name="mdi-eye" @click="toggleConfirm" style="cursor: pointer;"/> |
| 15 | + </template> |
| 16 | + </q-input> |
| 17 | + </div> |
| 18 | + <div class="col-12"> |
| 19 | + <p style="margin: 0px;"> |
| 20 | + <q-icon :name="has_len" :color="has_len_color" size="xs" style="margin: 0px;"></q-icon> |
| 21 | + doit avoir au moins {{min}} caractères |
| 22 | + </p> |
| 23 | + <p v-show="minUpper > 0" style="margin: 0px;"> |
| 24 | + <q-icon :name="has_upper" :color="has_upper_color" size="xs" ></q-icon> |
| 25 | + doit comporter au moins {{minUpper}} majuscules |
| 26 | + </p> |
| 27 | + <p v-show="minLower > 0" style="margin: 0px;"> |
| 28 | + <q-icon :name="has_lower" :color="has_lower_color" size="xs" ></q-icon> |
| 29 | + doit comporter au moins {{minLower}} minuscules |
| 30 | + </p> |
| 31 | + <p v-show="minNumber > 0" style="margin: 0px;"> |
| 32 | + <q-icon :name="has_number" :color="has_number_color" size="xs" ></q-icon> |
| 33 | + doit comporter au moins {{minNumber}} chiffre |
| 34 | + </p> |
| 35 | + <p v-show="minSpecial > 0" style="margin: 0px;"> |
| 36 | + <q-icon :name="has_special" :color="has_special_color" size="xs" ></q-icon> |
| 37 | + doit comporter au moins {{minNumber}} charactère special |
| 38 | + </p> |
| 39 | + <p v-show="minEntropy > 0" style="margin: 0px;"> |
| 40 | + <q-icon :name="has_complexity" :color="has_complexity_color" size="xs" ></q-icon> |
| 41 | + Complexité |
| 42 | + <br> |
| 43 | + <q-linear-progress :value="progress" :color="progress_color" class="q-mt-sm" size="10px" /> |
| 44 | + </p> |
| 45 | + <p v-show="checkPwned"> |
| 46 | + <q-icon :name="isPwned" :color="isPwned_color" size="xs" ></q-icon> |
| 47 | + Exposition du mot de passe <a href="https://haveibeenpwned.com">haveiBeenPwned</a> |
| 48 | + </p> |
| 49 | + </div> |
| 50 | + </div> |
| 51 | +</template> |
| 52 | + |
| 53 | +<script setup> |
| 54 | +import { useQuasar } from 'quasar' |
| 55 | +import {ref} from 'vue' |
| 56 | +import stringEntropy from 'fast-password-entropy' |
| 57 | +import { pwnedPassword } from 'hibp'; |
| 58 | +const emit = defineEmits(['update:modelValue']) |
| 59 | +const $q = useQuasar() |
| 60 | +const newPassword = ref('') |
| 61 | +const confirmNewPassword = ref('') |
| 62 | +const pwdColor = ref('') |
| 63 | +const confirmColor = ref('') |
| 64 | +const has_len=ref('mdi-alert-box') |
| 65 | +const has_len_color=ref('red') |
| 66 | +const has_upper=ref('mdi-alert-box') |
| 67 | +const has_upper_color=ref('red') |
| 68 | +const has_lower=ref('mdi-alert-box') |
| 69 | +const has_lower_color=ref('red') |
| 70 | +const has_number=ref('mdi-alert-box') |
| 71 | +const has_number_color=ref('red') |
| 72 | +const has_special=ref('mdi-alert-box') |
| 73 | +const has_special_color=ref('red') |
| 74 | +const has_complexity=ref('mdi-alert-box') |
| 75 | +const has_complexity_color=ref('red') |
| 76 | +const isPwned=ref('mdi-emoticon-neutral') |
| 77 | +const isPwned_color=ref('grey') |
| 78 | +const progress=ref(0) |
| 79 | +const progress_color=ref('red') |
| 80 | +const typePasswordProp=ref('password') |
| 81 | +const typeConfirmProp=ref('password') |
| 82 | +const props = defineProps({ |
| 83 | + min: { |
| 84 | + type: Number, |
| 85 | + default:8}, |
| 86 | + minUpper:{ |
| 87 | + type: Number, |
| 88 | + default:1 |
| 89 | + }, |
| 90 | + minLower:{ |
| 91 | + type: Number, |
| 92 | + default:1 |
| 93 | + }, |
| 94 | + minNumber:{ |
| 95 | + type: Number, |
| 96 | + default:1 |
| 97 | + }, |
| 98 | + minSpecial:{ |
| 99 | + type:Number, |
| 100 | + default:1 |
| 101 | + }, |
| 102 | + minEntropy:{ |
| 103 | + type:Number, |
| 104 | + default:30 |
| 105 | + }, |
| 106 | + entropyBad:{ |
| 107 | + type:Number, |
| 108 | + default:10 |
| 109 | + }, |
| 110 | + entropyGood:{ |
| 111 | + type:Number, |
| 112 | + default:80 |
| 113 | + }, |
| 114 | + checkPwned:{ |
| 115 | + type:Boolean, |
| 116 | + default:true |
| 117 | + } |
| 118 | +}) |
| 119 | +
|
| 120 | +async function checkPassword(ev, type) { |
| 121 | + let newP = newPassword.value |
| 122 | + let confirmP = confirmNewPassword.value |
| 123 | + if (type === 'pass') { |
| 124 | + newP = ev |
| 125 | + } else { |
| 126 | + confirmP = ev |
| 127 | + } |
| 128 | + if (checkPolicy(newP) === true) { |
| 129 | + if (newP === confirmP) { |
| 130 | + console.log('emit ' + newPassword.value) |
| 131 | + //avant d accepter on cherche dans l api de pwned |
| 132 | + try{ |
| 133 | + if (props.checkPwned === true ){ |
| 134 | + const numPwns = await pwnedPassword(newP); |
| 135 | +
|
| 136 | + if (numPwns >0){ |
| 137 | + iconIsPwnedOK(false) |
| 138 | + $q.notify({ |
| 139 | + message: '<text-weight-medium>Ce mot de passe est déjà apparu lors d\'une violation de données. Vous ne pouvez pas l\'utiliser</text-weight-medium>', |
| 140 | + }) |
| 141 | + emit('update:modelValue', '') |
| 142 | + return |
| 143 | + }else{ |
| 144 | + iconIsPwnedOK(true) |
| 145 | + } |
| 146 | + console.log('pwn :' + numPwns) |
| 147 | + } |
| 148 | + }catch(err){ |
| 149 | +
|
| 150 | + } |
| 151 | +
|
| 152 | +
|
| 153 | + confirmColor.value='green' |
| 154 | + emit('update:modelValue', newPassword.value) |
| 155 | + }else{ |
| 156 | + emit('update:modelValue', '') |
| 157 | + confirmColor.value='red' |
| 158 | + } |
| 159 | + }else{ |
| 160 | + emit('update:modelValue', '') |
| 161 | + } |
| 162 | +} |
| 163 | +
|
| 164 | +function checkPolicy(password) { |
| 165 | + has_len.value='highlight_off' |
| 166 | + let statut=true |
| 167 | + if (props.minSpecial >= 1){ |
| 168 | + if (/[!@#\$%\^\&*\)\(+=._-]/.test(password) === false){ |
| 169 | + pwdColor.value = 'red' |
| 170 | + iconSpecialOK(false) |
| 171 | + statut=false |
| 172 | + }else{ |
| 173 | + iconSpecialOK(true) |
| 174 | + } |
| 175 | + } |
| 176 | + if (props.minNumber >= 1) { |
| 177 | + if (/\d/.test(password) === false) { |
| 178 | + pwdColor.value = 'red' |
| 179 | + iconNumberOK(false) |
| 180 | + statut = false |
| 181 | + } else { |
| 182 | + iconNumberOK(true) |
| 183 | + } |
| 184 | + } |
| 185 | + if (props.minLower >= 1) { |
| 186 | + if (/[a-z]/.test(password) === false) { |
| 187 | + pwdColor.value = 'red' |
| 188 | + iconLowerOK(false) |
| 189 | + statut = false |
| 190 | + } else { |
| 191 | + iconLowerOK(true) |
| 192 | + } |
| 193 | + } |
| 194 | + if (props.minUpper >= 1) { |
| 195 | + if (/[A-Z]/.test(password) === false) { |
| 196 | + pwdColor.value = 'red' |
| 197 | + iconUpperOK(false) |
| 198 | + statut = false |
| 199 | + } else { |
| 200 | + iconUpperOK(true) |
| 201 | + } |
| 202 | + } |
| 203 | + if (password.length < props.min) { |
| 204 | + console.log('trop court ' + props.min) |
| 205 | + iconLenOK(false) |
| 206 | + statut=false |
| 207 | + }else{ |
| 208 | + iconLenOK(true) |
| 209 | + } |
| 210 | + console.log('password OK ') |
| 211 | + if (statut === true){ |
| 212 | + pwdColor.value = 'green' |
| 213 | + }else { |
| 214 | + pwdColor.value = 'red' |
| 215 | + } |
| 216 | + //entropie |
| 217 | + if (complexity(password) === false){ |
| 218 | + statut=false |
| 219 | + iconComplexityOK(false) |
| 220 | + }else{ |
| 221 | + iconComplexityOK(true) |
| 222 | + } |
| 223 | + return statut |
| 224 | +} |
| 225 | +function iconComplexityOK(value){ |
| 226 | + if (value === true){ |
| 227 | + has_complexity.value='mdi-check' |
| 228 | + has_complexity_color.value='green' |
| 229 | + }else{ |
| 230 | + has_complexity.value='mdi-alert-box' |
| 231 | + has_complexity_color.value='red' |
| 232 | + } |
| 233 | +} |
| 234 | +function iconLenOK(value){ |
| 235 | + if (value === true){ |
| 236 | + has_len.value='mdi-check' |
| 237 | + has_len_color.value='green' |
| 238 | + }else{ |
| 239 | + has_len.value='mdi-alert-box' |
| 240 | + has_len_color.value='red' |
| 241 | + } |
| 242 | +} |
| 243 | +function iconUpperOK(value){ |
| 244 | + if (value === true){ |
| 245 | + has_upper.value='mdi-check' |
| 246 | + has_upper_color.value='green' |
| 247 | + }else{ |
| 248 | + has_upper.value='mdi-alert-box' |
| 249 | + has_upper_color.value='red' |
| 250 | + } |
| 251 | +} |
| 252 | +function iconLowerOK(value){ |
| 253 | + if (value === true){ |
| 254 | + has_lower.value='mdi-check' |
| 255 | + has_lower_color.value='green' |
| 256 | + }else{ |
| 257 | + has_lower.value='mdi-alert-box' |
| 258 | + has_lower_color.value='red' |
| 259 | + } |
| 260 | +} |
| 261 | +function iconNumberOK(value){ |
| 262 | + if (value === true){ |
| 263 | + has_number.value='mdi-check' |
| 264 | + has_number_color.value='green' |
| 265 | + }else{ |
| 266 | + has_number.value='mdi-alert-box' |
| 267 | + has_number_color.value='red' |
| 268 | + } |
| 269 | +} |
| 270 | +function iconSpecialOK(value){ |
| 271 | + if (value === true){ |
| 272 | + has_special.value='mdi-check' |
| 273 | + has_special_color.value='green' |
| 274 | + }else{ |
| 275 | + has_special.value='mdi-alert-box' |
| 276 | + has_special_color.value='red' |
| 277 | + } |
| 278 | +} |
| 279 | +function iconIsPwnedOK(value){ |
| 280 | + if (value === true){ |
| 281 | + isPwned.value='mdi-emoticon' |
| 282 | + isPwned_color.value='green' |
| 283 | + }else{ |
| 284 | + isPwned.value='mdi-emoticon-angry' |
| 285 | + isPwned_color.value='red' |
| 286 | + } |
| 287 | +} |
| 288 | +function complexity(password){ |
| 289 | + console.log(stringEntropy(password)) |
| 290 | + if (props.minEntropy > 0){ |
| 291 | + let c = stringEntropy(password) |
| 292 | + progress.value = c / 100 |
| 293 | + console.log('entropy' + c) |
| 294 | + if (c < props.entropyBad) { |
| 295 | + progress_color.value = 'red' |
| 296 | + } else if (c >= props.entropyBad && c < props.entropyGood) { |
| 297 | + progress_color.value = 'warning' |
| 298 | + } else { |
| 299 | + progress_color.value = 'green' |
| 300 | + } |
| 301 | + if (c >= props.minEntropy) { |
| 302 | + return true |
| 303 | + } else { |
| 304 | + return false |
| 305 | + } |
| 306 | + } |
| 307 | +} |
| 308 | +function togglePassword(){ |
| 309 | + if (typePasswordProp.value === 'password'){ |
| 310 | + typePasswordProp.value='text' |
| 311 | + }else{ |
| 312 | + typePasswordProp.value='password' |
| 313 | + } |
| 314 | +} |
| 315 | +function toggleConfirm(){ |
| 316 | + if (typeConfirmProp.value === 'password'){ |
| 317 | + typeConfirmProp.value='text' |
| 318 | + }else{ |
| 319 | + typeConfirmProp.value='password' |
| 320 | + } |
| 321 | +} |
| 322 | +</script> |
| 323 | + |
| 324 | +<style scoped> |
| 325 | +
|
| 326 | +</style> |
0 commit comments