@@ -1,6 +1,6 @@ | |||
.DS_Store | |||
node_modules/ | |||
dist/ | |||
docs/ | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
@@ -4,8 +4,8 @@ var path = require('path') | |||
module.exports = { | |||
build: { | |||
env: require('./prod.env'), | |||
index: path.resolve(__dirname, '../dist/index.html'), | |||
assetsRoot: path.resolve(__dirname, '../dist'), | |||
index: path.resolve(__dirname, '../docs/index.html'), | |||
assetsRoot: path.resolve(__dirname, '../docs'), | |||
assetsSubDirectory: 'static', | |||
assetsPublicPath: '/', | |||
productionSourceMap: true, | |||
@@ -2,10 +2,9 @@ | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>lesspass-move</title> | |||
<title>LessPass Move</title> | |||
</head> | |||
<body> | |||
<div id="app"></div> | |||
<!-- built files will be auto injected --> | |||
</body> | |||
</html> |
@@ -9,66 +9,67 @@ | |||
"start": "node build/dev-server.js", | |||
"build": "node build/build.js", | |||
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", | |||
"test": "npm run unit" | |||
"test": "cross-env BABEL_ENV=test ava test.js" | |||
}, | |||
"dependencies": { | |||
"ava": "^0.20.0", | |||
"bootstrap": "^4.0.0-alpha.6", | |||
"lesspass": "^6.0.0", | |||
"lesspass-pure": "file:../pure", | |||
"vue": "^2.3.3", | |||
"vue": "^2.3.4", | |||
"vue-polyglot": "^0.2.1", | |||
"vue-router": "^2.3.1" | |||
"vue-router": "^2.6.0" | |||
}, | |||
"devDependencies": { | |||
"autoprefixer": "^6.7.2", | |||
"babel-core": "^6.22.1", | |||
"babel-loader": "^6.2.10", | |||
"babel-plugin-transform-runtime": "^6.22.0", | |||
"babel-preset-env": "^1.3.2", | |||
"babel-preset-stage-2": "^6.22.0", | |||
"babel-register": "^6.22.0", | |||
"autoprefixer": "^7.1.1", | |||
"babel-core": "^6.25.0", | |||
"babel-loader": "^7.1.1", | |||
"babel-plugin-istanbul": "^4.1.4", | |||
"babel-plugin-transform-runtime": "^6.23.0", | |||
"babel-preset-env": "^1.5.2", | |||
"babel-preset-stage-2": "^6.24.1", | |||
"babel-register": "^6.24.1", | |||
"chai": "^4.0.2", | |||
"chalk": "^1.1.3", | |||
"connect-history-api-fallback": "^1.3.0", | |||
"copy-webpack-plugin": "^4.0.1", | |||
"css-loader": "^0.28.0", | |||
"cross-env": "^5.0.1", | |||
"css-loader": "^0.28.4", | |||
"eventsource-polyfill": "^0.9.6", | |||
"express": "^4.14.1", | |||
"extract-text-webpack-plugin": "^2.0.0", | |||
"file-loader": "^0.11.1", | |||
"friendly-errors-webpack-plugin": "^1.1.3", | |||
"html-webpack-plugin": "^2.28.0", | |||
"http-proxy-middleware": "^0.17.3", | |||
"webpack-bundle-analyzer": "^2.2.1", | |||
"cross-env": "^4.0.0", | |||
"karma": "^1.4.1", | |||
"express": "^4.15.3", | |||
"extract-text-webpack-plugin": "^2.1.2", | |||
"file-loader": "^0.11.2", | |||
"friendly-errors-webpack-plugin": "^1.6.1", | |||
"html-webpack-plugin": "^2.29.0", | |||
"http-proxy-middleware": "^0.17.4", | |||
"inject-loader": "^3.0.0", | |||
"karma": "^1.7.0", | |||
"karma-coverage": "^1.1.1", | |||
"karma-mocha": "^1.3.0", | |||
"karma-phantomjs-launcher": "^1.0.2", | |||
"karma-phantomjs-launcher": "^1.0.4", | |||
"karma-phantomjs-shim": "^1.4.0", | |||
"karma-sinon-chai": "^1.3.1", | |||
"karma-sourcemap-loader": "^0.3.7", | |||
"karma-spec-reporter": "0.0.30", | |||
"karma-webpack": "^2.0.2", | |||
"lolex": "^1.5.2", | |||
"mocha": "^3.2.0", | |||
"chai": "^3.5.0", | |||
"sinon": "^2.1.0", | |||
"sinon-chai": "^2.8.0", | |||
"inject-loader": "^3.0.0", | |||
"babel-plugin-istanbul": "^4.1.1", | |||
"karma-spec-reporter": "0.0.31", | |||
"karma-webpack": "^2.0.3", | |||
"lolex": "^1.6.0", | |||
"mocha": "^3.4.2", | |||
"opn": "^5.1.0", | |||
"optimize-css-assets-webpack-plugin": "^2.0.0", | |||
"ora": "^1.3.0", | |||
"phantomjs-prebuilt": "^2.1.14", | |||
"rimraf": "^2.6.1", | |||
"semver": "^5.3.0", | |||
"shelljs": "^0.7.6", | |||
"opn": "^4.0.2", | |||
"optimize-css-assets-webpack-plugin": "^1.3.0", | |||
"ora": "^1.2.0", | |||
"rimraf": "^2.6.0", | |||
"url-loader": "^0.5.8", | |||
"vue-loader": "^12.1.0", | |||
"shelljs": "^0.7.8", | |||
"sinon": "^2.3.5", | |||
"sinon-chai": "^2.11.0", | |||
"url-loader": "^0.5.9", | |||
"vue-loader": "^12.2.1", | |||
"vue-style-loader": "^3.0.1", | |||
"vue-template-compiler": "^2.3.3", | |||
"webpack": "^2.6.1", | |||
"webpack-dev-middleware": "^1.10.0", | |||
"vue-template-compiler": "^2.3.4", | |||
"webpack": "^3.0.0", | |||
"webpack-bundle-analyzer": "^2.8.2", | |||
"webpack-dev-middleware": "^1.11.0", | |||
"webpack-hot-middleware": "^2.18.0", | |||
"webpack-merge": "^4.1.0" | |||
}, | |||
@@ -81,5 +82,8 @@ | |||
"last 2 versions", | |||
"not ie <= 8" | |||
], | |||
"false": {} | |||
"ava": { | |||
"require": "babel-register", | |||
"babel": "inherit" | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
<template> | |||
<div id="app"> | |||
<app-menu></app-menu> | |||
<!--<app-menu></app-menu>--> | |||
<router-view></router-view> | |||
</div> | |||
</template> | |||
@@ -1,10 +1,204 @@ | |||
<template> | |||
<div class="index"> | |||
index | |||
<div class="container-fluid pt-3"> | |||
<div class="row"> | |||
<div class="col"> | |||
<h1>LessPass Move</h1> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<h3 class="mb-2">1 - Copy paste list of old profiles</h3> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<textarea id="oldProfiles" name="oldProfiles" class="form-control" rows="10" v-model="oldProfiles" | |||
placeholder="Copy list of password profiles"></textarea> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<h3 class="mt-5 mb-2">2 - Set your(s) master password(s)</h3> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<form class="form-inline"> | |||
<input | |||
id="oldMasterPassword" | |||
type="password" | |||
placeholder="Old Master Password" | |||
v-model="oldMasterPassword"> | |||
<input | |||
id="newMasterPassword" | |||
type="password" | |||
v-model="newMasterPassword" | |||
placeholder="New Master Password" | |||
v-if="changeMyMasterPassword"> | |||
</form> | |||
<div class="form-check"> | |||
<label class="form-check-label"> | |||
<input type="checkbox" class="form-check-input" v-model="changeMyMasterPassword"> | |||
Click me to change your master password at the same time | |||
</label> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<h3 class="mt-5 mb-2">3 - Build your passwords:</h3> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<button class="btn btn-primary" v-on:click="buildAllPasswords()">Build all passwords</button> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<h3 class="mt-5 mb-2">4 - Copy paste old and new generated passwords:</h3> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<table class="table table-bordered table-sm"> | |||
<thead class="thead-inverse"> | |||
<tr> | |||
<th class="text-center" colspan="8">Old profile</th> | |||
<th></th> | |||
<th class="text-center" colspan="8">New profile</th> | |||
<th></th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td class="text-center"><b>site</b></td> | |||
<td class="text-center"><b>login</b></td> | |||
<td class="text-center"><b>a-z</b></td> | |||
<td class="text-center"><b>A-Z</b></td> | |||
<td class="text-center"><b>0-9</b></td> | |||
<td class="text-center"><b>%!@</b></td> | |||
<td class="text-center"><b>length</b></td> | |||
<td class="text-center"><b>counter</b></td> | |||
<td></td> | |||
<td class="text-center"><b>site</b></td> | |||
<td class="text-center"><b>login</b></td> | |||
<td class="text-center"><b>a-z</b></td> | |||
<td class="text-center"><b>A-Z</b></td> | |||
<td class="text-center"><b>0-9</b></td> | |||
<td class="text-center"><b>%!@</b></td> | |||
<td class="text-center"><b>length</b></td> | |||
<td class="text-center"><b>counter</b></td> | |||
</tr> | |||
<tr v-for="profile in newPasswordProfiles" v-if="profile.oldPassword!==profile.newPassword"> | |||
<td> | |||
{{profile.oldProfile.site}} | |||
</td> | |||
<td> | |||
{{profile.oldProfile.login}} | |||
</td> | |||
<td class="text-center"> | |||
<option-thumb :thumbUp="profile.oldProfile.lowercase"></option-thumb> | |||
</td> | |||
<td class="text-center"> | |||
<option-thumb :thumbUp="profile.oldProfile.uppercase"></option-thumb> | |||
</td> | |||
<td class="text-center"> | |||
<option-thumb :thumbUp="profile.oldProfile.numbers"></option-thumb> | |||
</td> | |||
<td class="text-center"> | |||
<option-thumb :thumbUp="profile.oldProfile.symbols"></option-thumb> | |||
</td> | |||
<td class="text-center" | |||
v-bind:class="{'bg-warning': profile.oldProfile.length !== profile.newProfile.length }"> | |||
{{profile.oldProfile.length}} | |||
</td> | |||
<td class="text-center" | |||
v-bind:class="{'bg-warning': profile.oldProfile.counter !== profile.newProfile.counter }"> | |||
{{profile.oldProfile.counter}} | |||
</td> | |||
<td class="text-center"> | |||
<button class="btn btn-default btn-sm" v-on:click="copyPassword(profile.oldPassword)"> | |||
<i class="fa fa-clipboard"></i> | |||
</button> | |||
<button class="btn btn-default btn-sm" v-on:click="copyPassword(profile.newPassword)"> | |||
<i class="fa fa-clipboard"></i> | |||
</button> | |||
</td> | |||
<td> | |||
{{profile.newProfile.site}} | |||
</td> | |||
<td> | |||
{{profile.newProfile.login}} | |||
</td> | |||
<td class="text-center"> | |||
<option-thumb :thumbUp="profile.newProfile.lowercase"></option-thumb> | |||
</td> | |||
<td class="text-center"> | |||
<option-thumb :thumbUp="profile.newProfile.uppercase"></option-thumb> | |||
</td> | |||
<td class="text-center"> | |||
<option-thumb :thumbUp="profile.newProfile.numbers"></option-thumb> | |||
</td> | |||
<td class="text-center"> | |||
<option-thumb :thumbUp="profile.newProfile.symbols"></option-thumb> | |||
</td> | |||
<td class="text-center" | |||
v-bind:class="{'bg-warning': profile.oldProfile.length !== profile.newProfile.length }" | |||
contenteditable="true"> | |||
{{profile.newProfile.length}} | |||
</td> | |||
<td class="text-center" | |||
v-bind:class="{'bg-warning': profile.oldProfile.counter !== profile.newProfile.counter }"> | |||
{{profile.newProfile.counter}} | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import MasterPassword from 'lesspass-pure/src/components/MasterPassword.vue' | |||
import migration from '@/services/migration'; | |||
import copy from '@/services/copy-text-to-clipboard'; | |||
import OptionThumb from '@/components/OptionThumb' | |||
export default { | |||
name: 'index' | |||
name: 'index', | |||
components: { | |||
MasterPassword, | |||
OptionThumb | |||
}, | |||
data(){ | |||
return { | |||
oldProfiles: "[]", | |||
changeMyMasterPassword: false, | |||
oldMasterPassword: '', | |||
newMasterPassword: '', | |||
newPasswordProfiles: [] | |||
} | |||
}, | |||
methods: { | |||
buildAllPasswords(){ | |||
var oldProfiles = JSON.parse(this.oldProfiles); | |||
if (!this.changeMyMasterPassword) { | |||
this.newMasterPassword = this.oldMasterPassword | |||
} | |||
migration.buildAllPasswords(migration.transformProfilesFromV1ToV2(oldProfiles), this.oldMasterPassword, this.newMasterPassword) | |||
.then(newPasswordProfiles => { | |||
this.newPasswordProfiles = newPasswordProfiles; | |||
}); | |||
}, | |||
copyPassword(password){ | |||
const copied = copy(password); | |||
}, | |||
clearProfileUpdate(profile){ | |||
var version = profile.newProfile.version; | |||
profile.newProfile = profile.oldProfile; | |||
profile.newProfile.version = version; | |||
profile.updated = false; | |||
} | |||
} | |||
} | |||
</script> |
@@ -0,0 +1,16 @@ | |||
<template> | |||
<div> | |||
<i class="fa fa-thumbs-up text-success" v-if="thumbUp"></i> | |||
<i class="fa fa-thumbs-down text-danger" v-else></i> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'OptionThumb', | |||
props: { | |||
thumbUp: { | |||
type: Boolean | |||
} | |||
}, | |||
} | |||
</script> |
@@ -1,10 +1,10 @@ | |||
import Vue from 'vue' | |||
import App from './App' | |||
import App from './App.vue' | |||
import 'lesspass-pure/dist/lesspass.min.css' | |||
import router from './router' | |||
import Polyglot from 'vue-polyglot'; | |||
Vue.use(Polyglot) | |||
Vue.use(Polyglot); | |||
new Vue({ | |||
el: '#app', | |||
@@ -0,0 +1,38 @@ | |||
'use strict'; | |||
module.exports = function (input) { | |||
var el = document.createElement('textarea'); | |||
el.value = input; | |||
// Prevent keyboard from showing on mobile | |||
el.setAttribute('readonly', ''); | |||
el.style.contain = 'strict'; | |||
el.style.position = 'absolute'; | |||
el.style.left = '-9999px'; | |||
el.style.fontSize = '12pt'; // Prevent zooming on iOS | |||
var selection = getSelection(); | |||
var originalRange = false; | |||
if (selection.rangeCount > 0) { | |||
originalRange = selection.getRangeAt(0); | |||
} | |||
document.body.appendChild(el); | |||
el.select(); | |||
var success = false; | |||
try { | |||
success = document.execCommand('copy'); | |||
} catch (err) {} | |||
document.body.removeChild(el); | |||
if (originalRange) { | |||
selection.removeAllRanges(); | |||
selection.addRange(originalRange); | |||
} | |||
return success; | |||
}; |
@@ -0,0 +1,60 @@ | |||
import LessPass from 'lesspass'; | |||
export default { | |||
transformProfilesFromV1ToV2: function(profiles) { | |||
const newProfiles = []; | |||
profiles.forEach(profile => { | |||
const newProfile = { | |||
"site": profile.site, | |||
"login": profile.login, | |||
"lowercase": profile.lowercase, | |||
"uppercase": profile.uppercase, | |||
"numbers": profile.numbers, | |||
"symbols": profile.symbols, | |||
"counter": profile.counter, | |||
"length": profile.length, | |||
"version": profile.version, | |||
}; | |||
let updated = false; | |||
if (profile.version === 1) { | |||
newProfile.version = 2; | |||
if (profile.length === 12) { | |||
updated = true; | |||
newProfile.length = 16; | |||
} | |||
} | |||
newProfiles.push({ | |||
updated: updated, | |||
oldProfile: profile, | |||
newProfile: newProfile | |||
}) | |||
}); | |||
return newProfiles | |||
}, | |||
buildPromise(profile, masterPassword){ | |||
return LessPass.generatePassword(profile.site, profile.login, masterPassword, { | |||
lowercase: profile.lowercase, | |||
uppercase: profile.uppercase, | |||
numbers: profile.numbers, | |||
symbols: profile.symbols, | |||
length: profile.length, | |||
counter: profile.counter, | |||
version: profile.version, | |||
}); | |||
}, | |||
buildAllPasswords: function(profiles, oldMasterPassword, newMasterPassword) { | |||
const buildPasswordPromises = []; | |||
profiles.forEach(profile => { | |||
buildPasswordPromises.push(this.buildPromise(profile.oldProfile, oldMasterPassword)); | |||
buildPasswordPromises.push(this.buildPromise(profile.newProfile, newMasterPassword)); | |||
}); | |||
return Promise.all(buildPasswordPromises).then(values => { | |||
profiles.forEach((profile, i) => { | |||
profile.oldPassword = values[i * 2]; | |||
profile.newPassword = values[i * 2 + 1] | |||
}); | |||
return profiles; | |||
}); | |||
} | |||
}; |
@@ -0,0 +1,154 @@ | |||
import test from 'ava'; | |||
import migration from './src/services/migration' | |||
test('transformProfilesFromV1ToV2', t => { | |||
var profiles = [ | |||
{ | |||
"site": "example.org", | |||
"login": "test@example.org", | |||
"lowercase": true, | |||
"uppercase": true, | |||
"numbers": true, | |||
"symbols": true, | |||
"counter": 1, | |||
"length": 12, | |||
"version": 1 | |||
}, | |||
{ | |||
"site": "bank.example.org", | |||
"login": "test@example.org", | |||
"lowercase": false, | |||
"uppercase": false, | |||
"numbers": true, | |||
"symbols": false, | |||
"counter": 2, | |||
"length": 6, | |||
"version": 1 | |||
}, | |||
{ | |||
"site": "example.com", | |||
"login": "test@example.org", | |||
"lowercase": true, | |||
"uppercase": true, | |||
"numbers": true, | |||
"symbols": true, | |||
"counter": 1, | |||
"length": 16, | |||
"version": 2 | |||
} | |||
]; | |||
var expectedProfiles = [ | |||
{ | |||
oldProfile: profiles[0], | |||
newProfile: { | |||
"site": "example.org", | |||
"login": "test@example.org", | |||
"lowercase": true, | |||
"uppercase": true, | |||
"numbers": true, | |||
"symbols": true, | |||
"counter": 1, | |||
"length": 16, | |||
"version": 2 | |||
}, | |||
updated: true | |||
}, | |||
{ | |||
oldProfile: profiles[1], | |||
newProfile: { | |||
"site": "bank.example.org", | |||
"login": "test@example.org", | |||
"lowercase": false, | |||
"uppercase": false, | |||
"numbers": true, | |||
"symbols": false, | |||
"counter": 2, | |||
"length": 6, | |||
"version": 2 | |||
}, | |||
updated: false | |||
}, | |||
{ | |||
oldProfile: profiles[2], | |||
newProfile: { | |||
"site": "example.com", | |||
"login": "test@example.org", | |||
"lowercase": true, | |||
"uppercase": true, | |||
"numbers": true, | |||
"symbols": true, | |||
"counter": 1, | |||
"length": 16, | |||
"version": 2 | |||
}, | |||
updated: false | |||
} | |||
]; | |||
t.deepEqual(migration.transformProfilesFromV1ToV2(profiles), expectedProfiles); | |||
}); | |||
test('buildAllPasswords', t => { | |||
var profiles = [{ | |||
"site": "example.org", | |||
"login": "test@example.org", | |||
"lowercase": true, | |||
"uppercase": true, | |||
"numbers": true, | |||
"symbols": true, | |||
"counter": 1, | |||
"length": 12, | |||
"version": 1 | |||
}, { | |||
"site": "bank.example.org", | |||
"login": "test@example.org", | |||
"lowercase": false, | |||
"uppercase": false, | |||
"numbers": true, | |||
"symbols": false, | |||
"counter": 2, | |||
"length": 6, | |||
"version": 1 | |||
}]; | |||
return migration | |||
.buildAllPasswords(migration.transformProfilesFromV1ToV2(profiles), 'password', 'password') | |||
.then(newPasswordProfiles => { | |||
t.deepEqual(newPasswordProfiles[0].oldPassword, 'esIZ9,amEW5,'); | |||
t.deepEqual(newPasswordProfiles[0].newPassword, '[nYxh6=osW)aH99b'); | |||
t.deepEqual(newPasswordProfiles[1].oldPassword, '342387'); | |||
t.deepEqual(newPasswordProfiles[1].newPassword, '446545'); | |||
}); | |||
}); | |||
test('buildAllPasswords different master passwords', t => { | |||
var profiles = [{ | |||
"site": "example.org", | |||
"login": "test@example.org", | |||
"lowercase": true, | |||
"uppercase": true, | |||
"numbers": true, | |||
"symbols": true, | |||
"counter": 1, | |||
"length": 12, | |||
"version": 1 | |||
}, { | |||
"site": "bank.example.org", | |||
"login": "test@example.org", | |||
"lowercase": false, | |||
"uppercase": false, | |||
"numbers": true, | |||
"symbols": false, | |||
"counter": 2, | |||
"length": 6, | |||
"version": 1 | |||
}]; | |||
return migration | |||
.buildAllPasswords(migration.transformProfilesFromV1ToV2(profiles), 'password', 'new_password') | |||
.then(newPasswordProfiles => { | |||
t.is(newPasswordProfiles[0].oldPassword, 'esIZ9,amEW5,'); | |||
t.is(newPasswordProfiles[0].newPassword, '\\-q5pba3wj1AHABY'); | |||
t.is(newPasswordProfiles[1].oldPassword, '342387'); | |||
t.is(newPasswordProfiles[1].newPassword, '559909'); | |||
}); | |||
}); |
@@ -1,11 +0,0 @@ | |||
import Vue from 'vue' | |||
import Hello from '@/components/Hello' | |||
describe('Hello.vue', () => { | |||
it('should render correct contents', () => { | |||
const Constructor = Vue.extend(Hello) | |||
const vm = new Constructor().$mount() | |||
expect(vm.$el.querySelector('.hello h1').textContent) | |||
.to.equal('Welcome to Your Vue.js App') | |||
}) | |||
}) |
@@ -0,0 +1,10 @@ | |||
import Vue from 'vue' | |||
import Hello from '@/components/Index' | |||
describe('Index.vue', () => { | |||
it('should render correct contents', () => { | |||
const Constructor = Vue.extend(Hello); | |||
const vm = new Constructor().$mount(); | |||
expect(vm.$el.querySelector('.index h1').textContent).to.equal('Index'); | |||
}) | |||
}); |