Skip to content

Commit 7cff8ad

Browse files
committed
save
1 parent de7a755 commit 7cff8ad

File tree

4 files changed

+388
-1
lines changed

4 files changed

+388
-1
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
"@quasar/extras": "^1.16.9",
2525
"@vueuse/router": "^10.7.2",
2626
"cookie": "^0.6.0",
27+
"fast-password-entropy": "^1.1.1",
28+
"hibp": "^14.1.2",
2729
"moment": "^2.30.1",
2830
"openapi-fetch": "^0.8.2",
2931
"pinia": "^2.1.7",

src/components/identityForm/actions.vue

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ div.flex
1313
:true-value="1"
1414
:false-value="0"
1515
)
16+
q-btn.q-mx-xs(@click="resetPasswordModal = true" color="red-8" icon="mdi-account-key" :disabled="props.identity.state != IdentityState.SYNCED")
17+
q-tooltip.text-body2(slot="trigger") Définir le mot de passe
1618
q-btn.q-mx-xs(@click="sendInit" color="primary" icon="mdi-email-arrow-right" :disabled="props.identity.state != IdentityState.SYNCED")
1719
q-tooltip.text-body2(slot="trigger") Envoyer le mail d'invitation
1820
q-btn.q-mx-xs(@click="submit" color="positive" icon="mdi-check" v-show="!isNew" v-if="crud.update")
@@ -24,6 +26,24 @@ div.flex
2426
q-tooltip.text-body2(slot="trigger") Voir les logs de l'identité
2527
q-btn.q-mx-xs(v-if="props.identity?._id" @click="deleteIdentity" color="negative" icon="mdi-delete")
2628
q-tooltip.text-body2(slot="trigger") Supprimer l'identité
29+
q-dialog(v-model="resetPasswordModal" persistent medium)
30+
q-card(style="width:800px")
31+
q-card-section(class="text-h6 bg-primary text-white") definition du mot de passe
32+
q-card-section
33+
input-new-password(v-model="newpassword"
34+
:min="passwordPolicies.len"
35+
:min-upper="passwordPolicies.hasUpperCase"
36+
:min-lower="passwordPolicies.hasLowerCase"
37+
:min-number="passwordPolicies.hasNumbers"
38+
:min-special="passwordPolicies.hasSpecialChars"
39+
:min-entropy="passwordPolicies.minComplexity"
40+
:entropy-bad="passwordPolicies.minComplexity"
41+
:entropy-good="passwordPolicies.goodComplexity"
42+
:check-pwned="passwordPolicies.checkPwned")
43+
q-card-actions(align="right" class="bg-white text-teal")
44+
q-btn( label="Abandonner" color="negative" @click="resetPasswordModal = false" )
45+
q-btn( label="Sauver" color="positive" @click="resetPasswordModal = false" :disabled="newpassword === ''")
46+
2747
</template>
2848

2949
<script lang="ts" setup>
@@ -35,8 +55,24 @@ import { useRouter } from 'vue-router'
3555
import { useFetch } from 'nuxt/app'
3656
import { useIdentityStates } from '~/composables'
3757
import { useErrorHandling } from '#imports'
58+
import InputNewPassword from "~/components/inputNewPassword.vue";
59+
const resetPasswordModal=ref(false)
60+
const passwordPolicies = ref({
61+
bannedTime: 3600,
62+
checkPwned: true,
63+
goodComplexity: 60,
64+
hasLowerCase: 1,
65+
hasNumbers: 1,
66+
hasSpecialChars: 1,
67+
hasUpperCase: 1,
68+
len: 10,
69+
maxRetry: 10,
70+
minComplexity: 20,
71+
resetBySms: false,
72+
redirectUrl: ''
73+
})
3874
39-
75+
const newpassword=ref('')
4076
type IdentityResponse = operations['IdentitiesController_search']['responses']['200']['content']['application/json']
4177
type Identity = components['schemas']['IdentitiesDto']
4278
const activation=ref(true)
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
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+
&nbsp;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+
&nbsp;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+
&nbsp;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+
&nbsp;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+
&nbsp;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+
&nbsp; 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+
&nbsp; 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

Comments
 (0)