mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into backend_rights
# Conflicts: # admin/src/router/guards.js # admin/src/store/store.js # backend/src/graphql/resolver/UserResolver.ts # frontend/src/components/SidebarPlugin/SideBar.vue # frontend/src/graphql/queries.js # frontend/src/routes/guards.js
This commit is contained in:
commit
77dc54155a
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -441,7 +441,7 @@ jobs:
|
|||||||
report_name: Coverage Admin Interface
|
report_name: Coverage Admin Interface
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
min_coverage: 47
|
min_coverage: 51
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
@ -7,11 +7,19 @@ const stubs = {
|
|||||||
RouterView: true,
|
RouterView: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$store: {
|
||||||
|
state: {
|
||||||
|
token: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
describe('App', () => {
|
describe('App', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
return shallowMount(App, { localVue, stubs })
|
return shallowMount(App, { localVue, stubs, mocks })
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('shallowMount', () => {
|
describe('shallowMount', () => {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import CONFIG from '../config'
|
|||||||
const addNavigationGuards = (router, store) => {
|
const addNavigationGuards = (router, store) => {
|
||||||
// store token on `authenticate`
|
// store token on `authenticate`
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
if (to.path === '/authenticate' && to.query.token) {
|
if (to.path === '/authenticate' && to.query && to.query.token) {
|
||||||
// TODO verify user to get user data
|
// TODO verify user to get user data
|
||||||
store.commit('token', to.query.token)
|
store.commit('token', to.query.token)
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
|
|||||||
64
admin/src/router/guards.test.js
Normal file
64
admin/src/router/guards.test.js
Normal file
@ -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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -44,19 +44,19 @@ describe('router', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('routes', () => {
|
describe('routes', () => {
|
||||||
|
it('has seven routes defined', () => {
|
||||||
|
expect(routes).toHaveLength(7)
|
||||||
|
})
|
||||||
|
|
||||||
it('has "/overview" as default', async () => {
|
it('has "/overview" as default', async () => {
|
||||||
const component = await routes.find((r) => r.path === '/').component()
|
const component = await routes.find((r) => r.path === '/').component()
|
||||||
expect(component.default.name).toBe('overview')
|
expect(component.default.name).toBe('overview')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has fourteen routes defined', () => {
|
describe('logout', () => {
|
||||||
expect(routes).toHaveLength(6)
|
it('loads the "NotFoundPage" component', async () => {
|
||||||
})
|
const component = await routes.find((r) => r.path === '/logout').component()
|
||||||
|
expect(component.default.name).toBe('not-found')
|
||||||
describe('overview', () => {
|
|
||||||
it('loads the "Overview" component', async () => {
|
|
||||||
const component = await routes.find((r) => r.path === '/overview').component()
|
|
||||||
expect(component.default.name).toBe('overview')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export const mutations = {
|
|||||||
export const actions = {
|
export const actions = {
|
||||||
logout: ({ commit, state }) => {
|
logout: ({ commit, state }) => {
|
||||||
commit('token', null)
|
commit('token', null)
|
||||||
localStorage.clear()
|
window.localStorage.clear()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { mutations } from './store'
|
import store, { mutations, actions } from './store'
|
||||||
|
|
||||||
const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations } = mutations
|
const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations } = mutations
|
||||||
|
const { logout } = actions
|
||||||
|
|
||||||
|
const CONFIG = {
|
||||||
|
DEBUG_DISABLE_AUTH: true,
|
||||||
|
}
|
||||||
|
|
||||||
describe('Vuex store', () => {
|
describe('Vuex store', () => {
|
||||||
describe('mutations', () => {
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export class User {
|
|||||||
@Field(() => number)
|
@Field(() => number)
|
||||||
created: number
|
created: number
|
||||||
|
|
||||||
@Field(() => Boolean)
|
@Field(() =>>> Boolean)
|
||||||
emailChecked: boolean
|
emailChecked: boolean
|
||||||
|
|
||||||
@Field(() => Boolean)
|
@Field(() => Boolean)
|
||||||
|
|||||||
@ -195,24 +195,28 @@ const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: B
|
|||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class UserResolver {
|
export class UserResolver {
|
||||||
/*
|
|
||||||
@Authorized()
|
@Authorized()
|
||||||
@Query(() => User)
|
@Query(() => User)
|
||||||
|
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||||
async verifyLogin(@Ctx() context: any): Promise<User> {
|
async verifyLogin(@Ctx() context: any): Promise<User> {
|
||||||
|
// 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 loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
loginUser = loginUserRepository.findByPubkeyHex()
|
const loginUser = await loginUserRepository.findByEmail(userEntity.email)
|
||||||
const user = new User(result.data.user)
|
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
|
||||||
|
|
||||||
this.email = json.email
|
// Elopage Status & Stored PublisherId
|
||||||
this.firstName = json.first_name
|
user.hasElopage = await this.hasElopage(context)
|
||||||
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
|
|
||||||
|
|
||||||
|
// coinAnimation
|
||||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||||
const coinanimation = await userSettingRepository
|
const coinanimation = await userSettingRepository
|
||||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||||
@ -223,7 +227,6 @@ export class UserResolver {
|
|||||||
user.isAdmin = true // TODO implement
|
user.isAdmin = true // TODO implement
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.LOGIN])
|
@Authorized([RIGHTS.LOGIN])
|
||||||
@Query(() => User)
|
@Query(() => User)
|
||||||
@ -298,6 +301,7 @@ export class UserResolver {
|
|||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
})
|
})
|
||||||
user.coinanimation = coinanimation
|
user.coinanimation = coinanimation
|
||||||
|
user.isAdmin = true // TODO implement
|
||||||
|
|
||||||
user.isAdmin = true // TODO implement
|
user.isAdmin = true // TODO implement
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import SideBar from './SideBar'
|
|||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const storeDispatchMock = jest.fn()
|
||||||
|
|
||||||
describe('SideBar', () => {
|
describe('SideBar', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ describe('SideBar', () => {
|
|||||||
lastName: 'example',
|
lastName: 'example',
|
||||||
hasElopage: false,
|
hasElopage: false,
|
||||||
},
|
},
|
||||||
commit: jest.fn(),
|
dispatch: storeDispatchMock,
|
||||||
},
|
},
|
||||||
$i18n: {
|
$i18n: {
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
@ -154,6 +156,42 @@ describe('SideBar', () => {
|
|||||||
expect(wrapper.emitted('logout')).toEqual([[]])
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -50,7 +50,15 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a :href="getElopageLink()" class="nav-link" target="_blank">
|
<a :href="getElopageLink()" class="nav-link" target="_blank">
|
||||||
{{ $t('members_area') }}
|
{{ $t('members_area') }}
|
||||||
<b-badge v-if="!this.$store.state.hasElopage" pill variant="danger">!</b-badge>
|
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">!</b-badge>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="navbar-nav ml-3" v-if="$store.state.isAdmin">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link pointer" @click="admin">
|
||||||
|
{{ $t('admin_area') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -122,7 +130,7 @@ export default {
|
|||||||
this.$emit('logout')
|
this.$emit('logout')
|
||||||
},
|
},
|
||||||
admin() {
|
admin() {
|
||||||
window.location = CONFIG.ADMIN_AUTH_URL.replace('$1', this.$store.state.token)
|
window.location.assign(CONFIG.ADMIN_AUTH_URL.replace('$1', this.$store.state.token))
|
||||||
this.$store.dispatch('logout') // logout without redirect
|
this.$store.dispatch('logout') // logout without redirect
|
||||||
},
|
},
|
||||||
getElopageLink() {
|
getElopageLink() {
|
||||||
|
|||||||
@ -20,6 +20,26 @@ export const login = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const verifyLogin = gql`
|
||||||
|
query {
|
||||||
|
verifyLogin {
|
||||||
|
email
|
||||||
|
username
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
language
|
||||||
|
description
|
||||||
|
coinanimation
|
||||||
|
klickTipp {
|
||||||
|
newsletterState
|
||||||
|
}
|
||||||
|
hasElopage
|
||||||
|
publisherId
|
||||||
|
isAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export const logout = gql`
|
export const logout = gql`
|
||||||
query {
|
query {
|
||||||
logout
|
logout
|
||||||
|
|||||||
@ -51,7 +51,7 @@ Vue.config.productionTip = false
|
|||||||
|
|
||||||
loadAllRules(i18n)
|
loadAllRules(i18n)
|
||||||
|
|
||||||
addNavigationGuards(router, store)
|
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
/* eslint-disable no-new */
|
||||||
new Vue({
|
new Vue({
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
const addNavigationGuards = (router, store) => {
|
import { verifyLogin } from '../graphql/queries'
|
||||||
|
|
||||||
|
const addNavigationGuards = (router, store, apollo) => {
|
||||||
// handle publisherId
|
// handle publisherId
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const publisherId = to.query.pid
|
const publisherId = to.query.pid
|
||||||
@ -10,10 +12,14 @@ const addNavigationGuards = (router, store) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// store token on authenticate
|
// store token on authenticate
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
if (to.path === '/authenticate' && to.query.token) {
|
if (to.path === '/authenticate' && to.query.token) {
|
||||||
// TODO verify user in order to get user data
|
|
||||||
store.commit('token', to.query.token)
|
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' })
|
next({ path: '/overview' })
|
||||||
} else {
|
} else {
|
||||||
next()
|
next()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user