Browse Source

Migrate lesspass-pure to vue-cli and apply prettier

pull/483/head
Guillaume Vincent 5 years ago
parent
commit
1b7a609612
40 changed files with 4156 additions and 12337 deletions
  1. +3
    -1
      package.json
  2. +3
    -0
      packages/lesspass-pure/babel.config.js
  3. +1
    -0
      packages/lesspass-pure/dist/css/app.c1c55914.css
  4. +10
    -0
      packages/lesspass-pure/dist/css/chunk-vendors.194c8e8d.css
  5. +0
    -0
      packages/lesspass-pure/dist/fonts/fontawesome-webfont.674f50d2.eot
  6. +0
    -0
      packages/lesspass-pure/dist/fonts/fontawesome-webfont.af7ae505.woff2
  7. +0
    -0
      packages/lesspass-pure/dist/fonts/fontawesome-webfont.b06871f2.ttf
  8. +0
    -0
      packages/lesspass-pure/dist/fonts/fontawesome-webfont.fee66e71.woff
  9. +0
    -0
      packages/lesspass-pure/dist/img/fontawesome-webfont.912ec66d.svg
  10. +2
    -22
      packages/lesspass-pure/dist/index.html
  11. +2
    -0
      packages/lesspass-pure/dist/js/app.6752e6e6.js
  12. +1
    -0
      packages/lesspass-pure/dist/js/app.6752e6e6.js.map
  13. +34
    -0
      packages/lesspass-pure/dist/js/chunk-vendors.c12ad38e.js
  14. +1
    -0
      packages/lesspass-pure/dist/js/chunk-vendors.c12ad38e.js.map
  15. +0
    -9145
      packages/lesspass-pure/dist/lesspass.min.css
  16. +0
    -1
      packages/lesspass-pure/dist/lesspass.min.css.map
  17. +0
    -20
      packages/lesspass-pure/dist/lesspass.min.js
  18. +21
    -26
      packages/lesspass-pure/package.json
  19. +0
    -0
      packages/lesspass-pure/public/favicon.ico
  20. +8
    -8
      packages/lesspass-pure/public/index.html
  21. +0
    -100
      packages/lesspass-pure/src/LessPass.scss
  22. +81
    -16
      packages/lesspass-pure/src/LessPass.vue
  23. +61
    -64
      packages/lesspass-pure/src/components/Avatar.vue
  24. +69
    -64
      packages/lesspass-pure/src/components/InputSite.vue
  25. +182
    -115
      packages/lesspass-pure/src/components/MasterPassword.vue
  26. +39
    -38
      packages/lesspass-pure/src/components/Message.vue
  27. +0
    -1
      packages/lesspass-pure/src/components/Options.vue
  28. +41
    -39
      packages/lesspass-pure/src/components/PasswordProfile.vue
  29. +10
    -5
      packages/lesspass-pure/src/main.js
  30. +2
    -2
      packages/lesspass-pure/src/router.js
  31. +1
    -1
      packages/lesspass-pure/src/services/url-parser.js
  32. +1
    -1
      packages/lesspass-pure/src/store/actions.js
  33. +131
    -89
      packages/lesspass-pure/src/views/Login.vue
  34. +77
    -68
      packages/lesspass-pure/src/views/PasswordGenerator.vue
  35. +62
    -50
      packages/lesspass-pure/src/views/Passwords.vue
  36. +0
    -1
      packages/lesspass-pure/src/views/Settings.vue
  37. +0
    -55
      packages/lesspass-pure/webpack.common.js
  38. +0
    -13
      packages/lesspass-pure/webpack.dev.js
  39. +0
    -9
      packages/lesspass-pure/webpack.prod.js
  40. +3313
    -2383
      yarn.lock

+ 3
- 1
package.json View File

@@ -19,9 +19,11 @@
"@babel/plugin-transform-react-jsx": "^7.3.0", "@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/preset-env": "^7.5.4", "@babel/preset-env": "^7.5.4",
"@babel/register": "^7.4.4", "@babel/register": "^7.4.4",
"@vue/cli-plugin-babel": "^4.0.4",
"@vue/cli-service": "^4.0.4",
"@vue/test-utils": "^1.0.0-beta.29", "@vue/test-utils": "^1.0.0-beta.29",
"babel-core": "7.0.0-bridge.0",
"axios-mock-adapter": "^1.17.0", "axios-mock-adapter": "^1.17.0",
"babel-core": "7.0.0-bridge.0",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"chrome-webstore-upload-cli": "^1.2.0", "chrome-webstore-upload-cli": "^1.2.0",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",


+ 3
- 0
packages/lesspass-pure/babel.config.js View File

@@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};

+ 1
- 0
packages/lesspass-pure/dist/css/app.c1c55914.css View File

@@ -0,0 +1 @@
#menu .text-white,#menu .white-link{color:inherit}#menu .white-link:active,#menu .white-link:focus,#menu .white-link:hover{text-decoration:none;color:inherit}.card-inverse{background-color:#333;border-color:#333}.fade-enter-active{transition:opacity .5s}.fade-leave-active{transition:opacity 2s}.fade-enter,.fade-leave-to{opacity:0}#message{position:absolute;top:49px;left:0;right:0;z-index:20}.close-notification{float:right;position:absolute;top:0;right:1em;cursor:pointer}.lesspass--unbordered #lesspass{border:none}.lesspass--full-width #lesspass{max-width:none!important}#lesspass{color:#464646;max-width:420px}.lesspass__inner-box{min-height:240px}@media (max-width:419px){.lesspass__inner-box{border:none}}#lesspass,#lesspass *{border-radius:0!important}.pointer,button{cursor:pointer}.inner-addon i{position:absolute;padding:10px;pointer-events:none;z-index:10}.inner-addon{position:relative}.left-addon i{left:0}.right-addon i{right:0}.left-addon input{padding-left:30px}.right-addon input{padding-right:30px}.border-blue{border-color:#007bff!important}#fingerprint{min-width:90px;text-align:center;background-color:transparent;color:#fff}#fingerprint i{color:#000;position:relative;padding:0;text-shadow:1px 1px 0 #fff;font-size:1.3em}#signInButton{border-right:none}#registerButton{border-left:none}.awesomplete mark{background-color:transparent!important;padding:0;margin:0;color:inherit}#options input[type=number]{-moz-appearance:textfield}#options input[type=number]::-webkit-inner-spin-button,#options input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}#generated-password{font-family:Consolas,Menlo,Monaco,Courier New,monospace,sans-serif}div.awesomplete{display:block}div.awesomplete>ul{z-index:11}.passwordProfile__avatar{display:inline-block;width:2rem;height:2rem;text-align:center;line-height:2rem;margin-right:1em;text-transform:uppercase;color:#fff;font-family:monospace}.passwordProfile{display:flex;cursor:pointer;margin-bottom:1rem;justify-content:space-between;align-items:center}.passwordProfile__info{display:flex;align-items:center;flex-grow:1}.passwordProfile__meta{font-size:.8rem;line-height:1rem;flex-grow:1}#passwords__list{min-height:11rem}#passwords__pagination .pagination{margin-bottom:0}#passwords__pagination .page-link{cursor:pointer}

+ 10
- 0
packages/lesspass-pure/dist/css/chunk-vendors.194c8e8d.css
File diff suppressed because it is too large
View File


packages/lesspass-pure/dist/674f50d287a8c48dc19ba404d20fe713.eot → packages/lesspass-pure/dist/fonts/fontawesome-webfont.674f50d2.eot View File


packages/lesspass-pure/dist/af7ae505a9eed503f8b8e6982036873e.woff2 → packages/lesspass-pure/dist/fonts/fontawesome-webfont.af7ae505.woff2 View File


packages/lesspass-pure/dist/b06871f281fee6b241d60582ae9369b9.ttf → packages/lesspass-pure/dist/fonts/fontawesome-webfont.b06871f2.ttf View File


packages/lesspass-pure/dist/fee66e712a8a08eef5805a46892932ad.woff → packages/lesspass-pure/dist/fonts/fontawesome-webfont.fee66e71.woff View File


packages/lesspass-pure/dist/fontawesome-webfont.svg → packages/lesspass-pure/dist/img/fontawesome-webfont.912ec66d.svg View File


+ 2
- 22
packages/lesspass-pure/dist/index.html View File

@@ -1,16 +1,4 @@
<!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="shortcut icon" href="dist/favicon.ico" />
<style>
div.center {
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>LessPass</title><style>div.center {
max-width: 420px; max-width: 420px;
display: block; display: block;
margin-left: auto; margin-left: auto;
@@ -21,12 +9,4 @@
#lesspass { #lesspass {
margin-top: 3em; margin-top: 3em;
} }
}
</style>
<link href="lesspass.min.css" rel="stylesheet"></head>
<body>
<div class="center lesspass--full-width">
<div id="lesspass"></div>
</div>
<script type="text/javascript" src="lesspass.min.js"></script></body>
</html>
}</style><link href=/css/app.c1c55914.css rel=preload as=style><link href=/css/chunk-vendors.194c8e8d.css rel=preload as=style><link href=/js/app.6752e6e6.js rel=preload as=script><link href=/js/chunk-vendors.c12ad38e.js rel=preload as=script><link href=/css/chunk-vendors.194c8e8d.css rel=stylesheet><link href=/css/app.c1c55914.css rel=stylesheet></head><body><noscript><strong>We're sorry but LessPass doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div class="center lesspass--full-width"><div id=lesspass></div></div><script src=/js/chunk-vendors.c12ad38e.js></script><script src=/js/app.6752e6e6.js></script></body></html>

+ 2
- 0
packages/lesspass-pure/dist/js/app.6752e6e6.js
File diff suppressed because it is too large
View File


+ 1
- 0
packages/lesspass-pure/dist/js/app.6752e6e6.js.map
File diff suppressed because it is too large
View File


+ 34
- 0
packages/lesspass-pure/dist/js/chunk-vendors.c12ad38e.js
File diff suppressed because it is too large
View File


+ 1
- 0
packages/lesspass-pure/dist/js/chunk-vendors.c12ad38e.js.map
File diff suppressed because it is too large
View File


+ 0
- 9145
packages/lesspass-pure/dist/lesspass.min.css
File diff suppressed because it is too large
View File


+ 0
- 1
packages/lesspass-pure/dist/lesspass.min.css.map
File diff suppressed because it is too large
View File


+ 0
- 20
packages/lesspass-pure/dist/lesspass.min.js
File diff suppressed because it is too large
View File


+ 21
- 26
packages/lesspass-pure/package.json View File

@@ -5,23 +5,14 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"author": "Guillaume Vincent <guillaume@oslab.fr>", "author": "Guillaume Vincent <guillaume@oslab.fr>",
"scripts": { "scripts": {
"start": "http-server dist -p 8080",
"build": "rm -rf dist && NODE_ENV=production webpack --mode=production --config webpack.prod.js",
"start": "vue-cli-service serve --port 8000",
"build": "vue-cli-service build",
"build:i18n": "cd scripts && node buildI18n.js", "build:i18n": "cd scripts && node buildI18n.js",
"dev": "webpack-dev-server --config webpack.dev.js",
"cypress:run": "cypress run", "cypress:run": "cypress run",
"cypress:open": "cypress open", "cypress:open": "cypress open",
"test": "npm run test:unit && npm run test:e2e", "test": "npm run test:unit && npm run test:e2e",
"test:unit": "jest", "test:unit": "jest",
"test:e2e": "start-server-and-test dev http://localhost:8000 cypress:run"
},
"babel": {
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread"
]
"test:e2e": "start-server-and-test start http://localhost:8000 cypress:run"
}, },
"jest": { "jest": {
"testPathIgnorePatterns": [ "testPathIgnorePatterns": [
@@ -37,24 +28,28 @@
} }
}, },
"dependencies": { "dependencies": {
"@oslab/atob": "0.1.0",
"@oslab/btoa": "0.1.0",
"awesomplete": "^1.1.4",
"@oslab/atob": "^0.1.0",
"@oslab/btoa": "^0.1.0",
"awesomplete": "^1.1.5",
"axios": "^0.19.0", "axios": "^0.19.0",
"balloon-css": "^1.0.2",
"balloon-css": "^1.0.3",
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
"copy-text-to-clipboard": "^2.0.0",
"font-awesome": "4.7.0",
"jwt-decode": "2.2.0",
"lesspass": "9.0.2",
"lodash.debounce": "4.0.8",
"lodash.uniqby": "4.7.0",
"copy-text-to-clipboard": "^2.1.1",
"core-js": "^3.1.2",
"font-awesome": "^4.7.0",
"jwt-decode": "^2.2.0",
"lesspass": "^9.0.0",
"lodash": "^4.17.15",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-polyglot": "^2.0.1", "vue-polyglot": "^2.0.1",
"vue-router": "^3.0.2",
"vue-router": "^3.1.3",
"vuejs-paginate": "^2.1.0", "vuejs-paginate": "^2.1.0",
"vuex": "^3.1.0",
"vuex": "^3.1.1",
"vuex-persistedstate": "^2.5.4", "vuex-persistedstate": "^2.5.4",
"vuex-router-sync": "5.0.0"
}
"vuex-router-sync": "^5.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
} }

packages/lesspass-pure/src/images/favicon.ico → packages/lesspass-pure/public/favicon.ico View File


packages/lesspass-pure/src/index.html → packages/lesspass-pure/public/index.html View File

@@ -1,14 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html>
<html lang="en">
<head> <head>
<meta charset="utf-8" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>LessPass</title> <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="shortcut icon" href="dist/favicon.ico" />
<style> <style>
div.center { div.center {
max-width: 420px; max-width: 420px;
@@ -25,6 +22,9 @@
</style> </style>
</head> </head>
<body> <body>
<noscript>
<strong>We're sorry but LessPass doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div class="center lesspass--full-width"> <div class="center lesspass--full-width">
<div id="lesspass"></div> <div id="lesspass"></div>
</div> </div>

+ 0
- 100
packages/lesspass-pure/src/LessPass.scss View File

@@ -1,100 +0,0 @@
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/images";
//@import "~bootstrap/scss/code";
@import "~bootstrap/scss/grid";
@import "~bootstrap/scss/tables";
@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/transitions";
@import "~bootstrap/scss/dropdown";
@import "~bootstrap/scss/button-group";
@import "~bootstrap/scss/input-group";
@import "~bootstrap/scss/custom-forms";
@import "~bootstrap/scss/nav";
@import "~bootstrap/scss/navbar";
@import "~bootstrap/scss/card";
//@import "~bootstrap/scss/breadcrumb";
@import "~bootstrap/scss/pagination";
@import "~bootstrap/scss/badge";
@import "~bootstrap/scss/jumbotron";
@import "~bootstrap/scss/alert";
//@import "~bootstrap/scss/progress";
@import "~bootstrap/scss/media";
@import "~bootstrap/scss/list-group";
@import "~bootstrap/scss/close";
//@import "~bootstrap/scss/modal";
//@import "~bootstrap/scss/tooltip";
//@import "~bootstrap/scss/popover";
//@import "~bootstrap/scss/carousel";
@import "~bootstrap/scss/utilities";
//@import "~bootstrap/scss/print";
@import '~font-awesome/css/font-awesome.css';
@import '~balloon-css/balloon.css';
@import '~awesomplete/awesomplete.css';

.lesspass--unbordered #lesspass{
border: none;
}

.lesspass--full-width #lesspass {
max-width: none !important;
}

#lesspass {
color: #464646;
max-width: 420px;
}

.lesspass__inner-box {
min-height: 240px;
}

@media (max-width: 419px) {
.lesspass__inner-box {
border: none;
}
}

#lesspass, #lesspass * {
border-radius: 0 !important;
}

button, .pointer {
cursor: pointer;
}

.inner-addon i {
position: absolute;
padding: 10px;
pointer-events: none;
z-index: 10;
}

.inner-addon {
position: relative;
}

.left-addon i {
left: 0;
}

.right-addon i {
right: 0;
}

.left-addon input {
padding-left: 30px;
}

.right-addon input {
padding-right: 30px;
}

.border-blue {
border-color: $blue !important;
}

+ 81
- 16
packages/lesspass-pure/src/LessPass.vue View File

@@ -1,3 +1,68 @@
<style>
.lesspass--unbordered #lesspass {
border: none;
}

.lesspass--full-width #lesspass {
max-width: none !important;
}

#lesspass {
color: #464646;
max-width: 420px;
}

.lesspass__inner-box {
min-height: 240px;
}

@media (max-width: 419px) {
.lesspass__inner-box {
border: none;
}
}

#lesspass,
#lesspass * {
border-radius: 0 !important;
}

button,
.pointer {
cursor: pointer;
}

.inner-addon i {
position: absolute;
padding: 10px;
pointer-events: none;
z-index: 10;
}

.inner-addon {
position: relative;
}

.left-addon i {
left: 0;
}

.right-addon i {
right: 0;
}

.left-addon input {
padding-left: 30px;
}

.right-addon input {
padding-right: 30px;
}

.border-blue {
border-color: #007bff !important;
}
</style>
<template> <template>
<div id="lesspass" class="card"> <div id="lesspass" class="card">
<lesspass-menu></lesspass-menu> <lesspass-menu></lesspass-menu>
@@ -7,21 +72,21 @@
</div> </div>
</div> </div>
</template> </template>
<script type="text/ecmascript-6">
import './LessPass.scss';
import Menu from './components/Menu.vue';
import Message from './components/Message.vue';

export default {
name: 'LessPass',
components: {
'lesspass-menu': Menu,
'lesspass-message': Message
},
created(){
this.$store.dispatch('cleanMessage');
this.$store.dispatch('refreshToken');
this.$store.dispatch('resetPassword');
}
<script>
import Menu from "./components/Menu.vue";
import Message from "./components/Message.vue";

export default {
name: "lesspass",
components: {
"lesspass-menu": Menu,
"lesspass-message": Message
},
created() {
this.$store.dispatch("cleanMessage");
this.$store.dispatch("refreshToken");
this.$store.dispatch("resetPassword");
} }
};
</script> </script>


+ 61
- 64
packages/lesspass-pure/src/components/Avatar.vue View File

@@ -1,74 +1,71 @@
<style> <style>
.passwordProfile__avatar {
display: inline-block;
width: 2rem;
height: 2rem;
text-align: center;
line-height: 2rem;
margin-right: 1em;
text-transform: uppercase;
color: white;
font-family: monospace;
}
.passwordProfile__avatar {
display: inline-block;
width: 2rem;
height: 2rem;
text-align: center;
line-height: 2rem;
margin-right: 1em;
text-transform: uppercase;
color: white;
font-family: monospace;
}
</style> </style>
<template> <template>
<div v-bind:style="avatarStyle"
class="passwordProfile__avatar">
{{firstLetter}}
</div>
<div v-bind:style="avatarStyle" class="passwordProfile__avatar">{{firstLetter}}</div>
</template> </template>
<script type="text/ecmascript-6"> <script type="text/ecmascript-6">
export default {
name: 'avatar',
props: {
name: {
type: String,
required: true
}
},
data() {
export default {
name: "avatar",
props: {
name: {
type: String,
required: true
}
},
data() {
return {
alphabetColors: {
a: "#5A8770",
b: "#B2B7BB",
c: "#6FA9AB",
d: "#F5AF29",
e: "#0088B9",
f: "#F18636",
g: "#D93A37",
h: "#A6B12E",
i: "#5C9BBC",
j: "#F5888D",
k: "#9A89B5",
l: "#407887",
m: "#9A89B5",
n: "#5A8770",
o: "#D33F33",
p: "#A2B01F",
q: "#F0B126",
r: "#0087BF",
s: "#F18636",
t: "#0087BF",
u: "#B2B7BB",
v: "#72ACAE",
w: "#9C8AB4",
x: "#5A8770",
y: "#EEB424",
z: "#407887"
},
firstLetter: ""
};
},
mounted() {
this.firstLetter = this.$props.name.charAt(0);
},
computed: {
avatarStyle: function() {
return { return {
alphabetColors: {
a: "#5A8770",
b: "#B2B7BB",
c: "#6FA9AB",
d: "#F5AF29",
e: "#0088B9",
f: "#F18636",
g: "#D93A37",
h: "#A6B12E",
i: "#5C9BBC",
j: "#F5888D",
k: "#9A89B5",
l: "#407887",
m: "#9A89B5",
n: "#5A8770",
o: "#D33F33",
p: "#A2B01F",
q: "#F0B126",
r: "#0087BF",
s: "#F18636",
t: "#0087BF",
u: "#B2B7BB",
v: "#72ACAE",
w: "#9C8AB4",
x: "#5A8770",
y: "#EEB424",
z: "#407887"
},
firstLetter: ''
}
},
mounted() {
this.firstLetter = this.$props.name.charAt(0);
},
computed: {
avatarStyle: function() {
return {
backgroundColor: this.alphabetColors[this.firstLetter] || '#5A8770'
}
}
backgroundColor: this.alphabetColors[this.firstLetter] || "#5A8770"
};
} }
} }
};
</script> </script>



+ 69
- 64
packages/lesspass-pure/src/components/InputSite.vue View File

@@ -1,10 +1,10 @@
<style> <style>
.awesomplete mark {
background-color: transparent !important;
padding: 0;
margin: 0;
color: inherit;
}
.awesomplete mark {
background-color: transparent !important;
padding: 0;
margin: 0;
color: inherit;
}
</style> </style>
<template> <template>
<div class="inputSite"> <div class="inputSite">
@@ -20,69 +20,74 @@
autocorrect="off" autocorrect="off"
autocapitalize="none" autocapitalize="none"
v-bind:placeholder="label" v-bind:placeholder="label"
v-model="site">
v-model="site"
/>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import Awesomplete from "awesomplete";
import {getSuggestions} from "../services/url-parser";
import Awesomplete from "awesomplete";
import { getSuggestions } from "../services/url-parser";


export default {
name: "inputSite",
props: {
value: String,
label: String,
passwords: {
type: Array,
default: () => []
export default {
name: "inputSite",
props: {
value: String,
label: String,
passwords: {
type: Array,
default: () => []
}
},
mounted() {
this.awesomplete = new Awesomplete(this.$refs.siteField);
this.awesomplete.item = (element, input) => {
let item = Awesomplete.ITEM(element.value.site, input);
item.innerHTML += ` ${element.value.login}`;
return item;
};
this.awesomplete.filter = (site, input) => {
return (
Awesomplete.FILTER_CONTAINS(site, input) ||
Awesomplete.FILTER_CONTAINS(input, site)
);
};
this.awesomplete.data = data => {
return { label: data.site, value: data };
};
this.awesomplete.replace = password => {
this.$refs.siteField.value = password.label;
if (password.value.suggestion) {
this.$emit("suggestionSelected", password.value.site);
} else {
this.$emit("passwordProfileSelected", password.value);
} }
},
mounted() {
this.awesomplete = new Awesomplete(this.$refs.siteField);
this.awesomplete.item = (element, input) => {
let item = Awesomplete.ITEM(element.value.site, input);
item.innerHTML += ` ${element.value.login}`;
return item;
};
this.awesomplete.filter = (site, input) => {
return Awesomplete.FILTER_CONTAINS(site, input) ||
Awesomplete.FILTER_CONTAINS(input, site);
};
this.awesomplete.data = data => {
return {label: data.site, value: data}
};
this.awesomplete.replace = password => {
this.$refs.siteField.value = password.label;
if (password.value.suggestion) {
this.$emit("suggestionSelected", password.value.site);
} else {
this.$emit("passwordProfileSelected", password.value);
}
};
this.awesomplete.sort = (a,b) => {
return a.value.site.localeCompare(b.value.site) ||
a.value.login.localeCompare(b.value.login);
};
this.awesomplete.sort = (a, b) => {
return (
a.value.site.localeCompare(b.value.site) ||
a.value.login.localeCompare(b.value.login)
);
};
},
computed: {
site: {
get: function() {
return this.value;
},
set: function(newValue) {
this.$emit("input", newValue);
} }
},
computed: {
site: {
get: function () {
return this.value;
},
set: function (newValue) {
this.$emit("input", newValue);
}
}
},
watch: {
site: function (newValue, _) {
const suggestions = getSuggestions(newValue).map(suggestion => {
return {site: suggestion, suggestion: true, login: ''}
});
this.awesomplete.list = this.passwords.concat(suggestions);
}
},
methods: {}
};
}
},
watch: {
site: function (newValue) {
const suggestions = getSuggestions(newValue).map(suggestion => {
return { site: suggestion, suggestion: true, login: "" };
});
this.awesomplete.list = this.passwords.concat(suggestions);
}
},
methods: {}
};
</script> </script>

+ 182
- 115
packages/lesspass-pure/src/components/MasterPassword.vue View File

@@ -1,141 +1,208 @@
<style> <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> </style>
<template> <template>
<div class="masterPassword"> <div class="masterPassword">
<div class="input-group inner-addon left-addon"> <div class="input-group inner-addon left-addon">
<label for="passwordField" class="sr-only">
{{ label }}
</label>
<label for="passwordField" class="sr-only">{{ label }}</label>
<i class="fa fa-lock"></i> <i class="fa fa-lock"></i>
<input id="passwordField"
name="passwordField"
ref="passwordField"
type="password"
class="form-control"
autocorrect="off"
autocapitalize="none"
v-bind:value="value"
v-bind:placeholder="label"
v-on:input="updateValue($event.target.value)"
v-on:keyup.enter="$emit('keyupEnter')">
<span class="input-group-btn"
v-if="fingerprint && value"
v-on:click="togglePasswordType">
<input
id="passwordField"
name="passwordField"
ref="passwordField"
type="password"
class="form-control"
autocorrect="off"
autocapitalize="none"
v-bind:value="value"
v-bind:placeholder="label"
v-on:input="updateValue($event.target.value)"
v-on:keyup.enter="$emit('keyupEnter')"
/>
<span class="input-group-btn" v-if="fingerprint && value" v-on:click="togglePasswordType">
<button id="fingerprint" class="btn" type="button" tabindex="-1"> <button id="fingerprint" class="btn" type="button" tabindex="-1">
<small>
<i class="fa fa-fw" v-bind:class="[icon1]" v-bind:style="{ color: color1 }"></i>
<i class="fa fa-fw" v-bind:class="[icon2]" v-bind:style="{ color: color2 }"></i>
<i class="fa fa-fw" v-bind:class="[icon3]" v-bind:style="{ color: color3 }"></i>
</small>
<small>
<i class="fa fa-fw" v-bind:class="[icon1]" v-bind:style="{ color: color1 }"></i>
<i class="fa fa-fw" v-bind:class="[icon2]" v-bind:style="{ color: color2 }"></i>
<i class="fa fa-fw" v-bind:class="[icon3]" v-bind:style="{ color: color3 }"></i>
</small>
</button> </button>
</span> </span>
</div> </div>
<button id="encryptMasterPassword__btn"
type="button"
class="btn btn-link btn-sm p-0"
v-if="showEncryptButton"
v-on:click="encryptMasterPassword()"
v-bind:class="{'disabled': email === ''}">
<button
id="encryptMasterPassword__btn"
type="button"
class="btn btn-link btn-sm p-0"
v-if="showEncryptButton"
v-on:click="encryptMasterPassword()"
v-bind:class="{'disabled': email === ''}"
>
<small>{{ EncryptButtonText }}</small> <small>{{ EncryptButtonText }}</small>
</button> </button>
</div> </div>
</template> </template>
<script> <script>
import LessPass from 'lesspass';
import debounce from 'lodash.debounce';
import defaultPasswordProfile from '../store/defaultPassword';
import LessPass from "lesspass";
import { debounce } from "lodash";
import defaultPasswordProfile from "../store/defaultPassword";


export default {
name: 'masterPassword',
props: {
value: String,
label: String,
email: String,
showEncryptButton: {
type: Boolean,
default: false
},
EncryptButtonText: String
export default {
name: "masterPassword",
props: {
value: String,
label: String,
email: String,
showEncryptButton: {
type: Boolean,
default: false
}, },
data(){
return {
fingerprint: null,
icon1: '',
icon2: '',
icon3: '',
color1: '',
color2: '',
color3: ''
EncryptButtonText: String
},
data() {
return {
fingerprint: null,
icon1: "",
icon2: "",
icon3: "",
color1: "",
color2: "",
color3: ""
};
},
methods: {
updateValue(newPassword) {
const fakePassword = Math.random()
.toString(36)
.substring(7);
this.setFingerprint(fakePassword);
this.showRealFingerprint(newPassword);
this.$refs.passwordField.value = newPassword;
this.$emit("input", newPassword);
},
togglePasswordType() {
const element = this.$refs.passwordField;
if (element.type === "password") {
element.type = "text";
} else {
element.type = "password";
} }
}, },
methods: {
updateValue(newPassword){
const fakePassword = Math.random().toString(36).substring(7);
this.setFingerprint(fakePassword);
this.showRealFingerprint(newPassword);
this.$refs.passwordField.value = newPassword;
this.$emit('input', newPassword);
},
togglePasswordType(){
const element = this.$refs.passwordField;
if (element.type === 'password') {
element.type = 'text';
} else {
element.type = 'password';
}
},
hidePassword(){
this.$refs.passwordField.type = 'password';
},
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];
},
setFingerprint(password){
LessPass.createFingerprint(password).then(fingerprint => {
this.icon1 = fingerprint[0].icon;
this.color1 = fingerprint[0].color;
hidePassword() {
this.$refs.passwordField.type = "password";
},
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];
},
setFingerprint(password) {
LessPass.createFingerprint(password).then(fingerprint => {
this.icon1 = fingerprint[0].icon;
this.color1 = fingerprint[0].color;


this.icon2 = fingerprint[1].icon;
this.color2 = fingerprint[1].color;
this.icon2 = fingerprint[1].icon;
this.color2 = fingerprint[1].color;


this.icon3 = fingerprint[2].icon;
this.color3 = fingerprint[2].color;
this.icon3 = fingerprint[2].icon;
this.color3 = fingerprint[2].color;


this.fingerprint = fingerprint;
});
},
showRealFingerprint: debounce(function(password) {
this.setFingerprint(password);
}, 500),
encryptMasterPassword(){
const password = this.$refs.passwordField.value;
return LessPass
.generatePassword('lesspass.com', this.email, password, defaultPasswordProfile)
.then(generatedPassword => {
this.updateValue(generatedPassword);
});
}
this.fingerprint = fingerprint;
});
},
showRealFingerprint: debounce(function(password) {
this.setFingerprint(password);
}, 500),
encryptMasterPassword() {
const password = this.$refs.passwordField.value;
return LessPass.generatePassword(
"lesspass.com",
this.email,
password,
defaultPasswordProfile
).then(generatedPassword => {
this.updateValue(generatedPassword);
});
} }
} }
};
</script> </script>

+ 39
- 38
packages/lesspass-pure/src/components/Message.vue View File

@@ -1,38 +1,41 @@
<style> <style>
.fade-enter-active {
transition: opacity .5s
}
.fade-enter-active {
transition: opacity 0.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> </style>
<template> <template>
<div id="message" v-on:click="keepMessage"> <div id="message" v-on:click="keepMessage">
<transition name="fade"> <transition name="fade">
<div v-if="message.text"> <div v-if="message.text">
<div class="card-header text-white"
v-bind:class="{ 'bg-warning': message.status==='warning', 'bg-danger': message.status==='error', 'bg-success': message.status==='success' }">
<div
class="card-header text-white"
v-bind:class="{ 'bg-warning': message.status==='warning', 'bg-danger': message.status==='error', 'bg-success': message.status==='success' }"
>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<small>{{message.text}}</small> <small>{{message.text}}</small>
@@ -47,20 +50,18 @@
</div> </div>
</template> </template>
<script type="text/ecmascript-6"> <script type="text/ecmascript-6">
import {mapState} from 'vuex';
import message from '../services/message';
import { mapState } from "vuex";
import message from "../services/message";


export default {
computed: mapState([
'message'
]),
methods: {
keepMessage(){
message.keepMessage();
},
hideMessage(){
message.hideMessage();
}
export default {
computed: mapState(["message"]),
methods: {
keepMessage() {
message.keepMessage();
},
hideMessage() {
message.hideMessage();
} }
} }
};
</script> </script>

+ 0
- 1
packages/lesspass-pure/src/components/Options.vue View File

@@ -109,7 +109,6 @@
</template> </template>


<script type="text/ecmascript-6"> <script type="text/ecmascript-6">
import message from '../services/message';
import {increment, decrement} from "../services/form-validator"; import {increment, decrement} from "../services/form-validator";
import { mapState} from 'vuex'; import { mapState} from 'vuex';




+ 41
- 39
packages/lesspass-pure/src/components/PasswordProfile.vue View File

@@ -1,23 +1,23 @@
<style> <style>
.passwordProfile {
display: flex;
cursor: pointer;
margin-bottom: 1rem;
justify-content: space-between;
align-items: center;
}
.passwordProfile {
display: flex;
cursor: pointer;
margin-bottom: 1rem;
justify-content: space-between;
align-items: center;
}


.passwordProfile__info {
display: flex;
align-items: center;
flex-grow: 1;
}
.passwordProfile__info {
display: flex;
align-items: center;
flex-grow: 1;
}


.passwordProfile__meta {
font-size: 0.8rem;
line-height: 1rem;
flex-grow: 1;
}
.passwordProfile__meta {
font-size: 0.8rem;
line-height: 1rem;
flex-grow: 1;
}
</style> </style>
<template> <template>
<div class="passwordProfile"> <div class="passwordProfile">
@@ -25,39 +25,41 @@
<avatar v-bind:name="password.site"></avatar> <avatar v-bind:name="password.site"></avatar>
<div class="passwordProfile__meta"> <div class="passwordProfile__meta">
<b>{{password.site}}</b> <b>{{password.site}}</b>
<br>
<br />
{{password.login}} {{password.login}}
</div> </div>
</div> </div>
<div class="passwordProfile__actions"> <div class="passwordProfile__actions">
<i class="passwordProfile__delete-icon fa fa-trash fa-fw text-danger"
v-on:click="deletePassword()"></i>
<i
class="passwordProfile__delete-icon fa fa-trash fa-fw text-danger"
v-on:click="deletePassword()"
></i>
</div> </div>
</div> </div>
</template> </template>
<script type="text/ecmascript-6"> <script type="text/ecmascript-6">
import Avatar from './Avatar.vue';
import Avatar from "./Avatar.vue";


export default {
name: 'passwordProfile',
props: {
password: {
type: Object,
required: true
}
},
components: {
Avatar
export default {
name: "passwordProfile",
props: {
password: {
type: Object,
required: true
}
},
components: {
Avatar
},
methods: {
deletePassword() {
this.$store.dispatch("deletePassword", { id: this.password.id });
}, },
methods: {
deletePassword() {
this.$store.dispatch('deletePassword', {id: this.password.id});
},
setPassword() {
this.$store.dispatch('savePassword', {password: this.password});
this.$router.push({name: 'home'});
}
setPassword() {
this.$store.dispatch("savePassword", { password: this.password });
this.$router.push({ name: "home" });
} }
} }
};
</script> </script>



+ 10
- 5
packages/lesspass-pure/src/main.js View File

@@ -1,10 +1,14 @@
import Vue from "vue"; import Vue from "vue";
import "./images/favicon.ico";
import LessPass from "./LessPass.vue";
import Polyglot from "vue-polyglot";
import { sync } from "vuex-router-sync"; import { sync } from "vuex-router-sync";

import LessPass from "./LessPass.vue";
import store from "./store"; import store from "./store";
import router from "./router"; import router from "./router";
import Polyglot from "vue-polyglot";
import "bootstrap/dist/css/bootstrap.css";
import "font-awesome/css/font-awesome.css";
import "balloon-css/balloon.css";
import "awesomplete/awesomplete.css";


import frLocales from "./i18n/fr.json"; import frLocales from "./i18n/fr.json";
import esLocales from "./i18n/es.json"; import esLocales from "./i18n/es.json";
@@ -31,9 +35,10 @@ Vue.locales({


sync(store, router); sync(store, router);


Vue.config.productionTip = true;

new Vue({ new Vue({
el: "#lesspass",
store, store,
router, router,
render: h => h(LessPass) render: h => h(LessPass)
});
}).$mount("#lesspass");

+ 2
- 2
packages/lesspass-pure/src/router.js View File

@@ -6,14 +6,14 @@ import PasswordGenerator from "./views/PasswordGenerator.vue";
import PasswordReset from "./views/PasswordReset.vue"; import PasswordReset from "./views/PasswordReset.vue";
import PasswordResetConfirm from "./views/PasswordResetConfirm.vue"; import PasswordResetConfirm from "./views/PasswordResetConfirm.vue";
import Passwords from "./views/Passwords.vue"; import Passwords from "./views/Passwords.vue";
import OptionsPage from "./views/OptionsPage.vue";
import SettingsPage from "./views/Settings.vue";


Vue.use(VueRouter); Vue.use(VueRouter);


const routes = [ const routes = [
{ path: "/", name: "home", component: PasswordGenerator }, { path: "/", name: "home", component: PasswordGenerator },
{ path: "/login", name: "login", component: Login }, { path: "/login", name: "login", component: Login },
{ path: "/options", name: "options", component: OptionsPage },
{ path: "/settings", name: "settings", component: SettingsPage },
{ path: "/passwords/", name: "passwords", component: Passwords }, { path: "/passwords/", name: "passwords", component: Passwords },
{ path: "/password/reset", name: "passwordReset", component: PasswordReset }, { path: "/password/reset", name: "passwordReset", component: PasswordReset },
{ {


+ 1
- 1
packages/lesspass-pure/src/services/url-parser.js View File

@@ -6,7 +6,7 @@ export function cleanUrl(url) {
if (!url) { if (!url) {
return ""; return "";
} }
var matchesDomainName = url.match(/^(?:https?\:\/\/)([^\/?#]+)(?:[\/?#]|$)/i);
var matchesDomainName = url.match(/^(?:https?:\/\/)([^/?#]+)(?:[/?#]|$)/i);
return matchesDomainName && matchesDomainName[1] ? matchesDomainName[1] : ""; return matchesDomainName && matchesDomainName[1] ? matchesDomainName[1] : "";
} }




+ 1
- 1
packages/lesspass-pure/src/store/actions.js View File

@@ -33,7 +33,7 @@ export const savePassword = ({ commit }, payload) => {
commit(types.SET_PASSWORD, payload); commit(types.SET_PASSWORD, payload);
}; };


export const resetPassword = ({ commit, state }) => {
export const resetPassword = ({ commit }) => {
commit(types.RESET_PASSWORD); commit(types.RESET_PASSWORD);
}; };




+ 131
- 89
packages/lesspass-pure/src/views/Login.vue View File

@@ -1,37 +1,41 @@
<style> <style>
#signInButton {
border-right: none;
}
#signInButton {
border-right: none;
}


#registerButton {
border-left: none;
}
#registerButton {
border-left: none;
}
</style> </style>
<template> <template>
<form v-on:submit.prevent="signIn"> <form v-on:submit.prevent="signIn">
<div class="form-group"> <div class="form-group">
<div class="inner-addon left-addon"> <div class="inner-addon left-addon">
<i class="fa fa-globe"></i> <i class="fa fa-globe"></i>
<input id="baseURL"
type="text"
class="form-control"
autocapitalize="none"
v-bind:placeholder="$t('LessPass Database Url')"
v-model="baseURL">
<input
id="baseURL"
type="text"
class="form-control"
autocapitalize="none"
v-bind:placeholder="$t('LessPass Database Url')"
v-model="baseURL"
/>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-12"> <div class="col-12">
<div class="inner-addon left-addon"> <div class="inner-addon left-addon">
<i class="fa fa-user"></i> <i class="fa fa-user"></i>
<input id="email"
class="form-control"
name="username"
type="email"
autocapitalize="none"
v-bind:placeholder="$t('Email')"
required
v-model="email">
<input
id="email"
class="form-control"
name="username"
type="email"
autocapitalize="none"
v-bind:placeholder="$t('Email')"
required
v-model="email"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -41,96 +45,134 @@
v-bind:label="$t('Master Password')" v-bind:label="$t('Master Password')"
v-bind:email="email" v-bind:email="email"
v-bind:showEncryptButton="true" v-bind:showEncryptButton="true"
v-bind:EncryptButtonText="$t('Encrypt my master password')"></master-password>
v-bind:EncryptButtonText="$t('Encrypt my master password')"
></master-password>
</div> </div>
<div class="form-group row no-gutters mb-0"> <div class="form-group row no-gutters mb-0">
<div class="col"> <div class="col">
<button id="signInButton" class="btn btn-primary btn-block">
{{$t('Sign In')}}
</button>
<button id="signInButton" class="btn btn-primary btn-block">{{$t('Sign In')}}</button>
</div> </div>
<div class="col"> <div class="col">
<button id="registerButton" class="btn btn-secondary btn-block" type="button" v-on:click="register">
{{$t('Register')}}
</button>
<button
id="registerButton"
class="btn btn-secondary btn-block"
type="button"
v-on:click="register"
>{{$t('Register')}}</button>
</div> </div>
</div> </div>
<div class="form-group mb-0"> <div class="form-group mb-0">
<button id="login__forgot-password-btn"
type="button"
class="btn btn-link btn-sm p-0"
v-on:click="$router.push({name: 'passwordReset'})">
<button
id="login__forgot-password-btn"
type="button"
class="btn btn-link btn-sm p-0"
v-on:click="$router.push({name: 'passwordReset'})"
>
<small>{{$t('ForgotPassword', 'Forgot your password?')}}</small> <small>{{$t('ForgotPassword', 'Forgot your password?')}}</small>
</button> </button>
</div> </div>
</form> </form>
</template> </template>
<script type="text/ecmascript-6"> <script type="text/ecmascript-6">
import User from '../api/user';
import MasterPassword from '../components/MasterPassword.vue';
import message from '../services/message';
import User from "../api/user";
import MasterPassword from "../components/MasterPassword.vue";
import message from "../services/message";


export default {
data() {
return {
email: '',
password: '',
baseURL: 'https://lesspass.com'
};
export default {
data() {
return {
email: "",
password: "",
baseURL: "https://lesspass.com"
};
},
components: {
MasterPassword
},
methods: {
formIsValid() {
if (!this.email || !this.password || !this.baseURL) {
message.error(
this.$t(
"LoginFormInvalid",
"LessPass URL, email, and password are mandatory"
)
);
return false;
}
return true;
}, },
components: {
MasterPassword
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(
this.$t("DBNotRunning", "Your LessPass Database is not running")
);
} else if (err.response && err.response.status === 400) {
message.error(
this.$t(
"LoginIncorrectError",
"The email and password you entered did not match our records. Please double-check and try again."
)
);
} else {
message.displayGenericError();
}
});
}
}, },
methods: {
formIsValid(){
if (!this.email || !this.password || !this.baseURL) {
message.error(this.$t('LoginFormInvalid', '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(this.$t('DBNotRunning', 'Your LessPass Database is not running'));
} else if (err.response && err.response.status === 400) {
message.error(this.$t('LoginIncorrectError', '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(
this.$t(
"WelcomeRegister",
"Welcome {email}, thank you for signing up.",
{ email: this.email }
)
);
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.$t(
"EmailAlreadyExist",
"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(this.$t('WelcomeRegister', 'Welcome {email}, thank you for signing up.', {email: this.email}));
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.$t('EmailAlreadyExist', 'This email is already registered. Want to login or recover your password?'));
}
if (err.response.data.email[0].indexOf('valid email') !== -1) {
message.error(this.$t('EmailInvalid', 'Please enter a valid email'));
}
} else {
message.displayGenericError();
if (err.response.data.email[0].indexOf("valid email") !== -1) {
message.error(
this.$t("EmailInvalid", "Please enter a valid email")
);
} }
});
}
} else {
message.displayGenericError();
}
});
} }
} }
} }
};
</script> </script>



+ 77
- 68
packages/lesspass-pure/src/views/PasswordGenerator.vue View File

@@ -14,87 +14,104 @@ div.awesomplete > ul {
<template> <template>
<form id="password-generator" v-on:submit.prevent="generatePassword" novalidate> <form id="password-generator" v-on:submit.prevent="generatePassword" novalidate>
<div class="form-group"> <div class="form-group">
<input-site ref="site"
v-model="password.site"
v-bind:passwords="passwords"
v-bind:label="$t('Site')"
v-on:suggestionSelected="setSite"
v-on:passwordProfileSelected="setPasswordProfile"></input-site>
<input-site
ref="site"
v-model="password.site"
v-bind:passwords="passwords"
v-bind:label="$t('Site')"
v-on:suggestionSelected="setSite"
v-on:passwordProfileSelected="setPasswordProfile"
></input-site>
</div> </div>
<remove-auto-complete></remove-auto-complete> <remove-auto-complete></remove-auto-complete>
<div class="form-group"> <div class="form-group">
<label for="login" class="sr-only">{{ $t('Login') }}</label> <label for="login" class="sr-only">{{ $t('Login') }}</label>
<div class="inner-addon left-addon"> <div class="inner-addon left-addon">
<i class="fa fa-user"></i> <i class="fa fa-user"></i>
<input id="login"
type="text"
name="login"
ref="login"
class="form-control"
autocomplete="off"
autocorrect="off"
autocapitalize="none"
v-bind:placeholder="$t('Login')"
v-model="password.login">
<input
id="login"
type="text"
name="login"
ref="login"
class="form-control"
autocomplete="off"
autocorrect="off"
autocapitalize="none"
v-bind:placeholder="$t('Login')"
v-model="password.login"
/>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<master-password ref="masterPassword"
v-model="masterPassword"
v-on:generatePassword="generatePassword"
v-bind:label="$t('Master Password')"></master-password>
<master-password
ref="masterPassword"
v-model="masterPassword"
v-on:generatePassword="generatePassword"
v-bind:label="$t('Master Password')"
></master-password>
</div> </div>
<div class="form-group"
v-bind:class="{ 'mb-0': !showOptions }">
<div class="form-group" v-bind:class="{ 'mb-0': !showOptions }">
<div v-if="!passwordGenerated"> <div v-if="!passwordGenerated">
<button id="generatePassword__btn"
type="submit"
class="btn btn-primary border-blue">
{{ $t('Generate') }}
</button>
<button type="button"
class="btn btn-secondary pull-right showOptions__btn"
v-show="!passwordGenerated"
v-on:click="showOptions =! showOptions">
<button
id="generatePassword__btn"
type="submit"
class="btn btn-primary border-blue"
>{{ $t('Generate') }}</button>
<button
type="button"
class="btn btn-secondary pull-right showOptions__btn"
v-show="!passwordGenerated"
v-on:click="showOptions =! showOptions"
>
<i class="fa fa-sliders"></i> <i class="fa fa-sliders"></i>
</button> </button>
</div> </div>
<div class="btn-group" v-show="passwordGenerated"> <div class="btn-group" v-show="passwordGenerated">
<div class="input-group"> <div class="input-group">
<span class="input-group-btn"> <span class="input-group-btn">
<button id="copyPasswordButton"
class="btn btn-primary border-blue"
type="button"
v-on:click="copyPassword()">
<button
id="copyPasswordButton"
class="btn btn-primary border-blue"
type="button"
v-on:click="copyPassword()"
>
<i class="fa fa-clipboard"></i> <i class="fa fa-clipboard"></i>
</button> </button>
</span> </span>
<input id="generated-password"
type="password"
class="form-control"
tabindex="-1"
ref="passwordGenerated"
v-bind:value="passwordGenerated">
<input
id="generated-password"
type="password"
class="form-control"
tabindex="-1"
ref="passwordGenerated"
v-bind:value="passwordGenerated"
/>
<span class="input-group-btn"> <span class="input-group-btn">
<button id="revealGeneratedPassword"
type="button"
class="btn btn-secondary"
v-on:click="togglePasswordType($refs.passwordGenerated)">
<button
id="revealGeneratedPassword"
type="button"
class="btn btn-secondary"
v-on:click="togglePasswordType($refs.passwordGenerated)"
>
<i class="fa fa-eye"></i> <i class="fa fa-eye"></i>
</button> </button>
</span> </span>
<span class="input-group-btn"> <span class="input-group-btn">
<button id="sharePasswordProfileButton"
type="button"
class="btn btn-secondary"
v-on:click="sharePasswordProfile()">
<button
id="sharePasswordProfileButton"
type="button"
class="btn btn-secondary"
v-on:click="sharePasswordProfile()"
>
<i class="fa fa-share-alt pointer"></i> <i class="fa fa-share-alt pointer"></i>
</button> </button>
</span> </span>
<span class="input-group-btn"> <span class="input-group-btn">
<button type="button"
class="btn btn-secondary showOptions__btn"
v-on:click="showOptions =! showOptions">
<button
type="button"
class="btn btn-secondary showOptions__btn"
v-on:click="showOptions =! showOptions"
>
<i class="fa fa-sliders"></i> <i class="fa fa-sliders"></i>
</button> </button>
</span> </span>
@@ -114,7 +131,6 @@ import InputSite from "../components/InputSite.vue";
import Options from "../components/Options.vue"; import Options from "../components/Options.vue";
import { showTooltip, hideTooltip } from "../services/tooltip"; import { showTooltip, hideTooltip } from "../services/tooltip";
import message from "../services/message"; import message from "../services/message";
import Awesomplete from "awesomplete";
import * as urlParser from "../services/url-parser"; import * as urlParser from "../services/url-parser";


export default { export default {
@@ -159,7 +175,7 @@ export default {
}, },
deep: true deep: true
}, },
masterPassword: function (newMasterPassword, oldMasterPassword) {
masterPassword: function(newMasterPassword) {
this.masterPassword = newMasterPassword; this.masterPassword = newMasterPassword;
this.cleanErrors(); this.cleanErrors();
} }
@@ -199,7 +215,7 @@ export default {
return; return;
} }
const length = this.password.length; const length = this.password.length;
if (length > 35){
if (length > 35) {
message.warning( message.warning(
this.$t( this.$t(
"LengthDeprecationWarning", "LengthDeprecationWarning",
@@ -235,9 +251,8 @@ export default {
if (site && !site.value) return void site.focus(); if (site && !site.value) return void site.focus();
if (login && !login.value) return void login.focus(); if (login && !login.value) return void login.focus();
masterPassword.$refs.passwordField.focus(); masterPassword.$refs.passwordField.focus();
}
catch(err) {
console.error("Can't focus password field")
} catch (err) {
console.error("Can't focus password field");
} }
}, },
copyPassword() { copyPassword() {
@@ -248,10 +263,7 @@ export default {
setTimeout(() => hideTooltip(element), 2000); setTimeout(() => hideTooltip(element), 2000);
} else { } else {
message.warning( message.warning(
this.$t(
"SorryCopy",
"Sorry, copying only works in modern browsers."
)
this.$t("SorryCopy", "Sorry, copying only works in modern browsers.")
); );
} }
}, },
@@ -267,15 +279,12 @@ export default {
setTimeout(() => hideTooltip(element), 2000); setTimeout(() => hideTooltip(element), 2000);
} else { } else {
message.warning( message.warning(
this.$t(
"SorryCopy",
"Sorry, copying only works in modern browsers."
)
this.$t("SorryCopy", "Sorry, copying only works in modern browsers.")
); );
} }
}, },
setSite(site){
this.password.site = site
setSite(site) {
this.password.site = site;
}, },
setPasswordProfile(passwordProfile) { setPasswordProfile(passwordProfile) {
this.$store this.$store


+ 62
- 50
packages/lesspass-pure/src/views/Passwords.vue View File

@@ -1,15 +1,15 @@
<style> <style>
#passwords__list {
min-height: 11rem;
}
#passwords__list {
min-height: 11rem;
}


#passwords__pagination .pagination {
margin-bottom: 0;
}
#passwords__pagination .pagination {
margin-bottom: 0;
}


#passwords__pagination .page-link {
cursor: pointer;
}
#passwords__pagination .page-link {
cursor: pointer;
}
</style> </style>
<template> <template>
<div id="passwords"> <div id="passwords">
@@ -18,7 +18,13 @@
<div class="col"> <div class="col">
<div class="inner-addon left-addon"> <div class="inner-addon left-addon">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
<input class="form-control" type="text" name="search" :placeholder="$t('Search')" v-model="searchQuery">
<input
class="form-control"
type="text"
name="search"
:placeholder="$t('Search')"
v-model="searchQuery"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -28,7 +34,9 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
{{$t('NoPassword', "You don't have any password profile saved in your database.")}} {{$t('NoPassword', "You don't have any password profile saved in your database.")}}
<router-link :to="{ name: 'home'}">{{$t('CreatePassword', 'Would you like to create one?')}}</router-link>
<router-link
:to="{ name: 'home'}"
>{{$t('CreatePassword', 'Would you like to create one?')}}</router-link>
</div> </div>
</div> </div>
</div> </div>
@@ -44,7 +52,8 @@
v-bind:password="password" v-bind:password="password"
v-on:deleted="pagination.currentPage=1" v-on:deleted="pagination.currentPage=1"
v-for="password in filteredPasswords" v-for="password in filteredPasswords"
:key="password.id"></password-profile>
:key="password.id"
></password-profile>
</div> </div>
<div id="passwords__pagination" v-if="pagination.pageCount > 1"> <div id="passwords__pagination" v-if="pagination.pageCount > 1">
<paginate <paginate
@@ -60,51 +69,54 @@
:prev-link-class="'page-link'" :prev-link-class="'page-link'"
:next-link-class="'page-link'" :next-link-class="'page-link'"
:prev-text="$t('Previous')" :prev-text="$t('Previous')"
:next-text="$t('Next')">
</paginate>
:next-text="$t('Next')"
></paginate>
</div> </div>
</div> </div>
</template> </template>
<script type="text/ecmascript-6"> <script type="text/ecmascript-6">
import PasswordProfile from '../components/PasswordProfile.vue';
import {mapState} from 'vuex';
import Paginate from 'vuejs-paginate';
import PasswordProfile from "../components/PasswordProfile.vue";
import { mapState } from "vuex";
import Paginate from "vuejs-paginate";


export default {
name: 'passwords-view',
data() {
return {
searchQuery: '',
pagination: {
pageCount: 1,
perPage: 4,
currentPage: 1
},
export default {
name: "passwords-view",
data() {
return {
searchQuery: "",
pagination: {
pageCount: 1,
perPage: 4,
currentPage: 1
} }
},
components: {
PasswordProfile,
Paginate
},
computed: {
...mapState(['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.pageCount = Math.ceil(passwords.length / this.pagination.perPage);
return passwords.slice(
this.pagination.currentPage * this.pagination.perPage - this.pagination.perPage,
this.pagination.currentPage * this.pagination.perPage
};
},
components: {
PasswordProfile,
Paginate
},
computed: {
...mapState(["passwords"]),
filteredPasswords() {
const passwords = this.passwords.filter(password => {
var loginMatch = password.login.match(
new RegExp(this.searchQuery, "i")
); );
}
},
methods: {
setCurrentPage(page) {
this.pagination.currentPage = page;
}
var siteMatch = password.site.match(new RegExp(this.searchQuery, "i"));
return loginMatch || siteMatch;
});
this.pagination.pageCount = Math.ceil(passwords.length / this.pagination.perPage);
return passwords.slice(
this.pagination.currentPage * this.pagination.perPage -
this.pagination.perPage,
this.pagination.currentPage * this.pagination.perPage
);
}
},
methods: {
setCurrentPage(page) {
this.pagination.currentPage = page;
} }
} }
};
</script> </script>

packages/lesspass-pure/src/views/OptionsPage.vue → packages/lesspass-pure/src/views/Settings.vue View File

@@ -24,7 +24,6 @@


<script> <script>
import { mapState } from "vuex"; import { mapState } from "vuex";
import { SET_DEFAULT_OPTIONS } from "../store/mutation-types";


export default { export default {
computed: mapState(["defaultPassword"]), computed: mapState(["defaultPassword"]),

+ 0
- 55
packages/lesspass-pure/webpack.common.js View File

@@ -1,55 +0,0 @@
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const productionMode = process.env.NODE_ENV === "production";

module.exports = {
entry: "./src/main.js",
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
},
plugins: [
new MiniCssExtractPlugin({ filename: "lesspass.min.css" }),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({ template: "./src/index.html", inject: "body" }),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new VueLoaderPlugin()
],
output: {
filename: "lesspass.min.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader"
},
{
test: /\.js$/,
exclude: /node_modules\/(?!copy-text-to-clipboard)/,
loader: "babel-loader"
},
{
test: /\.(sa|sc|c)ss$/,
use: [
productionMode ? MiniCssExtractPlugin.loader : "style-loader",
"css-loader",
"sass-loader"
]
},
{
test: /\.(png|svg|jpg|gif|ico)$/,
use: ["file-loader?name=[name].[ext]"]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ["file-loader"]
},
{ test: /\.html$/, loader: "raw-loader" }
]
}
};

+ 0
- 13
packages/lesspass-pure/webpack.dev.js View File

@@ -1,13 +0,0 @@
const merge = require("webpack-merge");
const common = require("./webpack.common.js");

module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
devServer: {
host: "0.0.0.0",
port: 8000,
contentBase: "./dist",
historyApiFallback: true
}
});

+ 0
- 9
packages/lesspass-pure/webpack.prod.js View File

@@ -1,9 +0,0 @@
const merge = require("webpack-merge");
const common = require("./webpack.common.js");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = merge(common, {
mode: "production",
devtool: "source-map",
optimization: { minimizer: [new TerserPlugin()] }
});

+ 3313
- 2383
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save