Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/twofactor_u2f.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristoph Wurst <christoph@winzerhof-wurst.at>2018-10-19 11:33:27 +0300
committerChristoph Wurst <christoph@winzerhof-wurst.at>2018-10-22 17:22:00 +0300
commit0bb0a8e61b1d835e5d9f8f2553c01441fd989d1a (patch)
treefbea358a1e1c178d672fb48c7d28eaabb53cb6ec /src
parent4da04fd450d7ce3a6ab6da399ddd95899c78d51b (diff)
Migrate to Vue
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Diffstat (limited to 'src')
-rw-r--r--src/components/AddDeviceDialog.vue193
-rw-r--r--src/components/Device.vue112
-rw-r--r--src/components/PersonalSettings.vue73
-rw-r--r--src/main-settings.js46
-rw-r--r--src/mixins/Nextcloud.js26
-rw-r--r--src/services/RegistrationService.js47
-rw-r--r--src/store.js45
-rw-r--r--src/tests/components/Device.spec.js98
-rw-r--r--src/tests/components/PersonalSettings.spec.js81
-rw-r--r--src/tests/setup.js34
-rw-r--r--src/webpack.common.js51
-rw-r--r--src/webpack.dev.js7
-rw-r--r--src/webpack.prod.js7
-rw-r--r--src/webpack.test.js30
14 files changed, 850 insertions, 0 deletions
diff --git a/src/components/AddDeviceDialog.vue b/src/components/AddDeviceDialog.vue
new file mode 100644
index 0000000..8a72126
--- /dev/null
+++ b/src/components/AddDeviceDialog.vue
@@ -0,0 +1,193 @@
+<!--
+ - @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ -
+ - @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -->
+
+<template>
+ <div v-if="step === RegistrationSteps.READY">
+ <button
+ v-on:click="start">{{ t('twofactor_u2f', 'Add U2F device') }}
+ </button>
+ </div>
+
+ <div v-else-if="step === RegistrationSteps.U2F_REGISTRATION"
+ class="new-u2f-device">
+ <span class="icon-loading-small u2f-loading"></span>
+ {{ t('twofactor_u2f', 'Please plug in your U2F device and press the device button to authorize.') }}
+ </div>
+
+ <div v-else-if="step === RegistrationSteps.NAMING"
+ class="new-u2f-device">
+ <span class="icon-loading-small u2f-loading"></span>
+ <input type="text"
+ :placeholder="t('twofactor_u2f', 'Name your device')"
+ v-model="name">
+ <button v-on:click="submit">{{ t('twofactor_u2f', 'Add') }}</button>
+ </div>
+
+ <div v-else-if="step === RegistrationSteps.PERSIST"
+ class="new-u2f-device">
+ <span class="icon-loading-small u2f-loading"></span>
+ {{ t('twofactor_u2f', 'Adding your device …') }}
+ </div>
+
+ <div v-else>
+ Invalid registration step. This should not have happened.
+ </div>
+</template>
+
+<script>
+ import confirmPassword from 'nextcloud-password-confirmation'
+ import u2f from 'u2f-api'
+
+ import {
+ startRegistration,
+ finishRegistration
+ } from '../services/RegistrationService'
+
+ const logAndPass = (text) => (data) => {
+ console.debug(text)
+ return data
+ }
+
+ const RegistrationSteps = Object.freeze({
+ READY: 1,
+ U2F_REGISTRATION: 2,
+ NAMING: 3,
+ PERSIST: 4,
+ })
+
+ export default {
+ name: 'AddDeviceDialog',
+ props: {
+ httpWarning: Boolean
+ },
+ data () {
+ return {
+ name: '',
+ registrationData: {},
+ RegistrationSteps,
+ step: RegistrationSteps.READY,
+ }
+ },
+ methods: {
+ start () {
+ this.step = RegistrationSteps.U2F_REGISTRATION
+
+ return confirmPassword()
+ .then(this.getRegistrationData)
+ .then(this.register)
+ .then(() => this.step = RegistrationSteps.NAMING)
+ .catch(console.error.bind(this))
+ },
+
+ getRegistrationData () {
+ return startRegistration()
+ .catch(err => {
+ console.error('Error getting u2f registration data from server', err)
+ throw new Error(t('twofactor_u2f', 'Server error while trying to add U2F device'))
+ })
+ },
+
+ register ({req, sigs}) {
+ console.debug('starting u2f registration')
+
+ return u2f.register([req], sigs)
+ .then(data => {
+ console.debug('u2f registration was successful', data)
+
+ if (data.errorCode && data.errorCode !== 0) {
+ return this.rejectRegistration(data)
+ }
+
+ this.registrationData = data
+ })
+ },
+
+ rejectRegistration (data) {
+ // https://developers.yubico.com/U2F/Libraries/Client_error_codes.html
+ switch (data.errorCode) {
+ case 4:
+ // 4 - DEVICE_INELIGIBLE
+ Promise.reject(new Error(t('twofactor_u2f', 'U2F device is already registered (error code {errorCode})', {
+ errorCode: data.errorCode
+ })));
+ break;
+ case 5:
+ // 5 - TIMEOUT
+ Promise.reject(new Error(t('twofactor_u2f', 'U2F device registration timeout reached (error code {errorCode})', {
+ errorCode: data.errorCode
+ })));
+ break;
+ default:
+ // 1 - OTHER_ERROR
+ // 2 - BAD_REQUEST
+ // 3 - CONFIGURATION_UNSUPPORTED
+ Promise.reject(new Error(t('twofactor_u2f', 'U2F device registration failed (error code {errorCode})', {
+ errorCode: data.errorCode
+ })));
+ }
+ },
+
+ submit () {
+ this.step = RegistrationSteps.PERSIST
+
+ return confirmPassword()
+ .then(logAndPass('confirmed password'))
+ .then(this.saveRegistrationData)
+ .then(logAndPass('registration data saved'))
+ .then(() => this.reset())
+ .then(logAndPass('app reset'))
+ .catch(console.error.bind(this))
+ },
+
+ saveRegistrationData () {
+ const data = this.registrationData
+ data.name = this.name
+
+ return finishRegistration(data)
+ .then(device => this.$store.commit('addDevice', device))
+ .then(logAndPass('new device added to store'))
+ .catch(err => {
+ console.error('Error persisting u2f registration', err)
+ throw new Error(t('twofactor_u2f', 'Server error while trying to complete U2F device registration'))
+ })
+ },
+
+ reset () {
+ this.name = ''
+ this.registrationData = {}
+ this.step = RegistrationSteps.READY
+ }
+ }
+ }
+</script>
+
+<style scoped>
+ .u2f-loading {
+ display: inline-block;
+ vertical-align: sub;
+ margin-left: 2px;
+ margin-right: 2px;
+ }
+
+ .new-u2f-device {
+ line-height: 300%;
+ }
+</style> \ No newline at end of file
diff --git a/src/components/Device.vue b/src/components/Device.vue
new file mode 100644
index 0000000..6344d17
--- /dev/null
+++ b/src/components/Device.vue
@@ -0,0 +1,112 @@
+<!--
+ - @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ -
+ - @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -->
+
+<template>
+ <div class="u2f-device" :data-u2f-id="id">
+ <span class="icon-u2f-device"></span>
+ {{name || t('twofactor_u2f', 'Unnamed device') }}
+ <span class="more">
+ <a class="icon icon-more"
+ v-on:click.stop="togglePopover"></a>
+ <div class="popovermenu"
+ :class="{open: showPopover}"
+ v-click-outside="hidePopover">
+ <PopoverMenu :menu="menu"/>
+ </div>
+ </span>
+ </div>
+</template>
+
+<script>
+ import ClickOutside from 'vue-click-outside'
+ import confirmPassword from 'nextcloud-password-confirmation'
+ import {PopoverMenu} from 'nextcloud-vue'
+
+ export default {
+ name: 'Device',
+ props: {
+ id: Number,
+ name: String,
+ },
+ components: {
+ PopoverMenu
+ },
+ directives: {
+ ClickOutside
+ },
+ data () {
+ return {
+ showPopover: false,
+ menu: [
+ {
+ text: t('twofactor_u2f', 'Remove'),
+ icon: 'icon-delete',
+ action: () => {
+ confirmPassword()
+ .then(() => this.$store.dispatch('removeDevice', this.id))
+ }
+ }
+ ]
+ }
+ },
+ methods: {
+ togglePopover () {
+ this.showPopover = !this.showPopover
+ },
+
+ hidePopover () {
+ this.showPopover = false
+ }
+ }
+ }
+</script>
+
+<style scoped>
+ .u2f-device {
+ line-height: 300%;
+ display: flex;
+ }
+
+ .u2f-device .more {
+ position: relative;
+ }
+
+ .u2f-device .more .icon-more {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ padding-left: 20px;
+ vertical-align: middle;
+ opacity: .7;
+ }
+
+ .u2f-device .popovermenu {
+ right: -12px;
+ top: 42px;
+ }
+
+ .icon-u2f-device {
+ display: inline-block;
+ background-size: 100%;
+ padding: 3px;
+ margin: 3px;
+ }
+</style> \ No newline at end of file
diff --git a/src/components/PersonalSettings.vue b/src/components/PersonalSettings.vue
new file mode 100644
index 0000000..559f01b
--- /dev/null
+++ b/src/components/PersonalSettings.vue
@@ -0,0 +1,73 @@
+<!--
+ - @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ -
+ - @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -->
+
+<template>
+ <div>
+ <p v-if="devices.length === 0">{{ t('twofactor_u2f', 'No U2F devices configured. You are not using U2F as second factor at the moment.') }}</p>
+ <p v-else>{{ t('twofactor_u2f', 'The following devices are configured for U2F second-factor authentication:') }}</p>
+ <Device v-for="device in devices"
+ :key="device.id"
+ :id="device.id"
+ :name="device.name" />
+
+ <AddDeviceDialog :httpWarning="httpWarning" />
+ <p v-if="notSupported">
+ {{ t('twofactor_u2f', 'Your browser does not support u2f.') }}
+ {{ t('twofactor_u2f', 'Chrome is the only browser that supports U2F devices. You need to install the "U2F Support Add-on" on Firefox to use U2F.') }}
+ </p>
+ <p v-if="httpWarning"
+ id="u2f-http-warning">
+ {{ t('twofactor_u2f', 'You are accessing this site via an insecure connection. Browsers might therefore refuse the U2F authentication.') }}
+ </p>
+ </div>
+</template>
+
+<script>
+ import u2f from 'u2f-api'
+
+ import AddDeviceDialog from './AddDeviceDialog'
+ import Device from './Device'
+
+ export default {
+ name: 'PersonalSettings',
+ props: {
+ httpWarning: Boolean
+ },
+ components: {
+ AddDeviceDialog,
+ Device
+ },
+ data() {
+ return {
+ notSupported: !u2f.isSupported()
+ }
+ },
+ computed: {
+ devices() {
+ return this.$store.state.devices
+ }
+ }
+ }
+</script>
+
+<style scoped>
+
+</style> \ No newline at end of file
diff --git a/src/main-settings.js b/src/main-settings.js
new file mode 100644
index 0000000..9bd0c03
--- /dev/null
+++ b/src/main-settings.js
@@ -0,0 +1,46 @@
+/*
+ * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import store from './store'
+import Vue from 'vue'
+
+import Nextcloud from './mixins/Nextcloud'
+
+Vue.mixin(Nextcloud)
+
+const initialStateElement = document.getElementById('twofactor-u2f-initial-state')
+if (initialStateElement) {
+ const devices = JSON.parse(initialStateElement.value)
+ devices.sort((d1, d2) => d1.name.localeCompare(d2.name))
+ store.replaceState({
+ devices
+ })
+}
+
+import PersonalSettings from './components/PersonalSettings'
+
+const View = Vue.extend(PersonalSettings)
+new View({
+ propsData: {
+ httpWarning: document.location.protocol !== 'https:',
+ },
+ store,
+}).$mount('#twofactor-u2f-settings')
diff --git a/src/mixins/Nextcloud.js b/src/mixins/Nextcloud.js
new file mode 100644
index 0000000..8e35420
--- /dev/null
+++ b/src/mixins/Nextcloud.js
@@ -0,0 +1,26 @@
+/*
+ * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+export default {
+ methods: {
+ t,
+ }
+}
diff --git a/src/services/RegistrationService.js b/src/services/RegistrationService.js
new file mode 100644
index 0000000..241ffcb
--- /dev/null
+++ b/src/services/RegistrationService.js
@@ -0,0 +1,47 @@
+/*
+ * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import Axios from 'nextcloud-axios'
+import {generateUrl} from 'nextcloud-server/dist/router'
+
+export function startRegistration () {
+ const url = generateUrl('/apps/twofactor_u2f/settings/startregister')
+
+ return Axios.post(url)
+ .then(resp => resp.data)
+}
+
+export function finishRegistration (data) {
+ const url = generateUrl('/apps/twofactor_u2f/settings/finishregister')
+
+ return Axios.post(url, data)
+ .then(resp => resp.data)
+}
+
+export function removeRegistration (id) {
+ const url = generateUrl('/apps/twofactor_u2f/settings/remove')
+ const data = {
+ id
+ }
+
+ return Axios.post(url, data)
+ .then(resp => resp.data)
+}
diff --git a/src/store.js b/src/store.js
new file mode 100644
index 0000000..2590776
--- /dev/null
+++ b/src/store.js
@@ -0,0 +1,45 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+import {removeRegistration} from './services/RegistrationService'
+
+Vue.use(Vuex)
+
+export const mutations = {
+ addDevice (state, device) {
+ state.devices.push(device)
+ state.devices.sort((d1, d2) => d1.name.localeCompare(d2.name))
+ },
+
+ removeDevice (state, id) {
+ state.devices = state.devices.filter(device => device.id !== id)
+ }
+}
+
+export const actions = {
+ removeDevice ({state, commit}, id) {
+ const device = state.devices[id]
+
+ commit('removeDevice', id)
+
+ removeRegistration(id)
+ .catch(err => {
+ // Rollback
+ commit('addDevice', device)
+
+ throw err
+ })
+ }
+}
+
+export const getters = {}
+
+export default new Vuex.Store({
+ strict: process.env.NODE_ENV !== 'production',
+ state: {
+ devices: []
+ },
+ getters,
+ mutations,
+ actions
+})
diff --git a/src/tests/components/Device.spec.js b/src/tests/components/Device.spec.js
new file mode 100644
index 0000000..dad0ac9
--- /dev/null
+++ b/src/tests/components/Device.spec.js
@@ -0,0 +1,98 @@
+/*
+ * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import {shallowMount, createLocalVue} from '@vue/test-utils'
+import Vuex from 'vuex'
+
+import Nextcloud from '../../mixins/Nextcloud'
+
+const localVue = createLocalVue()
+
+localVue.use(Vuex)
+localVue.mixin(Nextcloud)
+
+import Device from '../../components/Device'
+
+describe('Device component', () => {
+ var actions
+ var store
+
+ beforeEach(() => {
+ actions = {}
+ store = new Vuex.Store({
+ state: {
+ devices: []
+ },
+ actions
+ })
+ })
+
+ it('renders devices without a name', () => {
+ store.state.devices.push({
+ id: 1,
+ name: undefined
+ })
+ const device = shallowMount(Device, {
+ store,
+ localVue
+ })
+
+ expect(device.text()).to.have.string('Unnamed device')
+ })
+
+ it('has a closed popover menu by default', () => {
+ const device = shallowMount(Device, {
+ store,
+ localVue
+ })
+
+ expect(device.contains('.popovermenu.open')).to.be.false
+ })
+
+ it('opens popover menu on click', done => {
+ const device = shallowMount(Device, {
+ store,
+ localVue
+ })
+
+ device.find('.icon-more').trigger('click')
+
+ localVue.nextTick(() => {
+ expect(device.vm.showPopover).to.be.true
+ done()
+ })
+ })
+
+ it('closed popover menu on second click', done => {
+ const device = shallowMount(Device, {
+ store,
+ localVue
+ })
+ device.vm.showPopover = true
+
+ device.find('.icon-more').trigger('click')
+
+ localVue.nextTick(() => {
+ expect(device.vm.showPopover).to.be.false
+ done()
+ })
+ })
+}) \ No newline at end of file
diff --git a/src/tests/components/PersonalSettings.spec.js b/src/tests/components/PersonalSettings.spec.js
new file mode 100644
index 0000000..ef74937
--- /dev/null
+++ b/src/tests/components/PersonalSettings.spec.js
@@ -0,0 +1,81 @@
+/*
+ * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import {shallowMount, createLocalVue} from '@vue/test-utils'
+import Vuex from 'vuex'
+
+import Nextcloud from '../../mixins/Nextcloud'
+
+const localVue = createLocalVue()
+
+localVue.use(Vuex)
+localVue.mixin(Nextcloud)
+
+import PersonalSettings from '../../components/PersonalSettings'
+
+describe('Device component', () => {
+ var actions
+ var store
+
+ beforeEach(() => {
+ actions = {}
+ store = new Vuex.Store({
+ state: {
+ devices: []
+ },
+ actions
+ })
+ })
+
+ it('shows text if no devices are configured', () => {
+ const settings = shallowMount(PersonalSettings, {
+ store,
+ localVue
+ })
+
+ expect(settings.text()).to.contain('No U2F devices configured')
+ })
+
+ it('shows no info text if devices are configured', () => {
+ const settings = shallowMount(PersonalSettings, {
+ store,
+ localVue
+ })
+ store.state.devices.push({
+ id: 1,
+ name: 'a'
+ })
+
+ expect(settings.text()).to.not.contain('No U2F devices configured')
+ })
+
+ it('shows a HTTP warning', () => {
+ const settings = shallowMount(PersonalSettings, {
+ store,
+ localVue,
+ propsData: {
+ httpWarning: true
+ }
+ })
+
+ expect(settings.text()).to.contain('You are accessing this site via an')
+ })
+})
diff --git a/src/tests/setup.js b/src/tests/setup.js
new file mode 100644
index 0000000..ed7cd78
--- /dev/null
+++ b/src/tests/setup.js
@@ -0,0 +1,34 @@
+/*
+ * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+require('jsdom-global')()
+
+const t = (app, str) => str
+
+require('vue').mixin({
+ methods: {
+ t
+ }
+})
+
+global.expect = require('chai').expect
+global.OC = {}
+global.t = t
diff --git a/src/webpack.common.js b/src/webpack.common.js
new file mode 100644
index 0000000..789da78
--- /dev/null
+++ b/src/webpack.common.js
@@ -0,0 +1,51 @@
+const path = require('path');
+const { VueLoaderPlugin } = require('vue-loader');
+
+module.exports = {
+ entry: path.join(__dirname, 'main-settings.js'),
+ output: {
+ path: path.resolve(__dirname, '../js'),
+ publicPath: '/js/',
+ filename: 'settings.js'
+ },
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: ['vue-style-loader', 'css-loader']
+ },
+ {
+ test: /\.scss$/,
+ use: ['vue-style-loader', 'css-loader', 'sass-loader']
+ },
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader'
+ },
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ exclude: /node_modules/
+ },
+ {
+ test: /\.(png|jpg|gif)$/,
+ loader: 'file-loader',
+ options: {
+ name: '[name].[ext]?[hash]'
+ }
+ },
+ {
+ test: /\.(svg)$/i,
+ use: [
+ {
+ loader: 'url-loader'
+ }
+ ]
+ }
+ ]
+ },
+ plugins: [new VueLoaderPlugin()],
+ resolve: {
+ extensions: ['*', '.js', '.vue']
+ }
+}; \ No newline at end of file
diff --git a/src/webpack.dev.js b/src/webpack.dev.js
new file mode 100644
index 0000000..da0084b
--- /dev/null
+++ b/src/webpack.dev.js
@@ -0,0 +1,7 @@
+const merge = require('webpack-merge');
+const common = require('./webpack.common.js');
+
+module.exports = merge(common, {
+ mode: 'development',
+ devtool: 'source-map',
+})
diff --git a/src/webpack.prod.js b/src/webpack.prod.js
new file mode 100644
index 0000000..8e5e2f8
--- /dev/null
+++ b/src/webpack.prod.js
@@ -0,0 +1,7 @@
+const merge = require('webpack-merge')
+const common = require('./webpack.common.js')
+
+module.exports = merge(common, {
+ mode: 'production',
+ devtool: 'source-map'
+})
diff --git a/src/webpack.test.js b/src/webpack.test.js
new file mode 100644
index 0000000..8630396
--- /dev/null
+++ b/src/webpack.test.js
@@ -0,0 +1,30 @@
+/*
+ * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+const merge = require('webpack-merge');
+const common = require('./webpack.common.js');
+const nodeExternals = require('webpack-node-externals')
+
+module.exports = merge(common, {
+ mode: 'development',
+ devtool: 'inline-cheap-module-source-map',
+ externals: [nodeExternals()]
+})