From 879ca63be591f8778c2371cd71e823495508f66e Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sun, 11 Jun 2017 21:11:01 -0400 Subject: [PATCH] feat: 2FA UI + modal --- .vscode/settings.json | 1 + client/js/app.js | 2 + client/js/components/modal-profile-2fa.vue | 66 ++++++++++++++++++++ client/js/pages/admin-profile.component.js | 7 ++- client/js/store/index.js | 2 + client/js/store/modules/modal-profile-2fa.js | 21 +++++++ client/scss/components/form.scss | 9 +++ fuse.js | 9 +-- package.json | 1 + server/locales/en/admin.json | 7 ++- server/locales/en/browser.json | 6 +- server/views/modals/admin-upgrade.pug | 25 -------- server/views/pages/admin/profile.pug | 12 +++- yarn.lock | 19 +++++- 14 files changed, 147 insertions(+), 40 deletions(-) create mode 100644 client/js/components/modal-profile-2fa.vue create mode 100644 client/js/store/modules/modal-profile-2fa.js delete mode 100644 server/views/modals/admin-upgrade.pug diff --git a/.vscode/settings.json b/.vscode/settings.json index 1456bd73..0808cb54 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "eslint.enable": true, + "eslint.autoFixOnSave": false, "puglint.enable": true, "standard.enable": false, "editor.formatOnSave": true, diff --git a/client/js/app.js b/client/js/app.js index 7e0ab444..0972238c 100644 --- a/client/js/app.js +++ b/client/js/app.js @@ -70,6 +70,7 @@ import modalCreateUserComponent from './components/modal-create-user.vue' import modalDeleteUserComponent from './components/modal-delete-user.vue' import modalDiscardPageComponent from './components/modal-discard-page.vue' import modalMovePageComponent from './components/modal-move-page.vue' +import modalProfile2faComponent from './components/modal-profile-2fa.vue' import modalUpgradeSystemComponent from './components/modal-upgrade-system.vue' import pageLoaderComponent from './components/page-loader.vue' import searchComponent from './components/search.vue' @@ -181,6 +182,7 @@ $(() => { modalDeleteUser: modalDeleteUserComponent, modalDiscardPage: modalDiscardPageComponent, modalMovePage: modalMovePageComponent, + modalProfile2fa: modalProfile2faComponent, modalUpgradeSystem: modalUpgradeSystemComponent, pageLoader: pageLoaderComponent, search: searchComponent, diff --git a/client/js/components/modal-profile-2fa.vue b/client/js/components/modal-profile-2fa.vue new file mode 100644 index 00000000..81ed361b --- /dev/null +++ b/client/js/components/modal-profile-2fa.vue @@ -0,0 +1,66 @@ + + + diff --git a/client/js/pages/admin-profile.component.js b/client/js/pages/admin-profile.component.js index a12dcad5..ec5843c9 100644 --- a/client/js/pages/admin-profile.component.js +++ b/client/js/pages/admin-profile.component.js @@ -2,13 +2,18 @@ export default { name: 'admin-profile', - props: ['email', 'name', 'provider'], + props: ['email', 'name', 'provider', 'tfaIsActive'], data() { return { password: '********', passwordVerify: '********' } }, + computed: { + tfaStatus() { + return this.tfaIsActive ? this.$t('profile.tfaenabled') : this.$t('profile.tfadisabled') + } + }, methods: { saveUser() { let self = this diff --git a/client/js/store/index.js b/client/js/store/index.js index 64b96fbd..cf151045 100644 --- a/client/js/store/index.js +++ b/client/js/store/index.js @@ -12,6 +12,7 @@ import modalCreateUser from './modules/modal-create-user' import modalDeleteUser from './modules/modal-delete-user' import modalDiscardPage from './modules/modal-discard-page' import modalMovePage from './modules/modal-move-page' +import modalProfile2fa from './modules/modal-profile-2fa' import modalUpgradeSystem from './modules/modal-upgrade-system' import pageLoader from './modules/page-loader' @@ -41,6 +42,7 @@ export default new Vuex.Store({ modalDeleteUser, modalDiscardPage, modalMovePage, + modalProfile2fa, modalUpgradeSystem, pageLoader } diff --git a/client/js/store/modules/modal-profile-2fa.js b/client/js/store/modules/modal-profile-2fa.js new file mode 100644 index 00000000..bb0f9e8f --- /dev/null +++ b/client/js/store/modules/modal-profile-2fa.js @@ -0,0 +1,21 @@ +'use strict' + +export default { + namespaced: true, + state: { + shown: false, + step: 'confirm' + }, + getters: {}, + mutations: { + shownChange: (state, shownState) => { state.shown = shownState }, + stepChange: (state, stepState) => { state.step = stepState } + }, + actions: { + open({ commit }, opts) { + commit('shownChange', true) + commit('stepChange', 'confirm') + }, + close({ commit }) { commit('shownChange', false) } + } +} diff --git a/client/scss/components/form.scss b/client/scss/components/form.scss index d7dcbb25..81cc0305 100644 --- a/client/scss/components/form.scss +++ b/client/scss/components/form.scss @@ -161,6 +161,15 @@ font-size: 14px; font-weight: 500; display: block; + + strong { + @each $color, $colorvalue in $material-colors { + &.is-#{$color} { + color: mc($color, '600'); + } + } + } + } .form-sections { diff --git a/fuse.js b/fuse.js index f1d11100..107d0ff9 100644 --- a/fuse.js +++ b/fuse.js @@ -99,14 +99,7 @@ globalTasks.then(() => { log: true }) - if (dev) { - fuse.dev({ - port: 4444, - httpServer: false - }) - } - - const bundleVendor = fuse.bundle('vendor').instructions('~ index.js') + const bundleVendor = fuse.bundle('vendor').instructions('~ index.js') // eslint-disable-line no-unused-vars const bundleApp = fuse.bundle('app').instructions('!> [index.js]') const bundleSetup = fuse.bundle('configure').instructions('> configure.js') diff --git a/package.json b/package.json index 3b69a636..804f7d47 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "mongodb": "^2.2.28", "mongoose": "^4.10.5", "multer": "^1.3.0", + "node-2fa": "^1.1.2", "node-graceful": "^0.2.3", "ora": "^1.2.0", "passport": "^0.3.2", diff --git a/server/locales/en/admin.json b/server/locales/en/admin.json index 7a46d992..c51091a6 100644 --- a/server/locales/en/admin.json +++ b/server/locales/en/admin.json @@ -9,7 +9,10 @@ "passwordverify": "Verify Password", "provider": "Provider", "savechanges": "Save Changes", - "subtitle": "Profile and authentication info" + "subtitle": "Profile and authentication info", + "tfa": "Two-Factor Authentication (2FA)", + "tfaenable": "Enable 2FA", + "tfadisable": "Disable 2FA" }, "stats": { "subtitle": "General site-wide statistics", @@ -48,4 +51,4 @@ "edituser": "Edit User", "uniqueid": "Unique ID" } -} \ No newline at end of file +} diff --git a/server/locales/en/browser.json b/server/locales/en/browser.json index c203362b..b8fd7fc3 100644 --- a/server/locales/en/browser.json +++ b/server/locales/en/browser.json @@ -97,10 +97,14 @@ "nav": { "home": "Home" }, + "profile": { + "tfaenabled": "Enabled", + "tfadisabled": "Disabled" + }, "search": { "didyoumean": "Did you mean...?", "nomatch": "No results matching your query", "placeholder": "Search...", "results": "Search Results" } -} \ No newline at end of file +} diff --git a/server/views/modals/admin-upgrade.pug b/server/views/modals/admin-upgrade.pug deleted file mode 100644 index 2fa36b3e..00000000 --- a/server/views/modals/admin-upgrade.pug +++ /dev/null @@ -1,25 +0,0 @@ -.modal(v-bind:class='{ "is-active": upgradeModal.state }') - .modal-background - .modal-container - .modal-content - template(v-if='upgradeModal.step === "running"') - header.is-blue Install - section.modal-loading - i - span Wiki.js {{ upgradeModal.mode }} in progress... - em Please wait - template(v-if='upgradeModal.step === "error"') - header.is-red Installation Error - section.modal-loading - span {{ upgradeModal.error }} - footer - a.button.is-grey.is-outlined(v-on:click='upgradeCancel') Abort - a.button.is-deep-orange(v-on:click='upgradeStart') Try Again - template(v-if='upgradeModal.step === "confirm"') - header.is-deep-orange Are you sure? - section - label.label You are about to {{ upgradeModal.mode }} Wiki.js. - span.note You will not be able to access your wiki during the operation. Content will not be affected. However, it is your responsability to ensure you have a backup in the unexpected event content gets lost or corrupted. - footer - a.button.is-grey.is-outlined(v-on:click='upgradeCancel') Abort - a.button.is-deep-orange(v-on:click='upgradeStart') Start diff --git a/server/views/pages/admin/profile.pug b/server/views/pages/admin/profile.pug index 0ba46dcc..05263ef2 100644 --- a/server/views/pages/admin/profile.pug +++ b/server/views/pages/admin/profile.pug @@ -27,7 +27,15 @@ block adminContent p.control.is-fullwidth input.input(type='text', placeholder=t('admin:profile.displaynameexample'), v-model='name') section - button.button.is-green(v-on:click='saveUser') + label.label #{t('admin:profile.tfa')}: #[strong.is-red(v-cloak) {{ tfaStatus }}] + button.button.is-blue(@click='$store.dispatch("modalProfile2fa/open")', :disabled='tfaIsActive') + i.icon-circle-plus + span= t('admin:profile.tfaenable') + button.button.is-blue(@click='saveUser', :disabled='!tfaIsActive') + i.icon-circle-minus + span= t('admin:profile.tfadisable') + section + button.button.is-green(@click='saveUser') i.icon-check span= t('admin:profile.savechanges') .column @@ -49,3 +57,5 @@ block adminContent p.control= moment(user.createdAt).format('LL') label.label= t('admin:profile.lastprofileupdate') p.control= moment(user.updatedAt).format('LL') + + modal-profile-2fa diff --git a/yarn.lock b/yarn.lock index b2925746..6d34c8f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4617,11 +4617,11 @@ mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" -mute-stream@0.0.6, mute-stream@~0.0.4: +mute-stream@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" -mute-stream@0.0.7: +mute-stream@0.0.7, mute-stream@~0.0.4: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -4682,6 +4682,13 @@ ngraminator@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/ngraminator/-/ngraminator-0.0.1.tgz#29cfd699df6970f42de9b2f0bdc7f4b60fad6f8e" +node-2fa@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/node-2fa/-/node-2fa-1.1.2.tgz#5bc5691474afe35ae6b3b76459b98b7c20c7158c" + dependencies: + notp "^2.0.3" + thirty-two "0.0.2" + node-abi@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.0.2.tgz#00f3e0a58100eb480133b48c99a32cc1f9e6c93e" @@ -4830,6 +4837,10 @@ normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" +notp@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/notp/-/notp-2.0.3.tgz#a9fd11e25cfe1ccb39fc6689544ee4c10ef9a577" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -6774,6 +6785,10 @@ then-fs@^2.0.0: dependencies: promise ">=3.2 <8" +thirty-two@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-0.0.2.tgz#4253e29d8cb058f0480267c5698c0e4927e54b6a" + throat@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/throat/-/throat-3.0.0.tgz#e7c64c867cbb3845f10877642f7b60055b8ec0d6"