├── .prettierrc
├── lib
└── main.ts
├── src
├── main.ts
├── shims-vue.d.ts
├── types
│ └── index.d.ts
├── logic
│ ├── index.ts
│ ├── checkStrength.ts
│ ├── isCommonPassword.ts
│ ├── nameScore.ts
│ └── scorePassword.ts
├── data
│ └── commonPasswords.ts
├── App.vue
└── password-meter.vue
├── .editorconfig
├── vue.config.js
├── tests
├── password-examples.ts
└── unit
│ ├── logic.spec.ts
│ └── main.spec.ts
├── .gitignore
├── tsconfig.json
├── index.html
├── licence.txt
├── vite.config.js
├── package.json
└── README.md
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "arrowParens": "always",
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/lib/main.ts:
--------------------------------------------------------------------------------
1 | import PasswordMeter from '../src/password-meter.vue'
2 |
3 | export default PasswordMeter
4 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './App.vue'
3 |
4 | createApp( App ).mount('#app')
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue'
3 | const component: DefineComponent<{}, {}, any>
4 | export default component
5 | }
6 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { Component } from 'vue'
2 |
3 | declare module 'vue-simple-password-meter' {
4 | const PasswordMeter: Component
5 | export default PasswordMeter
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | configureWebpack: {
3 | optimization: {
4 | splitChunks: false
5 | }
6 | },
7 | css: {
8 | extract: false
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/logic/index.ts:
--------------------------------------------------------------------------------
1 | import scorePassword from './scorePassword'
2 | import nameScore from './nameScore'
3 | import checkStrength from './checkStrength'
4 |
5 | export { scorePassword, nameScore, checkStrength }
6 |
--------------------------------------------------------------------------------
/tests/password-examples.ts:
--------------------------------------------------------------------------------
1 | export const risky = 'sdwasdwq'
2 | export const guessable = 'adsasdasd'
3 | export const weak = 'asdasd1212'
4 | export const safe = 'ASSAasd123'
5 | export const secure = '$p@RYr3y6'
6 |
7 | export const common = '123456789'
8 |
--------------------------------------------------------------------------------
/src/logic/checkStrength.ts:
--------------------------------------------------------------------------------
1 | import scorePassword from './scorePassword'
2 | import nameScore from './nameScore'
3 |
4 | const checkStrength = (pass: string) => {
5 | const score = scorePassword(pass)
6 | return nameScore(score)
7 | }
8 |
9 | export default checkStrength
10 |
--------------------------------------------------------------------------------
/src/logic/isCommonPassword.ts:
--------------------------------------------------------------------------------
1 | /*
2 | check to see if it is a common password
3 | */
4 |
5 | import commonPasswords from '../data/commonPasswords';
6 |
7 | export const isCommonPassword = (password: string): boolean => {
8 | return commonPasswords.includes(password);
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/src/logic/nameScore.ts:
--------------------------------------------------------------------------------
1 | const nameScore = (score: number): string => {
2 | switch (score) {
3 | case 0:
4 | return 'risky'
5 | case 1:
6 | return 'guessable'
7 | case 2:
8 | return 'weak'
9 | case 3:
10 | return 'safe'
11 | case 4:
12 | return 'secure'
13 | default:
14 | return ''
15 | }
16 | }
17 |
18 | export default nameScore
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "isolatedModules": true,
8 | "skipLibCheck": true,
9 | "noEmit": true,
10 | "paths": {
11 | "@/*": ["./src/*"],
12 | }
13 | },
14 | "include": ["src/**/*.ts", "tests/**/*.ts"],
15 | "exclude": ["node_modules", ".vscode"]
16 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | vue simple password meter
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/data/commonPasswords.ts:
--------------------------------------------------------------------------------
1 | // common passwords as an array of strings
2 |
3 | const commonPasswords = [
4 | "123456",
5 | "qwerty",
6 | "password",
7 | "111111",
8 | "Abc123",
9 | "123456789",
10 | "12345678",
11 | "123123",
12 | "1234567890",
13 | "12345",
14 | "1234567",
15 | "qwertyuiop",
16 | "qwerty123",
17 | "1q2w3e",
18 | "password1",
19 | "123321",
20 | "Iloveyou",
21 | "12345"
22 | ]
23 |
24 | export default commonPasswords
25 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
28 |
29 |
31 |
--------------------------------------------------------------------------------
/licence.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Milad Dehghan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | ///
2 | import { resolve } from 'path'
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue';
5 | import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
6 |
7 | export default defineConfig({
8 | plugins: [vue(),cssInjectedByJsPlugin()],
9 | resolve: {
10 | alias: {
11 | '@': resolve(__dirname, './src'),
12 | },
13 | },
14 | build: {
15 | lib: {
16 | // Could also be a dictionary or array of multiple entry points
17 | entry: resolve(__dirname, 'lib/main.ts'),
18 | name: 'vue-simple-password-meter',
19 | // the proper extensions will be added
20 | fileName: 'vue-simple-password-meter'
21 | },
22 | rollupOptions: {
23 | // make sure to externalize deps that shouldn't be bundled
24 | // into your library
25 | external: ['vue'],
26 | output: {
27 | // Provide global variables to use in the UMD build
28 | // for externalized deps
29 | globals: {
30 | vue: 'Vue'
31 | }
32 | }
33 | }
34 | },
35 | test: {
36 | environment: 'happy-dom'
37 | },
38 | })
39 |
--------------------------------------------------------------------------------
/src/password-meter.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
32 |
66 |
--------------------------------------------------------------------------------
/src/logic/scorePassword.ts:
--------------------------------------------------------------------------------
1 | import { isCommonPassword } from '@/logic/isCommonPassword'
2 |
3 | const scorePassword = (pass : string): number => {
4 | let score = 0
5 | let length = 0
6 | let specialChar = 0
7 | let caseMix = 0
8 | let numCharMix = 0
9 |
10 | const specialCharRegex = /[^A-Za-z0-9]/g
11 | const lowercaseRegex = /(.*[a-z].*)/g
12 | const uppercaseRegex = /(.*[A-Z].*)/g
13 | const numberRegex = /(.*[0-9].*)/g
14 | const repeatCharRegex = /(\w)(\1+\1+\1+\1+)/g
15 |
16 | const hasSpecialChar = specialCharRegex.test(pass)
17 | const hasLowerCase = lowercaseRegex.test(pass)
18 | const hasUpperCase = uppercaseRegex.test(pass)
19 | const hasNumber = numberRegex.test(pass)
20 | const hasRepeatChars = repeatCharRegex.test(pass)
21 |
22 | if (pass.length > 4) {
23 | if (isCommonPassword(pass)) {
24 | return 0
25 | }
26 |
27 | if ((hasLowerCase || hasUpperCase) && hasNumber) {
28 | numCharMix = 1
29 | }
30 |
31 | if (hasUpperCase && hasLowerCase) {
32 | caseMix = 1
33 | }
34 |
35 | if ((hasLowerCase || hasUpperCase || hasNumber) && hasSpecialChar) {
36 | specialChar = 1
37 | }
38 |
39 | if (pass.length > 8) {
40 | length = 1
41 | }
42 |
43 | if (pass.length > 12 && !hasRepeatChars) {
44 | length = 2
45 | }
46 |
47 | if (pass.length > 20 && !hasRepeatChars) {
48 | length = 3
49 | }
50 |
51 | score = length + specialChar + caseMix + numCharMix
52 |
53 | if (score > 4) {
54 | score = 4
55 | }
56 | }
57 |
58 | return score
59 | }
60 |
61 | export default scorePassword
62 |
--------------------------------------------------------------------------------
/tests/unit/logic.spec.ts:
--------------------------------------------------------------------------------
1 | import { checkStrength, scorePassword } from '@/logic'
2 | import { risky, guessable, weak, safe, secure, common } from '../password-examples'
3 | import {describe, it, expect} from 'vitest'
4 |
5 | describe('checkStrength', () => {
6 | it('return risky', () => {
7 | expect(checkStrength(risky)).toBe('risky')
8 | })
9 |
10 | it('return guessable', () => {
11 | expect(checkStrength(guessable)).toBe('guessable')
12 | })
13 |
14 | it('return weak', () => {
15 | expect(checkStrength(weak)).toBe('weak')
16 | })
17 |
18 | it('return safe', () => {
19 | expect(checkStrength(safe)).toBe('safe')
20 | })
21 |
22 | it('return secure', () => {
23 | expect(checkStrength(secure)).toBe('secure')
24 | })
25 |
26 | it('return risky common', () => {
27 | expect(checkStrength(common)).toBe('risky')
28 | })
29 |
30 | it('return risky empty', () => {
31 | expect(checkStrength('')).toBe('risky')
32 | });
33 |
34 | })
35 |
36 | describe('scorePassword', () => {
37 | it('return risky = 0', () => {
38 | expect(scorePassword(risky)).toBe(0)
39 | })
40 |
41 | it('return guessable = 1', () => {
42 | expect(scorePassword(guessable)).toBe(1)
43 | })
44 |
45 | it('return weak = 2', () => {
46 | expect(scorePassword(weak)).toBe(2)
47 | })
48 |
49 | it('return safe = 3', () => {
50 | expect(scorePassword(safe)).toBe(3)
51 | })
52 |
53 | it('return secure = 4', () => {
54 | expect(scorePassword(secure)).toBe(4)
55 | })
56 |
57 | it('return risky common = 0', () => {
58 | expect(scorePassword(common)).toBe(0)
59 | })
60 |
61 | it('return risky empty = 0', () => {
62 | expect(scorePassword('')).toBe(0)
63 | });
64 | })
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-simple-password-meter",
3 | "version": "1.3.4",
4 | "private": false,
5 | "description": "A really simple password strength meter. customizable with css",
6 | "author": {
7 | "name": "Milad Dehghan",
8 | "email": "miladd3@gmail.com",
9 | "url": "https://github.com/miladd3"
10 | },
11 | "scripts": {
12 | "serve": "vite serve",
13 | "clean": "rimraf ./dist",
14 | "build": "npm run clean && vite build",
15 | "build:npm": "npm run clean && vite build",
16 | "gitvue2": "git checkout vue-2 && git pull && npm clean-install",
17 | "test": "vitest"
18 | },
19 | "main": "dist/vue-simple-password-meter.umd.js",
20 | "unpkg": "dist/vue-simple-password-meter.umd.js",
21 | "typings": "src/types/index.d.ts",
22 | "types": "src/types/index.d.ts",
23 | "files": [
24 | "dist",
25 | "src/types"
26 | ],
27 | "devDependencies": {
28 | "@typescript-eslint/eslint-plugin": "^2.33.0",
29 | "@typescript-eslint/parser": "^2.33.0",
30 | "@vitejs/plugin-vue": "^3.2.0",
31 | "@vue/test-utils": "^2.3.2",
32 | "happy-dom": "^9.1.9",
33 | "rimraf": "^4.4.1",
34 | "typescript": "^5.0.4",
35 | "vite": "^3.2.4",
36 | "vite-plugin-css-injected-by-js": "^2.1.1",
37 | "vitest": "^0.30.0",
38 | "vue": "^3.2",
39 | "vue-jest": "^5.0.0-0"
40 | },
41 | "browserslist": [
42 | "> 1%",
43 | "last 2 versions"
44 | ],
45 | "bugs": {
46 | "url": "https://github.com/miladd3/vue-simple-password-meter/issues",
47 | "email": "miladd3@gmail.com"
48 | },
49 | "keywords": [
50 | "vue",
51 | "password strength",
52 | "password meter",
53 | "password",
54 | "simple",
55 | "strength"
56 | ],
57 | "license": "MIT",
58 | "repository": {
59 | "url": "https://github.com/miladd3/vue-simple-password-meter"
60 | },
61 | "homepage": "https://github.com/miladd3/vue-simple-password-meter#readme",
62 | "resolutions": {
63 | "yargs-parser": "15.0.1"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/unit/main.spec.ts:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import PasswordMeter from '@/password-meter.vue'
3 | import { risky, guessable, weak, safe, secure, common } from '../password-examples'
4 | import {describe, it, expect} from 'vitest'
5 |
6 | describe('password-meter.vue', () => {
7 | /**
8 | * Test risky
9 | */
10 | it('renders and detects: risky', () => {
11 | const wrapper = shallowMount(PasswordMeter, {
12 | props: {
13 | password: risky
14 | }
15 | })
16 |
17 | expect(wrapper.classes()).toContain('risky')
18 | })
19 |
20 | /**
21 | * Test guessable
22 | */
23 | it('renders and detects : guessable', () => {
24 | const wrapper = shallowMount(PasswordMeter, {
25 | props: {
26 | password: guessable
27 | }
28 | })
29 |
30 | expect(wrapper.classes()).toContain('guessable')
31 | })
32 |
33 | /**
34 | * Test weak
35 | */
36 | it('renders and detects : weak', () => {
37 | const wrapper = shallowMount(PasswordMeter, {
38 | props: {
39 | password: weak
40 | }
41 | })
42 |
43 | expect(wrapper.classes()).toContain('weak')
44 | })
45 |
46 | /**
47 | * Test safe
48 | */
49 | it('renders and detects : safe', () => {
50 | const wrapper = shallowMount(PasswordMeter, {
51 | props: {
52 | password: safe
53 | }
54 | })
55 |
56 | expect(wrapper.classes()).toContain('safe')
57 | })
58 |
59 | /**
60 | * Test secure
61 | */
62 | it('renders and detects : secure', () => {
63 | const wrapper = shallowMount(PasswordMeter, {
64 | props: {
65 | password: secure
66 | }
67 | })
68 |
69 | expect(wrapper.classes()).toContain('secure')
70 | })
71 |
72 | /**
73 | * Test common
74 | */
75 | it('renders and detects : common', () => {
76 | const wrapper = shallowMount(PasswordMeter, {
77 | props: {
78 | password: common
79 | }
80 | })
81 |
82 | expect(wrapper.classes()).toContain('risky')
83 | });
84 |
85 | })
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-simple-password-meter
2 |
3 | **Vue Simple Password Meter** is a simple password checker written in vanilla js and extremely lightweight (** less than 1kb minified + Gzipped**)
4 |
5 | #### This is `Vue 3.*` compatible version. If you are using `Vue 2.*` [Click Here](https://github.com/miladd3/vue-simple-password-meter/tree/vue-2)
6 |
7 | ## Demo
8 |
9 | [Demo](https://vspm-next.herokuapp.com/)
10 |
11 | ## Install
12 |
13 | `npm install vue-simple-password-meter --save`
14 |
15 | ## Usage
16 |
17 | Simply use v-model and send it to the component using password prop
18 |
19 | ```vue
20 |
21 |
22 |
Password
23 |
24 |
25 |
26 |
27 |
28 |
45 | ```
46 |
47 | ### Customize using css
48 |
49 | If you want to customize the bar its really simple with some easy css you can customize it
50 |
51 | Overwrite these css styles globally and change each state color and style
52 |
53 | ```css
54 | .po-password-strength-bar {
55 | border-radius: 2px;
56 | transition: all 0.2s linear;
57 | height: 5px;
58 | margin-top: 8px;
59 | }
60 |
61 | .po-password-strength-bar.risky {
62 | background-color: #f95e68;
63 | width: 10%;
64 | }
65 |
66 | .po-password-strength-bar.guessable {
67 | background-color: #fb964d;
68 | width: 32.5%;
69 | }
70 |
71 | .po-password-strength-bar.weak {
72 | background-color: #fdd244;
73 | width: 55%;
74 | }
75 |
76 | .po-password-strength-bar.safe {
77 | background-color: #b0dc53;
78 | width: 77.5%;
79 | }
80 |
81 | .po-password-strength-bar.secure {
82 | background-color: #35cc62;
83 | width: 100%;
84 | }
85 | ```
86 |
87 |
88 | ## Events
89 |
90 | You can use event `score` to use scored number between `0` to `4` that scores password from risky to secure with 4 is a secure password and 0 is risky and between.
91 |
92 | You can use this as a form verification tool
93 |
94 | See below example for more detail
95 |
96 | ```vue
97 |
98 |
99 |
Password
100 |
101 |
Use better password
102 |
103 |
104 |
105 |
106 |
133 | ```
134 |
135 | ## Contributing
136 |
137 | If you want to contribute to this project simply fork it and clone it then run
138 | `npm i`
139 | in the root of the project, then run
140 | `npm run serve`
141 | to run development server.
142 |
143 | ## Motivation
144 |
145 | Since Every other components and libraries mostly were using [zxcvbn](https://github.com/dropbox/zxcvbn) was 799.5kb minified and 388.3kb minified and Gzipped, so I decided to make simpler approach and use regex instead of dictionary for validating.
146 |
147 | ### Licence and cast
148 |
149 | MIT Licence
150 |
151 | by [Milad Dehghan](http://dehghan.net)
152 |
--------------------------------------------------------------------------------