From 010bead72377e4de4774ffce8dcc1ec15f75c3bc Mon Sep 17 00:00:00 2001 From: Arno Kaimbacher Date: Wed, 7 Aug 2024 14:22:36 +0200 Subject: [PATCH] - add password strength meter for creating or editing user passwords - add public opensearch api host --- commands/index_datasets.ts | 2 +- public/assets/manifest.json | 17 ++-- resources/js/Components/Map/SearchMap.vue | 22 ++--- .../data/commonPasswords.ts | 26 +++++ .../SimplePasswordMeter/logic/TieNode.ts | 9 ++ .../SimplePasswordMeter/logic/Trie.ts | 30 ++++++ .../logic/checkStrength.ts | 93 ++++++++++++++++++ .../SimplePasswordMeter/logic/index.ts | 5 + .../logic/scorePassword.ts | 66 +++++++++++++ .../SimplePasswordMeter/password-meter.vue | 95 +++++++++++++++++++ resources/js/Pages/Admin/User/Create.vue | 16 +++- resources/js/Pages/Admin/User/Edit.vue | 16 +++- resources/js/Pages/Auth/AccountInfo.vue | 18 +++- 13 files changed, 392 insertions(+), 23 deletions(-) create mode 100644 resources/js/Components/SimplePasswordMeter/data/commonPasswords.ts create mode 100644 resources/js/Components/SimplePasswordMeter/logic/TieNode.ts create mode 100644 resources/js/Components/SimplePasswordMeter/logic/Trie.ts create mode 100644 resources/js/Components/SimplePasswordMeter/logic/checkStrength.ts create mode 100644 resources/js/Components/SimplePasswordMeter/logic/index.ts create mode 100644 resources/js/Components/SimplePasswordMeter/logic/scorePassword.ts create mode 100644 resources/js/Components/SimplePasswordMeter/password-meter.vue diff --git a/commands/index_datasets.ts b/commands/index_datasets.ts index 5f0d0b7..b8f832a 100644 --- a/commands/index_datasets.ts +++ b/commands/index_datasets.ts @@ -14,7 +14,7 @@ import env from '#start/env'; // import { default as Dataset } from '#models/dataset'; const opensearchNode = env.get('OPENSEARCH_HOST', 'localhost'); -const client = new Client({ node: `http://${opensearchNode}` }); // replace with your OpenSearch endpoint +const client = new Client({ node: `${opensearchNode}` }); // replace with your OpenSearch endpoint export default class IndexDatasets extends BaseCommand { static commandName = 'index:datasets'; diff --git a/public/assets/manifest.json b/public/assets/manifest.json index bf5bc77..8faee7e 100644 --- a/public/assets/manifest.json +++ b/public/assets/manifest.json @@ -13,12 +13,13 @@ "assets/resources_js_Pages_Admin_Role_Show_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Role_Show_vue.js", "assets/resources_js_Pages_Admin_Settings_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Settings_vue.js", "assets/resources_js_Pages_Admin_User_Create_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Create_vue.js", - "assets/resources_js_Pages_Admin_User_Edit_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Edit_vue.js", + "assets/resources_js_Pages_Admin_User_Edit_vue-resources_js_Components_SimplePasswordMeter_password-m-6dc207.css": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Edit_vue-resources_js_Components_SimplePasswordMeter_password-m-6dc207.css", + "assets/resources_js_Pages_Admin_User_Edit_vue-resources_js_Components_SimplePasswordMeter_password-m-6dc207.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Edit_vue-resources_js_Components_SimplePasswordMeter_password-m-6dc207.js", "assets/resources_js_Pages_Admin_User_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Index_vue.js", "assets/resources_js_Pages_Admin_User_Show_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Show_vue.js", "assets/resources_js_Pages_App_vue.js": "http://localhost:8080/assets/resources_js_Pages_App_vue.js", - "assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css.css": "http://localhost:8080/assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css.css", - "assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css.js": "http://localhost:8080/assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css.js", + "assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css-resources_js_Components_-06c7b5.css": "http://localhost:8080/assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css-resources_js_Components_-06c7b5.css", + "assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css-resources_js_Components_-06c7b5.js": "http://localhost:8080/assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css-resources_js_Components_-06c7b5.js", "assets/resources_js_Pages_Auth_Login_vue.js": "http://localhost:8080/assets/resources_js_Pages_Auth_Login_vue.js", "assets/resources_js_Pages_Auth_Register_vue.js": "http://localhost:8080/assets/resources_js_Pages_Auth_Register_vue.js", "assets/resources_js_Pages_Dashboard_vue.js": "http://localhost:8080/assets/resources_js_Pages_Dashboard_vue.js", @@ -49,9 +50,9 @@ "assets/vendors-node_modules_mdi_js_mdi_js-node_modules_vue-loader_dist_exportHelper_js.js": "http://localhost:8080/assets/vendors-node_modules_mdi_js_mdi_js-node_modules_vue-loader_dist_exportHelper_js.js", "assets/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_notiwind_dist_index_esm_js.js": "http://localhost:8080/assets/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_notiwind_dist_index_esm_js.js", "assets/vendors-node_modules_vue-facing-decorator_dist_esm_utils_js.js": "http://localhost:8080/assets/vendors-node_modules_vue-facing-decorator_dist_esm_utils_js.js", - "assets/vendors-node_modules_leaflet_dist_leaflet-src_js-node_modules_leaflet_src_control_Control_Att-4ee9a6.js": "http://localhost:8080/assets/vendors-node_modules_leaflet_dist_leaflet-src_js-node_modules_leaflet_src_control_Control_Att-4ee9a6.js", + "assets/vendors-node_modules_leaflet_dist_leaflet-src_js-node_modules_leaflet_src_control_Control_Att-adabdc.js": "http://localhost:8080/assets/vendors-node_modules_leaflet_dist_leaflet-src_js-node_modules_leaflet_src_control_Control_Att-adabdc.js", "assets/vendors-node_modules_toastify-js_src_toastify_js.js": "http://localhost:8080/assets/vendors-node_modules_toastify-js_src_toastify_js.js", - "assets/vendors-node_modules_buffer_index_js-node_modules_leaflet_src_layer_tile_TileLayer_WMS_js-nod-e7bc71.js": "http://localhost:8080/assets/vendors-node_modules_buffer_index_js-node_modules_leaflet_src_layer_tile_TileLayer_WMS_js-nod-e7bc71.js", + "assets/vendors-node_modules_buffer_index_js-node_modules_vuedraggable_dist_vuedraggable_umd_js.js": "http://localhost:8080/assets/vendors-node_modules_buffer_index_js-node_modules_vuedraggable_dist_vuedraggable_umd_js.js", "assets/vendors-node_modules_numeral_numeral_js-node_modules_chart_js_dist_chart_js.js": "http://localhost:8080/assets/vendors-node_modules_numeral_numeral_js-node_modules_chart_js_dist_chart_js.js", "assets/resources_js_Components_BaseButton_vue.js": "http://localhost:8080/assets/resources_js_Components_BaseButton_vue.js", "assets/resources_js_Stores_main_ts-resources_js_Components_BaseDivider_vue-resources_js_Components_C-b45805.js": "http://localhost:8080/assets/resources_js_Stores_main_ts-resources_js_Components_BaseDivider_vue-resources_js_Components_C-b45805.js", @@ -64,6 +65,7 @@ "assets/resources_js_Components_Admin_Sort_vue-resources_js_Components_SectionTitleLineWithButton_vue.js": "http://localhost:8080/assets/resources_js_Components_Admin_Sort_vue-resources_js_Components_SectionTitleLineWithButton_vue.js", "assets/resources_js_Components_CardBoxModal_vue.js": "http://localhost:8080/assets/resources_js_Components_CardBoxModal_vue.js", "assets/resources_js_Components_FileUpload_vue-resources_js_Components_FormCheckRadioGroup_vue-resour-bdf2f9.js": "http://localhost:8080/assets/resources_js_Components_FileUpload_vue-resources_js_Components_FormCheckRadioGroup_vue-resour-bdf2f9.js", + "assets/resources_js_Components_SectionTitleLineWithButton_vue-resources_js_Components_SimplePassword-945989.js": "http://localhost:8080/assets/resources_js_Components_SectionTitleLineWithButton_vue-resources_js_Components_SimplePassword-945989.js", "assets/fonts/inter-latin-ext-400-normal.woff": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.40b3b0d5.woff", "assets/fonts/inter-latin-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.0f9e8d4e.woff2", "assets/fonts/inter-latin-400-normal.woff": "http://localhost:8080/assets/fonts/inter-latin-400-normal.08a02fd2.woff", @@ -85,5 +87,8 @@ "assets/images/marker-icon.png": "http://localhost:8080/assets/images/marker-icon.2b3e1faf.png", "assets/images/layers-2x.png": "http://localhost:8080/assets/images/layers-2x.8f2c4d11.png", "assets/images/layers.png": "http://localhost:8080/assets/images/layers.416d9136.png", - "assets/images/Close.svg": "http://localhost:8080/assets/images/Close.e4887675.svg" + "assets/images/Close.svg": "http://localhost:8080/assets/images/Close.e4887675.svg", + "assets/vendors-node_modules_vue-facing-decorator_dist_esm_index_js-node_modules_vue-facing-decorator-818045.js": "http://localhost:8080/assets/vendors-node_modules_vue-facing-decorator_dist_esm_index_js-node_modules_vue-facing-decorator-818045.js", + "assets/resources_js_Pages_Admin_User_Create_vue-resources_js_Components_SimplePasswordMeter_password-f3312a.css": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Create_vue-resources_js_Components_SimplePasswordMeter_password-f3312a.css", + "assets/resources_js_Pages_Admin_User_Create_vue-resources_js_Components_SimplePasswordMeter_password-f3312a.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Create_vue-resources_js_Components_SimplePasswordMeter_password-f3312a.js" } \ No newline at end of file diff --git a/resources/js/Components/Map/SearchMap.vue b/resources/js/Components/Map/SearchMap.vue index 6f0a335..f1eb9e9 100644 --- a/resources/js/Components/Map/SearchMap.vue +++ b/resources/js/Components/Map/SearchMap.vue @@ -7,7 +7,7 @@ import { canvas } from 'leaflet/src/layer/vector/Canvas'; import { svg } from 'leaflet/src/layer/vector/SVG'; import axios from 'axios'; import { LatLngBoundsExpression } from 'leaflet/src/geo/LatLngBounds'; -// import { tileLayerWMS } from 'leaflet/src/layer/tile/TileLayer.WMS'; +import { tileLayerWMS } from 'leaflet/src/layer/tile/TileLayer.WMS'; import { TileLayer } from 'leaflet/src/layer/tile/TileLayer'; import { Attribution } from 'leaflet/src/control/Control.Attribution'; import DrawControlComponent from '@/Components/Map/draw.component.vue'; @@ -60,7 +60,7 @@ Map.include({ const DEFAULT_BASE_LAYER_NAME = 'BaseLayer'; const DEFAULT_BASE_LAYER_ATTRIBUTION = '© OpenStreetMap contributors'; // const OPENSEARCH_HOST = 'http://localhost:9200'; -const OPENSEARCH_HOST = 'http://192.168.21.18'; +const OPENSEARCH_HOST = `${process.env.OPENSEARCH_HOST}`; // const OPENSEARCH_HOST = `http://${process.env.OPENSEARCH_PUBLIC_HOST}`; let map: Map; @@ -134,21 +134,21 @@ const initMap = async () => { const attributionControl = new Attribution().addTo(map); attributionControl.setPrefix(false); - // let osmGgray = tileLayerWMS('https://ows.terrestris.de/osm-gray/service', { - // format: 'image/png', - // attribution: DEFAULT_BASE_LAYER_ATTRIBUTION, - // layers: 'OSM-WMS', - // }); - - let baseAt = new TileLayer('https://{s}.wien.gv.at/basemap/bmapgrau/normal/google3857/{z}/{y}/{x}.png', { - subdomains: ['maps', 'maps1', 'maps2', 'maps3', 'maps4'], + let osmGgray = tileLayerWMS('https://ows.terrestris.de/osm-gray/service', { + format: 'image/png', attribution: DEFAULT_BASE_LAYER_ATTRIBUTION, + layers: 'OSM-WMS', }); + // let baseAt = new TileLayer('https://{s}.wien.gv.at/basemap/bmapgrau/normal/google3857/{z}/{y}/{x}.png', { + // subdomains: ['maps', 'maps1', 'maps2', 'maps3', 'maps4'], + // attribution: DEFAULT_BASE_LAYER_ATTRIBUTION, + // }); + let layerOptions = { label: DEFAULT_BASE_LAYER_NAME, visible: true, - layer: baseAt, + layer: osmGgray, }; layerOptions.layer.addTo(map); diff --git a/resources/js/Components/SimplePasswordMeter/data/commonPasswords.ts b/resources/js/Components/SimplePasswordMeter/data/commonPasswords.ts new file mode 100644 index 0000000..afe59f2 --- /dev/null +++ b/resources/js/Components/SimplePasswordMeter/data/commonPasswords.ts @@ -0,0 +1,26 @@ +// common passwords as an array of strings + +const commonPasswords = [ + '123456', + 'qwerty', + 'password', + '111111', + 'Abc123', + '123456789', + '12345678', + '123123', + '1234567890', + '12345', + '1234567', + 'qwertyuiop', + 'qwerty123', + '1q2w3e', + 'password1', + '123321', + 'Iloveyou', + '12345', + 'test', + 'test007' +]; + +export default commonPasswords; diff --git a/resources/js/Components/SimplePasswordMeter/logic/TieNode.ts b/resources/js/Components/SimplePasswordMeter/logic/TieNode.ts new file mode 100644 index 0000000..07b040c --- /dev/null +++ b/resources/js/Components/SimplePasswordMeter/logic/TieNode.ts @@ -0,0 +1,9 @@ +export default class TrieNode { + children: { [key: string]: TrieNode }; + isEndOfWord: boolean; + + constructor() { + this.children = {}; + this.isEndOfWord = false; + } +} \ No newline at end of file diff --git a/resources/js/Components/SimplePasswordMeter/logic/Trie.ts b/resources/js/Components/SimplePasswordMeter/logic/Trie.ts new file mode 100644 index 0000000..dcdf651 --- /dev/null +++ b/resources/js/Components/SimplePasswordMeter/logic/Trie.ts @@ -0,0 +1,30 @@ +import TrieNode from './TieNode'; + +export default class Trie { + private root: TrieNode; + constructor() { + this.root = new TrieNode(); + } + + insert(word: string) { + let node: TrieNode = this.root; + for (let char of word) { + if (!node.children[char]) { + node.children[char] = new TrieNode(); + } + node = node.children[char]; + } + node.isEndOfWord = true; + } + + search(word: string) { + let node = this.root; + for (let char of word) { + if (!node.children[char]) { + return false; + } + node = node.children[char]; + } + return node.isEndOfWord; + } +} diff --git a/resources/js/Components/SimplePasswordMeter/logic/checkStrength.ts b/resources/js/Components/SimplePasswordMeter/logic/checkStrength.ts new file mode 100644 index 0000000..70e72d0 --- /dev/null +++ b/resources/js/Components/SimplePasswordMeter/logic/checkStrength.ts @@ -0,0 +1,93 @@ +import commonPasswords from '../data/commonPasswords'; +import Trie from './Trie'; + + + +const checkStrength = (pass: string) => { + const score = scorePassword(pass); + const scoreLabel = mapScoreToLabel(score); + return { + score, + scoreLabel + } +}; + +export default checkStrength; + +// Function to score the password based on different criteria +const scorePassword = (password: string): number => { + if (password.length <= 6) return 0; + if (isCommonPassword(password)) return 0; + + let score = 0; + score += getLengthScore(password); + score += getSpecialCharScore(password); + score += getCaseMixScore(password); + score += getNumberMixScore(password); + + return Math.min(score, 4); // Maximum score is 4 +}; + +// Initialize the Trie with common passwords +const trie = new Trie(); +commonPasswords.forEach(password => trie.insert(password)); +const isCommonPassword = (password: string): boolean => { + // return commonPasswords.includes(password); + return trie.search(password); +}; + +// Function to get the score based on password length +const getLengthScore = (password: string): number => { + if (password.length > 20 && !hasRepeatChars(password)) return 3; + if (password.length > 12 && !hasRepeatChars(password)) return 2; + if (password.length > 8) return 1; + return 0; +}; +// Function to check if the password contains repeated characters +const hasRepeatChars = (password: string): boolean => { + const repeatCharRegex = /(\w)(\1+\1+\1+\1+)/g; + return repeatCharRegex.test(password); +}; + +// Function to get the score based on the presence of special characters +const getSpecialCharScore = (password: string): number => { + const specialCharRegex = /[^A-Za-z0-9]/g; + return specialCharRegex.test(password) ? 1 : 0; +}; + +// Function to get the score based on the mix of uppercase and lowercase letters +const getCaseMixScore = (password: string): number => { + const hasUpperCase = /[A-Z]/.test(password); + const hasLowerCase = /[a-z]/.test(password); + return hasUpperCase && hasLowerCase ? 1 : 0; +}; + +// Function to get the score based on the mix of letters and numbers +const getNumberMixScore = (password: string): number => { + const hasLetter = /[A-Za-z]/.test(password); + const hasNumber = /[0-9]/.test(password); + return hasLetter && hasNumber ? 1 : 0; +}; + +// Function to map the score to a corresponding label +const mapScoreToLabel = (score: number): string => { + const labels = ['risky', 'guessable', 'weak', 'safe', 'secure']; + return labels[score] || ''; +}; + +// const nameScore = (score: number): string => { +// switch (score) { +// case 0: +// return 'risky'; +// case 1: +// return 'guessable'; +// case 2: +// return 'weak'; +// case 3: +// return 'safe'; +// case 4: +// return 'secure'; +// default: +// return ''; +// } +// }; diff --git a/resources/js/Components/SimplePasswordMeter/logic/index.ts b/resources/js/Components/SimplePasswordMeter/logic/index.ts new file mode 100644 index 0000000..799e12c --- /dev/null +++ b/resources/js/Components/SimplePasswordMeter/logic/index.ts @@ -0,0 +1,5 @@ +// import scorePassword from './scorePassword' +// import nameScore from './nameScore' +import checkStrength from './checkStrength' + +export { checkStrength } \ No newline at end of file diff --git a/resources/js/Components/SimplePasswordMeter/logic/scorePassword.ts b/resources/js/Components/SimplePasswordMeter/logic/scorePassword.ts new file mode 100644 index 0000000..5cba526 --- /dev/null +++ b/resources/js/Components/SimplePasswordMeter/logic/scorePassword.ts @@ -0,0 +1,66 @@ +// import { isCommonPassword } from "./isCommonPassword" +import commonPasswords from '../data/commonPasswords'; + +const scorePassword = (pass: string): number => { + let score = 0; + let length = 0; + let specialChar = 0; + let caseMix = 0; + let numCharMix = 0; + + const specialCharRegex = /[^A-Za-z0-9]/g; + const lowercaseRegex = /(.*[a-z].*)/g; + const uppercaseRegex = /(.*[A-Z].*)/g; + const numberRegex = /(.*[0-9].*)/g; + const repeatCharRegex = /(\w)(\1+\1+\1+\1+)/g; + + const hasSpecialChar = specialCharRegex.test(pass); + const hasLowerCase = lowercaseRegex.test(pass); + const hasUpperCase = uppercaseRegex.test(pass); + const hasNumber = numberRegex.test(pass); + const hasRepeatChars = repeatCharRegex.test(pass); + + if (pass.length > 4) { + if (isCommonPassword(pass)) { + return 0; + } + + if ((hasLowerCase || hasUpperCase) && hasNumber) { + numCharMix = 1; + } + + if (hasUpperCase && hasLowerCase) { + caseMix = 1; + } + + if ((hasLowerCase || hasUpperCase || hasNumber) && hasSpecialChar) { + specialChar = 1; + } + + if (pass.length > 8) { + length = 1; + } + + if (pass.length > 12 && !hasRepeatChars) { + length = 2; + } + + if (pass.length > 20 && !hasRepeatChars) { + length = 3; + } + + score = length + specialChar + caseMix + numCharMix; + + if (score > 4) { + score = 4; + } + } + + return score; +}; + +export default scorePassword; + +const isCommonPassword = (password: string): boolean => { + return commonPasswords.includes(password); +}; diff --git a/resources/js/Components/SimplePasswordMeter/password-meter.vue b/resources/js/Components/SimplePasswordMeter/password-meter.vue new file mode 100644 index 0000000..3a9bf57 --- /dev/null +++ b/resources/js/Components/SimplePasswordMeter/password-meter.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/resources/js/Pages/Admin/User/Create.vue b/resources/js/Pages/Admin/User/Create.vue index 9fee019..08753f5 100644 --- a/resources/js/Pages/Admin/User/Create.vue +++ b/resources/js/Pages/Admin/User/Create.vue @@ -1,4 +1,5 @@ diff --git a/resources/js/Pages/Auth/AccountInfo.vue b/resources/js/Pages/Auth/AccountInfo.vue index e5021d6..ebd2a75 100644 --- a/resources/js/Pages/Auth/AccountInfo.vue +++ b/resources/js/Pages/Auth/AccountInfo.vue @@ -1,5 +1,6 @@