@@ -0,0 +1,17 @@ | |||
# editorconfig.org | |||
root = true | |||
[*] | |||
charset = utf-8 | |||
end_of_line = lf | |||
indent_size = 2 | |||
indent_style = space | |||
insert_final_newline = true | |||
trim_trailing_whitespace = true | |||
[*.md] | |||
trim_trailing_whitespace = false | |||
[*.py] | |||
indent_size = 4 |
@@ -1,30 +1,30 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>LessPass</title> | |||
<meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |||
<link rel="stylesheet" href="dist/lesspass.min.css"> | |||
<style> | |||
div.center { | |||
width: 470px; | |||
max-width: 100%; | |||
display: block; | |||
margin-left: auto; | |||
margin-right: auto; | |||
} | |||
<meta charset="utf-8"> | |||
<title>LessPass</title> | |||
<meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |||
<link rel="stylesheet" href="dist/lesspass.min.css"> | |||
<style> | |||
div.center { | |||
width: 470px; | |||
max-width: 100%; | |||
display: block; | |||
margin-left: auto; | |||
margin-right: auto; | |||
} | |||
@media (min-width: 544px) { | |||
#lesspass { | |||
margin-top: 1em; | |||
} | |||
} | |||
</style> | |||
@media (min-width: 544px) { | |||
#lesspass { | |||
margin-top: 1em; | |||
} | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="center"> | |||
<div id="lesspass" class="m-x-auto"></div> | |||
<div id="lesspass" class="m-x-auto"></div> | |||
</div> | |||
<script src="dist/lesspass.min.js"></script> | |||
</body> | |||
@@ -1,11 +1,9 @@ | |||
@import "~bootstrap/scss/variables"; | |||
@import "~bootstrap/scss/mixins"; | |||
@import "~bootstrap/scss/custom"; | |||
// Reset and dependencies | |||
@import "~bootstrap/scss/normalize"; | |||
//@import "~bootstrap/scss/print"; | |||
// Core CSS | |||
@import "~bootstrap/scss/reboot"; | |||
@import "~bootstrap/scss/type"; | |||
@@ -15,7 +13,6 @@ | |||
@import "~bootstrap/scss/tables"; | |||
@import "~bootstrap/scss/forms"; | |||
@import "~bootstrap/scss/buttons"; | |||
// Components | |||
@import "~bootstrap/scss/transitions"; | |||
@import "~bootstrap/scss/dropdown"; | |||
@@ -35,13 +32,11 @@ | |||
@import "~bootstrap/scss/list-group"; | |||
@import "~bootstrap/scss/responsive-embed"; | |||
@import "~bootstrap/scss/close"; | |||
// Components w/ JavaScript | |||
//@import "~bootstrap/scss/modal"; | |||
//@import "~bootstrap/scss/tooltip"; | |||
//@import "~bootstrap/scss/popover"; | |||
//@import "~bootstrap/scss/carousel"; | |||
// Utility classes | |||
@import "~bootstrap/scss/utilities"; | |||
@import '~font-awesome/css/font-awesome.css'; | |||
@@ -53,7 +48,7 @@ | |||
} | |||
@media (max-width: 419px) { | |||
#lesspass{ | |||
#lesspass { | |||
border: none; | |||
} | |||
} | |||
@@ -64,4 +59,4 @@ | |||
button, .pointer { | |||
cursor: pointer; | |||
} | |||
} |
@@ -1,29 +1,29 @@ | |||
<template> | |||
<div id="lesspass" class="card" style="max-width: 420px;"> | |||
<lesspass-menu></lesspass-menu> | |||
<lesspass-message></lesspass-message> | |||
<div class="card-block"> | |||
<router-view></router-view> | |||
</div> | |||
<div id="lesspass" class="card" style="max-width: 420px;"> | |||
<lesspass-menu></lesspass-menu> | |||
<lesspass-message></lesspass-message> | |||
<div class="card-block"> | |||
<router-view></router-view> | |||
</div> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import './LessPass.scss'; | |||
import Menu from './components/Menu.vue'; | |||
import Message from './components/Message.vue'; | |||
import {mapGetters} from 'vuex'; | |||
import './LessPass.scss'; | |||
import Menu from './components/Menu.vue'; | |||
import Message from './components/Message.vue'; | |||
import {mapGetters} from 'vuex'; | |||
export default { | |||
name: 'LessPass', | |||
components: { | |||
'lesspass-menu': Menu, | |||
'lesspass-message': Message | |||
}, | |||
computed: mapGetters(['version']), | |||
created(){ | |||
this.$store.dispatch('cleanMessage'); | |||
this.$store.dispatch('loadPasswordFirstTime'); | |||
this.$store.dispatch('refreshToken'); | |||
} | |||
export default { | |||
name: 'LessPass', | |||
components: { | |||
'lesspass-menu': Menu, | |||
'lesspass-message': Message | |||
}, | |||
computed: mapGetters(['version']), | |||
created(){ | |||
this.$store.dispatch('cleanMessage'); | |||
this.$store.dispatch('loadPasswordFirstTime'); | |||
this.$store.dispatch('refreshToken'); | |||
} | |||
</script> | |||
} | |||
</script> |
@@ -1,25 +1,25 @@ | |||
import axios from 'axios'; | |||
export default { | |||
addAuthorizationHeader(config) { | |||
return { | |||
...config, | |||
headers: {Authorization: `JWT ${config.token}`} | |||
}; | |||
}, | |||
all(config) { | |||
return axios.get('/api/passwords/', this.addAuthorizationHeader(config)); | |||
}, | |||
create(resource, config) { | |||
return axios.post('/api/passwords/', resource, this.addAuthorizationHeader(config)); | |||
}, | |||
read(resource, config) { | |||
return axios.get('/api/passwords/' + resource.id + '/', this.addAuthorizationHeader(config)); | |||
}, | |||
update(resource, config) { | |||
return axios.put('/api/passwords/' + resource.id + '/', resource, this.addAuthorizationHeader(config)); | |||
}, | |||
delete(resource, config) { | |||
return axios.delete('/api/passwords/' + resource.id + '/', this.addAuthorizationHeader(config)); | |||
} | |||
addAuthorizationHeader(config) { | |||
return { | |||
...config, | |||
headers: {Authorization: `JWT ${config.token}`} | |||
}; | |||
}, | |||
all(config) { | |||
return axios.get('/api/passwords/', this.addAuthorizationHeader(config)); | |||
}, | |||
create(resource, config) { | |||
return axios.post('/api/passwords/', resource, this.addAuthorizationHeader(config)); | |||
}, | |||
read(resource, config) { | |||
return axios.get('/api/passwords/' + resource.id + '/', this.addAuthorizationHeader(config)); | |||
}, | |||
update(resource, config) { | |||
return axios.put('/api/passwords/' + resource.id + '/', resource, this.addAuthorizationHeader(config)); | |||
}, | |||
delete(resource, config) { | |||
return axios.delete('/api/passwords/' + resource.id + '/', this.addAuthorizationHeader(config)); | |||
} | |||
} |
@@ -1,25 +1,25 @@ | |||
import axios from 'axios'; | |||
export default { | |||
login(user, config) { | |||
return axios.post('/api/tokens/auth/', user, config).then(response => { | |||
return response.data; | |||
}); | |||
}, | |||
register(user, config) { | |||
return axios.post('/api/auth/register/', user, config).then(response => { | |||
return response.data; | |||
}); | |||
}, | |||
resetPassword(email, config) { | |||
return axios.post('/api/auth/password/reset/', email, config); | |||
}, | |||
confirmResetPassword(password, config) { | |||
return axios.post('/api/auth/password/reset/confirm/', password, config); | |||
}, | |||
requestNewToken(token, config){ | |||
return axios.post('/api/tokens/refresh/', token, config).then(response => { | |||
return response.data.token; | |||
}); | |||
} | |||
login(user, config) { | |||
return axios.post('/api/tokens/auth/', user, config).then(response => { | |||
return response.data; | |||
}); | |||
}, | |||
register(user, config) { | |||
return axios.post('/api/auth/register/', user, config).then(response => { | |||
return response.data; | |||
}); | |||
}, | |||
resetPassword(email, config) { | |||
return axios.post('/api/auth/password/reset/', email, config); | |||
}, | |||
confirmResetPassword(password, config) { | |||
return axios.post('/api/auth/password/reset/confirm/', password, config); | |||
}, | |||
requestNewToken(token, config){ | |||
return axios.post('/api/tokens/refresh/', token, config).then(response => { | |||
return response.data.token; | |||
}); | |||
} | |||
} |
@@ -1,47 +1,47 @@ | |||
<template> | |||
<div id="delete-button"> | |||
<div class="form-group has-danger" v-if="confirm"> | |||
<button type="button" class="btn btn-danger btn-sm" v-on:click.prevent="confirmDelete"> | |||
{{ confirmButton }} | |||
</button> | |||
<button type="button" class="btn btn-secondary btn-sm" v-on:click.prevent="confirm=false"> | |||
{{ cancelButton }} | |||
</button> | |||
<div class="form-control-feedback">{{ confirmText }}</div> | |||
</div> | |||
<button type="button" class="btn btn-outline-danger btn-sm" | |||
v-if="!confirm" | |||
v-on:click.prevent="confirm=true"> | |||
<i class="fa fa-trash fa-fw"></i> | |||
</button> | |||
<div id="delete-button"> | |||
<div class="form-group has-danger" v-if="confirm"> | |||
<button type="button" class="btn btn-danger btn-sm" v-on:click.prevent="confirmDelete"> | |||
{{ confirmButton }} | |||
</button> | |||
<button type="button" class="btn btn-secondary btn-sm" v-on:click.prevent="confirm=false"> | |||
{{ cancelButton }} | |||
</button> | |||
<div class="form-control-feedback">{{ confirmText }}</div> | |||
</div> | |||
<button type="button" class="btn btn-outline-danger btn-sm" | |||
v-if="!confirm" | |||
v-on:click.prevent="confirm=true"> | |||
<i class="fa fa-trash fa-fw"></i> | |||
</button> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
export default { | |||
data() { | |||
return { | |||
confirm: false | |||
} | |||
}, | |||
props: { | |||
confirmText: { | |||
type: String, | |||
default: 'Are you sure you want to delete this?' | |||
}, | |||
confirmButton: { | |||
type: String, | |||
default: 'Sure delete it' | |||
}, | |||
cancelButton: { | |||
type: String, | |||
default: 'Oups no!' | |||
} | |||
}, | |||
methods: { | |||
confirmDelete(){ | |||
this.confirm = false; | |||
this.$emit('remove'); | |||
} | |||
} | |||
export default { | |||
data() { | |||
return { | |||
confirm: false | |||
} | |||
}, | |||
props: { | |||
confirmText: { | |||
type: String, | |||
default: 'Are you sure you want to delete this?' | |||
}, | |||
confirmButton: { | |||
type: String, | |||
default: 'Sure delete it' | |||
}, | |||
cancelButton: { | |||
type: String, | |||
default: 'Oups no!' | |||
} | |||
}, | |||
methods: { | |||
confirmDelete(){ | |||
this.confirm = false; | |||
this.$emit('remove'); | |||
} | |||
} | |||
} | |||
</script> |
@@ -1,18 +1,18 @@ | |||
<style> | |||
#fingerprint { | |||
min-width: 90px; | |||
text-align: center; | |||
background-color: transparent; | |||
color: white; | |||
} | |||
#fingerprint { | |||
min-width: 90px; | |||
text-align: center; | |||
background-color: transparent; | |||
color: white; | |||
} | |||
#fingerprint i { | |||
color: black; | |||
position: relative; | |||
padding: 0; | |||
text-shadow: 1px 1px 0 white; | |||
font-size: 1.3em; | |||
} | |||
#fingerprint i { | |||
color: black; | |||
position: relative; | |||
padding: 0; | |||
text-shadow: 1px 1px 0 white; | |||
font-size: 1.3em; | |||
} | |||
</style> | |||
<template> | |||
<span class="input-group-btn" v-if="fingerprint"> | |||
@@ -27,49 +27,49 @@ | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import LessPass from 'lesspass'; | |||
import LessPass from 'lesspass'; | |||
export default { | |||
data(){ | |||
return { | |||
icon1: '', | |||
icon2: '', | |||
icon3: '', | |||
color1: '', | |||
color2: '', | |||
color3: '' | |||
} | |||
}, | |||
props: ['fingerprint'], | |||
watch: { | |||
fingerprint(newFingerprint) { | |||
if (!newFingerprint) { | |||
return; | |||
} | |||
LessPass.createFingerprint(newFingerprint).then(sha256 => { | |||
const hash1 = sha256.substring(0, 6); | |||
const hash2 = sha256.substring(6, 12); | |||
const hash3 = sha256.substring(12, 18); | |||
this.icon1 = this.getIcon(hash1); | |||
this.icon2 = this.getIcon(hash2); | |||
this.icon3 = this.getIcon(hash3); | |||
this.color1 = this.getColor(hash1); | |||
this.color2 = this.getColor(hash2); | |||
this.color3 = this.getColor(hash3); | |||
}); | |||
} | |||
}, | |||
methods: { | |||
getColor(color) { | |||
var colors = ['#000000', '#074750', '#009191', '#FF6CB6', '#FFB5DA', '#490092', '#006CDB', '#B66DFF', '#6DB5FE', '#B5DAFE', '#920000', '#924900', '#DB6D00', '#24FE23']; | |||
var index = parseInt(color, 16) % colors.length; | |||
return colors[index]; | |||
}, | |||
getIcon(hash) { | |||
var icons = ['fa-hashtag', 'fa-heart', 'fa-hotel', 'fa-university', 'fa-plug', 'fa-ambulance', 'fa-bus', 'fa-car', 'fa-plane', 'fa-rocket', 'fa-ship', 'fa-subway', 'fa-truck', 'fa-jpy', 'fa-eur', 'fa-btc', 'fa-usd', 'fa-gbp', 'fa-archive', 'fa-area-chart', 'fa-bed', 'fa-beer', 'fa-bell', 'fa-binoculars', 'fa-birthday-cake', 'fa-bomb', 'fa-briefcase', 'fa-bug', 'fa-camera', 'fa-cart-plus', 'fa-certificate', 'fa-coffee', 'fa-cloud', 'fa-coffee', 'fa-comment', 'fa-cube', 'fa-cutlery', 'fa-database', 'fa-diamond', 'fa-exclamation-circle', 'fa-eye', 'fa-flag', 'fa-flask', 'fa-futbol-o', 'fa-gamepad', 'fa-graduation-cap']; | |||
var index = parseInt(hash, 16) % icons.length; | |||
return icons[index]; | |||
} | |||
export default { | |||
data(){ | |||
return { | |||
icon1: '', | |||
icon2: '', | |||
icon3: '', | |||
color1: '', | |||
color2: '', | |||
color3: '' | |||
} | |||
}, | |||
props: ['fingerprint'], | |||
watch: { | |||
fingerprint(newFingerprint) { | |||
if (!newFingerprint) { | |||
return; | |||
} | |||
LessPass.createFingerprint(newFingerprint).then(sha256 => { | |||
const hash1 = sha256.substring(0, 6); | |||
const hash2 = sha256.substring(6, 12); | |||
const hash3 = sha256.substring(12, 18); | |||
this.icon1 = this.getIcon(hash1); | |||
this.icon2 = this.getIcon(hash2); | |||
this.icon3 = this.getIcon(hash3); | |||
this.color1 = this.getColor(hash1); | |||
this.color2 = this.getColor(hash2); | |||
this.color3 = this.getColor(hash3); | |||
}); | |||
} | |||
}, | |||
methods: { | |||
getColor(color) { | |||
var colors = ['#000000', '#074750', '#009191', '#FF6CB6', '#FFB5DA', '#490092', '#006CDB', '#B66DFF', '#6DB5FE', '#B5DAFE', '#920000', '#924900', '#DB6D00', '#24FE23']; | |||
var index = parseInt(color, 16) % colors.length; | |||
return colors[index]; | |||
}, | |||
getIcon(hash) { | |||
var icons = ['fa-hashtag', 'fa-heart', 'fa-hotel', 'fa-university', 'fa-plug', 'fa-ambulance', 'fa-bus', 'fa-car', 'fa-plane', 'fa-rocket', 'fa-ship', 'fa-subway', 'fa-truck', 'fa-jpy', 'fa-eur', 'fa-btc', 'fa-usd', 'fa-gbp', 'fa-archive', 'fa-area-chart', 'fa-bed', 'fa-beer', 'fa-bell', 'fa-binoculars', 'fa-birthday-cake', 'fa-bomb', 'fa-briefcase', 'fa-bug', 'fa-camera', 'fa-cart-plus', 'fa-certificate', 'fa-coffee', 'fa-cloud', 'fa-coffee', 'fa-comment', 'fa-cube', 'fa-cutlery', 'fa-database', 'fa-diamond', 'fa-exclamation-circle', 'fa-eye', 'fa-flag', 'fa-flask', 'fa-futbol-o', 'fa-gamepad', 'fa-graduation-cap']; | |||
var index = parseInt(hash, 16) % icons.length; | |||
return icons[index]; | |||
} | |||
} | |||
} | |||
</script> |
@@ -1,64 +1,64 @@ | |||
<template> | |||
<div id="masterPassword" class="inner-addon left-addon input-group"> | |||
<label for="password" class="sr-only">Master Password</label> | |||
<i class="fa fa-lock"></i> | |||
<input id="password" | |||
name="password" | |||
ref="password" | |||
type="password" | |||
class="form-control" | |||
placeholder="Master password" | |||
autocorrect="off" | |||
autocapitalize="off" | |||
v-model="password" | |||
v-on:input="updatePassword($event.target.value)" | |||
v-on:keyup.enter="triggerEnterMethod"> | |||
<fingerprint v-bind:fingerprint="fingerprint" v-on:click.native="togglePasswordType($refs.password)"> | |||
</fingerprint> | |||
</div> | |||
<div id="masterPassword" class="inner-addon left-addon input-group"> | |||
<label for="password" class="sr-only">Master Password</label> | |||
<i class="fa fa-lock"></i> | |||
<input id="password" | |||
name="password" | |||
ref="password" | |||
type="password" | |||
class="form-control" | |||
placeholder="Master password" | |||
autocorrect="off" | |||
autocapitalize="off" | |||
v-model="password" | |||
v-on:input="updatePassword($event.target.value)" | |||
v-on:keyup.enter="triggerEnterMethod"> | |||
<fingerprint v-bind:fingerprint="fingerprint" v-on:click.native="togglePasswordType($refs.password)"> | |||
</fingerprint> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import debounce from 'lodash.debounce'; | |||
import Fingerprint from './Fingerprint.vue'; | |||
import debounce from 'lodash.debounce'; | |||
import Fingerprint from './Fingerprint.vue'; | |||
export default { | |||
components: { | |||
Fingerprint | |||
}, | |||
props: ['value', 'keyupEnter'], | |||
data(){ | |||
return { | |||
fingerprint: '', | |||
password: this.value | |||
} | |||
}, | |||
watch: { | |||
'value': function (password) { | |||
this.password = password; | |||
this.updatePassword(password); | |||
} | |||
}, | |||
methods: { | |||
updatePassword: function (password) { | |||
this.fingerprint = Math.random().toString(36).substring(7); | |||
this.showRealFingerprint(password); | |||
this.$emit('input', password) | |||
}, | |||
showRealFingerprint: debounce(function (password) { | |||
this.fingerprint = password; | |||
}, 500), | |||
togglePasswordType(element){ | |||
if (element.type === 'password') { | |||
element.type = 'text'; | |||
} else { | |||
element.type = 'password'; | |||
} | |||
}, | |||
triggerEnterMethod(){ | |||
if (typeof this.keyupEnter !== 'undefined' && this.password) { | |||
this.keyupEnter() | |||
} | |||
} | |||
export default { | |||
components: { | |||
Fingerprint | |||
}, | |||
props: ['value', 'keyupEnter'], | |||
data(){ | |||
return { | |||
fingerprint: '', | |||
password: this.value | |||
} | |||
}, | |||
watch: { | |||
'value': function(password) { | |||
this.password = password; | |||
this.updatePassword(password); | |||
} | |||
}, | |||
methods: { | |||
updatePassword: function(password) { | |||
this.fingerprint = Math.random().toString(36).substring(7); | |||
this.showRealFingerprint(password); | |||
this.$emit('input', password) | |||
}, | |||
showRealFingerprint: debounce(function(password) { | |||
this.fingerprint = password; | |||
}, 500), | |||
togglePasswordType(element){ | |||
if (element.type === 'password') { | |||
element.type = 'text'; | |||
} else { | |||
element.type = 'password'; | |||
} | |||
}, | |||
triggerEnterMethod(){ | |||
if (typeof this.keyupEnter !== 'undefined' && this.password) { | |||
this.keyupEnter() | |||
} | |||
} | |||
} | |||
} | |||
</script> |
@@ -1,97 +1,97 @@ | |||
<style> | |||
#menu .white-link, #menu .text-white { | |||
color: inherit; | |||
} | |||
#menu .white-link, #menu .text-white { | |||
color: inherit; | |||
} | |||
#menu .white-link:hover, #menu .white-link:focus, #menu .white-link:active { | |||
text-decoration: none; | |||
color: inherit; | |||
} | |||
#menu .white-link:hover, #menu .white-link:focus, #menu .white-link:active { | |||
text-decoration: none; | |||
color: inherit; | |||
} | |||
.card-inverse { | |||
background-color: #333; | |||
border-color: #333; | |||
} | |||
.card-inverse { | |||
background-color: #333; | |||
border-color: #333; | |||
} | |||
</style> | |||
<template> | |||
<div id="menu"> | |||
<div class="card-header" v-bind:class="{ 'card-inverse': isGuest}"> | |||
<div class="row"> | |||
<div class="col-3"> | |||
<span v-on:click="fullReload()" class="white-link pointer">LessPass</span> | |||
</div> | |||
<div class="col-9 text-right"> | |||
<div id="menu"> | |||
<div class="card-header" v-bind:class="{ 'card-inverse': isGuest}"> | |||
<div class="row"> | |||
<div class="col-3"> | |||
<span v-on:click="fullReload()" class="white-link pointer">LessPass</span> | |||
</div> | |||
<div class="col-9 text-right"> | |||
<span class="text-white" v-if="saved && isAuthenticated"> | |||
<small><i class="fa fa-lg fa-check pl-3" aria-hidden="true"></i> saved</small> | |||
</span> | |||
<span v-on:click="saveOrUpdatePassword()" class="white-link" | |||
v-if="!saved && isAuthenticated && $store.state.password.site !== ''"> | |||
<span v-on:click="saveOrUpdatePassword()" class="white-link" | |||
v-if="!saved && isAuthenticated && $store.state.password.site !== ''"> | |||
<i class="fa fa-lg fa-save pointer"></i> | |||
</span> | |||
<span class="white-link btn-copy pl-3" v-bind:data-clipboard-text="passwordURL" | |||
v-if="$store.state.password.site !== ''"> | |||
<span class="white-link btn-copy pl-3" v-bind:data-clipboard-text="passwordURL" | |||
v-if="$store.state.password.site !== ''"> | |||
<i class="fa fa-lg fa-share-alt pointer"></i> | |||
</span> | |||
<router-link class="white-link pl-3" :to="{ name: 'configureOptions'}"> | |||
<i class="fa fa-lg fa-cog" aria-hidden="true"></i> | |||
</router-link> | |||
<router-link class="white-link pl-3" :to="{ name: 'passwords'}" v-if="isAuthenticated"> | |||
<i class="fa fa-lg fa-key" aria-hidden="true"></i> | |||
</router-link> | |||
<button class="white-link btn btn-link p-0 m-0 pl-3" type="button" v-if="isAuthenticated" | |||
v-on:click="logout"> | |||
<i class="fa fa-lg fa-sign-out" aria-hidden="true"></i> | |||
</button> | |||
<router-link class="white-link pl-3" :to="{ name: 'login'}" v-if="isGuest"> | |||
<i class="fa fa-lg fa-user-secret pointer" aria-hidden="true"></i> | |||
</router-link> | |||
</div> | |||
</div> | |||
<router-link class="white-link pl-3" :to="{ name: 'configureOptions'}"> | |||
<i class="fa fa-lg fa-cog" aria-hidden="true"></i> | |||
</router-link> | |||
<router-link class="white-link pl-3" :to="{ name: 'passwords'}" v-if="isAuthenticated"> | |||
<i class="fa fa-lg fa-key" aria-hidden="true"></i> | |||
</router-link> | |||
<button class="white-link btn btn-link p-0 m-0 pl-3" type="button" v-if="isAuthenticated" | |||
v-on:click="logout"> | |||
<i class="fa fa-lg fa-sign-out" aria-hidden="true"></i> | |||
</button> | |||
<router-link class="white-link pl-3" :to="{ name: 'login'}" v-if="isGuest"> | |||
<i class="fa fa-lg fa-user-secret pointer" aria-hidden="true"></i> | |||
</router-link> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import {mapGetters} from 'vuex'; | |||
import Clipboard from 'clipboard'; | |||
import {showTooltip} from '../services/tooltip'; | |||
import {mapGetters} from 'vuex'; | |||
import Clipboard from 'clipboard'; | |||
import {showTooltip} from '../services/tooltip'; | |||
export default { | |||
data(){ | |||
return { | |||
saved: false | |||
} | |||
}, | |||
created(){ | |||
const clipboard = new Clipboard('.btn-copy'); | |||
clipboard.on('success', event => { | |||
if (event.text) { | |||
showTooltip(event.trigger, 'copied !'); | |||
} | |||
}); | |||
}, | |||
methods: { | |||
fullReload(){ | |||
this.$store.dispatch('savePassword', {password: this.defaultPassword}); | |||
this.$router.push({name: 'home'}); | |||
}, | |||
logout(){ | |||
this.$store.dispatch('logout'); | |||
this.$router.push({name: 'home'}); | |||
}, | |||
saveOrUpdatePassword(){ | |||
this.$store.dispatch('saveOrUpdatePassword'); | |||
this.saved = true; | |||
setTimeout(() => { | |||
this.saved = false; | |||
}, 3000); | |||
} | |||
}, | |||
computed: mapGetters([ | |||
'isAuthenticated', | |||
'isGuest', | |||
'password', | |||
'defaultPassword', | |||
'passwordURL' | |||
]) | |||
} | |||
</script> | |||
export default { | |||
data(){ | |||
return { | |||
saved: false | |||
} | |||
}, | |||
created(){ | |||
const clipboard = new Clipboard('.btn-copy'); | |||
clipboard.on('success', event => { | |||
if (event.text) { | |||
showTooltip(event.trigger, 'copied !'); | |||
} | |||
}); | |||
}, | |||
methods: { | |||
fullReload(){ | |||
this.$store.dispatch('savePassword', {password: this.defaultPassword}); | |||
this.$router.push({name: 'home'}); | |||
}, | |||
logout(){ | |||
this.$store.dispatch('logout'); | |||
this.$router.push({name: 'home'}); | |||
}, | |||
saveOrUpdatePassword(){ | |||
this.$store.dispatch('saveOrUpdatePassword'); | |||
this.saved = true; | |||
setTimeout(() => { | |||
this.saved = false; | |||
}, 3000); | |||
} | |||
}, | |||
computed: mapGetters([ | |||
'isAuthenticated', | |||
'isGuest', | |||
'password', | |||
'defaultPassword', | |||
'passwordURL' | |||
]) | |||
} | |||
</script> |
@@ -1,66 +1,66 @@ | |||
<style> | |||
.fade-enter-active { | |||
transition: opacity .5s | |||
} | |||
.fade-enter-active { | |||
transition: opacity .5s | |||
} | |||
.fade-leave-active { | |||
transition: opacity 2s | |||
} | |||
.fade-leave-active { | |||
transition: opacity 2s | |||
} | |||
.fade-enter, .fade-leave-to { | |||
opacity: 0 | |||
} | |||
.fade-enter, .fade-leave-to { | |||
opacity: 0 | |||
} | |||
#message { | |||
position: absolute; | |||
top: 49px; | |||
left: 0; | |||
right: 0; | |||
z-index: 20; | |||
} | |||
#message { | |||
position: absolute; | |||
top: 49px; | |||
left: 0; | |||
right: 0; | |||
z-index: 20; | |||
} | |||
.close-notification{ | |||
float: right; | |||
position: absolute; | |||
top:0; | |||
right: 1em; | |||
cursor: pointer; | |||
} | |||
.close-notification { | |||
float: right; | |||
position: absolute; | |||
top: 0; | |||
right: 1em; | |||
cursor: pointer; | |||
} | |||
</style> | |||
<template> | |||
<div id="message" v-on:click="keepMessage"> | |||
<transition name="fade"> | |||
<div v-if="message.text"> | |||
<div class="card-header text-white" | |||
v-bind:class="{ 'card-warning': message.status==='warning', 'card-danger': message.status==='error', 'card-success': message.status==='success' }"> | |||
<div class="row"> | |||
<div class="col-12"> | |||
<small>{{message.text}}</small> | |||
<span class="close-notification" v-on:click="hideMessage"> | |||
<div id="message" v-on:click="keepMessage"> | |||
<transition name="fade"> | |||
<div v-if="message.text"> | |||
<div class="card-header text-white" | |||
v-bind:class="{ 'card-warning': message.status==='warning', 'card-danger': message.status==='error', 'card-success': message.status==='success' }"> | |||
<div class="row"> | |||
<div class="col-12"> | |||
<small>{{message.text}}</small> | |||
<span class="close-notification" v-on:click="hideMessage"> | |||
<i class="fa fa-close"></i> | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</transition> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</transition> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import {mapGetters} from 'vuex'; | |||
import message from '../services/message'; | |||
import {mapGetters} from 'vuex'; | |||
import message from '../services/message'; | |||
export default { | |||
computed: mapGetters([ | |||
'message' | |||
]), | |||
methods: { | |||
keepMessage(){ | |||
message.keepMessage(); | |||
}, | |||
hideMessage(){ | |||
message.hideMessage(); | |||
} | |||
} | |||
export default { | |||
computed: mapGetters([ | |||
'message' | |||
]), | |||
methods: { | |||
keepMessage(){ | |||
message.keepMessage(); | |||
}, | |||
hideMessage(){ | |||
message.hideMessage(); | |||
} | |||
} | |||
</script> | |||
} | |||
</script> |
@@ -1,164 +1,164 @@ | |||
<style> | |||
#options input[type="number"] { | |||
-moz-appearance: textfield; | |||
} | |||
#options input[type="number"] { | |||
-moz-appearance: textfield; | |||
} | |||
#options input[type="number"]::-webkit-outer-spin-button, | |||
#options input[type="number"]::-webkit-inner-spin-button { | |||
-webkit-appearance: none; | |||
margin: 0; | |||
} | |||
#options input[type="number"]::-webkit-outer-spin-button, | |||
#options input[type="number"]::-webkit-inner-spin-button { | |||
-webkit-appearance: none; | |||
margin: 0; | |||
} | |||
</style> | |||
<template> | |||
<div id="options"> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<div class="row"> | |||
<div class="col"> | |||
<label for="types">Advanced options</label> | |||
</div> | |||
</div> | |||
<div id="types" class="row"> | |||
<div class="col-3"> | |||
<button type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':options.lowercase===true && options.version===2,'btn-warning':options.lowercase===true && options.version===1,'btn-secondary':options.lowercase===false}" | |||
v-on:click="options.lowercase=!options.lowercase"> | |||
a-z | |||
</button> | |||
</div> | |||
<div class="col-3"> | |||
<button type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':options.uppercase===true && options.version===2,'btn-warning':options.uppercase===true && options.version===1,'btn-secondary':options.uppercase===false}" | |||
v-on:click="options.uppercase=!options.uppercase"> | |||
A-Z | |||
</button> | |||
</div> | |||
<div class="col-3"> | |||
<button type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':options.numbers===true && options.version===2,'btn-warning':options.numbers===true && options.version===1,'btn-secondary':options.numbers===false}" | |||
v-on:click="options.numbers=!options.numbers"> | |||
0-9 | |||
</button> | |||
</div> | |||
<div class="col-3"> | |||
<button type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':options.symbols===true && options.version===2,'btn-warning':options.symbols===true && options.version===1,'btn-secondary':options.symbols===false}" | |||
v-on:click="options.symbols=!options.symbols"> | |||
%!@ | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
<div id="options"> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<div class="row"> | |||
<div class="col"> | |||
<label for="types">Advanced options</label> | |||
</div> | |||
</div> | |||
<div id="types" class="row"> | |||
<div class="col-3"> | |||
<button type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':options.lowercase===true && options.version===2,'btn-warning':options.lowercase===true && options.version===1,'btn-secondary':options.lowercase===false}" | |||
v-on:click="options.lowercase=!options.lowercase"> | |||
a-z | |||
</button> | |||
</div> | |||
<div class="col-3"> | |||
<button type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':options.uppercase===true && options.version===2,'btn-warning':options.uppercase===true && options.version===1,'btn-secondary':options.uppercase===false}" | |||
v-on:click="options.uppercase=!options.uppercase"> | |||
A-Z | |||
</button> | |||
</div> | |||
<div class="col-3"> | |||
<button type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':options.numbers===true && options.version===2,'btn-warning':options.numbers===true && options.version===1,'btn-secondary':options.numbers===false}" | |||
v-on:click="options.numbers=!options.numbers"> | |||
0-9 | |||
</button> | |||
</div> | |||
<div class="col-3"> | |||
<button type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':options.symbols===true && options.version===2,'btn-warning':options.symbols===true && options.version===1,'btn-secondary':options.symbols===false}" | |||
v-on:click="options.symbols=!options.symbols"> | |||
%!@ | |||
</button> | |||
</div> | |||
</div> | |||
<div class="form-group row mb-0"> | |||
<div class="col-6 col-sm-4 mb-3 mb-sm-0"> | |||
<label for="passwordLength">Length</label> | |||
<div class="input-group input-group-sm"> | |||
</div> | |||
</div> | |||
<div class="form-group row mb-0"> | |||
<div class="col-6 col-sm-4 mb-3 mb-sm-0"> | |||
<label for="passwordLength">Length</label> | |||
<div class="input-group input-group-sm"> | |||
<span class="input-group-btn" v-on:click="options.length-=1"> | |||
<button class="btn btn-secondary" type="button"><i class="fa fa-minus"></i></button> | |||
</span> | |||
<input id="passwordLength" class="form-control form-control-sm" type="number" | |||
v-model="options.length" min="5" max="35"> | |||
<span class="input-group-btn" v-on:click="options.length+=1"> | |||
<input id="passwordLength" class="form-control form-control-sm" type="number" | |||
v-model="options.length" min="5" max="35"> | |||
<span class="input-group-btn" v-on:click="options.length+=1"> | |||
<button class="btn btn-secondary" type="button"><i class="fa fa-plus"></i></button> | |||
</span> | |||
</div> | |||
</div> | |||
<div class="col-6 col-sm-4 mb-3 mb-sm-0"> | |||
<label for="passwordCounter" | |||
class="hint--top hint--medium" | |||
aria-label="Increment counter field to change generated password without changing your master password.">Counter</label> | |||
<div class="input-group input-group-sm"> | |||
</div> | |||
</div> | |||
<div class="col-6 col-sm-4 mb-3 mb-sm-0"> | |||
<label for="passwordCounter" | |||
class="hint--top hint--medium" | |||
aria-label="Increment counter field to change generated password without changing your master password.">Counter</label> | |||
<div class="input-group input-group-sm"> | |||
<span class="input-group-btn" v-on:click="options.counter-=1"> | |||
<button class="btn btn-secondary" type="button"><i class="fa fa-minus"></i></button> | |||
</span> | |||
<input id="passwordCounter" class="form-control form-control-sm" type="number" | |||
v-model="options.counter" min="1"> | |||
<span class="input-group-btn" v-on:click="options.counter+=1"> | |||
<input id="passwordCounter" class="form-control form-control-sm" type="number" | |||
v-model="options.counter" min="1"> | |||
<span class="input-group-btn" v-on:click="options.counter+=1"> | |||
<button class="btn btn-secondary" type="button"><i class="fa fa-plus"></i></button> | |||
</span> | |||
</div> | |||
</div> | |||
<div class="clearfix hidden-sm-up"></div> | |||
<div class="col-12 col-sm-4"> | |||
<div class="row hidden-sm-down"> | |||
<div class="col"> | |||
<label>Version</label> | |||
</div> | |||
</div> | |||
<div class="row no-gutters"> | |||
<div class="col-6"> | |||
<button type="button" class="btn btn-block btn-sm border-right-0" | |||
v-bind:class="{'btn-primary':options.version===2,'btn-secondary':options.version!==2}" | |||
v-on:click="setVersion(2)"> | |||
v<span class="hidden-sm-up">ersion </span>2 | |||
</button> | |||
</div> | |||
</div> | |||
<div class="clearfix hidden-sm-up"></div> | |||
<div class="col-12 col-sm-4"> | |||
<div class="row hidden-sm-down"> | |||
<div class="col"> | |||
<label>Version</label> | |||
</div> | |||
</div> | |||
<div class="row no-gutters"> | |||
<div class="col-6"> | |||
<button type="button" class="btn btn-block btn-sm border-right-0" | |||
v-bind:class="{'btn-primary':options.version===2,'btn-secondary':options.version!==2}" | |||
v-on:click="setVersion(2)"> | |||
v<span class="hidden-sm-up">ersion </span>2 | |||
</button> | |||
</div> | |||
<div class="col-6"> | |||
<button type="button" | |||
class="btn btn-block btn-sm border-left-0" | |||
v-bind:class="{'btn-warning':options.version===1,'btn-secondary':options.version!==1}" | |||
v-on:click="setVersion(1)"> | |||
v<span class="hidden-sm-up">ersion </span>1 | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="col-6"> | |||
<button type="button" | |||
class="btn btn-block btn-sm border-left-0" | |||
v-bind:class="{'btn-warning':options.version===1,'btn-secondary':options.version!==1}" | |||
v-on:click="setVersion(1)"> | |||
v<span class="hidden-sm-up">ersion </span>1 | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import Message from '../services/message'; | |||
import Message from '../services/message'; | |||
export default { | |||
name: 'options', | |||
props: { | |||
password: { | |||
type: Object, | |||
required: true | |||
} | |||
}, | |||
data() { | |||
return { | |||
options: { | |||
uppercase: this.password.uppercase, | |||
lowercase: this.password.lowercase, | |||
numbers: this.password.numbers, | |||
symbols: this.password.symbols, | |||
length: this.password.length, | |||
counter: this.password.counter, | |||
version: this.password.version | |||
} | |||
}; | |||
}, | |||
watch: { | |||
options: { | |||
handler: function(newOptions) { | |||
if (newOptions.version === 1) { | |||
const dayBeforeOnlyV2 = this.getDayBeforeOnlyV2(); | |||
const message = `Version 1 is deprecated and will be removed in ${dayBeforeOnlyV2} days. We strongly advise you to migrate your passwords to version 2.`; | |||
Message.error(message); | |||
} | |||
export default { | |||
name: 'options', | |||
props: { | |||
password: { | |||
type: Object, | |||
required: true | |||
} | |||
}, | |||
data() { | |||
return { | |||
options: { | |||
uppercase: this.password.uppercase, | |||
lowercase: this.password.lowercase, | |||
numbers: this.password.numbers, | |||
symbols: this.password.symbols, | |||
length: this.password.length, | |||
counter: this.password.counter, | |||
version: this.password.version | |||
} | |||
}; | |||
}, | |||
watch: { | |||
options: { | |||
handler: function(newOptions) { | |||
if (newOptions.version === 1) { | |||
const dayBeforeOnlyV2 = this.getDayBeforeOnlyV2(); | |||
const message = `Version 1 is deprecated and will be removed in ${dayBeforeOnlyV2} days. We strongly advise you to migrate your passwords to version 2.`; | |||
Message.error(message); | |||
} | |||
this.$emit('optionsUpdated', newOptions) | |||
}, | |||
deep: true | |||
} | |||
this.$emit('optionsUpdated', newOptions) | |||
}, | |||
methods: { | |||
setVersion(value){ | |||
this.options.length = value === 1 ? 12 : 16; | |||
this.options.version = value; | |||
this.$store.dispatch('saveVersion', {version: value}); | |||
}, | |||
getDayBeforeOnlyV2(){ | |||
const oneDay = 24 * 60 * 60 * 1000; | |||
const now = new Date(); | |||
const onlyV2DefaultDate = new Date(2017, 4, 10); | |||
return Math.round(Math.abs((now.getTime() - onlyV2DefaultDate.getTime()) / (oneDay))); | |||
}, | |||
} | |||
deep: true | |||
} | |||
}, | |||
methods: { | |||
setVersion(value){ | |||
this.options.length = value === 1 ? 12 : 16; | |||
this.options.version = value; | |||
this.$store.dispatch('saveVersion', {version: value}); | |||
}, | |||
getDayBeforeOnlyV2(){ | |||
const oneDay = 24 * 60 * 60 * 1000; | |||
const now = new Date(); | |||
const onlyV2DefaultDate = new Date(2017, 4, 10); | |||
return Math.round(Math.abs((now.getTime() - onlyV2DefaultDate.getTime()) / (oneDay))); | |||
}, | |||
} | |||
} | |||
</script> |
@@ -1,10 +1,10 @@ | |||
<template> | |||
<div style="display: none;"> | |||
<label for="username"> | |||
<input type="text" id="username" name="username" autocomplete="username"> | |||
</label> | |||
<label for="password"> | |||
<input type="password" id="password" name="password" autocomplete="current-password"> | |||
</label> | |||
</div> | |||
</template> | |||
<div style="display: none;"> | |||
<label for="username"> | |||
<input type="text" id="username" name="username" autocomplete="username"> | |||
</label> | |||
<label for="password"> | |||
<input type="password" id="password" name="password" autocomplete="current-password"> | |||
</label> | |||
</div> | |||
</template> |
@@ -1,45 +1,45 @@ | |||
'use strict'; | |||
export function getDomainName(urlStr) { | |||
if (typeof urlStr === 'undefined') { | |||
return ''; | |||
} | |||
var matchesDomainName = urlStr.match(/^(?:https?\:\/\/)([^\/?#]+)(?:[\/?#]|$)/i); | |||
return matchesDomainName && matchesDomainName[1]; | |||
if (typeof urlStr === 'undefined') { | |||
return ''; | |||
} | |||
var matchesDomainName = urlStr.match(/^(?:https?\:\/\/)([^\/?#]+)(?:[\/?#]|$)/i); | |||
return matchesDomainName && matchesDomainName[1]; | |||
} | |||
export function getSite() { | |||
return new Promise(resolve => { | |||
if (typeof chrome !== 'undefined' && typeof chrome.tabs !== 'undefined' && typeof chrome.tabs.query !== 'undefined') { | |||
chrome.tabs.query({active: true, currentWindow: true}, tabs => { | |||
const url = tabs[0].url; | |||
resolve({ | |||
site: getDomainName(url), | |||
url: url | |||
}); | |||
}); | |||
} else { | |||
resolve(''); | |||
} | |||
}); | |||
return new Promise(resolve => { | |||
if (typeof chrome !== 'undefined' && typeof chrome.tabs !== 'undefined' && typeof chrome.tabs.query !== 'undefined') { | |||
chrome.tabs.query({active: true, currentWindow: true}, tabs => { | |||
const url = tabs[0].url; | |||
resolve({ | |||
site: getDomainName(url), | |||
url: url | |||
}); | |||
}); | |||
} else { | |||
resolve(''); | |||
} | |||
}); | |||
} | |||
export function getPasswordFromUrlQuery(query) { | |||
const password = {}; | |||
['uppercase', 'lowercase', 'numbers', 'symbols'].forEach(booleanishQuery => { | |||
if (booleanishQuery in query) { | |||
password[booleanishQuery] = (query[booleanishQuery].toLowerCase() === "true" || query[booleanishQuery].toLowerCase() === "1"); | |||
} | |||
}); | |||
['site', 'login'].forEach(stringQuery => { | |||
if (stringQuery in query) { | |||
password[stringQuery] = query[stringQuery] | |||
} | |||
}); | |||
['length', 'counter', 'version'].forEach(intQuery => { | |||
if (intQuery in query) { | |||
password[intQuery] = parseInt(query[intQuery], 10) | |||
} | |||
}); | |||
return password; | |||
const password = {}; | |||
['uppercase', 'lowercase', 'numbers', 'symbols'].forEach(booleanishQuery => { | |||
if (booleanishQuery in query) { | |||
password[booleanishQuery] = (query[booleanishQuery].toLowerCase() === "true" || query[booleanishQuery].toLowerCase() === "1"); | |||
} | |||
}); | |||
['site', 'login'].forEach(stringQuery => { | |||
if (stringQuery in query) { | |||
password[stringQuery] = query[stringQuery] | |||
} | |||
}); | |||
['length', 'counter', 'version'].forEach(intQuery => { | |||
if (intQuery in query) { | |||
password[intQuery] = parseInt(query[intQuery], 10) | |||
} | |||
}); | |||
return password; | |||
} |
@@ -7,8 +7,8 @@ import router from './router'; | |||
sync(store, router); | |||
new Vue({ | |||
el: '#lesspass', | |||
store, | |||
router, | |||
render: h => h(LessPass) | |||
el: '#lesspass', | |||
store, | |||
router, | |||
render: h => h(LessPass) | |||
}); |
@@ -11,18 +11,18 @@ import Passwords from './views/Passwords.vue'; | |||
Vue.use(VueRouter); | |||
const routes = [ | |||
{path: '/', name: 'home', component: PasswordGenerator}, | |||
{path: '/login', name: 'login', component: Login}, | |||
{path: '/passwords/', name: 'passwords', component: Passwords}, | |||
{path: '/options/default/', name: 'configureOptions', component: ConfigureOptions}, | |||
{path: '/passwords/:id', name: 'password', component: PasswordGenerator}, | |||
{path: '/password/reset', name: 'passwordReset', component: PasswordReset}, | |||
{path: '/password/reset/confirm/:uid/:token', name: 'passwordResetConfirm', component: PasswordResetConfirm}, | |||
{path: '*', redirect: '/'} | |||
{path: '/', name: 'home', component: PasswordGenerator}, | |||
{path: '/login', name: 'login', component: Login}, | |||
{path: '/passwords/', name: 'passwords', component: Passwords}, | |||
{path: '/options/default/', name: 'configureOptions', component: ConfigureOptions}, | |||
{path: '/passwords/:id', name: 'password', component: PasswordGenerator}, | |||
{path: '/password/reset', name: 'passwordReset', component: PasswordReset}, | |||
{path: '/password/reset/confirm/:uid/:token', name: 'passwordResetConfirm', component: PasswordResetConfirm}, | |||
{path: '*', redirect: '/'} | |||
]; | |||
const router = new VueRouter({ | |||
routes | |||
routes | |||
}); | |||
export default router; | |||
export default router; |
@@ -1,40 +1,40 @@ | |||
import Store from '../store'; | |||
export default { | |||
timeout: 0, | |||
deleteMessage: true, | |||
success(text){ | |||
const message = {text, status: 'success'}; | |||
Store.dispatch('displayMessage', {message}); | |||
this.autoHideMessage(text); | |||
}, | |||
warning(text){ | |||
const message = {text, status: 'warning'}; | |||
Store.dispatch('displayMessage', {message}); | |||
this.autoHideMessage(text); | |||
}, | |||
error(text){ | |||
const message = {text, status: 'error'}; | |||
Store.dispatch('displayMessage', {message}); | |||
this.autoHideMessage(text); | |||
}, | |||
autoHideMessage(text){ | |||
clearTimeout(this.timeout); | |||
this.deleteMessage = true; | |||
const duration = Math.min(Math.max(text.length * 100, 3000), 8000); | |||
this.timeout = setTimeout(() => { | |||
if (this.deleteMessage) { | |||
Store.dispatch('cleanMessage'); | |||
} | |||
}, duration); | |||
}, | |||
keepMessage(){ | |||
this.deleteMessage = false; | |||
}, | |||
hideMessage(){ | |||
timeout: 0, | |||
deleteMessage: true, | |||
success(text){ | |||
const message = {text, status: 'success'}; | |||
Store.dispatch('displayMessage', {message}); | |||
this.autoHideMessage(text); | |||
}, | |||
warning(text){ | |||
const message = {text, status: 'warning'}; | |||
Store.dispatch('displayMessage', {message}); | |||
this.autoHideMessage(text); | |||
}, | |||
error(text){ | |||
const message = {text, status: 'error'}; | |||
Store.dispatch('displayMessage', {message}); | |||
this.autoHideMessage(text); | |||
}, | |||
autoHideMessage(text){ | |||
clearTimeout(this.timeout); | |||
this.deleteMessage = true; | |||
const duration = Math.min(Math.max(text.length * 100, 3000), 8000); | |||
this.timeout = setTimeout(() => { | |||
if (this.deleteMessage) { | |||
Store.dispatch('cleanMessage'); | |||
}, | |||
displayGenericError(){ | |||
this.error('Oops! Something went wrong. Retry in a few minutes.'); | |||
} | |||
} | |||
} | |||
}, duration); | |||
}, | |||
keepMessage(){ | |||
this.deleteMessage = false; | |||
}, | |||
hideMessage(){ | |||
Store.dispatch('cleanMessage'); | |||
}, | |||
displayGenericError(){ | |||
this.error('Oops! Something went wrong. Retry in a few minutes.'); | |||
} | |||
} |
@@ -1,8 +1,8 @@ | |||
export function showTooltip(elem, msg) { | |||
var classNames = elem.className; | |||
elem.setAttribute('class', classNames + ' hint--right'); | |||
elem.setAttribute('aria-label', msg); | |||
setTimeout(function () { | |||
elem.setAttribute('class', classNames); | |||
}, 2000); | |||
} | |||
var classNames = elem.className; | |||
elem.setAttribute('class', classNames + ' hint--right'); | |||
elem.setAttribute('aria-label', msg); | |||
setTimeout(function() { | |||
elem.setAttribute('class', classNames); | |||
}, 2000); | |||
} |
@@ -4,90 +4,90 @@ import User from '../api/user'; | |||
import * as types from './mutation-types' | |||
export const loadPasswordFirstTime = ({commit}) => { | |||
commit(types.LOAD_PASSWORD_FIRST_TIME); | |||
commit(types.LOAD_PASSWORD_FIRST_TIME); | |||
}; | |||
export const refreshToken = ({commit, state}) => { | |||
const token = state.token; | |||
if (token) { | |||
User.requestNewToken({token}, {baseURL: state.baseURL}) | |||
.then(newToken => commit(types.SET_TOKEN, {token: newToken})) | |||
.catch(() => commit(types.LOGOUT)); | |||
} | |||
const token = state.token; | |||
if (token) { | |||
User.requestNewToken({token}, {baseURL: state.baseURL}) | |||
.then(newToken => commit(types.SET_TOKEN, {token: newToken})) | |||
.catch(() => commit(types.LOGOUT)); | |||
} | |||
}; | |||
export const loadPasswordForSite = ({commit}, payload) => { | |||
commit(types.LOAD_PASSWORD_FOR_SITE, payload); | |||
commit(types.LOAD_PASSWORD_FOR_SITE, payload); | |||
}; | |||
export const saveDefaultPassword = ({commit}, payload) => { | |||
commit(types.SET_DEFAULT_PASSWORD, payload); | |||
commit(types.SET_DEFAULT_PASSWORD, payload); | |||
}; | |||
export const passwordGenerated = ({commit}) => { | |||
commit(types.PASSWORD_GENERATED); | |||
commit(types.PASSWORD_GENERATED); | |||
}; | |||
export const savePassword = ({commit}, payload) => { | |||
commit(types.SET_PASSWORD, payload); | |||
commit(types.SET_PASSWORD, payload); | |||
}; | |||
export const saveVersion = ({commit}, payload) => { | |||
commit(types.SET_VERSION, payload); | |||
commit(types.SET_VERSION, payload); | |||
}; | |||
export const login = ({commit}, payload) => { | |||
commit(types.SET_BASE_URL, payload); | |||
commit(types.SET_TOKEN, payload); | |||
commit(types.LOGIN); | |||
commit(types.SET_BASE_URL, payload); | |||
commit(types.SET_TOKEN, payload); | |||
commit(types.LOGIN); | |||
}; | |||
export const logout = ({commit}) => { | |||
commit(types.LOGOUT); | |||
commit(types.LOGOUT); | |||
}; | |||
export const getPasswords = ({commit, state}) => { | |||
if (state.authenticated) { | |||
Password.all(state).then(response => commit(types.SET_PASSWORDS, {passwords: response.data.results})); | |||
} | |||
if (state.authenticated) { | |||
Password.all(state).then(response => commit(types.SET_PASSWORDS, {passwords: response.data.results})); | |||
} | |||
}; | |||
export const getPassword = ({commit, state}, payload) => { | |||
if (state.authenticated) { | |||
Password.read(payload, state).then(response => commit(types.SET_PASSWORD, {password: response.data})); | |||
} | |||
if (state.authenticated) { | |||
Password.read(payload, state).then(response => commit(types.SET_PASSWORD, {password: response.data})); | |||
} | |||
}; | |||
export const saveOrUpdatePassword = ({commit, state}) => { | |||
if (state.password && typeof state.password.id === 'undefined') { | |||
const site = state.password.site; | |||
const login = state.password.login; | |||
if (site || login) { | |||
Password.create(state.password, state) | |||
.then(response => { | |||
savePassword({commit}, {password: response.data}); | |||
getPasswords({commit, state}); | |||
}) | |||
} | |||
} else { | |||
Password.update(state.password, state) | |||
.then(() => { | |||
getPasswords({commit, state}); | |||
}) | |||
if (state.password && typeof state.password.id === 'undefined') { | |||
const site = state.password.site; | |||
const login = state.password.login; | |||
if (site || login) { | |||
Password.create(state.password, state) | |||
.then(response => { | |||
savePassword({commit}, {password: response.data}); | |||
getPasswords({commit, state}); | |||
}) | |||
} | |||
} else { | |||
Password.update(state.password, state) | |||
.then(() => { | |||
getPasswords({commit, state}); | |||
}) | |||
} | |||
}; | |||
export const deletePassword = ({commit, state}, payload) => { | |||
Password.delete(payload, state) | |||
.then(() => { | |||
commit(types.DELETE_PASSWORD, payload); | |||
}); | |||
Password.delete(payload, state) | |||
.then(() => { | |||
commit(types.DELETE_PASSWORD, payload); | |||
}); | |||
}; | |||
export const displayMessage = ({commit}, payload) => { | |||
commit(types.SET_MESSAGE, payload); | |||
commit(types.SET_MESSAGE, payload); | |||
}; | |||
export const cleanMessage = ({commit}) => { | |||
commit(types.CLEAN_MESSAGE); | |||
commit(types.CLEAN_MESSAGE); | |||
}; |
@@ -13,12 +13,12 @@ export const baseURL = state => state.baseURL; | |||
export const message = state => state.message; | |||
export const version = state => { | |||
if (state.password === null || state.route.path === '/options/default') { | |||
return state.defaultPassword.version; | |||
} | |||
return state.password.version; | |||
if (state.password === null || state.route.path === '/options/default') { | |||
return state.defaultPassword.version; | |||
} | |||
return state.password.version; | |||
}; | |||
export const passwordURL = state => { | |||
return `${state.baseURL}/#/?login=${state.password.login}&site=${state.password.site}&uppercase=${state.password.uppercase}&lowercase=${state.password.lowercase}&numbers=${state.password.numbers}&symbols=${state.password.symbols}&length=${state.password.length}&counter=${state.password.counter}&version=${state.password.version}`; | |||
}; | |||
return `${state.baseURL}/#/?login=${state.password.login}&site=${state.password.site}&uppercase=${state.password.uppercase}&lowercase=${state.password.lowercase}&numbers=${state.password.numbers}&symbols=${state.password.symbols}&length=${state.password.length}&counter=${state.password.counter}&version=${state.password.version}`; | |||
}; |
@@ -8,31 +8,31 @@ import createPersistedState from 'vuex-persistedstate'; | |||
Vue.use(Vuex); | |||
const defaultPassword = { | |||
login: '', | |||
site: '', | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
login: '', | |||
site: '', | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
}; | |||
const state = { | |||
authenticated: false, | |||
password: defaultPassword, | |||
passwords: [], | |||
defaultPassword: defaultPassword, | |||
lastUse: null, | |||
token: null, | |||
baseURL: 'https://lesspass.com', | |||
authenticated: false, | |||
password: defaultPassword, | |||
passwords: [], | |||
defaultPassword: defaultPassword, | |||
lastUse: null, | |||
token: null, | |||
baseURL: 'https://lesspass.com', | |||
}; | |||
export default new Vuex.Store({ | |||
state, | |||
getters, | |||
actions, | |||
mutations, | |||
plugins: [createPersistedState({key: 'lesspass'})] | |||
}); | |||
state, | |||
getters, | |||
actions, | |||
mutations, | |||
plugins: [createPersistedState({key: 'lesspass'})] | |||
}); |
@@ -1,70 +1,70 @@ | |||
import * as types from './mutation-types'; | |||
export default { | |||
[types.LOGIN](state){ | |||
state.authenticated = true; | |||
}, | |||
[types.SET_TOKEN](state, {token}){ | |||
state.token = token; | |||
}, | |||
[types.LOGOUT](state){ | |||
state.authenticated = false; | |||
state.token = null; | |||
state.passwords = []; | |||
state.password = {...state.defaultPassword}; | |||
}, | |||
[types.SET_PASSWORD](state, {password}){ | |||
[types.LOGIN](state){ | |||
state.authenticated = true; | |||
}, | |||
[types.SET_TOKEN](state, {token}){ | |||
state.token = token; | |||
}, | |||
[types.LOGOUT](state){ | |||
state.authenticated = false; | |||
state.token = null; | |||
state.passwords = []; | |||
state.password = {...state.defaultPassword}; | |||
}, | |||
[types.SET_PASSWORD](state, {password}){ | |||
state.password = {...password}; | |||
}, | |||
[types.PASSWORD_GENERATED](state){ | |||
state.lastUse = new Date().getTime(); | |||
}, | |||
[types.SET_DEFAULT_PASSWORD](state, {password}){ | |||
state.defaultPassword = {...password}; | |||
}, | |||
[types.SET_PASSWORDS](state, {passwords}){ | |||
state.passwords = passwords; | |||
}, | |||
[types.DELETE_PASSWORD](state, {id}){ | |||
state.passwords = state.passwords.filter(password => { | |||
return password.id !== id; | |||
}); | |||
if (state.password && state.password.id === id) { | |||
state.password = Object.assign({}, state.defaultPassword); | |||
} | |||
}, | |||
[types.SET_BASE_URL](state, {baseURL}){ | |||
state.baseURL = baseURL; | |||
}, | |||
[types.SET_VERSION](state, {version}){ | |||
const length = version === 1 ? 12 : 16; | |||
state.password.version = version; | |||
state.password.length = length; | |||
}, | |||
[types.LOAD_PASSWORD_FIRST_TIME](state){ | |||
const tenMinutesAgo = new Date().getTime() - 60 * 1000; | |||
if (tenMinutesAgo > state.lastUse) { | |||
state.password = {...state.defaultPassword}; | |||
} | |||
}, | |||
[types.LOAD_PASSWORD_FOR_SITE](state, {site, url}){ | |||
state.password.site = site; | |||
const passwords = state.passwords; | |||
for (let i = 0; i < state.passwords.length; i++) { | |||
const password = passwords[i]; | |||
if (password.site.endsWith(site)) { | |||
state.password = {...password}; | |||
}, | |||
[types.PASSWORD_GENERATED](state){ | |||
state.lastUse = new Date().getTime(); | |||
}, | |||
[types.SET_DEFAULT_PASSWORD](state, {password}){ | |||
state.defaultPassword = {...password}; | |||
}, | |||
[types.SET_PASSWORDS](state, {passwords}){ | |||
state.passwords = passwords; | |||
}, | |||
[types.DELETE_PASSWORD](state, {id}){ | |||
state.passwords = state.passwords.filter(password => { | |||
return password.id !== id; | |||
}); | |||
if (state.password && state.password.id === id) { | |||
state.password = Object.assign({}, state.defaultPassword); | |||
} | |||
}, | |||
[types.SET_BASE_URL](state, {baseURL}){ | |||
state.baseURL = baseURL; | |||
}, | |||
[types.SET_VERSION](state, {version}){ | |||
const length = version === 1 ? 12 : 16; | |||
state.password.version = version; | |||
state.password.length = length; | |||
}, | |||
[types.LOAD_PASSWORD_FIRST_TIME](state){ | |||
const tenMinutesAgo = new Date().getTime() - 60 * 1000; | |||
if (tenMinutesAgo > state.lastUse) { | |||
state.password = {...state.defaultPassword}; | |||
} | |||
}, | |||
[types.LOAD_PASSWORD_FOR_SITE](state, {site, url}){ | |||
state.password.site = site; | |||
const passwords = state.passwords; | |||
for (let i = 0; i < state.passwords.length; i++) { | |||
const password = passwords[i]; | |||
if (password.site.endsWith(site)) { | |||
state.password = {...password}; | |||
} | |||
if (typeof url !== 'undefined' && url.includes(password.site)) { | |||
state.password = {...password}; | |||
break; | |||
} | |||
} | |||
}, | |||
[types.SET_MESSAGE](state, {message}){ | |||
state.message = message; | |||
}, | |||
[types.CLEAN_MESSAGE](state){ | |||
state.message = {text: '', status: 'success'}; | |||
}, | |||
} | |||
if (typeof url !== 'undefined' && url.includes(password.site)) { | |||
state.password = {...password}; | |||
break; | |||
} | |||
} | |||
}, | |||
[types.SET_MESSAGE](state, {message}){ | |||
state.message = message; | |||
}, | |||
[types.CLEAN_MESSAGE](state){ | |||
state.message = {text: '', status: 'success'}; | |||
}, | |||
}; |
@@ -1,55 +1,55 @@ | |||
<template> | |||
<div> | |||
<div class="form-group"> | |||
<label for="login">Login</label> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-user"></i> | |||
<input id="login" | |||
name="login" | |||
type="text" | |||
class="form-control" | |||
placeholder="Login" | |||
autocomplete="off" | |||
autocorrect="off" | |||
autocapitalize="none" | |||
v-model="defaultOptions.login"> | |||
</div> | |||
</div> | |||
<options v-bind:password="defaultOptions" v-on:optionsUpdated="optionsUpdated"></options> | |||
<div class="form-group pt-3"> | |||
<button type="button" class="btn btn-sm btn-block hint--top hint--medium" | |||
aria-label="We use local storage to save default options locally. Each time you open the app, those options will be loaded by default." | |||
v-bind:class="{'btn-warning':defaultOptions.version===1,'btn-primary':defaultOptions.version!==1}" | |||
v-on:click="saveOptionsAsDefault"> | |||
Save default options locally | |||
</button> | |||
</div> | |||
<div> | |||
<div class="form-group"> | |||
<label for="login">Login</label> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-user"></i> | |||
<input id="login" | |||
name="login" | |||
type="text" | |||
class="form-control" | |||
placeholder="Login" | |||
autocomplete="off" | |||
autocorrect="off" | |||
autocapitalize="none" | |||
v-model="defaultOptions.login"> | |||
</div> | |||
</div> | |||
<options v-bind:password="defaultOptions" v-on:optionsUpdated="optionsUpdated"></options> | |||
<div class="form-group pt-3"> | |||
<button type="button" class="btn btn-sm btn-block hint--top hint--medium" | |||
aria-label="We use local storage to save default options locally. Each time you open the app, those options will be loaded by default." | |||
v-bind:class="{'btn-warning':defaultOptions.version===1,'btn-primary':defaultOptions.version!==1}" | |||
v-on:click="saveOptionsAsDefault"> | |||
Save default options locally | |||
</button> | |||
</div> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import Options from '../components/Options.vue'; | |||
import Options from '../components/Options.vue'; | |||
export default { | |||
name: 'configure-options-view', | |||
components: { | |||
Options | |||
}, | |||
data(){ | |||
return { | |||
defaultOptions: {} | |||
} | |||
}, | |||
created(){ | |||
this.defaultOptions = Object.assign({}, this.$store.state.defaultPassword); | |||
}, | |||
methods: { | |||
optionsUpdated(options){ | |||
this.defaultOptions = Object.assign({}, this.defaultOptions, options); | |||
}, | |||
saveOptionsAsDefault(){ | |||
this.$store.dispatch('saveDefaultPassword', {password: this.defaultOptions}); | |||
}, | |||
} | |||
export default { | |||
name: 'configure-options-view', | |||
components: { | |||
Options | |||
}, | |||
data(){ | |||
return { | |||
defaultOptions: {} | |||
} | |||
}, | |||
created(){ | |||
this.defaultOptions = Object.assign({}, this.$store.state.defaultPassword); | |||
}, | |||
methods: { | |||
optionsUpdated(options){ | |||
this.defaultOptions = Object.assign({}, this.defaultOptions, options); | |||
}, | |||
saveOptionsAsDefault(){ | |||
this.$store.dispatch('saveDefaultPassword', {password: this.defaultOptions}); | |||
}, | |||
} | |||
} | |||
</script> |
@@ -1,163 +1,163 @@ | |||
<style> | |||
#signInButton { | |||
border-right: none; | |||
} | |||
#signInButton { | |||
border-right: none; | |||
} | |||
#registerButton { | |||
border-left: none; | |||
} | |||
#registerButton { | |||
border-left: none; | |||
} | |||
</style> | |||
<template> | |||
<form v-on:submit.prevent="signIn"> | |||
<div class="form-group"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-globe"></i> | |||
<input id="baseURL" | |||
class="form-control" | |||
type="text" | |||
placeholder="https://lesspass.com" | |||
v-model="baseURL"> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-user"></i> | |||
<input id="email" | |||
class="form-control" | |||
name="username" | |||
type="email" | |||
placeholder="Email" | |||
required | |||
v-model="email"> | |||
</div> | |||
</div> | |||
<form v-on:submit.prevent="signIn"> | |||
<div class="form-group"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-globe"></i> | |||
<input id="baseURL" | |||
class="form-control" | |||
type="text" | |||
placeholder="https://lesspass.com" | |||
v-model="baseURL"> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-user"></i> | |||
<input id="email" | |||
class="form-control" | |||
name="username" | |||
type="email" | |||
placeholder="Email" | |||
required | |||
v-model="email"> | |||
</div> | |||
<div class="form-group mb-2"> | |||
<master-password v-model="password"></master-password> | |||
<label class="custom-control custom-checkbox hint--top hint--medium mb-0" | |||
data-hint="Check me to generate encrypted password for lesspass.com"> | |||
<input type="checkbox" class="custom-control-input" v-model="transformMasterPassword"> | |||
<span class="custom-control-indicator"></span> | |||
<span class="custom-control-description text-muted"> | |||
</div> | |||
</div> | |||
<div class="form-group mb-2"> | |||
<master-password v-model="password"></master-password> | |||
<label class="custom-control custom-checkbox hint--top hint--medium mb-0" | |||
data-hint="Check me to generate encrypted password for lesspass.com"> | |||
<input type="checkbox" class="custom-control-input" v-model="transformMasterPassword"> | |||
<span class="custom-control-indicator"></span> | |||
<span class="custom-control-description text-muted"> | |||
encrypt before use | |||
</span> | |||
</label> | |||
</div> | |||
<div class="form-group row no-gutters mb-0"> | |||
<div class="col"> | |||
<button id="signInButton" class="btn btn-block" type="submit" | |||
v-bind:class="{ 'btn-warning': version===1, 'btn-primary': version===2 }"> | |||
Sign In | |||
</button> | |||
</div> | |||
<div class="col"> | |||
<button id="registerButton" class="btn btn-secondary btn-block" type="button" v-on:click="register"> | |||
Register | |||
</button> | |||
</div> | |||
</div> | |||
<div class="form-group my-0"> | |||
<router-link :to="{ name: 'passwordReset'}"> | |||
<small>Forgot your password?</small> | |||
</router-link> | |||
</div> | |||
</form> | |||
</label> | |||
</div> | |||
<div class="form-group row no-gutters mb-0"> | |||
<div class="col"> | |||
<button id="signInButton" class="btn btn-block" type="submit" | |||
v-bind:class="{ 'btn-warning': version===1, 'btn-primary': version===2 }"> | |||
Sign In | |||
</button> | |||
</div> | |||
<div class="col"> | |||
<button id="registerButton" class="btn btn-secondary btn-block" type="button" v-on:click="register"> | |||
Register | |||
</button> | |||
</div> | |||
</div> | |||
<div class="form-group my-0"> | |||
<router-link :to="{ name: 'passwordReset'}"> | |||
<small>Forgot your password?</small> | |||
</router-link> | |||
</div> | |||
</form> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import LessPass from 'lesspass'; | |||
import User from '../api/user'; | |||
import {mapGetters} from 'vuex'; | |||
import MasterPassword from '../components/MasterPassword.vue'; | |||
import message from '../services/message'; | |||
import LessPass from 'lesspass'; | |||
import User from '../api/user'; | |||
import {mapGetters} from 'vuex'; | |||
import MasterPassword from '../components/MasterPassword.vue'; | |||
import message from '../services/message'; | |||
export default { | |||
data() { | |||
return { | |||
email: '', | |||
password: '', | |||
baseURL: 'https://lesspass.com', | |||
transformMasterPassword: false, | |||
}; | |||
}, | |||
components: { | |||
MasterPassword | |||
}, | |||
computed: { | |||
...mapGetters(['version']) | |||
}, | |||
watch: { | |||
password: function() { | |||
this.transformMasterPassword = false; | |||
}, | |||
transformMasterPassword: function(transformPassword) { | |||
if (!transformPassword) { | |||
return; | |||
} | |||
const defaultPasswordProfile = { | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2, | |||
}; | |||
return LessPass.generatePassword('lesspass.com', this.email, this.password, defaultPasswordProfile).then(generatedPassword => { | |||
this.password = generatedPassword; | |||
}); | |||
} | |||
}, | |||
methods: { | |||
formIsValid(){ | |||
if (!this.email || !this.password || !this.baseURL) { | |||
message.error('LessPass URL, email and password are mandatory'); | |||
return false; | |||
} | |||
return true; | |||
}, | |||
signIn(){ | |||
if (this.formIsValid()) { | |||
const baseURL = this.baseURL; | |||
User.login({email: this.email, password: this.password}, {baseURL}) | |||
.then(response => { | |||
this.$store.dispatch('login', {token: response.token, baseURL}); | |||
this.$router.push({name: 'home'}); | |||
}) | |||
.catch(err => { | |||
if (err.response === undefined && baseURL !== "https://lesspass.com") { | |||
message.error('Your LessPass Database is not running'); | |||
} else if (err.response.status === 400) { | |||
message.error('The email and password you entered did not match our records. Please double-check and try again.'); | |||
} else { | |||
message.displayGenericError(); | |||
} | |||
}); | |||
export default { | |||
data() { | |||
return { | |||
email: '', | |||
password: '', | |||
baseURL: 'https://lesspass.com', | |||
transformMasterPassword: false, | |||
}; | |||
}, | |||
components: { | |||
MasterPassword | |||
}, | |||
computed: { | |||
...mapGetters(['version']) | |||
}, | |||
watch: { | |||
password: function() { | |||
this.transformMasterPassword = false; | |||
}, | |||
transformMasterPassword: function(transformPassword) { | |||
if (!transformPassword) { | |||
return; | |||
} | |||
const defaultPasswordProfile = { | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2, | |||
}; | |||
return LessPass.generatePassword('lesspass.com', this.email, this.password, defaultPasswordProfile).then(generatedPassword => { | |||
this.password = generatedPassword; | |||
}); | |||
} | |||
}, | |||
methods: { | |||
formIsValid(){ | |||
if (!this.email || !this.password || !this.baseURL) { | |||
message.error('LessPass URL, email and password are mandatory'); | |||
return false; | |||
} | |||
return true; | |||
}, | |||
signIn(){ | |||
if (this.formIsValid()) { | |||
const baseURL = this.baseURL; | |||
User.login({email: this.email, password: this.password}, {baseURL}) | |||
.then(response => { | |||
this.$store.dispatch('login', {token: response.token, baseURL}); | |||
this.$router.push({name: 'home'}); | |||
}) | |||
.catch(err => { | |||
if (err.response === undefined && baseURL !== "https://lesspass.com") { | |||
message.error('Your LessPass Database is not running'); | |||
} else if (err.response.status === 400) { | |||
message.error('The email and password you entered did not match our records. Please double-check and try again.'); | |||
} else { | |||
message.displayGenericError(); | |||
} | |||
}); | |||
} | |||
}, | |||
register(){ | |||
if (this.formIsValid()) { | |||
const baseURL = this.baseURL; | |||
User.register({email: this.email, password: this.password}, {baseURL}) | |||
.then(() => { | |||
message.success(`Welcome ${this.email}, thank you for signing up.`); | |||
this.signIn(); | |||
}) | |||
.catch(err => { | |||
if (err.response && typeof err.response.data.email !== 'undefined') { | |||
if (err.response.data.email[0].indexOf('already exists') !== -1) { | |||
message.error('This email is already registered. Want to login or recover your password?'); | |||
} | |||
}, | |||
register(){ | |||
if (this.formIsValid()) { | |||
const baseURL = this.baseURL; | |||
User.register({email: this.email, password: this.password}, {baseURL}) | |||
.then(() => { | |||
message.success(`Welcome ${this.email}, thank you for signing up.`); | |||
this.signIn(); | |||
}) | |||
.catch(err => { | |||
if (err.response && typeof err.response.data.email !== 'undefined') { | |||
if (err.response.data.email[0].indexOf('already exists') !== -1) { | |||
message.error('This email is already registered. Want to login or recover your password?'); | |||
} | |||
if (err.response.data.email[0].indexOf('valid email') !== -1) { | |||
message.error('Please enter a valid email'); | |||
} | |||
} else { | |||
message.displayGenericError(); | |||
} | |||
}); | |||
if (err.response.data.email[0].indexOf('valid email') !== -1) { | |||
message.error('Please enter a valid email'); | |||
} | |||
} | |||
} else { | |||
message.displayGenericError(); | |||
} | |||
}); | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
@@ -1,93 +1,93 @@ | |||
<style> | |||
#generated-password { | |||
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif; | |||
} | |||
#generated-password { | |||
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif; | |||
} | |||
.inner-addon i { | |||
position: absolute; | |||
padding: 10px; | |||
pointer-events: none; | |||
z-index: 10; | |||
} | |||
.inner-addon i { | |||
position: absolute; | |||
padding: 10px; | |||
pointer-events: none; | |||
z-index: 10; | |||
} | |||
.inner-addon { | |||
position: relative; | |||
} | |||
.inner-addon { | |||
position: relative; | |||
} | |||
.left-addon i { | |||
left: 0; | |||
} | |||
.left-addon i { | |||
left: 0; | |||
} | |||
.right-addon i { | |||
right: 0; | |||
} | |||
.right-addon i { | |||
right: 0; | |||
} | |||
.left-addon input { | |||
padding-left: 30px; | |||
} | |||
.left-addon input { | |||
padding-left: 30px; | |||
} | |||
.right-addon input { | |||
padding-right: 30px; | |||
} | |||
.right-addon input { | |||
padding-right: 30px; | |||
} | |||
div.awesomplete { | |||
display: block; | |||
} | |||
div.awesomplete { | |||
display: block; | |||
} | |||
div.awesomplete > ul { | |||
z-index: 11; | |||
} | |||
div.awesomplete > ul { | |||
z-index: 11; | |||
} | |||
</style> | |||
<template> | |||
<form id="password-generator"> | |||
<div class="form-group"> | |||
<div class="inner-addon left-addon"> | |||
<label for="site" class="sr-only">Site</label> | |||
<i class="fa fa-globe"></i> | |||
<input id="site" | |||
name="site" | |||
type="text" | |||
ref="site" | |||
class="form-control awesomplete" | |||
placeholder="Site" | |||
autocorrect="off" | |||
autocapitalize="none" | |||
v-model="password.site"> | |||
</div> | |||
<form id="password-generator"> | |||
<div class="form-group"> | |||
<div class="inner-addon left-addon"> | |||
<label for="site" class="sr-only">Site</label> | |||
<i class="fa fa-globe"></i> | |||
<input id="site" | |||
name="site" | |||
type="text" | |||
ref="site" | |||
class="form-control awesomplete" | |||
placeholder="Site" | |||
autocorrect="off" | |||
autocapitalize="none" | |||
v-model="password.site"> | |||
</div> | |||
</div> | |||
<remove-auto-complete></remove-auto-complete> | |||
<div class="form-group"> | |||
<div class="inner-addon left-addon"> | |||
<label for="login" class="sr-only">Login</label> | |||
<i class="fa fa-user"></i> | |||
<input id="login" | |||
name="login" | |||
type="text" | |||
ref="login" | |||
class="form-control" | |||
placeholder="Login" | |||
autocomplete="off" | |||
autocorrect="off" | |||
autocapitalize="none" | |||
v-model="password.login"> | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<master-password ref="masterPassword" v-model="masterPassword" | |||
:keyupEnter="generatePassword"></master-password> | |||
</div> | |||
<div class="form-group row justify-content-between no-gutters" v-bind:class="{'mb-0':showOptions===false}"> | |||
<div class="col col-auto" v-show="!generatedPassword"> | |||
<div style="display: inline-block"> | |||
<button type="button" class="btn" v-on:click="generatePassword" | |||
v-bind:class="{ 'btn-warning': password.version===1, 'btn-primary': password.version===2 }"> | |||
<span v-if="!generatingPassword">Generate</span> | |||
<span v-if="generatingPassword">Generating...</span> | |||
</button> | |||
</div> | |||
<remove-auto-complete></remove-auto-complete> | |||
<div class="form-group"> | |||
<div class="inner-addon left-addon"> | |||
<label for="login" class="sr-only">Login</label> | |||
<i class="fa fa-user"></i> | |||
<input id="login" | |||
name="login" | |||
type="text" | |||
ref="login" | |||
class="form-control" | |||
placeholder="Login" | |||
autocomplete="off" | |||
autocorrect="off" | |||
autocapitalize="none" | |||
v-model="password.login"> | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<master-password ref="masterPassword" v-model="masterPassword" | |||
:keyupEnter="generatePassword"></master-password> | |||
</div> | |||
<div class="form-group row justify-content-between no-gutters" v-bind:class="{'mb-0':showOptions===false}"> | |||
<div class="col col-auto" v-show="!generatedPassword"> | |||
<div style="display: inline-block"> | |||
<button type="button" class="btn" v-on:click="generatePassword" | |||
v-bind:class="{ 'btn-warning': password.version===1, 'btn-primary': password.version===2 }"> | |||
<span v-if="!generatingPassword">Generate</span> | |||
<span v-if="generatingPassword">Generating...</span> | |||
</button> | |||
</div> | |||
</div> | |||
<div class="col-9" v-show="generatedPassword"> | |||
<div class="input-group"> | |||
</div> | |||
<div class="col-9" v-show="generatedPassword"> | |||
<div class="input-group"> | |||
<span class="input-group-btn"> | |||
<button id="copyPasswordButton" type="button" data-clipboard-text="" class="btn btn-copy" | |||
ref="copyPasswordButton" | |||
@@ -95,191 +95,191 @@ | |||
<i class="fa fa-clipboard" aria-hidden="true"></i> | |||
</button> | |||
</span> | |||
<input type="password" id="generated-password" class="form-control" tabindex="-1" | |||
ref="generatedPassword" v-bind:value="generatedPassword" | |||
v-bind:class="{ 'btn-outline-warning': password.version===1, 'btn-outline-primary': password.version===2 }"> | |||
<span class="input-group-btn"> | |||
<input type="password" id="generated-password" class="form-control" tabindex="-1" | |||
ref="generatedPassword" v-bind:value="generatedPassword" | |||
v-bind:class="{ 'btn-outline-warning': password.version===1, 'btn-outline-primary': password.version===2 }"> | |||
<span class="input-group-btn"> | |||
<button id="revealGeneratedPassword" type="button" class="btn" | |||
v-on:click="togglePasswordType($refs.generatedPassword)" | |||
v-bind:class="{ 'btn-outline-warning': password.version===1, 'btn-outline-primary': password.version===2 }"> | |||
<i class="fa fa-eye" aria-hidden="true"></i> | |||
</button> | |||
</span> | |||
</div> | |||
</div> | |||
<div class="col col-auto"> | |||
<button type="button" class="btn btn-secondary" v-on:click="showOptions=!showOptions"> | |||
<i class="fa fa-sliders" aria-hidden="true"></i> | |||
</button> | |||
</div> | |||
</div> | |||
<options :password="password" v-on:optionsUpdated="optionsUpdated" v-if="showOptions"></options> | |||
</form> | |||
</div> | |||
<div class="col col-auto"> | |||
<button type="button" class="btn btn-secondary" v-on:click="showOptions=!showOptions"> | |||
<i class="fa fa-sliders" aria-hidden="true"></i> | |||
</button> | |||
</div> | |||
</div> | |||
<options :password="password" v-on:optionsUpdated="optionsUpdated" v-if="showOptions"></options> | |||
</form> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import LessPass from 'lesspass'; | |||
import {mapGetters} from 'vuex'; | |||
import Clipboard from 'clipboard'; | |||
import {getSite, getPasswordFromUrlQuery} from '../domain/url-parser'; | |||
import RemoveAutoComplete from '../components/RemoveAutoComplete.vue'; | |||
import MasterPassword from '../components/MasterPassword.vue'; | |||
import Options from '../components/Options.vue'; | |||
import {showTooltip} from '../services/tooltip'; | |||
import message from '../services/message'; | |||
import Awesomplete from 'awesomplete'; | |||
import LessPass from 'lesspass'; | |||
import {mapGetters} from 'vuex'; | |||
import Clipboard from 'clipboard'; | |||
import {getSite, getPasswordFromUrlQuery} from '../domain/url-parser'; | |||
import RemoveAutoComplete from '../components/RemoveAutoComplete.vue'; | |||
import MasterPassword from '../components/MasterPassword.vue'; | |||
import Options from '../components/Options.vue'; | |||
import {showTooltip} from '../services/tooltip'; | |||
import message from '../services/message'; | |||
import Awesomplete from 'awesomplete'; | |||
function fetchPasswords(store) { | |||
return store.dispatch('getPasswords') | |||
} | |||
function fetchPasswords(store) { | |||
return store.dispatch('getPasswords') | |||
} | |||
export default { | |||
name: 'password-generator-view', | |||
components: { | |||
RemoveAutoComplete, | |||
MasterPassword, | |||
Options | |||
}, | |||
computed: mapGetters(['passwords', 'password', 'passwordURL']), | |||
preFetch: fetchPasswords, | |||
beforeMount () { | |||
const query = this.$route.query; | |||
if (Object.keys(query).length >= 9) { | |||
this.$store.dispatch('savePassword', {password: getPasswordFromUrlQuery(query)}); | |||
} | |||
export default { | |||
name: 'password-generator-view', | |||
components: { | |||
RemoveAutoComplete, | |||
MasterPassword, | |||
Options | |||
}, | |||
computed: mapGetters(['passwords', 'password', 'passwordURL']), | |||
preFetch: fetchPasswords, | |||
beforeMount () { | |||
const query = this.$route.query; | |||
if (Object.keys(query).length >= 9) { | |||
this.$store.dispatch('savePassword', {password: getPasswordFromUrlQuery(query)}); | |||
} | |||
const id = this.$route.params.id; | |||
if (id) { | |||
this.$store.dispatch('getPassword', {id}); | |||
} else { | |||
fetchPasswords(this.$store); | |||
} | |||
const id = this.$route.params.id; | |||
if (id) { | |||
this.$store.dispatch('getPassword', {id}); | |||
} else { | |||
fetchPasswords(this.$store); | |||
} | |||
getSite().then(site => { | |||
if (site) { | |||
this.$store.dispatch('loadPasswordForSite', site); | |||
} | |||
}); | |||
getSite().then(site => { | |||
if (site) { | |||
this.$store.dispatch('loadPasswordForSite', site); | |||
} | |||
}); | |||
const clipboard = new Clipboard('.btn-copy'); | |||
clipboard.on('success', event => { | |||
if (event.text) { | |||
showTooltip(event.trigger, 'copied !'); | |||
setTimeout(() => { | |||
this.cleanFormInSeconds(10); | |||
}, 2000); | |||
} | |||
}); | |||
}, | |||
mounted(){ | |||
setTimeout(() => { | |||
this.focusBestInputField(); | |||
}, 500); | |||
}, | |||
data(){ | |||
return { | |||
masterPassword: '', | |||
fingerprint: '', | |||
generatedPassword: '', | |||
cleanTimeout: null, | |||
showOptions: false, | |||
generatingPassword: false | |||
} | |||
}, | |||
watch: { | |||
'passwords': function (passwords) { | |||
var site = this.$refs.site; | |||
const self = this; | |||
if (site !== null && passwords.length > 0) { | |||
new Awesomplete(site, { | |||
list: passwords.map(password => { | |||
return {label: password.site + ' ' + password.login, value: password} | |||
}), | |||
replace: function (password) { | |||
self.$store.dispatch('savePassword', {password: password.value}); | |||
this.input.value = password.value.site; | |||
self.focusBestInputField(); | |||
} | |||
}); | |||
} | |||
}, | |||
'password.site': function () { | |||
this.cleanErrors(); | |||
}, | |||
'password.login': function () { | |||
this.cleanErrors(); | |||
}, | |||
'generatedPassword': function () { | |||
this.cleanFormInSeconds(30); | |||
}, | |||
'masterPassword': function () { | |||
this.cleanErrors(); | |||
this.cleanFormInSeconds(30); | |||
const clipboard = new Clipboard('.btn-copy'); | |||
clipboard.on('success', event => { | |||
if (event.text) { | |||
showTooltip(event.trigger, 'copied !'); | |||
setTimeout(() => { | |||
this.cleanFormInSeconds(10); | |||
}, 2000); | |||
} | |||
}); | |||
}, | |||
mounted(){ | |||
setTimeout(() => { | |||
this.focusBestInputField(); | |||
}, 500); | |||
}, | |||
data(){ | |||
return { | |||
masterPassword: '', | |||
fingerprint: '', | |||
generatedPassword: '', | |||
cleanTimeout: null, | |||
showOptions: false, | |||
generatingPassword: false | |||
} | |||
}, | |||
watch: { | |||
'passwords': function(passwords) { | |||
var site = this.$refs.site; | |||
const self = this; | |||
if (site !== null && passwords.length > 0) { | |||
new Awesomplete(site, { | |||
list: passwords.map(password => { | |||
return {label: password.site + ' ' + password.login, value: password} | |||
}), | |||
replace: function(password) { | |||
self.$store.dispatch('savePassword', {password: password.value}); | |||
this.input.value = password.value.site; | |||
self.focusBestInputField(); | |||
} | |||
}, | |||
methods: { | |||
togglePasswordType(element){ | |||
if (element.type === 'password') { | |||
element.type = 'text'; | |||
} else { | |||
element.type = 'password'; | |||
} | |||
}, | |||
cleanErrors(){ | |||
clearTimeout(this.cleanTimeout); | |||
this.generatedPassword = ''; | |||
}, | |||
cleanFormInSeconds(seconds){ | |||
clearTimeout(this.cleanTimeout); | |||
this.cleanTimeout = setTimeout(() => { | |||
this.masterPassword = ''; | |||
this.generatedPassword = ''; | |||
this.fingerprint = ''; | |||
}, 1000 * seconds); | |||
}, | |||
generatePassword(){ | |||
const site = this.password.site; | |||
const login = this.password.login; | |||
const masterPassword = this.masterPassword; | |||
}); | |||
} | |||
}, | |||
'password.site': function() { | |||
this.cleanErrors(); | |||
}, | |||
'password.login': function() { | |||
this.cleanErrors(); | |||
}, | |||
'generatedPassword': function() { | |||
this.cleanFormInSeconds(30); | |||
}, | |||
'masterPassword': function() { | |||
this.cleanErrors(); | |||
this.cleanFormInSeconds(30); | |||
} | |||
}, | |||
methods: { | |||
togglePasswordType(element){ | |||
if (element.type === 'password') { | |||
element.type = 'text'; | |||
} else { | |||
element.type = 'password'; | |||
} | |||
}, | |||
cleanErrors(){ | |||
clearTimeout(this.cleanTimeout); | |||
this.generatedPassword = ''; | |||
}, | |||
cleanFormInSeconds(seconds){ | |||
clearTimeout(this.cleanTimeout); | |||
this.cleanTimeout = setTimeout(() => { | |||
this.masterPassword = ''; | |||
this.generatedPassword = ''; | |||
this.fingerprint = ''; | |||
}, 1000 * seconds); | |||
}, | |||
generatePassword(){ | |||
const site = this.password.site; | |||
const login = this.password.login; | |||
const masterPassword = this.masterPassword; | |||
if (!site && !login || !masterPassword) { | |||
this.showOptions = false; | |||
message.error('Site, login and master password fields are mandatory.'); | |||
return; | |||
} | |||
if (!site && !login || !masterPassword) { | |||
this.showOptions = false; | |||
message.error('Site, login and master password fields are mandatory.'); | |||
return; | |||
} | |||
this.generatingPassword = true; | |||
this.cleanErrors(); | |||
this.fingerprint = this.masterPassword; | |||
this.generatingPassword = true; | |||
this.cleanErrors(); | |||
this.fingerprint = this.masterPassword; | |||
const passwordProfile = { | |||
lowercase: this.password.lowercase, | |||
uppercase: this.password.uppercase, | |||
numbers: this.password.numbers, | |||
symbols: this.password.symbols, | |||
length: this.password.length, | |||
counter: this.password.counter, | |||
version: this.password.version, | |||
}; | |||
return LessPass.generatePassword(site, login, masterPassword, passwordProfile).then(generatedPassword => { | |||
this.generatingPassword = false; | |||
this.generatedPassword = generatedPassword; | |||
this.$store.dispatch('savePassword', {password: this.password}); | |||
this.$store.dispatch('passwordGenerated'); | |||
window.document.getElementById('copyPasswordButton').setAttribute('data-clipboard-text', generatedPassword); | |||
}); | |||
}, | |||
optionsUpdated(options){ | |||
this.cleanErrors(); | |||
const password = Object.assign({}, this.password, options); | |||
this.$store.dispatch('savePassword', {password}); | |||
}, | |||
focusBestInputField(){ | |||
const site = this.$refs.site; | |||
const login = this.$refs.login; | |||
const masterPassword = this.$refs.masterPassword.$refs.password; | |||
site.value ? (login.value ? masterPassword.focus() : login.focus()) : site.focus(); | |||
} | |||
} | |||
const passwordProfile = { | |||
lowercase: this.password.lowercase, | |||
uppercase: this.password.uppercase, | |||
numbers: this.password.numbers, | |||
symbols: this.password.symbols, | |||
length: this.password.length, | |||
counter: this.password.counter, | |||
version: this.password.version, | |||
}; | |||
return LessPass.generatePassword(site, login, masterPassword, passwordProfile).then(generatedPassword => { | |||
this.generatingPassword = false; | |||
this.generatedPassword = generatedPassword; | |||
this.$store.dispatch('savePassword', {password: this.password}); | |||
this.$store.dispatch('passwordGenerated'); | |||
window.document.getElementById('copyPasswordButton').setAttribute('data-clipboard-text', generatedPassword); | |||
}); | |||
}, | |||
optionsUpdated(options){ | |||
this.cleanErrors(); | |||
const password = Object.assign({}, this.password, options); | |||
this.$store.dispatch('savePassword', {password}); | |||
}, | |||
focusBestInputField(){ | |||
const site = this.$refs.site; | |||
const login = this.$refs.login; | |||
const masterPassword = this.$refs.masterPassword.$refs.password; | |||
site.value ? (login.value ? masterPassword.focus() : login.focus()) : site.focus(); | |||
} | |||
} | |||
} | |||
</script> |
@@ -1,57 +1,57 @@ | |||
<template> | |||
<form v-on:submit.prevent="resetPassword"> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-user"></i> | |||
<input id="email" | |||
class="form-control" | |||
name="email" | |||
type="email" | |||
placeholder="Email" | |||
v-model="email"> | |||
</div> | |||
</div> | |||
<form v-on:submit.prevent="resetPassword"> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-user"></i> | |||
<input id="email" | |||
class="form-control" | |||
name="email" | |||
type="email" | |||
placeholder="Email" | |||
v-model="email"> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<button id="loginButton" class="btn" type="submit" | |||
v-bind:class="{ 'btn-warning': version===1, 'btn-primary': version===2 }"> | |||
Send me a reset link | |||
</button> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<button id="loginButton" class="btn" type="submit" | |||
v-bind:class="{ 'btn-warning': version===1, 'btn-primary': version===2 }"> | |||
Send me a reset link | |||
</button> | |||
</div> | |||
</div> | |||
</form> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import User from '../api/user'; | |||
import {mapActions, mapGetters} from 'vuex'; | |||
import message from '../services/message'; | |||
import User from '../api/user'; | |||
import {mapActions, mapGetters} from 'vuex'; | |||
import message from '../services/message'; | |||
export default { | |||
data() { | |||
return { | |||
email: '', | |||
}; | |||
}, | |||
computed: { | |||
...mapGetters(['version', 'baseURL']) | |||
}, | |||
methods: { | |||
resetPassword(){ | |||
if (!this.email) { | |||
message.error(`We need en email to find your account.`); | |||
return; | |||
} | |||
User.resetPassword({email: this.email}, {baseURL: this.baseURL}) | |||
.then(() => { | |||
message.success(`If the email address ${this.email} is associated with a LessPass account, you will shortly receive an email from LessPass with instructions on how to reset your password.`) | |||
}) | |||
.catch(() => { | |||
message.displayGenericError(); | |||
}); | |||
} | |||
export default { | |||
data() { | |||
return { | |||
email: '', | |||
}; | |||
}, | |||
computed: { | |||
...mapGetters(['version', 'baseURL']) | |||
}, | |||
methods: { | |||
resetPassword(){ | |||
if (!this.email) { | |||
message.error(`We need en email to find your account.`); | |||
return; | |||
} | |||
User.resetPassword({email: this.email}, {baseURL: this.baseURL}) | |||
.then(() => { | |||
message.success(`If the email address ${this.email} is associated with a LessPass account, you will shortly receive an email from LessPass with instructions on how to reset your password.`) | |||
}) | |||
.catch(() => { | |||
message.displayGenericError(); | |||
}); | |||
} | |||
} | |||
} | |||
</script> | |||
@@ -1,89 +1,89 @@ | |||
<template> | |||
<form v-on:submit.prevent="resetPasswordConfirm"> | |||
<div class="form-group row" v-if="showError"> | |||
<div class="col-12 text-muted text-danger"> | |||
{{errorMessage}} | |||
</div> | |||
<form v-on:submit.prevent="resetPasswordConfirm"> | |||
<div class="form-group row" v-if="showError"> | |||
<div class="col-12 text-muted text-danger"> | |||
{{errorMessage}} | |||
</div> | |||
</div> | |||
<div class="form-group row" v-if="successMessage"> | |||
<div class="col-12 text-muted text-success"> | |||
You're password was reset successfully. | |||
<router-link :to="{ name: 'login'}">Do you want to login ?</router-link> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-lock"></i> | |||
<input id="new-password" | |||
class="form-control" | |||
name="new-password" | |||
type="password" | |||
autocomplete="new-password" | |||
placeholder="New Password" | |||
v-model="new_password"> | |||
<small class="form-text text-muted text-danger"> | |||
<span v-if="passwordRequired">A password is required</span> | |||
</small> | |||
</div> | |||
<div class="form-group row" v-if="successMessage"> | |||
<div class="col-12 text-muted text-success"> | |||
You're password was reset successfully. | |||
<router-link :to="{ name: 'login'}">Do you want to login ?</router-link> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-lock"></i> | |||
<input id="new-password" | |||
class="form-control" | |||
name="new-password" | |||
type="password" | |||
autocomplete="new-password" | |||
placeholder="New Password" | |||
v-model="new_password"> | |||
<small class="form-text text-muted text-danger"> | |||
<span v-if="passwordRequired">A password is required</span> | |||
</small> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<button id="loginButton" class="btn" type="submit" | |||
v-bind:class="{ 'btn-warning': version===1, 'btn-primary': version===2 }"> | |||
Reset my password | |||
</button> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-12"> | |||
<button id="loginButton" class="btn" type="submit" | |||
v-bind:class="{ 'btn-warning': version===1, 'btn-primary': version===2 }"> | |||
Reset my password | |||
</button> | |||
</div> | |||
</div> | |||
</form> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import User from '../api/user'; | |||
import {mapActions, mapGetters} from 'vuex'; | |||
import User from '../api/user'; | |||
import {mapActions, mapGetters} from 'vuex'; | |||
export default { | |||
data() { | |||
return { | |||
new_password: '', | |||
passwordRequired: false, | |||
showError: false, | |||
successMessage: false, | |||
errorMessage: 'Oops! Something went wrong. Retry in a few minutes.' | |||
}; | |||
}, | |||
methods: { | |||
cleanErrors(){ | |||
this.passwordRequired = false; | |||
this.showError = false; | |||
this.successMessage = false; | |||
}, | |||
noErrors(){ | |||
return !(this.passwordRequired || this.showError); | |||
}, | |||
resetPasswordConfirm(){ | |||
this.cleanErrors(); | |||
if (!this.new_password) { | |||
this.passwordRequired = true; | |||
return; | |||
} | |||
const resetPassword = { | |||
uid: this.$route.params.uid, token: this.$route.params.token, new_password: this.new_password | |||
}; | |||
User.confirmResetPassword(resetPassword) | |||
.then(() => { | |||
this.successMessage = true | |||
}) | |||
.catch(err => { | |||
if (err.response.status === 400) { | |||
this.errorMessage = 'This password reset link become invalid.' | |||
} | |||
this.showError = true; | |||
}); | |||
export default { | |||
data() { | |||
return { | |||
new_password: '', | |||
passwordRequired: false, | |||
showError: false, | |||
successMessage: false, | |||
errorMessage: 'Oops! Something went wrong. Retry in a few minutes.' | |||
}; | |||
}, | |||
methods: { | |||
cleanErrors(){ | |||
this.passwordRequired = false; | |||
this.showError = false; | |||
this.successMessage = false; | |||
}, | |||
noErrors(){ | |||
return !(this.passwordRequired || this.showError); | |||
}, | |||
resetPasswordConfirm(){ | |||
this.cleanErrors(); | |||
if (!this.new_password) { | |||
this.passwordRequired = true; | |||
return; | |||
} | |||
const resetPassword = { | |||
uid: this.$route.params.uid, token: this.$route.params.token, new_password: this.new_password | |||
}; | |||
User.confirmResetPassword(resetPassword) | |||
.then(() => { | |||
this.successMessage = true | |||
}) | |||
.catch(err => { | |||
if (err.response.status === 400) { | |||
this.errorMessage = 'This password reset link become invalid.' | |||
} | |||
}, | |||
computed: { | |||
...mapGetters(['version']) | |||
}, | |||
} | |||
</script> | |||
this.showError = true; | |||
}); | |||
} | |||
}, | |||
computed: { | |||
...mapGetters(['version']) | |||
}, | |||
} | |||
</script> |
@@ -1,123 +1,121 @@ | |||
<style> | |||
.fa-none { | |||
display: none | |||
} | |||
.fa-none { | |||
display: none | |||
} | |||
#passwordsList { | |||
min-height: 320px; | |||
} | |||
#passwordsList { | |||
min-height: 320px; | |||
} | |||
</style> | |||
<template> | |||
<div id="passwords"> | |||
<div v-if="passwords.length === 0"> | |||
<div class="row"> | |||
<div class="col"> | |||
You don't have any password profile saved in your database. | |||
<router-link :to="{ name: 'home'}">Would you like to create one?</router-link> | |||
</div> | |||
</div> | |||
<div id="passwords"> | |||
<div v-if="passwords.length === 0"> | |||
<div class="row"> | |||
<div class="col"> | |||
You don't have any password profile saved in your database. | |||
<router-link :to="{ name: 'home'}">Would you like to create one?</router-link> | |||
</div> | |||
<div v-else> | |||
<div class="row pb-2"> | |||
<div class="col"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-search"></i> | |||
<input class="form-control" name="search" placeholder="Search" v-model="searchQuery"> | |||
</div> | |||
</div> | |||
</div> | |||
<div v-if="filteredPasswords.length === 0"> | |||
<div class="row"> | |||
<div class="col"> | |||
Oops! There are no matches for "{{searchQuery}}". Please try broadening your search. | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div v-else> | |||
<div class="row pb-2"> | |||
<div class="col"> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-search"></i> | |||
<input class="form-control" name="search" placeholder="Search" v-model="searchQuery"> | |||
</div> | |||
</div> | |||
</div> | |||
<div v-if="filteredPasswords.length === 0"> | |||
<div class="row"> | |||
<div class="col"> | |||
Oops! There are no matches for "{{searchQuery}}". Please try broadening your search. | |||
</div> | |||
</div> | |||
</div> | |||
<div v-else> | |||
<div id="passwordsList"> | |||
<div class="row py-2" v-for="password in filteredPasswords"> | |||
<div class="col-6"> | |||
<router-link :to="{ name: 'password', params: { id: password.id }}"> | |||
{{password.site}} | |||
</router-link> | |||
<br> | |||
{{password.login}} | |||
</div> | |||
<div v-else> | |||
<div id="passwordsList"> | |||
<div class="row py-2" v-for="password in filteredPasswords"> | |||
<div class="col-6"> | |||
<router-link :to="{ name: 'password', params: { id: password.id }}"> | |||
{{password.site}} | |||
</router-link> | |||
<br> | |||
{{password.login}} | |||
</div> | |||
<div class="col-6"> | |||
<delete-button class="float-right mt-2" | |||
confirmText="Are you sure you want to delete this password profile?" | |||
confirmButton="Sure" | |||
cancelButton="Oups no!" | |||
v-on:remove="deletePassword(password)"> | |||
</delete-button> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="row mt-2" v-if="pagination.number_of_pages > 1"> | |||
<div class="col-4"> | |||
<i class="fa fa-arrow-left pointer" | |||
v-on:click="pagination.current_page -= 1" | |||
v-bind:class="{'fa-none':pagination.current_page === 1}"></i> | |||
</div> | |||
<div class="col-4 text-center"> | |||
{{pagination.current_page}} / {{Math.ceil(passwords.length/pagination.per_page)}} | |||
</div> | |||
<div class="col-4 text-right"> | |||
<i class="fa fa-arrow-right pointer" | |||
v-on:click="pagination.current_page += 1" | |||
v-bind:class="{'fa-none':pagination.current_page === pagination.number_of_pages}"></i> | |||
</div> | |||
</div> | |||
<div class="col-6"> | |||
<delete-button class="float-right mt-2" | |||
confirmText="Are you sure you want to delete this password profile?" | |||
confirmButton="Sure" | |||
cancelButton="Oups no!" | |||
v-on:remove="deletePassword(password)"> | |||
</delete-button> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="row mt-2" v-if="pagination.number_of_pages > 1"> | |||
<div class="col-4"> | |||
<i class="fa fa-arrow-left pointer" | |||
v-on:click="pagination.current_page -= 1" | |||
v-bind:class="{'fa-none':pagination.current_page === 1}"></i> | |||
</div> | |||
<div class="col-4 text-center"> | |||
{{pagination.current_page}} / {{Math.ceil(passwords.length/pagination.per_page)}} | |||
</div> | |||
<div class="col-4 text-right"> | |||
<i class="fa fa-arrow-right pointer" | |||
v-on:click="pagination.current_page += 1" | |||
v-bind:class="{'fa-none':pagination.current_page === pagination.number_of_pages}"></i> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import DeleteButton from '../components/DeleteButton.vue'; | |||
import {mapGetters} from 'vuex'; | |||
import DeleteButton from '../components/DeleteButton.vue'; | |||
import {mapGetters} from 'vuex'; | |||
function fetchPasswords(store) { | |||
return store.dispatch('getPasswords') | |||
} | |||
function fetchPasswords(store) { | |||
return store.dispatch('getPasswords') | |||
} | |||
export default { | |||
name: 'passwords-view', | |||
data(){ | |||
return { | |||
searchQuery: '', | |||
pagination: { | |||
number_of_pages: 1, | |||
per_page: 5, | |||
current_page: 1 | |||
}, | |||
} | |||
export default { | |||
name: 'passwords-view', | |||
data(){ | |||
return { | |||
searchQuery: '', | |||
pagination: { | |||
number_of_pages: 1, | |||
per_page: 5, | |||
current_page: 1 | |||
}, | |||
components: {DeleteButton}, | |||
computed: { | |||
...mapGetters(['passwords']), | |||
filteredPasswords(){ | |||
const passwords = this.passwords.filter(password => { | |||
var loginMatch = password.login.match(new RegExp(this.searchQuery, 'i')); | |||
var siteMatch = password.site.match(new RegExp(this.searchQuery, 'i')); | |||
return loginMatch || siteMatch; | |||
}); | |||
this.pagination.number_of_pages = Math.ceil(passwords.length / this.pagination.per_page); | |||
return passwords.slice( | |||
this.pagination.current_page * this.pagination.per_page - 5, | |||
this.pagination.current_page * this.pagination.per_page | |||
); | |||
} | |||
}, | |||
preFetch: fetchPasswords, | |||
beforeMount () { | |||
fetchPasswords(this.$store); | |||
}, | |||
methods: { | |||
deletePassword(password){ | |||
return this.$store.dispatch('deletePassword', {id: password.id}); | |||
} | |||
} | |||
} | |||
}, | |||
components: {DeleteButton}, | |||
computed: { | |||
...mapGetters(['passwords']), | |||
filteredPasswords(){ | |||
const passwords = this.passwords.filter(password => { | |||
var loginMatch = password.login.match(new RegExp(this.searchQuery, 'i')); | |||
var siteMatch = password.site.match(new RegExp(this.searchQuery, 'i')); | |||
return loginMatch || siteMatch; | |||
}); | |||
this.pagination.number_of_pages = Math.ceil(passwords.length / this.pagination.per_page); | |||
return passwords.slice( | |||
this.pagination.current_page * this.pagination.per_page - 5, | |||
this.pagination.current_page * this.pagination.per_page | |||
); | |||
} | |||
}, | |||
preFetch: fetchPasswords, | |||
beforeMount () { | |||
fetchPasswords(this.$store); | |||
}, | |||
methods: { | |||
deletePassword(password){ | |||
return this.$store.dispatch('deletePassword', {id: password.id}); | |||
} | |||
} | |||
} | |||
</script> | |||
15 |
@@ -7,53 +7,53 @@ const config = {baseURL: 'https://lesspass.com', token: token}; | |||
const headers = {reqheaders: {Authorization: `JWT ${token}`}}; | |||
test('Passwords.create', t => { | |||
const password = {login: 'text@example.org'}; | |||
nock('https://lesspass.com').post('/api/passwords/', password).reply(201, {...password, id: '1'}); | |||
return Passwords.create(password, config).then(response => { | |||
const passwordCreated = response.data; | |||
t.is(passwordCreated.id, '1'); | |||
t.is(passwordCreated.login, password.login); | |||
}); | |||
const password = {login: 'text@example.org'}; | |||
nock('https://lesspass.com').post('/api/passwords/', password).reply(201, {...password, id: '1'}); | |||
return Passwords.create(password, config).then(response => { | |||
const passwordCreated = response.data; | |||
t.is(passwordCreated.id, '1'); | |||
t.is(passwordCreated.login, password.login); | |||
}); | |||
}); | |||
test('Passwords.create set Authorization header', t => { | |||
const password = {login: 'text@example.org'}; | |||
nock('https://lesspass.com', headers).post('/api/passwords/', password).query(true).reply(201, { | |||
id: '1', | |||
...password | |||
}); | |||
return Passwords.create(password, config).then(response => { | |||
const passwordCreated = response.data; | |||
t.is(passwordCreated.id, '1'); | |||
t.is(passwordCreated.login, password.login); | |||
}); | |||
const password = {login: 'text@example.org'}; | |||
nock('https://lesspass.com', headers).post('/api/passwords/', password).query(true).reply(201, { | |||
id: '1', | |||
...password | |||
}); | |||
return Passwords.create(password, config).then(response => { | |||
const passwordCreated = response.data; | |||
t.is(passwordCreated.id, '1'); | |||
t.is(passwordCreated.login, password.login); | |||
}); | |||
}); | |||
test('Passwords.all', t => { | |||
nock('https://lesspass.com', headers).get('/api/passwords/').query(true).reply(200, {}); | |||
return Passwords.all(config).then(response => { | |||
t.is(response.status, 200); | |||
}); | |||
nock('https://lesspass.com', headers).get('/api/passwords/').query(true).reply(200, {}); | |||
return Passwords.all(config).then(response => { | |||
t.is(response.status, 200); | |||
}); | |||
}); | |||
test('Passwords.get', t => { | |||
nock('https://lesspass.com', headers).get('/api/passwords/c8e4f983-8ffe-b705-4064-d3b7aa4a4782/').query(true).reply(200, {}); | |||
return Passwords.read({id: 'c8e4f983-8ffe-b705-4064-d3b7aa4a4782'}, config).then(response => { | |||
t.is(response.status, 200); | |||
}); | |||
nock('https://lesspass.com', headers).get('/api/passwords/c8e4f983-8ffe-b705-4064-d3b7aa4a4782/').query(true).reply(200, {}); | |||
return Passwords.read({id: 'c8e4f983-8ffe-b705-4064-d3b7aa4a4782'}, config).then(response => { | |||
t.is(response.status, 200); | |||
}); | |||
}); | |||
test('Passwords.update', t => { | |||
const password = {id: 'c8e4f983-4064-8ffe-b705-d3b7aa4a4782', login: 'test@example.org'}; | |||
nock('https://lesspass.com', headers).put('/api/passwords/c8e4f983-4064-8ffe-b705-d3b7aa4a4782/', password).query(true).reply(200, {}); | |||
return Passwords.update(password, config).then(response => { | |||
t.is(response.status, 200); | |||
}); | |||
const password = {id: 'c8e4f983-4064-8ffe-b705-d3b7aa4a4782', login: 'test@example.org'}; | |||
nock('https://lesspass.com', headers).put('/api/passwords/c8e4f983-4064-8ffe-b705-d3b7aa4a4782/', password).query(true).reply(200, {}); | |||
return Passwords.update(password, config).then(response => { | |||
t.is(response.status, 200); | |||
}); | |||
}); | |||
test('Passwords.delete', t => { | |||
nock('https://lesspass.com', headers).delete('/api/passwords/c8e4f983-8ffe-4064-b705-d3b7aa4a4782/').query(true).reply(204); | |||
return Passwords.delete({id: 'c8e4f983-8ffe-4064-b705-d3b7aa4a4782'}, config).then(response => { | |||
t.is(response.status, 204); | |||
}); | |||
}); | |||
nock('https://lesspass.com', headers).delete('/api/passwords/c8e4f983-8ffe-4064-b705-d3b7aa4a4782/').query(true).reply(204); | |||
return Passwords.delete({id: 'c8e4f983-8ffe-4064-b705-d3b7aa4a4782'}, config).then(response => { | |||
t.is(response.status, 204); | |||
}); | |||
}); |
@@ -3,43 +3,43 @@ import nock from 'nock'; | |||
import User from '../src/api/user'; | |||
test('login', t => { | |||
const token = '5e0651'; | |||
const user = {email: 'test@example.org', password: 'password'}; | |||
nock('https://lesspass.com').post('/api/tokens/auth/', user).reply(201, {token}); | |||
return User.login(user, {baseURL: 'https://lesspass.com'}).then(response => { | |||
t.is(response.token, token); | |||
}); | |||
const token = '5e0651'; | |||
const user = {email: 'test@example.org', password: 'password'}; | |||
nock('https://lesspass.com').post('/api/tokens/auth/', user).reply(201, {token}); | |||
return User.login(user, {baseURL: 'https://lesspass.com'}).then(response => { | |||
t.is(response.token, token); | |||
}); | |||
}); | |||
test('register', t => { | |||
const user = {email: 'test@example.org', password: 'password'}; | |||
nock('https://lesspass.com').post('/api/auth/register/', user).reply(201, {email: user.email, pk: 1}); | |||
return User.register(user, {baseURL: 'https://lesspass.com'}).then(response => { | |||
t.is(response.email, user.email); | |||
}); | |||
const user = {email: 'test@example.org', password: 'password'}; | |||
nock('https://lesspass.com').post('/api/auth/register/', user).reply(201, {email: user.email, pk: 1}); | |||
return User.register(user, {baseURL: 'https://lesspass.com'}).then(response => { | |||
t.is(response.email, user.email); | |||
}); | |||
}); | |||
test('resetPassword', t => { | |||
var email = 'test@lesspass.com'; | |||
nock('https://lesspass.com').post('/api/auth/password/reset/', {email}).reply(204); | |||
t.notThrows(User.resetPassword({email}, {baseURL: 'https://lesspass.com'})); | |||
var email = 'test@lesspass.com'; | |||
nock('https://lesspass.com').post('/api/auth/password/reset/', {email}).reply(204); | |||
t.notThrows(User.resetPassword({email}, {baseURL: 'https://lesspass.com'})); | |||
}); | |||
test('confirmResetPassword', t => { | |||
var newPassword = { | |||
uid: 'MQ', | |||
token: '5g1-2bd69bd6f6dcd73f8124', | |||
new_password: 'password1' | |||
}; | |||
nock('https://lesspass.com').post('/api/auth/password/reset/confirm/', newPassword).reply(204); | |||
t.notThrows(User.confirmResetPassword(newPassword, {baseURL: 'https://lesspass.com'})); | |||
var newPassword = { | |||
uid: 'MQ', | |||
token: '5g1-2bd69bd6f6dcd73f8124', | |||
new_password: 'password1' | |||
}; | |||
nock('https://lesspass.com').post('/api/auth/password/reset/confirm/', newPassword).reply(204); | |||
t.notThrows(User.confirmResetPassword(newPassword, {baseURL: 'https://lesspass.com'})); | |||
}); | |||
test('refresh token', t => { | |||
const token = '3e3231'; | |||
const newToken = 'wibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9eyJzdWIiOiIxMjM0NTY3ODkwIi'; | |||
nock('https://lesspass.com').post('/api/tokens/refresh/', {token}).reply(200, {token: newToken}); | |||
return User.requestNewToken({token}, {baseURL: 'https://lesspass.com'}).then(refreshedToken => { | |||
t.is(refreshedToken, newToken); | |||
}); | |||
}); | |||
const token = '3e3231'; | |||
const newToken = 'wibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9eyJzdWIiOiIxMjM0NTY3ODkwIi'; | |||
nock('https://lesspass.com').post('/api/tokens/refresh/', {token}).reply(200, {token: newToken}); | |||
return User.requestNewToken({token}, {baseURL: 'https://lesspass.com'}).then(refreshedToken => { | |||
t.is(refreshedToken, newToken); | |||
}); | |||
}); |
@@ -2,59 +2,59 @@ import test from 'ava'; | |||
import * as getters from '../src/store/getters'; | |||
test('version', t => { | |||
const state = { | |||
route: {path: '/'}, | |||
password: {version: 2}, | |||
defaultPassword: {version: 1} | |||
}; | |||
const version = getters.version(state); | |||
t.is(version, 2); | |||
const state = { | |||
route: {path: '/'}, | |||
password: {version: 2}, | |||
defaultPassword: {version: 1} | |||
}; | |||
const version = getters.version(state); | |||
t.is(version, 2); | |||
}); | |||
test('version path equal default options', t => { | |||
const state = { | |||
route: {path: '/options/default'}, | |||
password: {version: 2}, | |||
defaultPassword: {version: 1} | |||
}; | |||
const version = getters.version(state); | |||
t.is(version, 1); | |||
const state = { | |||
route: {path: '/options/default'}, | |||
password: {version: 2}, | |||
defaultPassword: {version: 1} | |||
}; | |||
const version = getters.version(state); | |||
t.is(version, 1); | |||
}); | |||
test('version no password', t => { | |||
const state = { | |||
route: {path: '/'}, | |||
password: null, | |||
defaultPassword: {version: 1} | |||
}; | |||
const version = getters.version(state); | |||
t.is(version, 1); | |||
const state = { | |||
route: {path: '/'}, | |||
password: null, | |||
defaultPassword: {version: 1} | |||
}; | |||
const version = getters.version(state); | |||
t.is(version, 1); | |||
}); | |||
test('passwordURL', t => { | |||
const state = { | |||
password: { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: false, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
}, | |||
baseURL: 'https://lesspass.com' | |||
}; | |||
const state = { | |||
password: { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: false, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
}, | |||
baseURL: 'https://lesspass.com' | |||
}; | |||
t.is(getters.passwordURL(state), 'https://lesspass.com/#/?login=test@example.org&site=example.org&uppercase=true&lowercase=true&numbers=true&symbols=false&length=16&counter=1&version=2') | |||
t.is(getters.passwordURL(state), 'https://lesspass.com/#/?login=test@example.org&site=example.org&uppercase=true&lowercase=true&numbers=true&symbols=false&length=16&counter=1&version=2') | |||
}); | |||
test('message', t => { | |||
const state = { | |||
message: {text: 'error message', status:'error'} | |||
}; | |||
const message = getters.message(state); | |||
t.is(message.text, state.message.text); | |||
t.is(message.status, state.message.status); | |||
}); | |||
const state = { | |||
message: {text: 'error message', status: 'error'} | |||
}; | |||
const message = getters.message(state); | |||
t.is(message.text, state.message.text); | |||
t.is(message.status, state.message.status); | |||
}); |
@@ -4,294 +4,294 @@ import mutations from '../src/store/mutations'; | |||
import * as types from '../src/store/mutation-types'; | |||
test('LOGOUT', t => { | |||
const LOGOUT = mutations[types.LOGOUT]; | |||
const state = { | |||
authenticated: true | |||
}; | |||
LOGOUT(state); | |||
t.false(state.authenticated); | |||
const LOGOUT = mutations[types.LOGOUT]; | |||
const state = { | |||
authenticated: true | |||
}; | |||
LOGOUT(state); | |||
t.false(state.authenticated); | |||
}); | |||
test('LOGOUT clean user personal info', t => { | |||
const LOGOUT = mutations[types.LOGOUT]; | |||
const state = { | |||
token: '123456', | |||
password: {counter: 2}, | |||
passwords: [{id: '1', site: 'test@example.org'}], | |||
defaultPassword: {counter: 1}, | |||
}; | |||
LOGOUT(state); | |||
t.true(state.token === null); | |||
t.is(state.passwords.length, 0); | |||
t.is(state.password.counter, 1); | |||
const LOGOUT = mutations[types.LOGOUT]; | |||
const state = { | |||
token: '123456', | |||
password: {counter: 2}, | |||
passwords: [{id: '1', site: 'test@example.org'}], | |||
defaultPassword: {counter: 1}, | |||
}; | |||
LOGOUT(state); | |||
t.true(state.token === null); | |||
t.is(state.passwords.length, 0); | |||
t.is(state.password.counter, 1); | |||
}); | |||
test('LOGIN', t => { | |||
const LOGIN = mutations[types.LOGIN]; | |||
const state = {authenticated: false}; | |||
LOGIN(state); | |||
t.true(state.authenticated); | |||
const LOGIN = mutations[types.LOGIN]; | |||
const state = {authenticated: false}; | |||
LOGIN(state); | |||
t.true(state.authenticated); | |||
}); | |||
test('SET_TOKEN', t => { | |||
const token = '123456'; | |||
const SET_TOKEN = mutations[types.SET_TOKEN]; | |||
const state = {token: null}; | |||
SET_TOKEN(state, {token}); | |||
t.is(state.token, token); | |||
const token = '123456'; | |||
const SET_TOKEN = mutations[types.SET_TOKEN]; | |||
const state = {token: null}; | |||
SET_TOKEN(state, {token}); | |||
t.is(state.token, token); | |||
}); | |||
test('SET_PASSWORD', t => { | |||
const SET_PASSWORD = mutations[types.SET_PASSWORD]; | |||
const state = {password: null}; | |||
SET_PASSWORD(state, {password: {uppercase: true, version: 2}}); | |||
t.is(state.password.version, 2); | |||
t.true(state.password.uppercase); | |||
const SET_PASSWORD = mutations[types.SET_PASSWORD]; | |||
const state = {password: null}; | |||
SET_PASSWORD(state, {password: {uppercase: true, version: 2}}); | |||
t.is(state.password.version, 2); | |||
t.true(state.password.uppercase); | |||
}); | |||
test('SET_PASSWORD dont change lastUse date', t => { | |||
const SET_PASSWORD = mutations[types.SET_PASSWORD]; | |||
const now = 1485989236000; | |||
const time = new Date(now); | |||
timekeeper.freeze(time); | |||
const state = {lastUse: null, password: null}; | |||
SET_PASSWORD(state, {password: {}}); | |||
t.true(state.lastUse === null); | |||
timekeeper.reset(); | |||
const SET_PASSWORD = mutations[types.SET_PASSWORD]; | |||
const now = 1485989236000; | |||
const time = new Date(now); | |||
timekeeper.freeze(time); | |||
const state = {lastUse: null, password: null}; | |||
SET_PASSWORD(state, {password: {}}); | |||
t.true(state.lastUse === null); | |||
timekeeper.reset(); | |||
}); | |||
test('PASSWORD_GENERATED change lastUse date', t => { | |||
const PASSWORD_GENERATED = mutations[types.PASSWORD_GENERATED]; | |||
const now = 1485989236000; | |||
const time = new Date(now); | |||
timekeeper.freeze(time); | |||
const state = {lastUse: null}; | |||
PASSWORD_GENERATED(state); | |||
t.is(now, state.lastUse); | |||
timekeeper.reset(); | |||
const PASSWORD_GENERATED = mutations[types.PASSWORD_GENERATED]; | |||
const now = 1485989236000; | |||
const time = new Date(now); | |||
timekeeper.freeze(time); | |||
const state = {lastUse: null}; | |||
PASSWORD_GENERATED(state); | |||
t.is(now, state.lastUse); | |||
timekeeper.reset(); | |||
}); | |||
test('SET_PASSWORD immutable', t => { | |||
const SET_PASSWORD = mutations[types.SET_PASSWORD]; | |||
const state = {}; | |||
const password = {version: 2}; | |||
SET_PASSWORD(state, {password}); | |||
password.version = 1; | |||
t.is(state.password.version, 2); | |||
const SET_PASSWORD = mutations[types.SET_PASSWORD]; | |||
const state = {}; | |||
const password = {version: 2}; | |||
SET_PASSWORD(state, {password}); | |||
password.version = 1; | |||
t.is(state.password.version, 2); | |||
}); | |||
test('SET_DEFAULT_PASSWORD', t => { | |||
const SET_DEFAULT_PASSWORD = mutations[types.SET_DEFAULT_PASSWORD]; | |||
const state = { | |||
defaultPassword: { | |||
site: '', | |||
login: '', | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
} | |||
}; | |||
SET_DEFAULT_PASSWORD(state, {password: {symbols: false, length: 30}}); | |||
t.is(state.defaultPassword.length, 30); | |||
t.false(state.defaultPassword.symbols); | |||
const SET_DEFAULT_PASSWORD = mutations[types.SET_DEFAULT_PASSWORD]; | |||
const state = { | |||
defaultPassword: { | |||
site: '', | |||
login: '', | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
} | |||
}; | |||
SET_DEFAULT_PASSWORD(state, {password: {symbols: false, length: 30}}); | |||
t.is(state.defaultPassword.length, 30); | |||
t.false(state.defaultPassword.symbols); | |||
}); | |||
test('SET_PASSWORDS', t => { | |||
const SET_PASSWORDS = mutations[types.SET_PASSWORDS]; | |||
const state = { | |||
passwords: [] | |||
}; | |||
SET_PASSWORDS(state, {passwords: [{site: 'site1'}, {site: 'site2'}]}); | |||
t.is(state.passwords[0].site, 'site1'); | |||
t.is(state.passwords[1].site, 'site2'); | |||
const SET_PASSWORDS = mutations[types.SET_PASSWORDS]; | |||
const state = { | |||
passwords: [] | |||
}; | |||
SET_PASSWORDS(state, {passwords: [{site: 'site1'}, {site: 'site2'}]}); | |||
t.is(state.passwords[0].site, 'site1'); | |||
t.is(state.passwords[1].site, 'site2'); | |||
}); | |||
test('DELETE_PASSWORD', t => { | |||
const DELETE_PASSWORD = mutations[types.DELETE_PASSWORD]; | |||
const state = { | |||
passwords: [{id: '1', site: 'site1'}, {id: '2', site: 'site2'}] | |||
}; | |||
t.is(state.passwords.length, 2); | |||
DELETE_PASSWORD(state, {id: '1'}); | |||
t.is(state.passwords.length, 1); | |||
const DELETE_PASSWORD = mutations[types.DELETE_PASSWORD]; | |||
const state = { | |||
passwords: [{id: '1', site: 'site1'}, {id: '2', site: 'site2'}] | |||
}; | |||
t.is(state.passwords.length, 2); | |||
DELETE_PASSWORD(state, {id: '1'}); | |||
t.is(state.passwords.length, 1); | |||
}); | |||
test('DELETE_PASSWORD clean password with default password if same id', t => { | |||
const DELETE_PASSWORD = mutations[types.DELETE_PASSWORD]; | |||
const state = { | |||
passwords: [{id: '1', length: 30}, {id: '2', length: 16}], | |||
password: {id: '1', length: 30}, | |||
defaultPassword: {length: 16} | |||
}; | |||
DELETE_PASSWORD(state, {id: '1'}); | |||
t.is(state.password.length, 16); | |||
const DELETE_PASSWORD = mutations[types.DELETE_PASSWORD]; | |||
const state = { | |||
passwords: [{id: '1', length: 30}, {id: '2', length: 16}], | |||
password: {id: '1', length: 30}, | |||
defaultPassword: {length: 16} | |||
}; | |||
DELETE_PASSWORD(state, {id: '1'}); | |||
t.is(state.password.length, 16); | |||
}); | |||
test('SET_BASE_URL', t => { | |||
const SET_BASE_URL = mutations[types.SET_BASE_URL]; | |||
const state = { | |||
baseURL: 'https://lesspass.com' | |||
}; | |||
const baseURL = 'https://example.org'; | |||
SET_BASE_URL(state, {baseURL: baseURL}); | |||
t.is(state.baseURL, baseURL); | |||
const SET_BASE_URL = mutations[types.SET_BASE_URL]; | |||
const state = { | |||
baseURL: 'https://lesspass.com' | |||
}; | |||
const baseURL = 'https://example.org'; | |||
SET_BASE_URL(state, {baseURL: baseURL}); | |||
t.is(state.baseURL, baseURL); | |||
}); | |||
test('SET_VERSION', t => { | |||
const SET_VERSION = mutations[types.SET_VERSION]; | |||
const state = { | |||
password: {version: 2}, | |||
}; | |||
SET_VERSION(state, {version: 1}); | |||
t.is(state.password.version, 1); | |||
const SET_VERSION = mutations[types.SET_VERSION]; | |||
const state = { | |||
password: {version: 2}, | |||
}; | |||
SET_VERSION(state, {version: 1}); | |||
t.is(state.password.version, 1); | |||
}); | |||
test('SET_VERSION 1 should modify length to 12', t => { | |||
const SET_VERSION = mutations[types.SET_VERSION]; | |||
const state = { | |||
password: {length: 16, version: 2}, | |||
}; | |||
SET_VERSION(state, {version: 1}); | |||
t.is(state.password.length, 12); | |||
const SET_VERSION = mutations[types.SET_VERSION]; | |||
const state = { | |||
password: {length: 16, version: 2}, | |||
}; | |||
SET_VERSION(state, {version: 1}); | |||
t.is(state.password.length, 12); | |||
}); | |||
test('SET_VERSION 2 should modify length to 16', t => { | |||
const SET_VERSION = mutations[types.SET_VERSION]; | |||
const state = { | |||
password: {length: 12, version: 1}, | |||
}; | |||
SET_VERSION(state, {version: 2}); | |||
t.is(state.password.length, 16); | |||
const SET_VERSION = mutations[types.SET_VERSION]; | |||
const state = { | |||
password: {length: 12, version: 1}, | |||
}; | |||
SET_VERSION(state, {version: 2}); | |||
t.is(state.password.length, 16); | |||
}); | |||
test('LOAD_PASSWORD_FIRST_TIME 30 seconds after last use', t => { | |||
const now = 1485989236000; | |||
const time = new Date(now); | |||
timekeeper.freeze(time); | |||
const thirtySecondBefore = now - 30 * 1000; | |||
const state = { | |||
lastUse: thirtySecondBefore, | |||
password: { | |||
login: 'test@example.org', | |||
length: 30 | |||
}, | |||
defaultPassword: { | |||
login: '', | |||
length: 16 | |||
} | |||
}; | |||
const LOAD_PASSWORD_FIRST_TIME = mutations[types.LOAD_PASSWORD_FIRST_TIME]; | |||
LOAD_PASSWORD_FIRST_TIME(state); | |||
t.is(state.password.login, 'test@example.org'); | |||
t.is(state.password.length, 30); | |||
timekeeper.reset(); | |||
const now = 1485989236000; | |||
const time = new Date(now); | |||
timekeeper.freeze(time); | |||
const thirtySecondBefore = now - 30 * 1000; | |||
const state = { | |||
lastUse: thirtySecondBefore, | |||
password: { | |||
login: 'test@example.org', | |||
length: 30 | |||
}, | |||
defaultPassword: { | |||
login: '', | |||
length: 16 | |||
} | |||
}; | |||
const LOAD_PASSWORD_FIRST_TIME = mutations[types.LOAD_PASSWORD_FIRST_TIME]; | |||
LOAD_PASSWORD_FIRST_TIME(state); | |||
t.is(state.password.login, 'test@example.org'); | |||
t.is(state.password.length, 30); | |||
timekeeper.reset(); | |||
}); | |||
test('LOAD_PASSWORD_FIRST_TIME more than 1 minute after last use', t => { | |||
const now = 1485989236000; | |||
const time = new Date(now); | |||
timekeeper.freeze(time); | |||
const oneMinuteAndOneSecond = now - 61 * 1000; | |||
const state = { | |||
lastUse: oneMinuteAndOneSecond, | |||
password: { | |||
login: 'test@example.org', | |||
length: 30 | |||
}, | |||
defaultPassword: { | |||
login: '', | |||
length: 16 | |||
} | |||
}; | |||
const LOAD_PASSWORD_FIRST_TIME = mutations[types.LOAD_PASSWORD_FIRST_TIME]; | |||
LOAD_PASSWORD_FIRST_TIME(state); | |||
t.is(state.password.login, ''); | |||
t.is(state.password.length, 16); | |||
timekeeper.reset(); | |||
const now = 1485989236000; | |||
const time = new Date(now); | |||
timekeeper.freeze(time); | |||
const oneMinuteAndOneSecond = now - 61 * 1000; | |||
const state = { | |||
lastUse: oneMinuteAndOneSecond, | |||
password: { | |||
login: 'test@example.org', | |||
length: 30 | |||
}, | |||
defaultPassword: { | |||
login: '', | |||
length: 16 | |||
} | |||
}; | |||
const LOAD_PASSWORD_FIRST_TIME = mutations[types.LOAD_PASSWORD_FIRST_TIME]; | |||
LOAD_PASSWORD_FIRST_TIME(state); | |||
t.is(state.password.login, ''); | |||
t.is(state.password.length, 16); | |||
timekeeper.reset(); | |||
}); | |||
test('LOAD_PASSWORD_FIRST_TIME last use null', t => { | |||
const time = new Date(1485989236000); | |||
timekeeper.freeze(time); | |||
const state = { | |||
lastUse: null, | |||
password: { | |||
site: '', | |||
version: 1 | |||
}, | |||
defaultPassword: { | |||
site: '', | |||
version: 2 | |||
} | |||
}; | |||
const LOAD_PASSWORD_FIRST_TIME = mutations[types.LOAD_PASSWORD_FIRST_TIME]; | |||
LOAD_PASSWORD_FIRST_TIME(state); | |||
t.is(state.password.version, 2); | |||
timekeeper.reset(); | |||
const time = new Date(1485989236000); | |||
timekeeper.freeze(time); | |||
const state = { | |||
lastUse: null, | |||
password: { | |||
site: '', | |||
version: 1 | |||
}, | |||
defaultPassword: { | |||
site: '', | |||
version: 2 | |||
} | |||
}; | |||
const LOAD_PASSWORD_FIRST_TIME = mutations[types.LOAD_PASSWORD_FIRST_TIME]; | |||
LOAD_PASSWORD_FIRST_TIME(state); | |||
t.is(state.password.version, 2); | |||
timekeeper.reset(); | |||
}); | |||
test('LOAD_PASSWORD_FOR_SITE', t => { | |||
const state = { | |||
password: { | |||
site: '' | |||
}, | |||
passwords: [ | |||
{id: '1', site: 'www.example.org'}, | |||
{id: '2', site: 'www.google.com'} | |||
] | |||
}; | |||
const LOAD_PASSWORD_FOR_SITE = mutations[types.LOAD_PASSWORD_FOR_SITE]; | |||
LOAD_PASSWORD_FOR_SITE(state, {site: 'www.google.com'}); | |||
t.is(state.password.id, '2'); | |||
t.is(state.password.site, 'www.google.com'); | |||
const state = { | |||
password: { | |||
site: '' | |||
}, | |||
passwords: [ | |||
{id: '1', site: 'www.example.org'}, | |||
{id: '2', site: 'www.google.com'} | |||
] | |||
}; | |||
const LOAD_PASSWORD_FOR_SITE = mutations[types.LOAD_PASSWORD_FOR_SITE]; | |||
LOAD_PASSWORD_FOR_SITE(state, {site: 'www.google.com'}); | |||
t.is(state.password.id, '2'); | |||
t.is(state.password.site, 'www.google.com'); | |||
}); | |||
test('LOAD_PASSWORD_FOR_SITE no passwords', t => { | |||
const state = { | |||
password: { | |||
site: '' | |||
}, | |||
passwords: [] | |||
}; | |||
const LOAD_PASSWORD_FOR_SITE = mutations[types.LOAD_PASSWORD_FOR_SITE]; | |||
LOAD_PASSWORD_FOR_SITE(state, {site: 'account.google.com'}); | |||
t.false('id' in state.password); | |||
t.is(state.password.site, 'account.google.com'); | |||
const state = { | |||
password: { | |||
site: '' | |||
}, | |||
passwords: [] | |||
}; | |||
const LOAD_PASSWORD_FOR_SITE = mutations[types.LOAD_PASSWORD_FOR_SITE]; | |||
LOAD_PASSWORD_FOR_SITE(state, {site: 'account.google.com'}); | |||
t.false('id' in state.password); | |||
t.is(state.password.site, 'account.google.com'); | |||
}); | |||
test('LOAD_PASSWORD_FOR_SITE multiple accounts matching criteria', t => { | |||
const state = { | |||
password: { | |||
site: '' | |||
}, | |||
passwords: [ | |||
{id: '1', site: 'www.example.org'}, | |||
{id: '2', site: 'www.google.com'}, | |||
{id: '3', site: 'account.google.com'}, | |||
] | |||
}; | |||
const LOAD_PASSWORD_FOR_SITE = mutations[types.LOAD_PASSWORD_FOR_SITE]; | |||
LOAD_PASSWORD_FOR_SITE(state, {site: 'www.google.com', url: 'https://www.google.com'}); | |||
t.is(state.password.id, '2'); | |||
t.is(state.password.site, 'www.google.com'); | |||
const state = { | |||
password: { | |||
site: '' | |||
}, | |||
passwords: [ | |||
{id: '1', site: 'www.example.org'}, | |||
{id: '2', site: 'www.google.com'}, | |||
{id: '3', site: 'account.google.com'}, | |||
] | |||
}; | |||
const LOAD_PASSWORD_FOR_SITE = mutations[types.LOAD_PASSWORD_FOR_SITE]; | |||
LOAD_PASSWORD_FOR_SITE(state, {site: 'www.google.com', url: 'https://www.google.com'}); | |||
t.is(state.password.id, '2'); | |||
t.is(state.password.site, 'www.google.com'); | |||
}); | |||
test('SET_MESSAGE', t => { | |||
const SET_MESSAGE = mutations[types.SET_MESSAGE]; | |||
const state = {}; | |||
SET_MESSAGE(state, {message: {text: 'success message', status: 'success'}}); | |||
t.is(state.message.text, 'success message'); | |||
t.is(state.message.status, 'success'); | |||
const SET_MESSAGE = mutations[types.SET_MESSAGE]; | |||
const state = {}; | |||
SET_MESSAGE(state, {message: {text: 'success message', status: 'success'}}); | |||
t.is(state.message.text, 'success message'); | |||
t.is(state.message.status, 'success'); | |||
}); | |||
test('CLEAN_MESSAGE', t => { | |||
const CLEAN_MESSAGE = mutations[types.CLEAN_MESSAGE]; | |||
const state = {message: {text: 'error message', status: 'error'}}; | |||
CLEAN_MESSAGE(state); | |||
t.is(state.message.text, ''); | |||
t.is(state.message.status, 'success'); | |||
}); | |||
const CLEAN_MESSAGE = mutations[types.CLEAN_MESSAGE]; | |||
const state = {message: {text: 'error message', status: 'error'}}; | |||
CLEAN_MESSAGE(state); | |||
t.is(state.message.text, ''); | |||
t.is(state.message.status, 'success'); | |||
}); |
@@ -2,72 +2,72 @@ import test from 'ava'; | |||
import * as urlParser from '../src/domain/url-parser'; | |||
test('urlParser.getDomainName', t => { | |||
t.is('lesspass.com', urlParser.getDomainName('https://lesspass.com/#!/')); | |||
t.is('lesspass.com', urlParser.getDomainName('https://lesspass.com/api/')); | |||
t.is('api.lesspass.com', urlParser.getDomainName('https://api.lesspass.com/')); | |||
t.is('lesspass.com', urlParser.getDomainName('http://lesspass.com')); | |||
t.is('stackoverflow.com', urlParser.getDomainName('http://stackoverflow.com/questions/3689423/google-chrome-plugin-how-to-get-domain-from-url-tab-url')); | |||
t.is('v4-alpha.getbootstrap.com', urlParser.getDomainName('http://v4-alpha.getbootstrap.com/components/buttons/')); | |||
t.is('accounts.google.com', urlParser.getDomainName('https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue=https://mail.google.com/mail/&ss=1&scc=1<mpl=default<mplcache=2&emr=1&osid=1#identifier')); | |||
t.is('www.netflix.com', urlParser.getDomainName('https://www.netflix.com/browse')); | |||
t.is('www.bbc.co.uk', urlParser.getDomainName('https://www.bbc.co.uk')); | |||
t.is('192.168.1.1:10443', urlParser.getDomainName('https://192.168.1.1:10443/webapp/')); | |||
t.is('', urlParser.getDomainName(undefined)); | |||
t.is('lesspass.com', urlParser.getDomainName('https://lesspass.com/#!/')); | |||
t.is('lesspass.com', urlParser.getDomainName('https://lesspass.com/api/')); | |||
t.is('api.lesspass.com', urlParser.getDomainName('https://api.lesspass.com/')); | |||
t.is('lesspass.com', urlParser.getDomainName('http://lesspass.com')); | |||
t.is('stackoverflow.com', urlParser.getDomainName('http://stackoverflow.com/questions/3689423/google-chrome-plugin-how-to-get-domain-from-url-tab-url')); | |||
t.is('v4-alpha.getbootstrap.com', urlParser.getDomainName('http://v4-alpha.getbootstrap.com/components/buttons/')); | |||
t.is('accounts.google.com', urlParser.getDomainName('https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue=https://mail.google.com/mail/&ss=1&scc=1<mpl=default<mplcache=2&emr=1&osid=1#identifier')); | |||
t.is('www.netflix.com', urlParser.getDomainName('https://www.netflix.com/browse')); | |||
t.is('www.bbc.co.uk', urlParser.getDomainName('https://www.bbc.co.uk')); | |||
t.is('192.168.1.1:10443', urlParser.getDomainName('https://192.168.1.1:10443/webapp/')); | |||
t.is('', urlParser.getDomainName(undefined)); | |||
}); | |||
test('get current tab', t => { | |||
const url = 'https://example.org'; | |||
global.chrome = { | |||
tabs: { | |||
query(a, callback){ | |||
callback([{url}]) | |||
} | |||
} | |||
}; | |||
return urlParser.getSite().then(response => { | |||
t.is(response.url, url); | |||
t.is(response.site, 'example.org') | |||
}); | |||
const url = 'https://example.org'; | |||
global.chrome = { | |||
tabs: { | |||
query(a, callback){ | |||
callback([{url}]) | |||
} | |||
} | |||
}; | |||
return urlParser.getSite().then(response => { | |||
t.is(response.url, url); | |||
t.is(response.site, 'example.org') | |||
}); | |||
}); | |||
test('getPasswordFromUrlQuery', t => { | |||
const query = { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: "true", | |||
lowercase: "true", | |||
numbers: "true", | |||
symbols: "false", | |||
length: "16", | |||
counter: "1", | |||
version: "2" | |||
}; | |||
const expectedPassword = { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: false, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
}; | |||
t.deepEqual(urlParser.getPasswordFromUrlQuery(query), expectedPassword); | |||
const query = { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: "true", | |||
lowercase: "true", | |||
numbers: "true", | |||
symbols: "false", | |||
length: "16", | |||
counter: "1", | |||
version: "2" | |||
}; | |||
const expectedPassword = { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: false, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
}; | |||
t.deepEqual(urlParser.getPasswordFromUrlQuery(query), expectedPassword); | |||
}); | |||
test('getPasswordFromUrlQuery booleanish', t => { | |||
const query = { | |||
uppercase: "true", | |||
lowercase: "TrUe", | |||
numbers: "1", | |||
symbols: "0", | |||
}; | |||
const expectedPassword = { | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: false, | |||
}; | |||
t.deepEqual(urlParser.getPasswordFromUrlQuery(query), expectedPassword); | |||
}); | |||
const query = { | |||
uppercase: "true", | |||
lowercase: "TrUe", | |||
numbers: "1", | |||
symbols: "0", | |||
}; | |||
const expectedPassword = { | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: false, | |||
}; | |||
t.deepEqual(urlParser.getPasswordFromUrlQuery(query), expectedPassword); | |||
}); |
@@ -4,41 +4,41 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin'); | |||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); | |||
module.exports = { | |||
entry: { | |||
app: './src/main.js', | |||
}, | |||
output: { | |||
path: path.resolve(__dirname, './dist'), | |||
publicPath: '/dist/', | |||
filename: 'lesspass.min.js' | |||
}, | |||
module: { | |||
rules: [ | |||
{test: /\.vue$/, loader: 'vue-loader'}, | |||
{test: /\.js$/, include: [path.resolve(__dirname, './src')], loader: 'babel-loader'}, | |||
{test: /\.json/, loader: 'json-loader'}, | |||
{test: /\.(png|jpg|jpeg|gif)$/, loader: 'file-loader?name=[name].[ext]'}, | |||
{test: /\.scss$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!sass-loader', publicPath: ''})}, | |||
{test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/font-woff'}, | |||
{test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/font-woff'}, | |||
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/octet-stream'}, | |||
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'}, | |||
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=image/svg+xml'}, | |||
] | |||
}, | |||
plugins: [ | |||
new ExtractTextPlugin('lesspass.min.css') | |||
entry: { | |||
app: './src/main.js', | |||
}, | |||
output: { | |||
path: path.resolve(__dirname, './dist'), | |||
publicPath: '/dist/', | |||
filename: 'lesspass.min.js' | |||
}, | |||
module: { | |||
rules: [ | |||
{test: /\.vue$/, loader: 'vue-loader'}, | |||
{test: /\.js$/, include: [path.resolve(__dirname, './src')], loader: 'babel-loader'}, | |||
{test: /\.json/, loader: 'json-loader'}, | |||
{test: /\.(png|jpg|jpeg|gif)$/, loader: 'file-loader?name=[name].[ext]'}, | |||
{test: /\.scss$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!sass-loader', publicPath: ''})}, | |||
{test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/font-woff'}, | |||
{test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/font-woff'}, | |||
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=application/octet-stream'}, | |||
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'}, | |||
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=8192&mimetype=image/svg+xml'}, | |||
] | |||
}, | |||
plugins: [ | |||
new ExtractTextPlugin('lesspass.min.css') | |||
] | |||
}; | |||
if (process.env.NODE_ENV === 'production') { | |||
module.exports.devtool = false; | |||
module.exports.plugins = (module.exports.plugins || []).concat([ | |||
new OptimizeCssAssetsPlugin(), | |||
new webpack.optimize.UglifyJsPlugin({ | |||
output: {comments: false}, | |||
compress: {warnings: false} | |||
}) | |||
]); | |||
module.exports.devtool = false; | |||
module.exports.plugins = (module.exports.plugins || []).concat([ | |||
new OptimizeCssAssetsPlugin(), | |||
new webpack.optimize.UglifyJsPlugin({ | |||
output: {comments: false}, | |||
compress: {warnings: false} | |||
}) | |||
]); | |||
} | |||