@@ -18,7 +18,7 @@ | |||
<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> | |||
<span id="title" v-on:click="fullReload()" class="white-link pointer">LessPass</span> | |||
</div> | |||
<div class="col-9 text-right"> | |||
<span v-if="saved && isAuthenticated"> | |||
@@ -22,32 +22,32 @@ | |||
<div class="col-3"> | |||
<button id="lowercase__btn" | |||
type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':password.lowercase===true && password.version===2,'btn-warning':password.lowercase===true && password.version===1,'btn-secondary':password.lowercase===false}" | |||
v-on:click="password.lowercase=!password.lowercase"> | |||
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 id="uppercase__btn" | |||
type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':password.uppercase===true && password.version===2,'btn-warning':password.uppercase===true && password.version===1,'btn-secondary':password.uppercase===false}" | |||
v-on:click="password.uppercase=!password.uppercase"> | |||
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 id="numbers__btn" | |||
type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':password.numbers===true && password.version===2,'btn-warning':password.numbers===true && password.version===1,'btn-secondary':password.numbers===false}" | |||
v-on:click="password.numbers=!password.numbers"> | |||
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 id="symbols__btn" | |||
type="button" class="btn btn-block btn-sm px-0" | |||
v-bind:class="{'btn-primary':password.symbols===true && password.version===2,'btn-warning':password.symbols===true && password.version===1,'btn-secondary':password.symbols===false}" | |||
v-on:click="password.symbols=!password.symbols"> | |||
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> | |||
@@ -58,7 +58,7 @@ | |||
<div class="col"> | |||
<label for="passwordLength">{{ $t('Length') }}</label> | |||
<div class="input-group input-group-sm"> | |||
<span class="input-group-btn" v-on:click="password.length=decrement(password.length, {min: 5, max: 35})"> | |||
<span class="input-group-btn" v-on:click="options.length=decrement(options.length, {min: 5, max: 35})"> | |||
<button id="decreaseLength__btn" class="btn btn-secondary p-1" type="button"> | |||
<i class="fa fa-minus"></i> | |||
</button> | |||
@@ -68,9 +68,9 @@ | |||
type="number" | |||
min="5" | |||
max="35" | |||
v-model.number="password.length"> | |||
v-model.number="options.length"> | |||
<span class="input-group-btn" | |||
v-on:click="password.length=increment(password.length, {min: 5, max: 35})"> | |||
v-on:click="options.length=increment(options.length, {min: 5, max: 35})"> | |||
<button id="increaseLength__btn" class="btn btn-secondary p-1" type="button"> | |||
<i class="fa fa-plus"></i> | |||
</button> | |||
@@ -80,11 +80,12 @@ | |||
<div class="col"> | |||
<label for="passwordCounter" | |||
class="hint--top hint--medium" | |||
v-bind:aria-label="$t('CounterFieldHelp','Increment this value to change the generated password without changing your master password.')"> | |||
v-bind:aria-label="$t('CounterFieldHelp','Increment this value to change the generated password without changing your master options.')"> | |||
{{$t('Counter')}} | |||
</label> | |||
<div class="input-group input-group-sm"> | |||
<span id="decreaseCounter__btn" class="input-group-btn" v-on:click="password.counter=decrement(password.counter, {min: 1})"> | |||
<span id="decreaseCounter__btn" class="input-group-btn" | |||
v-on:click="options.counter=decrement(options.counter, {min: 1})"> | |||
<button class="btn btn-secondary p-1" type="button"> | |||
<i class="fa fa-minus"></i> | |||
</button> | |||
@@ -93,8 +94,9 @@ | |||
class="form-control form-control-sm" | |||
type="number" | |||
min="1" | |||
v-model.number="password.counter"> | |||
<span id="increaseCounter__btn" class="input-group-btn" v-on:click="password.counter=increment(password.counter, {min: 1})"> | |||
v-model.number="options.counter"> | |||
<span id="increaseCounter__btn" class="input-group-btn" | |||
v-on:click="options.counter=increment(options.counter, {min: 1})"> | |||
<button class="btn btn-secondary p-1" type="button"> | |||
<i class="fa fa-plus"></i> | |||
</button> | |||
@@ -110,7 +112,7 @@ | |||
<div class="row no-gutters"> | |||
<div class="col"> | |||
<button type="button" class="btn btn-block btn-sm border-right-0" | |||
v-bind:class="{'btn-primary':password.version===2,'btn-secondary':password.version!==2}" | |||
v-bind:class="{'btn-primary':options.version===2,'btn-secondary':options.version!==2}" | |||
v-on:click="setVersion(2)"> | |||
<span class="hidden-xs-up">{{$t('version')}} </span> | |||
<span class="hidden-xs-down">{{$t('versionShortcut', 'v')}}</span>2 | |||
@@ -119,7 +121,7 @@ | |||
<div class="col"> | |||
<button type="button" | |||
class="btn btn-block btn-sm border-left-0" | |||
v-bind:class="{'btn-warning':password.version===1,'btn-secondary':password.version!==1}" | |||
v-bind:class="{'btn-warning':options.version===1,'btn-secondary':options.version!==1}" | |||
v-on:click="setVersion(1)"> | |||
<span class="hidden-xs-up">{{$t('version')}} </span> | |||
<span class="hidden-xs-down">{{$t('versionShortcut', 'v')}}</span>1 | |||
@@ -130,9 +132,10 @@ | |||
</div> | |||
<div class="form-group row mb-0"> | |||
<div class="col"> | |||
<button type="button" class="btn btn-sm hint--top-right hint--medium" | |||
<button id="saveOptions__btn" | |||
type="button" class="btn btn-sm hint--top-right hint--medium" | |||
v-bind:aria-label="$t('DefaultOptionLocalStorage', 'We use local storage to save default options locally. Each time you open the app, these options will be loaded by default.')" | |||
v-bind:class="{'btn-outline-warning':password.version===1,'btn-outline-primary':password.version!==1}" | |||
v-bind:class="{'btn-outline-warning':options.version===1,'btn-outline-primary':options.version!==1}" | |||
v-on:click="saveDefaultOptions()"> | |||
<i class="fa fa-floppy-o"></i> {{$t('Save options')}} | |||
</button> | |||
@@ -142,31 +145,39 @@ | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import {mapState} from 'vuex'; | |||
import message from '../services/message'; | |||
import {increment, decrement} from "../services/form-validator"; | |||
export default { | |||
name: 'options', | |||
computed: mapState(['password']), | |||
props: { | |||
options: Object | |||
}, | |||
watch: { | |||
'options': { | |||
handler: function(options) { | |||
this.$emit('update:options', options) | |||
}, | |||
deep: true | |||
} | |||
}, | |||
methods: { | |||
decrement, | |||
increment, | |||
setVersion(value){ | |||
setVersion(value) { | |||
if (value === 1) { | |||
message.error(this.$t( | |||
"WarningV1Deprecated", | |||
"Version 1 is deprecated and will be deleted soon. We strongly advise you to migrate your passwords to version 2." | |||
)); | |||
} | |||
const password = Object.assign({}, this.password, { | |||
this.options = Object.assign({}, this.options, { | |||
length: value === 1 ? 12 : 16, | |||
version: value | |||
}); | |||
this.$store.dispatch('savePassword', {password}); | |||
this.$store.dispatch('saveVersion', {version: value}); | |||
}, | |||
saveDefaultOptions(){ | |||
saveDefaultOptions() { | |||
this.$store.dispatch('saveDefaultOptions', {options: this.options}); | |||
message.success(this.$t('Your options have been saved successfully')); | |||
}, | |||
@@ -25,7 +25,6 @@ export const loadBestPasswordProfile = ({ commit }) => { | |||
if (site) { | |||
commit(types.SET_SITE, { site }); | |||
commit(types.LOAD_PASSWORD_PROFILE, { site }); | |||
commit(types.CHECK_SHOW_OPTIONS); | |||
} | |||
}); | |||
}; | |||
@@ -39,7 +38,6 @@ export const getPasswordFromUrlQuery = ({ commit }, { query }) => { | |||
export const savePassword = ({ commit }, payload) => { | |||
commit(types.SET_PASSWORD, payload); | |||
commit(types.CHECK_SHOW_OPTIONS); | |||
}; | |||
export const saveVersion = ({ commit }, payload) => { | |||
@@ -64,10 +62,6 @@ export const logout = ({ commit }) => { | |||
commit(types.LOGOUT); | |||
}; | |||
export const toggleShowOptions = ({ commit }) => { | |||
commit(types.TOGGLE_SHOW_OPTIONS); | |||
}; | |||
export const getPasswords = ({ commit, state }) => { | |||
if (state.authenticated) { | |||
Password.all(state) | |||
@@ -1,3 +1,5 @@ | |||
import { defaultOptions } from "./defaultPassword"; | |||
export const isAuthenticated = state => state.authenticated; | |||
export const isGuest = state => !state.authenticated; | |||
@@ -16,3 +18,13 @@ export const passwordURL = state => { | |||
.password.symbols}&length=${state.password.length}&counter=${state.password | |||
.counter}&version=${state.password.version}`; | |||
}; | |||
export const isDefaultProfile = state => { | |||
let defaultProfile = true; | |||
for (let key in defaultOptions) { | |||
if (defaultOptions[key] !== state.password[key]) { | |||
defaultProfile = false; | |||
} | |||
} | |||
return defaultProfile; | |||
}; |
@@ -24,5 +24,6 @@ export default new Vuex.Store({ | |||
getters, | |||
actions, | |||
mutations, | |||
plugins: [createPersistedState({ key: "lesspass" })] | |||
plugins: [createPersistedState({ key: "lesspass" })], | |||
strict: true | |||
}); |
@@ -12,5 +12,3 @@ export const SET_SITE = "SET_SITE"; | |||
export const LOAD_PASSWORD_PROFILE = "LOAD_PASSWORD_PROFILE"; | |||
export const DELETE_PASSWORD = "DELETE_PASSWORD"; | |||
export const CLEAN_MESSAGE = "CLEAN_MESSAGE"; | |||
export const CHECK_SHOW_OPTIONS = "CHECK_SHOW_OPTIONS"; | |||
export const TOGGLE_SHOW_OPTIONS = "TOGGLE_SHOW_OPTIONS"; |
@@ -1,4 +1,3 @@ | |||
import { defaultOptions } from "./defaultPassword"; | |||
import * as types from "./mutation-types"; | |||
export default { | |||
@@ -64,17 +63,5 @@ export default { | |||
}, | |||
[types.CLEAN_MESSAGE](state) { | |||
state.message = { text: "", status: "success" }; | |||
}, | |||
[types.CHECK_SHOW_OPTIONS](state) { | |||
let showOptions = false; | |||
for (let key in defaultOptions) { | |||
if (defaultOptions[key] !== state.password[key]) { | |||
showOptions = true; | |||
} | |||
} | |||
state.showOptions = showOptions; | |||
}, | |||
[types.TOGGLE_SHOW_OPTIONS](state) { | |||
state.showOptions = !state.showOptions; | |||
} | |||
}; |
@@ -61,7 +61,7 @@ | |||
<button type="button" | |||
class="btn btn-secondary pull-right showOptions__btn" | |||
v-show="!passwordGenerated" | |||
v-on:click="toggleShowOptions()"> | |||
v-on:click="showOptions =! showOptions"> | |||
<i class="fa fa-sliders"></i> | |||
</button> | |||
</div> | |||
@@ -101,20 +101,20 @@ | |||
<span class="input-group-btn"> | |||
<button type="button" | |||
class="btn btn-secondary showOptions__btn" | |||
v-on:click="toggleShowOptions()"> | |||
v-on:click="showOptions =! showOptions"> | |||
<i class="fa fa-sliders"></i> | |||
</button> | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
<options v-if="showOptions"></options> | |||
<options :options.sync="password" v-if="showOptions || !isDefaultProfile"></options> | |||
</form> | |||
</template> | |||
<script type="text/ecmascript-6"> | |||
import LessPass from 'lesspass'; | |||
import {mapState} from 'vuex'; | |||
import {mapGetters, mapState} from 'vuex'; | |||
import copy from 'copy-text-to-clipboard'; | |||
import RemoveAutoComplete from '../components/RemoveAutoComplete.vue'; | |||
import MasterPassword from '../components/MasterPassword.vue'; | |||
@@ -131,7 +131,8 @@ | |||
Options | |||
}, | |||
computed: { | |||
...mapState(['passwords', 'password', 'passwordURL', 'showOptions']), | |||
...mapState(['passwords']), | |||
...mapGetters(['passwordURL', 'isDefaultProfile']) | |||
}, | |||
beforeMount() { | |||
this.$store.dispatch('getPasswords'); | |||
@@ -145,6 +146,8 @@ | |||
}, | |||
data() { | |||
return { | |||
password: {...this.$store.state.password}, | |||
showOptions: false, | |||
masterPassword: '', | |||
fingerprint: '', | |||
passwordGenerated: '', | |||
@@ -183,9 +186,6 @@ | |||
} | |||
}, | |||
methods: { | |||
toggleShowOptions() { | |||
this.$store.dispatch('toggleShowOptions'); | |||
}, | |||
togglePasswordType(element) { | |||
if (element.type === 'password') { | |||
element.type = 'text'; | |||
@@ -235,8 +235,8 @@ | |||
focusBestInputField() { | |||
const site = this.$refs.site; | |||
const login = this.$refs.login; | |||
const masterPassword = this.$refs.masterPassword.$refs.passwordField; | |||
site.value ? (login.value ? masterPassword.focus() : login.focus()) : site.focus(); | |||
const masterPassword = this.$refs.masterPassword; | |||
site.value ? (login.value ? masterPassword.$refs.passwordField.focus() : login.focus()) : site.focus(); | |||
}, | |||
copyPassword() { | |||
const copied = copy(this.passwordGenerated); | |||
@@ -0,0 +1,20 @@ | |||
var assert = require("assert"); | |||
module.exports = { | |||
"Set default options": function(browser) { | |||
browser | |||
.url(browser.launch_url) | |||
.waitForElementVisible("#site") | |||
.setValue("#site", "lesspass.com") | |||
.setValue("#login", "test@lesspass.com") | |||
.click(".showOptions__btn") | |||
.waitForElementVisible("#saveOptions__btn") | |||
.click("#saveOptions__btn") | |||
.click("#title") | |||
.refresh() | |||
.assert.value("#site", "lesspass.com") | |||
.assert.value("#login", "test@lesspass.com"); | |||
browser.end(); | |||
} | |||
}; |
@@ -41,6 +41,62 @@ test("passwordURL", t => { | |||
); | |||
}); | |||
test("isDefaultProfile", t => { | |||
const state = { | |||
password: { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
}, | |||
defaultPassword: { | |||
login: "", | |||
site: "", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
} | |||
}; | |||
t.true(getters.isDefaultProfile(state)); | |||
}); | |||
test("isDefaultProfile false", t => { | |||
const state = { | |||
password: { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: false, | |||
length: 32, | |||
counter: 1, | |||
version: 1 | |||
}, | |||
defaultPassword: { | |||
login: "", | |||
site: "", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
} | |||
}; | |||
t.false(getters.isDefaultProfile(state)); | |||
}); | |||
test("isAuthenticated", t => { | |||
const state = { | |||
authenticated: true | |||
@@ -438,52 +438,3 @@ test("CLEAN_MESSAGE", t => { | |||
t.is(state.message.text, ""); | |||
t.is(state.message.status, "success"); | |||
}); | |||
test("CHECK_SHOW_OPTIONS false", t => { | |||
const state = { | |||
password: { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 16, | |||
counter: 1, | |||
version: 2 | |||
} | |||
}; | |||
const CHECK_SHOW_OPTIONS = mutations[types.CHECK_SHOW_OPTIONS]; | |||
CHECK_SHOW_OPTIONS(state); | |||
t.false(state.showOptions); | |||
}); | |||
test("CHECK_SHOW_OPTIONS true", t => { | |||
const state = { | |||
password: { | |||
login: "test@example.org", | |||
site: "example.org", | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: false, | |||
length: 32, | |||
counter: 1, | |||
version: 1 | |||
} | |||
}; | |||
const CHECK_SHOW_OPTIONS = mutations[types.CHECK_SHOW_OPTIONS]; | |||
CHECK_SHOW_OPTIONS(state); | |||
t.true(state.showOptions); | |||
}); | |||
test("TOGGLE_SHOW_OPTIONS", t => { | |||
const TOGGLE_SHOW_OPTIONS = mutations[types.TOGGLE_SHOW_OPTIONS]; | |||
const state = { | |||
showOptions: false | |||
}; | |||
TOGGLE_SHOW_OPTIONS(state); | |||
t.true(state.showOptions); | |||
TOGGLE_SHOW_OPTIONS(state); | |||
t.false(state.showOptions); | |||
}); |