diff --git a/README.md b/README.md index d9991c0..ff21bbb 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,6 @@ $ yarn serve ### Run as extension -Open developer mode on your Chrome extension page then click "load unpacked" button. Select `dist/` folder +Open developer mode on your Chrome extension page then click "load unpacked" button. Select `dist/` folder. In Firefox, go to `about:debugging` url, select "This Firefox" tab then click "install temporary extension" and select `manifest.json` in your `/dist` folder diff --git a/__tests__/integration/App.test.js b/__tests__/integration/App.test.js index 4b15229..e384dfe 100644 --- a/__tests__/integration/App.test.js +++ b/__tests__/integration/App.test.js @@ -17,18 +17,6 @@ const getTestId = id => { return `[data-testid='${id}']` } -const getLocalStorage = async page => { - const j = await page.evaluate(() => { - let json = {} - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i) - json[key] = localStorage.getItem(key) - } - return json - }) - return j -} - describe('Popup page', () => { let page, browser const usernameField = '[data-testid="username"] > input' @@ -91,8 +79,11 @@ describe('Popup page', () => { await page.waitForSelector(usernameLabel) const labelText = await page.$eval(usernameLabel, el => el.innerText) - const localStorageData = await getLocalStorage(page) - const userObj = JSON.parse(localStorageData.user) + const userObj = await page.evaluate(async () => { + const $vm = document.querySelector('body > div').__vue__ + const storage = $vm.$storage + return storage.getItem('user') + }) await page.screenshot({ path: path.join(ssPath, 'home.png') }) expect(labelText).toEqual('Erhan Yakut') @@ -121,8 +112,12 @@ describe('Popup page', () => { await page.waitForSelector(logoutBtn) await page.$eval(logoutBtn, el => el.click()) - const localStorageData = await getLocalStorage(page) + const userObj = await page.evaluate(async () => { + const $vm = document.querySelector('body > div').__vue__ + const storage = $vm.$storage + return storage.getItem('user') + }) await page.screenshot({ path: path.join(ssPath, 'logout.png') }) - expect(localStorageData.user).not.toBeDefined() + expect(userObj).toBeNull() }) }) diff --git a/package.json b/package.json index 6b6584e..898cde4 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "passwall-extension", - "version": "0.1.0", + "version": "1.0.0", "private": true, "scripts": { "serve": "vue-cli-service build --mode development --watch", "build": "vue-cli-service build", "lint": "vue-cli-service lint", - "test": "yarn build && jest" + "test": "jest", + "test:build": "yarn build && jest" }, "jest": { "verbose": true, @@ -32,6 +33,7 @@ "axios": "^0.21.1", "core-js": "^3.6.5", "crypto-js": "^4.0.0", + "localforage": "^1.9.0", "skeleton-loader-vue": "^1.0.6", "v-tooltip": "^2.1.1", "vee-validate": "2.2.15", diff --git a/src/manifest.json b/src/manifest.json index 9deb3de..9342c5d 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -5,7 +5,6 @@ "description": "Passwall official extension", "default_locale": "en", "permissions": ["tabs", "activeTab", "", "*://*/*", "webRequest"], - "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+KCvbvNcEFoYK6Wv2YDjMDSfXEY5czwNjy8tqe9dWXcKmV64zKahzXPkaS4g7ZA5S1fHD7sWuai4g78K7xUGMvgtDTY9AYcyjmNgW1bE+ZwRPgZFXEwKHQCwXAgUDiHfhRfngSf1sgvznAZrMzNWkppJc0Pi0gR1HqyxRBxO6f2b2BDkaPszlFxTi3AQ1mT+v34ogorgT6yKnfaD9wQfIoPW4eiPNb/OPWW2I/R7P+mGGuqKLAU2KddRuPjC2plS2VEVjXPXCG4w2M6SRyFClpCeWR2F0GCmTbjRbKWoAmtCiX5OeORa39jvSh4vZBEZBYPa57RlRdxRWVxhUso2aQIDAQAB", "icons": { "16": "icons/16.png", "48": "icons/48.png", diff --git a/src/popup/config.js b/src/popup/config.js index c4e9c2b..5869edf 100644 --- a/src/popup/config.js +++ b/src/popup/config.js @@ -11,6 +11,9 @@ Vue.prototype.$waiters = Waiters import * as Constants from '@/utils/constants' Vue.prototype.$c = Constants +import storage from '@/utils/storage' +Vue.prototype.$storage = storage + import * as Helpers from '@/utils/helpers' Vue.prototype.$helpers = Helpers diff --git a/src/popup/main.js b/src/popup/main.js index 259c475..cf39635 100644 --- a/src/popup/main.js +++ b/src/popup/main.js @@ -6,18 +6,21 @@ import router from '@p/router' import store from '@p/store' import i18n from '@/i18n' import HTTPClient from '@/api/HTTPClient' +import Storage from '@/utils/storage' import '../styles/app.scss' - -if (localStorage.access_token) { - HTTPClient.setHeader('Authorization', `Bearer ${localStorage.access_token}`) -} - -/* eslint-disable no-new */ -new Vue({ - router, - store, - i18n, - wait: window.wait, - el: '#app', - render: h => h(App) -}) +;(async () => { + await store.dispatch('init') + const token = await Storage.getItem('access_token') + if (token) { + HTTPClient.setHeader('Authorization', `Bearer ${token}`) + } + /* eslint-disable no-new */ + new Vue({ + router, + store, + i18n, + wait: window.wait, + el: '#app', + render: h => h(App) + }) +})() diff --git a/src/popup/router/auth-check.js b/src/popup/router/auth-check.js index 948bceb..14eff95 100644 --- a/src/popup/router/auth-check.js +++ b/src/popup/router/auth-check.js @@ -1,7 +1,9 @@ -export default (to, _, next) => { +import Storage from '@/utils/storage' + +export default async (to, _, next) => { const isAuthPage = to.matched.some(record => record.meta.auth) - const access_token = localStorage.getItem('access_token') + const access_token = await Storage.getItem('access_token') if (access_token) { if (isAuthPage) { return next({ name: 'Home' }) diff --git a/src/popup/store/index.js b/src/popup/store/index.js index 211357c..fc0be28 100644 --- a/src/popup/store/index.js +++ b/src/popup/store/index.js @@ -6,6 +6,8 @@ import CryptoUtils from '@/utils/crypto' import HTTPClient from '@/api/HTTPClient' import AuthService from '@/api/services/Auth' +import Storage from '@/utils/storage' + import Logins from '@p/views/Logins/store' import CreditCards from '@/popup/views/CreditCards/store' import Emails from '@p/views/Emails/store' @@ -15,14 +17,11 @@ import Servers from '@p/views/Servers/store' export default new Vuex.Store({ state() { - CryptoUtils.encryptKey = localStorage.master_hash - CryptoUtils.transmissionKey = localStorage.transmission_key - return { - access_token: localStorage.access_token, - refresh_token: localStorage.refresh_token, - transmission_key: localStorage.transmission_key, - master_hash: localStorage.master_hash, + access_token: '', + refresh_token: '', + transmission_key: '', + master_hash: '', searchQuery: '', user: {} @@ -35,10 +34,21 @@ export default new Vuex.Store({ }, actions: { + async init({ state }) { + CryptoUtils.encryptKey = await Storage.getItem('master_hash') + CryptoUtils.transmissionKey = await Storage.getItem('transmission_key') + + state.access_token = await Storage.getItem('access_token') + state.refresh_token = await Storage.getItem('refresh_token') + state.transmission_key = await Storage.getItem('transmission_key') + state.master_hash = await Storage.getItem('master_hash') + }, + async Login({ state }, payload) { payload.master_password = CryptoUtils.sha256Encrypt(payload.master_password) const { data } = await AuthService.Login(payload) + state.access_token = data.access_token state.refresh_token = data.refresh_token state.transmission_key = data.transmission_key.substr(0, 32) @@ -47,31 +57,31 @@ export default new Vuex.Store({ CryptoUtils.transmissionKey = state.transmission_key state.user = data - localStorage.email = payload.email - localStorage.server = payload.server - localStorage.access_token = data.access_token - localStorage.refresh_token = data.refresh_token - localStorage.user = JSON.stringify(data) - - localStorage.master_hash = state.master_hash - localStorage.transmission_key = state.transmission_key + await Promise.all([ + Storage.setItem('email', payload.email), + Storage.setItem('server', payload.server), + Storage.setItem('access_token', data.access_token), + Storage.setItem('refresh_token', data.refresh_token), + Storage.setItem('user', data), + Storage.setItem('master_hash', state.master_hash), + Storage.setItem('transmission_key', state.transmission_key) + ]) HTTPClient.setHeader('Authorization', `Bearer ${state.access_token}`) }, - Logout({ state }) { + async Logout({ state }) { + const email = await Storage.getItem('email') + await Storage.clear() state.access_token = null state.refresh_token = null state.transmission_key = null state.master_hash = null state.user = null - const lsKeys = Object.keys(localStorage).filter( - key => ['email', 'server'].includes(key) === false - ) - lsKeys.forEach(key => localStorage.removeItem(key)) + await Storage.setItem('email', email) }, - loadStore({ state }) { - state.user = JSON.parse(localStorage.user) + async loadStore({ state }) { + state.user = await Storage.getItem('user') } }, mutations: { diff --git a/src/popup/views/Auth/Login.vue b/src/popup/views/Auth/Login.vue index a857ccb..6183015 100644 --- a/src/popup/views/Auth/Login.vue +++ b/src/popup/views/Auth/Login.vue @@ -63,11 +63,16 @@ export default { data() { return { LoginForm: { - email: localStorage.email || '', + email: '', master_password: '' } } }, + mounted() { + this.$storage.getItem('email').then(e => { + if (e) this.LoginForm.email = e + }) + }, methods: { ...mapActions(['Login']), async onLogin() { diff --git a/src/popup/views/Home/index.vue b/src/popup/views/Home/index.vue index dc03a85..b7abd37 100644 --- a/src/popup/views/Home/index.vue +++ b/src/popup/views/Home/index.vue @@ -59,8 +59,7 @@ export default { }, logout() { - this.Logout() - this.$router.push('Login') + this.Logout().then(() => this.$router.push('Login')) } } } diff --git a/src/utils/storage.js b/src/utils/storage.js new file mode 100644 index 0000000..869842b --- /dev/null +++ b/src/utils/storage.js @@ -0,0 +1,7 @@ +import LocalForage from 'localforage' + +export default LocalForage.createInstance({ + driver: LocalForage.INDEXEDDB, + name: 'Passwall Storage', + storeName: "login_data" +}) diff --git a/yarn.lock b/yarn.lock index ff7e5b7..64ab01c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5508,6 +5508,11 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -6715,6 +6720,13 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + dependencies: + immediate "~3.0.5" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -6761,6 +6773,13 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +localforage@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1" + integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g== + dependencies: + lie "3.1.1" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"