From 7cff8ad8a8ca746dd4f76e0bdf75f46f62a5f91b Mon Sep 17 00:00:00 2001
From: Alain Abbas
Date: Mon, 11 Nov 2024 15:49:22 +0100
Subject: [PATCH 1/3] save
---
package.json | 2 +
src/components/identityForm/actions.vue | 38 ++-
src/components/inputNewPassword.vue | 326 ++++++++++++++++++++++++
yarn.lock | 23 ++
4 files changed, 388 insertions(+), 1 deletion(-)
create mode 100644 src/components/inputNewPassword.vue
diff --git a/package.json b/package.json
index 56ca89b..f17d6b9 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,8 @@
"@quasar/extras": "^1.16.9",
"@vueuse/router": "^10.7.2",
"cookie": "^0.6.0",
+ "fast-password-entropy": "^1.1.1",
+ "hibp": "^14.1.2",
"moment": "^2.30.1",
"openapi-fetch": "^0.8.2",
"pinia": "^2.1.7",
diff --git a/src/components/identityForm/actions.vue b/src/components/identityForm/actions.vue
index ac83596..7419730 100644
--- a/src/components/identityForm/actions.vue
+++ b/src/components/identityForm/actions.vue
@@ -13,6 +13,8 @@ div.flex
:true-value="1"
:false-value="0"
)
+ q-btn.q-mx-xs(@click="resetPasswordModal = true" color="red-8" icon="mdi-account-key" :disabled="props.identity.state != IdentityState.SYNCED")
+ q-tooltip.text-body2(slot="trigger") Définir le mot de passe
q-btn.q-mx-xs(@click="sendInit" color="primary" icon="mdi-email-arrow-right" :disabled="props.identity.state != IdentityState.SYNCED")
q-tooltip.text-body2(slot="trigger") Envoyer le mail d'invitation
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
q-tooltip.text-body2(slot="trigger") Voir les logs de l'identité
q-btn.q-mx-xs(v-if="props.identity?._id" @click="deleteIdentity" color="negative" icon="mdi-delete")
q-tooltip.text-body2(slot="trigger") Supprimer l'identité
+ q-dialog(v-model="resetPasswordModal" persistent medium)
+ q-card(style="width:800px")
+ q-card-section(class="text-h6 bg-primary text-white") definition du mot de passe
+ q-card-section
+ input-new-password(v-model="newpassword"
+ :min="passwordPolicies.len"
+ :min-upper="passwordPolicies.hasUpperCase"
+ :min-lower="passwordPolicies.hasLowerCase"
+ :min-number="passwordPolicies.hasNumbers"
+ :min-special="passwordPolicies.hasSpecialChars"
+ :min-entropy="passwordPolicies.minComplexity"
+ :entropy-bad="passwordPolicies.minComplexity"
+ :entropy-good="passwordPolicies.goodComplexity"
+ :check-pwned="passwordPolicies.checkPwned")
+ q-card-actions(align="right" class="bg-white text-teal")
+ q-btn( label="Abandonner" color="negative" @click="resetPasswordModal = false" )
+ q-btn( label="Sauver" color="positive" @click="resetPasswordModal = false" :disabled="newpassword === ''")
+
+
+
diff --git a/yarn.lock b/yarn.lock
index 2e8880e..3751c61 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3390,6 +3390,11 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+fast-password-entropy@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/fast-password-entropy/-/fast-password-entropy-1.1.1.tgz#47ba9933095fd5a32fb184915fc8e76ee19cf429"
+ integrity sha512-dxm29/BPFrNgyEDygg/lf9c2xQR0vnQhG7+hZjAI39M/3um9fD4xiqG6F0ZjW6bya5m9CI0u6YryHGRtxCGCiw==
+
fastq@^1.6.0:
version "1.17.1"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
@@ -3784,6 +3789,14 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+hibp@^14.1.2:
+ version "14.1.2"
+ resolved "https://registry.yarnpkg.com/hibp/-/hibp-14.1.2.tgz#a484b5ed24e4bd916ff0b4e63e13347545a645f7"
+ integrity sha512-DAMzWEEsjKFZMv4g8mDp1qPPo4FPmwLPhTlnJ6I1sBiC0x5FhjSyLhwvGA90uHCl8/6ckHwlgNuoinYrBCh3cQ==
+ dependencies:
+ jssha "^3.3.1"
+ undici "^6.14.1"
+
hookable@^5.5.3:
version "5.5.3"
resolved "https://registry.yarnpkg.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d"
@@ -4233,6 +4246,11 @@ jsonparse@^1.3.1:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
+jssha@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/jssha/-/jssha-3.3.1.tgz#c5b7fc7fb9aa745461923b87df0e247dd59c7ea8"
+ integrity sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==
+
jstransformer@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3"
@@ -6815,6 +6833,11 @@ undici@^5.28.2:
dependencies:
"@fastify/busboy" "^2.0.0"
+undici@^6.14.1:
+ version "6.20.1"
+ resolved "https://registry.yarnpkg.com/undici/-/undici-6.20.1.tgz#fbb87b1e2b69d963ff2d5410a40ffb4c9e81b621"
+ integrity sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==
+
unenv@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.9.0.tgz#469502ae85be1bd3a6aa60f810972b1a904ca312"
From 983d48a30333cabb0ac116c58977955c1338f3d0 Mon Sep 17 00:00:00 2001
From: Alain Abbas
Date: Wed, 27 Nov 2024 12:49:15 +0100
Subject: [PATCH 2/3] save
---
src/components/identityForm/actions.vue | 50 +++++++-------
src/components/inputNewPassword.vue | 86 +++++++++++--------------
2 files changed, 61 insertions(+), 75 deletions(-)
diff --git a/src/components/identityForm/actions.vue b/src/components/identityForm/actions.vue
index 7419730..08ee53d 100644
--- a/src/components/identityForm/actions.vue
+++ b/src/components/identityForm/actions.vue
@@ -30,19 +30,10 @@ div.flex
q-card(style="width:800px")
q-card-section(class="text-h6 bg-primary text-white") definition du mot de passe
q-card-section
- input-new-password(v-model="newpassword"
- :min="passwordPolicies.len"
- :min-upper="passwordPolicies.hasUpperCase"
- :min-lower="passwordPolicies.hasLowerCase"
- :min-number="passwordPolicies.hasNumbers"
- :min-special="passwordPolicies.hasSpecialChars"
- :min-entropy="passwordPolicies.minComplexity"
- :entropy-bad="passwordPolicies.minComplexity"
- :entropy-good="passwordPolicies.goodComplexity"
- :check-pwned="passwordPolicies.checkPwned")
+ input-new-password(v-model="newpassword")
q-card-actions(align="right" class="bg-white text-teal")
q-btn( label="Abandonner" color="negative" @click="resetPasswordModal = false" )
- q-btn( label="Sauver" color="positive" @click="resetPasswordModal = false" :disabled="newpassword === ''")
+ q-btn( label="Sauver" color="positive" @click="doChangePassword" :disabled="newpassword === ''")
@@ -57,20 +48,7 @@ import { useIdentityStates } from '~/composables'
import { useErrorHandling } from '#imports'
import InputNewPassword from "~/components/inputNewPassword.vue";
const resetPasswordModal=ref(false)
-const passwordPolicies = ref({
- bannedTime: 3600,
- checkPwned: true,
- goodComplexity: 60,
- hasLowerCase: 1,
- hasNumbers: 1,
- hasSpecialChars: 1,
- hasUpperCase: 1,
- len: 10,
- maxRetry: 10,
- minComplexity: 20,
- resetBySms: false,
- redirectUrl: ''
-})
+
const newpassword=ref('')
type IdentityResponse = operations['IdentitiesController_search']['responses']['200']['content']['application/json']
@@ -97,6 +75,27 @@ const { handleError } = useErrorHandling()
const emits = defineEmits(['submit', 'sync', 'logs', 'create', 'delete'])
+async function doChangePassword(){
+ const requestOptions={method: 'POST',
+ body:JSON.stringify({id:props.identity._id,newPassword: newpassword.value})}
+ try{
+ const data=await $http.post('/management/identities/forcepassword', requestOptions)
+ $q.notify({
+ message: 'Le mot de passe a été changé : ',
+ color: 'positive',
+ position: 'top-right',
+ icon: 'mdi-check-circle-outline',
+ })
+ }catch(error){
+ $q.notify({
+ message: 'Impossible de modifier le mot de passe : ' + error.response._data.message,
+ color: 'negative',
+ position: 'top-right',
+ icon: 'mdi-alert-circle-outline',
+ })
+ }
+ resetPasswordModal.value = false
+}
async function submit() {
// console.log('submit from actions')
emits('submit')
@@ -135,7 +134,6 @@ async function activate(){
bouton="Activer"
initialStatus=0
}
- debugger
if (showActivate() === false){
props.identity.dataStatus = initialStatus
return
diff --git a/src/components/inputNewPassword.vue b/src/components/inputNewPassword.vue
index 1fad79f..eadc38a 100644
--- a/src/components/inputNewPassword.vue
+++ b/src/components/inputNewPassword.vue
@@ -20,7 +20,7 @@
doit avoir au moins {{min}} caractères
-
+
doit comporter au moins {{minUpper}} majuscules
@@ -79,44 +79,29 @@ const progress=ref(0)
const progress_color=ref('red')
const typePasswordProp=ref('password')
const typeConfirmProp=ref('password')
-const props = defineProps({
- min: {
- type: Number,
- default:8},
- minUpper:{
- type: Number,
- default:1
- },
- minLower:{
- type: Number,
- default:1
- },
- minNumber:{
- type: Number,
- default:1
- },
- minSpecial:{
- type:Number,
- default:1
- },
- minEntropy:{
- type:Number,
- default:30
- },
- entropyBad:{
- type:Number,
- default:10
- },
- entropyGood:{
- type:Number,
- default:80
- },
- checkPwned:{
- type:Boolean,
- default:true
- }
-})
+const minLower=ref(1)
+const minUpper=ref(1)
+const minNumber=ref(1)
+const minSpecial=ref(1)
+const minEntropy=ref(20)
+const checkPwned=ref(false)
+const min=ref(5)
+const { data: props} = await useHttp('/management/passwd/getpolicies',
+ {
+ method:'GET',
+ transform:(result)=> {
+ return result.data
+ }
+ }
+)
+minLower.value=props.value.hasLowerCase
+minUpper.value=props.value.hasUpperCase
+minNumber.value=props.value.hasNumbers
+minSpecial.value=props.value.hasSpecialChars
+minEntropy.value=props.value.minComplexity
+checkPwned.value=props.value.checkPwned
+min.value=props.value.len
async function checkPassword(ev, type) {
let newP = newPassword.value
let confirmP = confirmNewPassword.value
@@ -130,13 +115,16 @@ async function checkPassword(ev, type) {
console.log('emit ' + newPassword.value)
//avant d accepter on cherche dans l api de pwned
try{
- if (props.checkPwned === true ){
+ if (checkPwned.value === true ){
const numPwns = await pwnedPassword(newP);
if (numPwns >0){
iconIsPwnedOK(false)
$q.notify({
message: 'Ce mot de passe est déjà apparu lors d\'une violation de données. Vous ne pouvez pas l\'utiliser',
+ html:true,
+ color: 'negative',
+ multiLine: true,
})
emit('update:modelValue', '')
return
@@ -164,7 +152,7 @@ async function checkPassword(ev, type) {
function checkPolicy(password) {
has_len.value='highlight_off'
let statut=true
- if (props.minSpecial >= 1){
+ if (minSpecial.value >= 1){
if (/[!@#\$%\^\&*\)\(+=._-]/.test(password) === false){
pwdColor.value = 'red'
iconSpecialOK(false)
@@ -173,7 +161,7 @@ function checkPolicy(password) {
iconSpecialOK(true)
}
}
- if (props.minNumber >= 1) {
+ if (minNumber.value >= 1) {
if (/\d/.test(password) === false) {
pwdColor.value = 'red'
iconNumberOK(false)
@@ -182,7 +170,7 @@ function checkPolicy(password) {
iconNumberOK(true)
}
}
- if (props.minLower >= 1) {
+ if (minLower.value >= 1) {
if (/[a-z]/.test(password) === false) {
pwdColor.value = 'red'
iconLowerOK(false)
@@ -191,7 +179,7 @@ function checkPolicy(password) {
iconLowerOK(true)
}
}
- if (props.minUpper >= 1) {
+ if (minUpper.value >= 1) {
if (/[A-Z]/.test(password) === false) {
pwdColor.value = 'red'
iconUpperOK(false)
@@ -200,8 +188,8 @@ function checkPolicy(password) {
iconUpperOK(true)
}
}
- if (password.length < props.min) {
- console.log('trop court ' + props.min)
+ if (password.length < min.value) {
+ console.log('trop court ' + min.value)
iconLenOK(false)
statut=false
}else{
@@ -287,18 +275,18 @@ function iconIsPwnedOK(value){
}
function complexity(password){
console.log(stringEntropy(password))
- if (props.minEntropy > 0){
+ if (minEntropy.value > 0){
let c = stringEntropy(password)
progress.value = c / 100
console.log('entropy' + c)
- if (c < props.entropyBad) {
+ if (c < props.value.minComplexity) {
progress_color.value = 'red'
- } else if (c >= props.entropyBad && c < props.entropyGood) {
+ } else if (c >= props.value.minComplexity && c < props.value.goodComplexity) {
progress_color.value = 'warning'
} else {
progress_color.value = 'green'
}
- if (c >= props.minEntropy) {
+ if (c >= minEntropy.value) {
return true
} else {
return false
From ff1dc7d5008f36d955ee0d49354ed408ada744b1 Mon Sep 17 00:00:00 2001
From: Alain Abbas
Date: Sun, 19 Jan 2025 22:12:24 +0100
Subject: [PATCH 3/3] Update actions.vue
---
src/components/identityForm/actions.vue | 44 +++++++++++++++++++++++--
1 file changed, 42 insertions(+), 2 deletions(-)
diff --git a/src/components/identityForm/actions.vue b/src/components/identityForm/actions.vue
index 08ee53d..f98f3cc 100644
--- a/src/components/identityForm/actions.vue
+++ b/src/components/identityForm/actions.vue
@@ -11,8 +11,11 @@ div.flex
label="Activation"
v-model="props.identity.dataStatus"
:true-value="1"
- :false-value="0"
+ :indeterminate-value="-2"
+ :false-value="-3"
)
+ q-btn.q-mx-xs( @click="forceChangePassword()" color="orange-8" icon="mdi-lock-reset" :disabled="props.identity.state != IdentityState.SYNCED")
+ q-tooltip.text-body2(slot="trigger") Obliger l'utilisateur à changer son mot de passe
q-btn.q-mx-xs(@click="resetPasswordModal = true" color="red-8" icon="mdi-account-key" :disabled="props.identity.state != IdentityState.SYNCED")
q-tooltip.text-body2(slot="trigger") Définir le mot de passe
q-btn.q-mx-xs(@click="sendInit" color="primary" icon="mdi-email-arrow-right" :disabled="props.identity.state != IdentityState.SYNCED")
@@ -48,7 +51,7 @@ import { useIdentityStates } from '~/composables'
import { useErrorHandling } from '#imports'
import InputNewPassword from "~/components/inputNewPassword.vue";
const resetPasswordModal=ref(false)
-
+const forcePasswordModal=ref(false)
const newpassword=ref('')
type IdentityResponse = operations['IdentitiesController_search']['responses']['200']['content']['application/json']
@@ -119,6 +122,43 @@ function showActivate(){
return false
}
}
+async function forceChangePassword(){
+ $q.dialog({
+ title: 'Confirmation',
+ message: "Voulez vous forcer le changement de mot de passe ? ",
+ persistent: true,
+ ok: {
+ push: true,
+ color: 'positive',
+ label: 'Forcer',
+ },
+ cancel: {
+ push: true,
+ color: 'negative',
+ label: 'Annuler',
+ },
+ }).onOk(async() => {
+ const requestOptions={method: 'POST',
+ body:JSON.stringify({id:props.identity._id})}
+ try{
+ const data=await $http.post('/management/identities/needtochangepassword', requestOptions)
+ $q.notify({
+ message: 'LE changement de mot de passe est forcé : ',
+ color: 'positive',
+ position: 'top-right',
+ icon: 'mdi-check-circle-outline',
+ })
+ }catch(error){
+ $q.notify({
+ message: 'Impossible de forcer le changement de mot de passe : ' + error.response._data.message,
+ color: 'negative',
+ position: 'top-right',
+ icon: 'mdi-alert-circle-outline',
+ })
+ }
+
+ })
+}
async function activate(){