Skip to content

Instantly share code, notes, and snippets.

@Aarondoran
Created July 21, 2023 10:27
Show Gist options
  • Save Aarondoran/874c44255452a7c12e53d481029a3b7d to your computer and use it in GitHub Desktop.
Save Aarondoran/874c44255452a7c12e53d481029a3b7d to your computer and use it in GitHub Desktop.
The Password Genie - Vue.js
<div id="app">
<section class="wrapper">
<h1>The Password Genie</h1>
<div class="password-box">
<span id="password" class="password" v-on:click="copyToClipboard">{{ password }}</span>
<span class="regenerate-password" v-on:click="generatePassword"></span>
<span class="copy-password" v-on:click="copyToClipboard"></span>
<span class="tooltip" v-if="copied">Password copied successfuly!</span>
</div>
<form @keydown.enter.prevent="">
<div class="field-wrap">
<label>Strength</label>
<span class="range-value">{{strength.text}}</span>
<div class="range-slider_wrapper slider-strength" v-bind:class="strength.text">
<span class="slider-bar" v-bind:style="{ width: strength.score + '%' }"></span>
<input type="range" class="range-slider" min="0" max="100" v-model="strength.score" disabled>
</div>
</div>
<div class="seperator"></div>
<div class="field-wrap">
<label>Length</label>
<span class="range-value">{{settings.length}}</span>
<div class="range-slider_wrapper">
<span class="slider-bar" v-bind:style="{ width: lengthThumbPosition + '%' }"></span>
<input type="range" class="range-slider" min="6" v-bind:max="settings.maxLength" v-model="settings.length">
</div>
</div>
<div class="field-wrap">
<label>Digits</label>
<span class="range-value">{{settings.digits}}</span>
<div class="range-slider_wrapper">
<span class="slider-bar" v-bind:style="{ width: digitsThumbPosition + '%' }"></span>
<input type="range" class="range-slider" min="0" v-bind:max="settings.maxDigits" v-model="settings.digits">
</div>
</div>
<div class="field-wrap">
<label>Symbols</label>
<span class="range-value">{{settings.symbols}}</span>
<div class="range-slider_wrapper">
<span class="slider-bar" v-bind:style="{ width: symbolsThumbPosition + '%' }"></span>
<input type="range" class="range-slider" min="0" v-bind:max="settings.maxSymbols" v-model="settings.symbols">
</div>
</div>
</form>
</section>
</div>
new Vue({
el: '#app',
data() {
return {
password: '',
copied: false,
settings: {
maxLength: 64,
maxDigits: 10,
maxSymbols: 10,
length: 12,
digits: 4,
symbols: 2,
ambiguous: true,
}
};
},
computed: {
lengthThumbPosition: function() {
return (( (this.settings.length - 6) / (this.settings.maxLength - 6)) * 100);
},
digitsThumbPosition: function() {
return (( (this.settings.digits - 0) / (this.settings.maxDigits - 0)) * 100);
},
symbolsThumbPosition: function() {
return (( (this.settings.symbols - 0) / (this.settings.maxSymbols - 0)) * 100);
},
strength: function() {
var count = {
excess: 0,
upperCase: 0,
numbers: 0,
symbols: 0
};
var weight = {
excess: 3,
upperCase: 4,
numbers: 5,
symbols: 5,
combo: 0,
flatLower: 0,
flatNumber: 0
};
var strength = {
text: '',
score: 0
};
var baseScore = 30;
for (i=0; i < this.password.length;i++){
if (this.password.charAt(i).match(/[A-Z]/g)) {count.upperCase++;}
if (this.password.charAt(i).match(/[0-9]/g)) {count.numbers++;}
if (this.password.charAt(i).match(/(.*[!,@,#,$,%,^,&,*,?,_,~])/)) {count.symbols++;}
}
count.excess = this.password.length - 6;
if (count.upperCase && count.numbers && count.symbols){
weight.combo = 25;
}
else if ((count.upperCase && count.numbers) || (count.upperCase && count.symbols) || (count.numbers && count.symbols)){
weight.combo = 15;
}
if (this.password.match(/^[\sa-z]+$/))
{
weight.flatLower = -30;
}
if (this.password.match(/^[\s0-9]+$/))
{
weight.flatNumber = -50;
}
var score =
baseScore +
(count.excess * weight.excess) +
(count.upperCase * weight.upperCase) +
(count.numbers * weight.numbers) +
(count.symbols * weight.symbols) +
weight.combo + weight.flatLower +
weight.flatNumber;
if(score < 30 ) {
strength.text = "weak";
strength.score = 10;
return strength;
} else if (score >= 30 && score < 75 ){
strength.text = "average";
strength.score = 40;
return strength;
} else if (score >= 75 && score < 150 ){
strength.text = "strong";
strength.score = 75;
return strength;
} else {
strength.text = "secure";
strength.score = 100;
return strength;
}
},
},
mounted() {
this.generatePassword();
},
watch: {
settings: {
handler: function() {
this.generatePassword();
},
deep: true
}
},
methods: {
// copy password to clipboard
copyToClipboard(){
// we should create a textarea, put the password inside it, select it and finally copy it
var copyElement = document.createElement("textarea");
copyElement.style.opacity = '0';
copyElement.style.position = 'fixed';
copyElement.textContent = this.password;
var body = document.getElementsByTagName('body')[0];
body.appendChild(copyElement);
copyElement.select();
document.execCommand('copy');
body.removeChild(copyElement);
this.copied = true;
// reset this.copied
setTimeout(() => {
this.copied = false;
}, 750);
},
// generate the password
generatePassword() {
var lettersSetArray = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
var symbolsSetArray = [ "=","+","-","^","?","!","%","&","*","$","#","^","@","|"];
//var ambiguousSetArray = ["(",")","{","}","[","]","(",")","/","~",";",":",".","<",">"];
var passwordArray = [];
var digitsArray = [];
var digitsPositionArray = [];
// first, fill the password array with letters, uppercase and lowecase
for (var i = 0; i < this.settings.length; i++) {
// get an array for all indexes of the password array
digitsPositionArray.push(i);
var upperCase = Math.round(Math.random() * 1);
if (upperCase === 0) {
passwordArray[i] = lettersSetArray[Math.floor(Math.random()*lettersSetArray.length)].toUpperCase();
}
else {
passwordArray[i] = lettersSetArray[Math.floor(Math.random()*lettersSetArray.length)];
}
}
// Add digits to password
for (i = 0; i < this.settings.digits; i++) {
digit = Math.round(Math.random() * 9);
numberIndex = digitsPositionArray[Math.floor(Math.random()*digitsPositionArray.length)];
passwordArray[numberIndex] = digit;
/* remove position from digitsPositionArray so we make sure to the have the exact number of digits in our password
since without this step, numbers may override other numbers */
var j = digitsPositionArray.indexOf(numberIndex);
if(i != -1) {
digitsPositionArray.splice(j, 1);
}
}
// add special charachters "symbols"
for (i = 0; i < this.settings.symbols; i++) {
var symbol = symbolsSetArray[Math.floor(Math.random()*symbolsSetArray.length)];
var symbolIndex = digitsPositionArray[Math.floor(Math.random()*digitsPositionArray.length)];
passwordArray[symbolIndex] = symbol;
/* remove position from digitsPositionArray so we make sure to the have the exact number of digits in our password
since without this step, numbers may override other numbers */
var j = digitsPositionArray.indexOf(symbolIndex);
if(i != -1) {
digitsPositionArray.splice(j, 1);
}
}
this.password = passwordArray.join("");
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
$blue: #3fa4f4;
$purple: #6e77f7;
$peach: #fc8680;
$pink: #ef5081;
$green: #8BC34A;
$dark-green: #4caf50;
$red: #ff6666;
$orange: #ff9800;
$wrapper-bg: #f4f7fc;
$wrapper-width: 400px;
*{
box-sizing: border-box;
}
body{
font-size: 15px;
font-family: Tahoma,Verdana,Segoe,sans-serif;
color: #444;
background-color: #fefefe;
background-image: linear-gradient(-45deg, #018bfd 0%, #3F51B5 100%);
background-repeat: no-repeat;
background-size: cover;
padding: 0 20px;
margin: 0;
min-height: 100vh;
position: relative;
}
.header{
padding: 2em 0;
text-align: center;
.title{
font-size: 1.2em;
font-weight: bold;
color: #fff;
img{
width: 12px;
margin: 0 2px 2px;
vertical-align: bottom;
}
}
}
.wrapper{
width: $wrapper-width;
max-width: 100%;
min-height: 400px;
margin: 40px auto;
position: relative;
border: 1px solid #eee;
border-radius: 3px;
padding: 40px 20px;
font-size: 0.85em;
-webkit-box-shadow: 0 0 15px 0 rgba(0,0,0,0.05);
box-shadow: 0 0 15px 0 rgba(0,0,0,0.05);
background-color: $wrapper-bg;
position: relative;
transition: all ease-in 0.25s;
}
h1{
text-align: center;
margin: 0 0 40px;
}
.field-wrap{
margin-bottom: 20px;
}
form{
overflow: overlay;
margin-top: 30px;
}
label{
display: inline-block;
min-width: 20%;
}
.range-slider_wrapper{
position: relative;
width: 100%;
margin: 10px 0 30px;
}
.range-slider{
-webkit-appearance: none;
appearance: none;
background: lighten($blue, 20%);
width: 100%;
border-radius: 3px;
vertical-align: bottom;
margin: 0;
height: 6px;
cursor: pointer;
transition: all ease-in 0.25s;
}
.range-slider::-webkit-slider-thumb{
-webkit-appearance: none;
appearance: none;
border-radius: 0;
border: 0;
position:relative;
width: 4px;
height: 15px;
background-color: darken($blue, 5%);
}
.range-slider::-moz-range-thumb{
-moz-appearance: none;
appearance: none;
border-radius: 0;
border: 0;
position:relative;
width: 4px;
height: 15px;
background-color: darken($blue, 5%);
}
.range-slider{
&:focus{
outline: none;
}
&:hover, &:active{
&::-webkit-slider-thumb{
top: 0px;
}
}
}
::-moz-range-track {
background: transparent;
border: 0;
}
input::-moz-focus-inner,
input::-moz-focus-outer {
border: 0;
}
.range-value{
text-transform: capitalize;
float: right;
vertical-align: bottom;
min-width: 30px;
display: inline-block;
text-align: center;
border-radius: 3px;
font-size: 0.9em;
}
.slider-bar{
position: absolute;
height: 6px;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
background: $blue;
left: 0;
bottom: 0;
pointer-events: none;
}
.slider{
&-strength{
.range-slider{
cursor: default;
}
.slider-bar{
border-radius: 3px;
transition: all ease-in 0.25s;
}
.range-slider::-webkit-slider-thumb{
background-color: transparent;
}
.range-slider::-moz-range-thumb{
background-color: transparent;
}
&.weak{
.range-slider{
background-color: lighten($red, 30%);
}
.slider-bar, .slider-bar:after{
background-color: $red;
}
}
&.average{
.range-slider{
background-color: lighten($orange, 30%);
}
.slider-bar, .slider-bar:after{
background-color: $orange;
}
}
&.strong{
.range-slider{
background-color: lighten($green, 30%);
}
.slider-bar, .slider-bar:after{
background-color: $green;
}
}
&.secure{
.range-slider{
background-color: lighten($green, 30%);
}
.slider-bar, .slider-bar:after{
background-color: $green;
}
}
}
}
.password-box{
width: 100%;
min-height: 80px;
margin-bottom: 40px;
position: relative;
text-align: center;
border-radius: 3px;
background: #fff;
letter-spacing: 2px;
transition: all ease-in 0.3s;
border: 1px solid rgb(189, 204, 230);
.password{
width: 70%;
padding: 1.5em 1em;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
word-wrap: break-word;
}
}
.regenerate-password, .copy-password{
width: 44px;
height: 50%;
position: absolute;
right: 0;
transition: all ease-in 0.25s;
&:hover{
opacity: 0.8;
}
}
.regenerate-password{
top: 0;
background-color: #fff;
background-image: url('https://nourabusoud.github.io/password-genie/images/regenerate.svg');
background-size: 40%;
background-position: center center;
background-repeat: no-repeat;
transition: all ease-in 0.25s;
cursor: pointer;
&:hover{
background-color: #fff;
}
}
.copy-password{
bottom: 0;
background-color: #fff;
background-image: url('https://nourabusoud.github.io/password-genie/images/copy-full.svg');
background-size: 50%;
background-position: center center;
background-repeat: no-repeat;
transition: all ease-in 0.25s;
cursor: pointer;
&:hover{
background-color: #fff;
}
}
.tooltip{
font-size: 0.8em;
display: block;
text-align: center;
padding: 0.5em;
border-radius: 3px;
position: absolute;
bottom: -35px;
left: 50%;
transform: translateX(-50%);
}
.seperator{
width: 100%;
height: 3px;
background-color: #fff;
margin: 60px 0 40px;
}
/* Footer */
footer{
width: 100%;
text-align: center;
color: #fff;
}
footer a{
color: #fff;
}
.github-links{
margin-bottom: 30px;
}
// fix for copy text
textarea, textarea:focus{
font-size: 16px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment