diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ff6f4e831..3d58752e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -441,7 +441,7 @@ jobs: report_name: Coverage Admin Interface type: lcov result_path: ./coverage/lcov.info - min_coverage: 47 + min_coverage: 51 token: ${{ github.token }} ############################################################################## diff --git a/admin/.env.dist b/admin/.env.dist new file mode 100644 index 000000000..6d78e6782 --- /dev/null +++ b/admin/.env.dist @@ -0,0 +1,3 @@ +GRAPHQL_URI=http://localhost:4000/graphql +WALLET_AUTH_URL=http://localhost/vue/authenticate?token=$1 +DEBUG_DISABLE_AUTH=false \ No newline at end of file diff --git a/admin/src/App.spec.js b/admin/src/App.spec.js index e77bc578b..6936394f1 100644 --- a/admin/src/App.spec.js +++ b/admin/src/App.spec.js @@ -7,11 +7,19 @@ const stubs = { RouterView: true, } +const mocks = { + $store: { + state: { + token: null, + }, + }, +} + describe('App', () => { let wrapper const Wrapper = () => { - return shallowMount(App, { localVue, stubs }) + return shallowMount(App, { localVue, stubs, mocks }) } describe('shallowMount', () => { diff --git a/admin/src/App.vue b/admin/src/App.vue index a76b1dcab..40460eda4 100644 --- a/admin/src/App.vue +++ b/admin/src/App.vue @@ -1,19 +1,15 @@ diff --git a/admin/src/components/NavBar.vue b/admin/src/components/NavBar.vue index de9cfe6b2..c52743857 100644 --- a/admin/src/components/NavBar.vue +++ b/admin/src/components/NavBar.vue @@ -16,15 +16,43 @@ > | {{ $store.state.openCreations }} offene Schöpfungen + Wallet + Logout - Profilbereich diff --git a/admin/src/config/index.js b/admin/src/config/index.js index eab63e903..69d30a66a 100644 --- a/admin/src/config/index.js +++ b/admin/src/config/index.js @@ -17,8 +17,13 @@ const environment = { PRODUCTION: process.env.NODE_ENV === 'production' || false, } -const server = { +const endpoints = { GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql', + WALLET_AUTH_URL: process.env.WALLET_AUTH_URL || 'http://localhost/vue/authenticate?token=$1', +} + +const debug = { + DEBUG_DISABLE_AUTH: process.env.DEBUG_DISABLE_AUTH === 'true' || false, } const options = {} @@ -26,8 +31,9 @@ const options = {} const CONFIG = { ...version, ...environment, - ...server, + ...endpoints, ...options, + ...debug, } export default CONFIG diff --git a/admin/src/layouts/defaultLayout.vue b/admin/src/layouts/defaultLayout.vue new file mode 100644 index 000000000..28babdd58 --- /dev/null +++ b/admin/src/layouts/defaultLayout.vue @@ -0,0 +1,19 @@ + + + diff --git a/admin/src/views/Creation.spec.js b/admin/src/pages/Creation.spec.js similarity index 100% rename from admin/src/views/Creation.spec.js rename to admin/src/pages/Creation.spec.js diff --git a/admin/src/views/Creation.vue b/admin/src/pages/Creation.vue similarity index 100% rename from admin/src/views/Creation.vue rename to admin/src/pages/Creation.vue diff --git a/admin/src/views/CreationConfirm.spec.js b/admin/src/pages/CreationConfirm.spec.js similarity index 100% rename from admin/src/views/CreationConfirm.spec.js rename to admin/src/pages/CreationConfirm.spec.js diff --git a/admin/src/views/CreationConfirm.vue b/admin/src/pages/CreationConfirm.vue similarity index 100% rename from admin/src/views/CreationConfirm.vue rename to admin/src/pages/CreationConfirm.vue diff --git a/admin/src/views/Overview.vue b/admin/src/pages/Overview.vue similarity index 100% rename from admin/src/views/Overview.vue rename to admin/src/pages/Overview.vue diff --git a/admin/src/views/UserSearch.spec.js b/admin/src/pages/UserSearch.spec.js similarity index 100% rename from admin/src/views/UserSearch.spec.js rename to admin/src/pages/UserSearch.spec.js diff --git a/admin/src/views/UserSearch.vue b/admin/src/pages/UserSearch.vue similarity index 100% rename from admin/src/views/UserSearch.vue rename to admin/src/pages/UserSearch.vue diff --git a/admin/src/router/guards.js b/admin/src/router/guards.js index c9baf61cb..d59234a25 100644 --- a/admin/src/router/guards.js +++ b/admin/src/router/guards.js @@ -1,7 +1,25 @@ +import CONFIG from '../config' + const addNavigationGuards = (router, store) => { + // store token on `authenticate` router.beforeEach((to, from, next) => { - // handle authentication - if (to.meta.requiresAuth && !store.state.token) { + if (to.path === '/authenticate' && to.query && to.query.token) { + // TODO verify user to get user data + store.commit('token', to.query.token) + next({ path: '/' }) + } else { + next() + } + }) + + // protect all routes but `not-found` + router.beforeEach((to, from, next) => { + if ( + !CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes + !store.state.token && // we do not have a token + to.path !== '/not-found' && // we are not on `not-found` + to.path !== '/logout' // we are not on `logout` + ) { next({ path: '/not-found' }) } else { next() diff --git a/admin/src/router/guards.test.js b/admin/src/router/guards.test.js new file mode 100644 index 000000000..e69846aab --- /dev/null +++ b/admin/src/router/guards.test.js @@ -0,0 +1,64 @@ +import addNavigationGuards from './guards' +import router from './router' + +const storeCommitMock = jest.fn() + +const store = { + commit: storeCommitMock, + state: { + token: null, + }, +} + +addNavigationGuards(router, store) + +describe('navigation guards', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('authenticate', () => { + const navGuard = router.beforeHooks[0] + const next = jest.fn() + + describe('with valid token', () => { + it('commits the token to the store', async () => { + navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next) + expect(storeCommitMock).toBeCalledWith('token', 'valid-token') + }) + + it('redirects to /', async () => { + navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next) + expect(next).toBeCalledWith({ path: '/' }) + }) + }) + + describe('without valid token', () => { + it('does not commit the token to the store', async () => { + navGuard({ path: '/authenticate' }, {}, next) + expect(storeCommitMock).not.toBeCalledWith() + }) + + it('calls next withou arguments', async () => { + navGuard({ path: '/authenticate' }, {}, next) + expect(next).toBeCalledWith() + }) + }) + }) + + describe('protect all routes', () => { + const navGuard = router.beforeHooks[1] + const next = jest.fn() + + it('redirects no not found with no token in store ', () => { + navGuard({ path: '/' }, {}, next) + expect(next).toBeCalledWith({ path: '/not-found' }) + }) + + it('does not redirect when token in store', () => { + store.state.token = 'valid token' + navGuard({ path: '/' }, {}, next) + expect(next).toBeCalledWith() + }) + }) +}) diff --git a/admin/src/router/router.test.js b/admin/src/router/router.test.js index 8e2e70d4d..eb9b646cb 100644 --- a/admin/src/router/router.test.js +++ b/admin/src/router/router.test.js @@ -44,19 +44,19 @@ describe('router', () => { }) describe('routes', () => { + it('has seven routes defined', () => { + expect(routes).toHaveLength(7) + }) + it('has "/overview" as default', async () => { const component = await routes.find((r) => r.path === '/').component() expect(component.default.name).toBe('overview') }) - it('has fourteen routes defined', () => { - expect(routes).toHaveLength(6) - }) - - describe('overview', () => { - it('loads the "Overview" component', async () => { - const component = await routes.find((r) => r.path === '/overview').component() - expect(component.default.name).toBe('overview') + describe('logout', () => { + it('loads the "NotFoundPage" component', async () => { + const component = await routes.find((r) => r.path === '/logout').component() + expect(component.default.name).toBe('not-found') }) }) diff --git a/admin/src/router/routes.js b/admin/src/router/routes.js index a13463e08..72e7b1ac5 100644 --- a/admin/src/router/routes.js +++ b/admin/src/router/routes.js @@ -1,38 +1,27 @@ const routes = [ { - path: '/', - component: () => import('@/views/Overview.vue'), - meta: { - requiresAuth: true, - }, + path: '/authenticate', }, { - path: '/overview', - component: () => import('@/views/Overview.vue'), - meta: { - requiresAuth: true, - }, + path: '/', + component: () => import('@/pages/Overview.vue'), + }, + { + // TODO: Implement a "You are logged out"-Page + path: '/logout', + component: () => import('@/components/NotFoundPage.vue'), }, { path: '/user', - component: () => import('@/views/UserSearch.vue'), - meta: { - requiresAuth: true, - }, + component: () => import('@/pages/UserSearch.vue'), }, { path: '/creation', - component: () => import('@/views/Creation.vue'), - meta: { - requiresAuth: true, - }, + component: () => import('@/pages/Creation.vue'), }, { path: '/creation-confirm', - component: () => import('@/views/CreationConfirm.vue'), - meta: { - requiresAuth: true, - }, + component: () => import('@/pages/CreationConfirm.vue'), }, { path: '*', diff --git a/admin/src/store/store.js b/admin/src/store/store.js index d199368fb..754c559c8 100644 --- a/admin/src/store/store.js +++ b/admin/src/store/store.js @@ -1,6 +1,7 @@ import Vuex from 'vuex' import Vue from 'vue' import createPersistedState from 'vuex-persistedstate' +import CONFIG from '../config' Vue.use(Vuex) @@ -19,6 +20,13 @@ export const mutations = { }, } +export const actions = { + logout: ({ commit, state }) => { + commit('token', null) + window.localStorage.clear() + }, +} + const store = new Vuex.Store({ plugins: [ createPersistedState({ @@ -26,12 +34,13 @@ const store = new Vuex.Store({ }), ], state: { - token: 'some-valid-token', + token: CONFIG.DEBUG_DISABLE_AUTH ? 'validToken' : null, moderator: 'Dertest Moderator', openCreations: 0, }, // Syncronous mutation of the state mutations, + actions, }) export default store diff --git a/admin/src/store/store.test.js b/admin/src/store/store.test.js index 81d75f05f..4482a46bf 100644 --- a/admin/src/store/store.test.js +++ b/admin/src/store/store.test.js @@ -1,6 +1,11 @@ -import { mutations } from './store' +import store, { mutations, actions } from './store' const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations } = mutations +const { logout } = actions + +const CONFIG = { + DEBUG_DISABLE_AUTH: true, +} describe('Vuex store', () => { describe('mutations', () => { @@ -36,4 +41,43 @@ describe('Vuex store', () => { }) }) }) + + describe('actions', () => { + describe('logout', () => { + const windowStorageMock = jest.fn() + const commit = jest.fn() + const state = {} + beforeEach(() => { + jest.clearAllMocks() + window.localStorage.clear = windowStorageMock + }) + + it('deletes the token in store', () => { + logout({ commit, state }) + expect(commit).toBeCalledWith('token', null) + }) + + it.skip('clears the window local storage', () => { + expect(windowStorageMock).toBeCalled() + }) + }) + }) + + describe('state', () => { + describe('authentication enabled', () => { + it('has no token', () => { + expect(store.state.token).toBe(null) + }) + }) + + describe('authentication enabled', () => { + beforeEach(() => { + CONFIG.DEBUG_DISABLE_AUTH = false + }) + + it.skip('has a token', () => { + expect(store.state.token).toBe('validToken') + }) + }) + }) }) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 5b7682e01..cdb46c954 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -20,6 +20,7 @@ export class User { this.pubkey = json.public_hex this.language = json.language this.publisherId = json.publisher_id + this.isAdmin = json.isAdmin } } @@ -48,7 +49,7 @@ export class User { @Field(() => number) created: number - @Field(() => Boolean) + @Field(() =>>> Boolean) emailChecked: boolean @Field(() => Boolean) @@ -71,6 +72,9 @@ export class User { @Field(() => Int, { nullable: true }) publisherId?: number + @Field(() => Boolean) + isAdmin: boolean + @Field(() => Boolean) coinanimation: boolean diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index bb769f377..235b6c90f 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -194,6 +194,69 @@ const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: B @Resolver() export class UserResolver { + /* + @Authorized() + @Query(() => User) + async verifyLogin(@Ctx() context: any): Promise { + const loginUserRepository = getCustomRepository(LoginUserRepository) + loginUser = loginUserRepository.findByPubkeyHex() + const user = new User(result.data.user) + + this.email = json.email + this.firstName = json.first_name + this.lastName = json.last_name + this.username = json.username + this.description = json.description + this.pubkey = json.public_hex + this.language = json.language + this.publisherId = json.publisher_id + this.isAdmin = json.isAdmin + + const userSettingRepository = getCustomRepository(UserSettingRepository) + const coinanimation = await userSettingRepository + .readBoolean(userEntity.id, Setting.COIN_ANIMATION) + .catch((error) => { + throw new Error(error) + }) + user.coinanimation = coinanimation + user.isAdmin = true // TODO implement + return user + } + */ + + @Authorized() + @Query(() => User) + @UseMiddleware(klicktippNewsletterStateMiddleware) + async verifyLogin(@Ctx() context: any): Promise { + // TODO refactor and do not have duplicate code with login(see below) + const userRepository = getCustomRepository(UserRepository) + const userEntity = await userRepository.findByPubkeyHex(context.pubKey) + const loginUserRepository = getCustomRepository(LoginUserRepository) + const loginUser = await loginUserRepository.findByEmail(userEntity.email) + const user = new User() + user.email = userEntity.email + user.firstName = userEntity.firstName + user.lastName = userEntity.lastName + user.username = userEntity.username + user.description = loginUser.description + user.pubkey = userEntity.pubkey.toString('hex') + user.language = loginUser.language + + // Elopage Status & Stored PublisherId + user.hasElopage = await this.hasElopage(context) + + // coinAnimation + const userSettingRepository = getCustomRepository(UserSettingRepository) + const coinanimation = await userSettingRepository + .readBoolean(userEntity.id, Setting.COIN_ANIMATION) + .catch((error) => { + throw new Error(error) + }) + user.coinanimation = coinanimation + user.isAdmin = true // TODO implement + return user + } + @Query(() => User) @UseMiddleware(klicktippNewsletterStateMiddleware) async login( @@ -266,6 +329,7 @@ export class UserResolver { throw new Error(error) }) user.coinanimation = coinanimation + user.isAdmin = true // TODO implement context.setHeaders.push({ key: 'token', diff --git a/frontend/.env.dist b/frontend/.env.dist index 8d4025a5d..80dafb7f9 100644 --- a/frontend/.env.dist +++ b/frontend/.env.dist @@ -1,3 +1,4 @@ GRAPHQL_URI=http://localhost:4000/graphql DEFAULT_PUBLISHER_ID=2896 -//BUILD_COMMIT=0000000 \ No newline at end of file +#BUILD_COMMIT=0000000 +ADMIN_AUTH_URL=http://localhost/admin/authenticate?token=$1 \ No newline at end of file diff --git a/frontend/DEV-README.md b/frontend/DEV-README.md deleted file mode 100644 index ab2b3e225..000000000 --- a/frontend/DEV-README.md +++ /dev/null @@ -1,21 +0,0 @@ -DEV README von Alex - -default Page: -´´´ - - - - -´´´ - diff --git a/frontend/ISSUE_TEMPLATE.md b/frontend/ISSUE_TEMPLATE.md deleted file mode 100644 index 8103f52e6..000000000 --- a/frontend/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/frontend/README.md b/frontend/README.md index e9ac0b097..f7c60552c 100755 --- a/frontend/README.md +++ b/frontend/README.md @@ -385,4 +385,13 @@ TODO: Update GDT-Server um paging und Zugriff auf alle Einträge zu erhalten, op GET https://staging.gradido.net/state-balances/ajaxGdtTransactions Liefert wenn alles in Ordnung ist: -wenn nicht type 7 dann "amount" in euro ansonsten in GDT \ No newline at end of file +wenn nicht type 7 dann "amount" in euro ansonsten in GDT + +## Additional Software + +For `yarn locales` you will need `jq` to use it. +You can install it (on arch) via + +``` +sudo pacman -S jq +``` \ No newline at end of file diff --git a/frontend/src/components/SidebarPlugin/SideBar.spec.js b/frontend/src/components/SidebarPlugin/SideBar.spec.js index 8204eb604..7b12b6473 100644 --- a/frontend/src/components/SidebarPlugin/SideBar.spec.js +++ b/frontend/src/components/SidebarPlugin/SideBar.spec.js @@ -3,6 +3,8 @@ import SideBar from './SideBar' const localVue = global.localVue +const storeDispatchMock = jest.fn() + describe('SideBar', () => { let wrapper @@ -23,7 +25,7 @@ describe('SideBar', () => { lastName: 'example', hasElopage: false, }, - commit: jest.fn(), + dispatch: storeDispatchMock, }, $i18n: { locale: 'en', @@ -154,6 +156,42 @@ describe('SideBar', () => { expect(wrapper.emitted('logout')).toEqual([[]]) }) }) + + describe('admin-area', () => { + it('is not visible when not an admin', () => { + expect(wrapper.findAll('li').at(1).text()).not.toBe('admin_area') + }) + + describe('logged in as admin', () => { + const assignLocationSpy = jest.fn() + beforeEach(async () => { + mocks.$store.state.isAdmin = true + mocks.$store.state.token = 'valid-token' + window.location.assign = assignLocationSpy + wrapper = Wrapper() + }) + + it('is visible', () => { + expect(wrapper.findAll('li').at(1).text()).toBe('admin_area') + }) + + describe('click on admin area', () => { + beforeEach(async () => { + await wrapper.findAll('li').at(1).find('a').trigger('click') + }) + + it('opens a new window when clicked', () => { + expect(assignLocationSpy).toHaveBeenCalledWith( + 'http://localhost/admin/authenticate?token=valid-token', + ) + }) + + it('dispatches logout to store', () => { + expect(storeDispatchMock).toHaveBeenCalledWith('logout') + }) + }) + }) + }) }) }) }) diff --git a/frontend/src/components/SidebarPlugin/SideBar.vue b/frontend/src/components/SidebarPlugin/SideBar.vue index c33c132b0..96882e816 100755 --- a/frontend/src/components/SidebarPlugin/SideBar.vue +++ b/frontend/src/components/SidebarPlugin/SideBar.vue @@ -45,11 +45,20 @@
+ + + @@ -112,6 +121,10 @@ export default { logout() { this.$emit('logout') }, + admin() { + window.location.assign(CONFIG.ADMIN_AUTH_URL.replace('$1', this.$store.state.token)) + this.$store.dispatch('logout') // logout without redirect + }, getElopageLink() { const pId = this.$store.state.publisherId ? this.$store.state.publisherId diff --git a/frontend/src/config/index.js b/frontend/src/config/index.js index 1f1819c62..b3a9366b7 100644 --- a/frontend/src/config/index.js +++ b/frontend/src/config/index.js @@ -18,8 +18,9 @@ const environment = { DEFAULT_PUBLISHER_ID: process.env.DEFAULT_PUBLISHER_ID || 2896, } -const server = { +const endpoints = { GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql', + ADMIN_AUTH_URL: process.env.ADMIN_AUTH_URL || 'http://localhost/admin/authenticate?token=$1', } const options = {} @@ -27,7 +28,7 @@ const options = {} const CONFIG = { ...version, ...environment, - ...server, + ...endpoints, ...options, } diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 01021f601..8b55f4098 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -15,6 +15,27 @@ export const login = gql` } hasElopage publisherId + isAdmin + } + } +` + +export const verifyLogin = gql` + query { + verifyLogin { + email + username + firstName + lastName + language + description + coinanimation + klickTipp { + newsletterState + } + hasElopage + publisherId + isAdmin } } ` diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index faa61886d..b0dfe36d4 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -1,4 +1,5 @@ { + "admin_area": "Adminbereich", "back": "Zurück", "community": { "choose-another-community": "Eine andere Gemeinschaft auswählen", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 91e25f61d..135729ffa 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -1,4 +1,5 @@ { + "admin_area": "Admin's area", "back": "Back", "community": { "choose-another-community": "Choose another community", diff --git a/frontend/src/main.js b/frontend/src/main.js index fd06bf9c0..1aa945608 100755 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -51,7 +51,7 @@ Vue.config.productionTip = false loadAllRules(i18n) -addNavigationGuards(router, store) +addNavigationGuards(router, store, apolloProvider.defaultClient) /* eslint-disable no-new */ new Vue({ diff --git a/frontend/src/routes/guards.js b/frontend/src/routes/guards.js index eebd6976e..dc8df4f13 100644 --- a/frontend/src/routes/guards.js +++ b/frontend/src/routes/guards.js @@ -1,12 +1,34 @@ -const addNavigationGuards = (router, store) => { +import { verifyLogin } from '../graphql/queries' + +const addNavigationGuards = (router, store, apollo) => { + // handle publisherId router.beforeEach((to, from, next) => { - // handle publisherId const publisherId = to.query.pid if (publisherId) { store.commit('publisherId', publisherId) delete to.query.pid } - // handle authentication + next() + }) + + // store token on authenticate + router.beforeEach(async (to, from, next) => { + if (to.path === '/authenticate' && to.query.token) { + // TODO verify user in order to get user data + store.commit('token', to.query.token) + const result = await apollo.query({ + query: verifyLogin, + fetchPolicy: 'network-only', + }) + store.dispatch('login', result.data.verifyLogin) + next({ path: '/overview' }) + } else { + next() + } + }) + + // handle authentication + router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !store.state.token) { next({ path: '/login' }) } else { diff --git a/frontend/src/routes/guards.test.js b/frontend/src/routes/guards.test.js index cf366eac8..f271c5427 100644 --- a/frontend/src/routes/guards.test.js +++ b/frontend/src/routes/guards.test.js @@ -30,7 +30,7 @@ describe('navigation guards', () => { }) describe('authorization', () => { - const navGuard = router.beforeHooks[0] + const navGuard = router.beforeHooks[2] const next = jest.fn() it('redirects to login when not authorized', () => { diff --git a/frontend/src/routes/router.test.js b/frontend/src/routes/router.test.js index df4f9c229..cd26b6f6b 100644 --- a/frontend/src/routes/router.test.js +++ b/frontend/src/routes/router.test.js @@ -49,8 +49,8 @@ describe('router', () => { expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' }) }) - it('has fourteen routes defined', () => { - expect(routes).toHaveLength(14) + it('has fifteen routes defined', () => { + expect(routes).toHaveLength(15) }) describe('overview', () => { diff --git a/frontend/src/routes/routes.js b/frontend/src/routes/routes.js index a3c1389ce..f6975d09d 100755 --- a/frontend/src/routes/routes.js +++ b/frontend/src/routes/routes.js @@ -1,6 +1,9 @@ import NotFound from '@/views/NotFoundPage.vue' const routes = [ + { + path: '/authenticate', + }, { path: '/', redirect: (to) => { diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 6a229c161..c49197059 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -34,6 +34,9 @@ export const mutations = { if (isNaN(pubId)) pubId = null state.publisherId = pubId }, + isAdmin: (state, isAdmin) => { + state.isAdmin = !!isAdmin + }, community: (state, community) => { state.community = community }, @@ -57,6 +60,7 @@ export const actions = { commit('newsletterState', data.klickTipp.newsletterState) commit('hasElopage', data.hasElopage) commit('publisherId', data.publisherId) + commit('isAdmin', data.isAdmin) }, logout: ({ commit, state }) => { commit('token', null) @@ -69,6 +73,7 @@ export const actions = { commit('newsletterState', null) commit('hasElopage', false) commit('publisherId', null) + commit('isAdmin', false) localStorage.clear() }, } @@ -87,6 +92,7 @@ export const store = new Vuex.Store({ username: '', description: '', token: null, + isAdmin: false, coinanimation: true, newsletterState: null, community: { diff --git a/frontend/src/store/store.test.js b/frontend/src/store/store.test.js index bdb98d03b..829678b44 100644 --- a/frontend/src/store/store.test.js +++ b/frontend/src/store/store.test.js @@ -148,11 +148,12 @@ describe('Vuex store', () => { }, hasElopage: false, publisherId: 1234, + isAdmin: true, } - it('calls ten commits', () => { + it('calls eleven commits', () => { login({ commit, state }, commitedData) - expect(commit).toHaveBeenCalledTimes(10) + expect(commit).toHaveBeenCalledTimes(11) }) it('commits email', () => { @@ -204,15 +205,20 @@ describe('Vuex store', () => { login({ commit, state }, commitedData) expect(commit).toHaveBeenNthCalledWith(10, 'publisherId', 1234) }) + + it('commits isAdmin', () => { + login({ commit, state }, commitedData) + expect(commit).toHaveBeenNthCalledWith(11, 'isAdmin', true) + }) }) describe('logout', () => { const commit = jest.fn() const state = {} - it('calls ten commits', () => { + it('calls eleven commits', () => { logout({ commit, state }) - expect(commit).toHaveBeenCalledTimes(10) + expect(commit).toHaveBeenCalledTimes(11) }) it('commits token', () => { @@ -265,6 +271,11 @@ describe('Vuex store', () => { expect(commit).toHaveBeenNthCalledWith(10, 'publisherId', null) }) + it('commits isAdmin', () => { + logout({ commit, state }) + expect(commit).toHaveBeenNthCalledWith(11, 'isAdmin', false) + }) + // how to get this working? it.skip('calls localStorage.clear()', () => { const clearStorageMock = jest.fn()