@@ -48,10 +48,12 @@ | |||
}, | |||
computed: mapGetters(['version']), | |||
created(){ | |||
this.$store.dispatch('loadPasswordFirstTime'); | |||
const fiveMinutes = 1000 * 60 * 5; | |||
this.$store.dispatch('REFRESH_TOKEN'); | |||
this.$store.dispatch('refreshToken'); | |||
setInterval(() => { | |||
this.$store.dispatch('REFRESH_TOKEN'); | |||
this.$store.dispatch('refreshToken'); | |||
}, fiveMinutes); | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
<style> | |||
.white-link { | |||
.white-link, .text-white { | |||
color: white; | |||
} | |||
@@ -18,20 +18,20 @@ | |||
v-bind:class="{ 'card-warning': version===1, 'card-primary': version===2 }"> | |||
<div class="row"> | |||
<div class="col-6"> | |||
<router-link class="white-link" :to="{ name: 'home'}">LessPass</router-link> | |||
<span v-on:click="saveOrUpdatePassword" class="white-link"> | |||
<i class="fa fa-save ml-1 fa-clickable" v-if="passwordStatus=='DIRTY'"></i> | |||
</span> | |||
<span v-if="passwordStatus=='CREATED' || passwordStatus=='UPDATED'" class="text-success"> | |||
<i class="fa fa-check ml-1 text-success"></i> | |||
</span> | |||
<a href="/" v-on:click="fullReload()" class="white-link">LessPass</a> | |||
</div> | |||
<div class="col-6 text-right"> | |||
<router-link class="white-link ml-1" :to="{ name: 'passwords'}"> | |||
<i class="fa fa-key" aria-hidden="true"></i> | |||
<span class="text-white" v-if="saved"> | |||
<small><i class="fa fa-lg fa-check" aria-hidden="true"></i> saved</small> | |||
</span> | |||
<span v-on:click="saveOrUpdatePassword" class="white-link" v-else> | |||
<i class="fa fa-lg fa-save fa-clickable"></i> | |||
</span> | |||
<router-link class="white-link pl-2" :to="{ name: 'passwords'}"> | |||
<i class="fa fa-lg fa-key" aria-hidden="true"></i> | |||
</router-link> | |||
<button class="white-link ml-1 btn btn-link p-0 m-0" type="button" v-on:click="logout"> | |||
<i class="fa fa-sign-out" aria-hidden="true"></i> | |||
<button class="white-link btn btn-link p-0 m-0 pl-2" type="button" v-on:click="logout"> | |||
<i class="fa fa-lg fa-sign-out" aria-hidden="true"></i> | |||
</button> | |||
</div> | |||
</div> | |||
@@ -44,7 +44,7 @@ | |||
</div> | |||
<div class="col-6 text-right"> | |||
<router-link class="white-link pl-1" :to="{ name: 'login'}"> | |||
<i class="fa fa-user-secret fa-clickable" aria-hidden="true"></i> | |||
<i class="fa fa-lg fa-user-secret fa-clickable" aria-hidden="true"></i> | |||
</router-link> | |||
</div> | |||
</div> | |||
@@ -55,19 +55,32 @@ | |||
import {mapGetters} from 'vuex'; | |||
export default { | |||
data(){ | |||
return { | |||
saved: false | |||
} | |||
}, | |||
methods: { | |||
fullReload(){ | |||
this.$store.dispatch('savePassword', {password: this.defaultPassword}); | |||
}, | |||
logout(){ | |||
this.$store.dispatch('LOGOUT'); | |||
this.$store.dispatch('logout'); | |||
this.$router.push({name: 'home'}); | |||
}, | |||
saveOrUpdatePassword(){ | |||
this.$store.dispatch('SAVE_OR_UPDATE_PASSWORD'); | |||
this.$store.dispatch('saveOrUpdatePassword'); | |||
this.saved = true; | |||
setTimeout(() => { | |||
this.saved = false; | |||
}, 3000); | |||
} | |||
}, | |||
computed: mapGetters([ | |||
'isAuthenticated', | |||
'isGuest', | |||
'passwordStatus', | |||
'password', | |||
'defaultPassword', | |||
'version' | |||
]) | |||
} |
@@ -5,11 +5,11 @@ | |||
<div class="btn-group btn-group-sm"> | |||
<button type="button" class="btn" | |||
v-bind:class="{'btn-primary':version===2,'btn-secondary':version!==2}" v-on:click="setVersion(2)"> | |||
v1 | |||
v2 | |||
</button> | |||
<button type="button" class="btn" | |||
v-bind:class="{'btn-warning':version===1,'btn-secondary':version!==1}" v-on:click="setVersion(1)"> | |||
v2 | |||
v1 | |||
</button> | |||
</div> | |||
</div> | |||
@@ -17,15 +17,12 @@ | |||
<script type="text/ecmascript-6"> | |||
import {mapGetters} from 'vuex'; | |||
export default { | |||
props: { | |||
version: { | |||
type: Number | |||
} | |||
computed: { | |||
...mapGetters(['version']) | |||
}, | |||
methods: { | |||
setVersion(value){ | |||
this.version = value; | |||
this.$store.commit('CHANGE_VERSION', {version: value}); | |||
this.$store.dispatch('saveVersion', {version: value}); | |||
} | |||
} | |||
} | |||
@@ -1,28 +0,0 @@ | |||
export default class Password { | |||
constructor(pwd) { | |||
let password = Object.assign({}, pwd); | |||
this.options = { | |||
uppercase: password.uppercase, | |||
lowercase: password.lowercase, | |||
numbers: password.numbers, | |||
symbols: password.symbols, | |||
length: password.length, | |||
counter: password.counter, | |||
}; | |||
this.password = password; | |||
} | |||
isNewPassword(passwords) { | |||
let isNew = true; | |||
passwords.forEach(pwd => { | |||
if (pwd.site === this.password.site && pwd.login === this.password.login) { | |||
isNew = false; | |||
} | |||
}); | |||
return isNew; | |||
} | |||
json() { | |||
return this.password | |||
} | |||
} |
@@ -1,149 +0,0 @@ | |||
import Vue from 'vue' | |||
import Vuex from 'vuex' | |||
import Auth from './api/auth'; | |||
import HTTP from './api/http'; | |||
import Storage from './api/storage'; | |||
import Password from './domain/password'; | |||
import * as getters from './store/getters'; | |||
Vue.use(Vuex); | |||
const storage = new Storage(); | |||
const auth = new Auth(storage); | |||
const PasswordsAPI = new HTTP('passwords', storage); | |||
const defaultPassword = { | |||
id: '', | |||
site: '', | |||
login: '', | |||
uppercase: true, | |||
lowercase: true, | |||
numbers: true, | |||
symbols: true, | |||
length: 12, | |||
counter: 1 | |||
}; | |||
function getDefaultPasswordProfile(version, passwordProfile = {}) { | |||
if (version === 1) { | |||
return Object.assign({}, defaultPassword, passwordProfile, {version: 1, length: 12}); | |||
} | |||
if (version === 2) { | |||
return Object.assign({}, defaultPassword, passwordProfile, {version: 2, length: 16}); | |||
} | |||
} | |||
const versionLoadedByDefault = storage.json().version || 2; | |||
const state = { | |||
authenticated: auth.isAuthenticated(), | |||
passwordStatus: 'CLEAN', | |||
passwords: [], | |||
baseURL: 'https://lesspass.com', | |||
password: getDefaultPasswordProfile(versionLoadedByDefault), | |||
version: versionLoadedByDefault | |||
}; | |||
export const mutations = { | |||
LOGOUT(state){ | |||
state.authenticated = false; | |||
}, | |||
LOGIN(state){ | |||
state.authenticated = true; | |||
}, | |||
SET_PASSWORDS(state, passwords){ | |||
state.passwords = passwords; | |||
}, | |||
SET_PASSWORD(state, {password}){ | |||
state.password = password; | |||
}, | |||
DELETE_PASSWORD(state, {id}){ | |||
const passwords = state.passwords; | |||
state.passwords = passwords.filter(password => { | |||
return password.id !== id; | |||
}); | |||
if (state.password.id === id) { | |||
state.password = state.defaultPassword; | |||
} | |||
}, | |||
PASSWORD_CLEAN(state){ | |||
setTimeout(() => { | |||
state.passwordStatus = 'CLEAN'; | |||
}, 5000); | |||
}, | |||
CHANGE_PASSWORD_STATUS(state, status){ | |||
state.passwordStatus = status; | |||
}, | |||
SET_DEFAULT_PASSWORD(state){ | |||
state.password = Object.assign({}, defaultPassword) | |||
}, | |||
UPDATE_SITE(state, {site}){ | |||
state.password.site = site | |||
}, | |||
UPDATE_BASE_URL(state, {baseURL}){ | |||
state.baseURL = baseURL | |||
}, | |||
UPDATE_EMAIL(state, {email}){ | |||
state.email = email | |||
}, | |||
CHANGE_VERSION(state, {version}){ | |||
state.password = getDefaultPasswordProfile(version, state.password); | |||
state.version = version; | |||
}, | |||
SAVE_DEFAULT_OPTIONS: (state) => { | |||
const password = new Password(state.password); | |||
const jsonPassword = password.json(); | |||
storage.save({password: jsonPassword, version: jsonPassword.version}); | |||
} | |||
}; | |||
const actions = { | |||
LOGOUT: ({commit}) => { | |||
auth.logout(); | |||
commit('LOGOUT'); | |||
}, | |||
SAVE_OR_UPDATE_PASSWORD: ({commit, state, dispatch}) => { | |||
const password = new Password(state.password); | |||
if (password.isNewPassword(state.passwords)) { | |||
PasswordsAPI.create(password.json()).then(() => { | |||
commit('CHANGE_PASSWORD_STATUS', 'CREATED'); | |||
commit('PASSWORD_CLEAN'); | |||
dispatch('FETCH_PASSWORDS'); | |||
}) | |||
} else { | |||
PasswordsAPI.update(password.json()).then(() => { | |||
commit('CHANGE_PASSWORD_STATUS', 'UPDATED'); | |||
commit('PASSWORD_CLEAN'); | |||
dispatch('FETCH_PASSWORDS'); | |||
}) | |||
} | |||
}, | |||
REFRESH_TOKEN: ({commit}) => { | |||
if (auth.isAuthenticated()) { | |||
auth.refreshToken().catch(() => { | |||
commit('LOGOUT'); | |||
}); | |||
} | |||
}, | |||
FETCH_PASSWORDS: ({commit}) => { | |||
if (auth.isAuthenticated()) { | |||
PasswordsAPI.all().then(response => commit('SET_PASSWORDS', response.data.results)); | |||
} | |||
}, | |||
FETCH_PASSWORD: ({commit}, {id}) => { | |||
PasswordsAPI.get({id}).then(response => commit('SET_PASSWORD', {password: response.data})); | |||
}, | |||
DELETE_PASSWORD: ({commit}, {id}) => { | |||
PasswordsAPI.remove({id}).then(() => { | |||
commit('DELETE_PASSWORD', {id}); | |||
}); | |||
} | |||
}; | |||
export default new Vuex.Store({ | |||
state: Object.assign(state, storage.json()), | |||
getters, | |||
actions, | |||
mutations | |||
}); |
@@ -51,9 +51,13 @@ export const getPassword = ({commit}, payload) => { | |||
export const saveOrUpdatePassword = ({commit, state}) => { | |||
if (state.password && typeof state.password.id === 'undefined') { | |||
Passwords.create(state.password).then(() => { | |||
getPasswords({commit}); | |||
}) | |||
const site = state.password.site; | |||
const login = state.password.login; | |||
if (site || login) { | |||
Passwords.create(state.password).then(() => { | |||
getPasswords({commit}); | |||
}) | |||
} | |||
} else { | |||
Passwords.update(state.password).then(() => { | |||
getPasswords({commit}); | |||
@@ -2,6 +2,8 @@ export const passwords = state => state.passwords; | |||
export const password = state => state.password; | |||
export const defaultPassword = state => state.defaultPassword; | |||
export const isAuthenticated = state => state.authenticated; | |||
export const isGuest = state => !state.authenticated; | |||
@@ -33,5 +33,5 @@ export default new Vuex.Store({ | |||
getters, | |||
actions, | |||
mutations, | |||
plugins: [createPersistedState({key: 'lesspass'})] | |||
plugins: [createPersistedState({key: 'lesspass-store'})] | |||
}); |
@@ -65,7 +65,7 @@ | |||
</div> | |||
</div> | |||
<div class="col-sm-4 col"> | |||
<version-button :version="version"></version-button> | |||
<version-button></version-button> | |||
</div> | |||
</div> | |||
<div class="form-group my-0"> | |||
@@ -135,7 +135,6 @@ | |||
version: this.version, | |||
}; | |||
return LessPass.generatePassword('lesspass.com', this.email, this.password, defaultPasswordProfile).then(generatedPassword => { | |||
console.log(generatedPassword) | |||
this.password = generatedPassword; | |||
}); | |||
} | |||
@@ -174,9 +173,8 @@ | |||
this.auth.login({email, password}, baseURL) | |||
.then(() => { | |||
this.storage.save({baseURL}); | |||
this.$store.commit('LOGIN'); | |||
this.$store.commit('UPDATE_BASE_URL', {baseURL}); | |||
this.$store.commit('UPDATE_EMAIL', {email}); | |||
this.$store.dispatch('login'); | |||
this.$store.dispatch('saveBaseURL', {baseURL}); | |||
this.$router.push({name: 'home'}); | |||
}) | |||
.catch(err => { | |||
@@ -158,20 +158,18 @@ | |||
v-model="password.counter" min="1"> | |||
</div> | |||
<div class="col-4 text-sm-left text-right"> | |||
<version-button :version="password.version" class="mr-1"></version-button> | |||
<version-button class="mr-1"></version-button> | |||
</div> | |||
</div> | |||
<div class="form-group row" v-if="showOptions"> | |||
<div class="col col-sm-8"> | |||
<button type="button" class="btn btn-secondary btn-sm btn-block" v-on:click="saveDefault"> | |||
<span v-if="optionsSaved" class="text-success"> | |||
<i class="fa fa-check" aria-hidden="true"></i> saved | |||
</span> | |||
<span v-else> | |||
save as default options | |||
</span> | |||
</button> | |||
</div> | |||
<div class="form-group" v-if="showOptions"> | |||
<button type="button" class="btn btn-secondary btn-sm" v-on:click="saveDefault"> | |||
<span v-if="optionsSaved" class="text-success"> | |||
<i class="fa fa-check" aria-hidden="true"></i> saved | |||
</span> | |||
<span v-else> | |||
save as default options | |||
</span> | |||
</button> | |||
</div> | |||
<div class="form-group mt-3" v-if="showError"> | |||
<div class="alert alert-danger" role="alert"> | |||
@@ -185,7 +183,6 @@ | |||
import LessPass from 'lesspass'; | |||
import {mapGetters} from 'vuex'; | |||
import Clipboard from 'clipboard'; | |||
import Password from '../domain/password'; | |||
import {getSite} from '../domain/url-parser'; | |||
import RemoveAutoComplete from '../components/RemoveAutoComplete.vue'; | |||
import MasterPassword from '../components/MasterPassword.vue'; | |||
@@ -193,7 +190,7 @@ | |||
import OptionsButton from '../components/OptionsButton.vue'; | |||
function fetchPasswords(store) { | |||
return store.dispatch('FETCH_PASSWORDS') | |||
return store.dispatch('getPasswords') | |||
} | |||
function showTooltip(elem, msg) { | |||
@@ -213,19 +210,20 @@ | |||
VersionButton, | |||
OptionsButton | |||
}, | |||
computed: mapGetters(['passwords', 'password', 'version']), | |||
computed: mapGetters(['passwords', 'password']), | |||
preFetch: fetchPasswords, | |||
beforeMount () { | |||
const id = this.$route.params.id; | |||
if (id) { | |||
this.$store.dispatch('FETCH_PASSWORD', {id}); | |||
this.$store.dispatch('getPassword', {id}); | |||
} else { | |||
fetchPasswords(this.$store); | |||
} | |||
getSite(this.version).then(site => { | |||
getSite(this.password.version).then(site => { | |||
if (site) { | |||
this.$store.commit('UPDATE_SITE', {site}); | |||
this.password.site = site; | |||
this.$store.dispatch('savePassword', {password: this.password}); | |||
} | |||
}); | |||
@@ -269,7 +267,7 @@ | |||
for (let i = 0; i < passwords.length; i++) { | |||
const password = passwords[i]; | |||
if (password.site === site && password.login === login) { | |||
this.$store.commit('SET_PASSWORD', {password}); | |||
this.$store.dispatch('savePassword', {password}); | |||
break; | |||
} | |||
} | |||
@@ -323,7 +321,6 @@ | |||
this.masterPassword = ''; | |||
this.generatedPassword = ''; | |||
this.fingerprint = ''; | |||
this.$store.commit('PASSWORD_CLEAN'); | |||
}, 1000 * seconds); | |||
}, | |||
generatePassword(){ | |||
@@ -348,20 +345,16 @@ | |||
symbols: this.password.symbols, | |||
length: this.password.length, | |||
counter: this.password.counter, | |||
version: this.password.version || this.version, | |||
version: this.password.version, | |||
}; | |||
return LessPass.generatePassword(site, login, masterPassword, passwordProfile).then(generatedPassword => { | |||
this.generatingPassword = false; | |||
this.generatedPassword = generatedPassword; | |||
window.document.getElementById('copyPasswordButton').setAttribute('data-clipboard-text', generatedPassword); | |||
this.$store.commit('CHANGE_PASSWORD_STATUS', 'DIRTY'); | |||
}); | |||
}, | |||
setDefaultVersion(version){ | |||
this.$store.commit('CHANGE_VERSION', {version}); | |||
}, | |||
saveDefault(){ | |||
this.$store.commit('SAVE_DEFAULT_OPTIONS'); | |||
this.$store.dispatch('saveDefaultPassword', {password: this.password}); | |||
this.optionsSaved = true; | |||
setTimeout(() => { | |||
this.optionsSaved = false; | |||
@@ -45,7 +45,7 @@ | |||
import {mapGetters} from 'vuex'; | |||
function fetchPasswords(store) { | |||
return store.dispatch('FETCH_PASSWORDS') | |||
return store.dispatch('getPasswords') | |||
} | |||
export default { | |||
@@ -57,7 +57,7 @@ | |||
}, | |||
components: {DeleteButton}, | |||
computed: { | |||
...mapGetters(['passwords', 'email']), | |||
...mapGetters(['passwords']), | |||
filteredPasswords(){ | |||
return this.passwords.filter(password => { | |||
var loginMatch = password.login.match(new RegExp(this.searchQuery, 'i')); | |||
@@ -72,7 +72,7 @@ | |||
}, | |||
methods: { | |||
deletePassword(password){ | |||
return this.$store.dispatch('DELETE_PASSWORD', {id: password.id}); | |||
return this.$store.dispatch('deletePassword', {id: password.id}); | |||
} | |||
} | |||
} | |||