mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 01:46:07 +00:00
Merge branch 'master' into change-contribution-by-link-text
This commit is contained in:
commit
9d91786d38
@ -21,6 +21,7 @@ const mocks = {
|
||||
moderator: {
|
||||
id: 0,
|
||||
name: 'test moderator',
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -45,7 +46,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
isAdmin: null,
|
||||
roles: [],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -61,7 +62,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 0,
|
||||
isAdmin: null,
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -88,7 +89,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
isAdmin: null,
|
||||
roles: [],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -120,13 +121,13 @@ describe('ChangeUserRoleFormular', () => {
|
||||
beforeEach(() => {
|
||||
apolloMutateMock.mockResolvedValue({
|
||||
data: {
|
||||
setUserRole: new Date(),
|
||||
setUserRole: 'ADMIN',
|
||||
},
|
||||
})
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
isAdmin: null,
|
||||
roles: ['USER'],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -134,7 +135,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
})
|
||||
|
||||
it('has selected option set to "usual user"', () => {
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('user')
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('USER')
|
||||
})
|
||||
|
||||
describe('change select to', () => {
|
||||
@ -149,7 +150,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role', () => {
|
||||
describe('new role "MODERATOR"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(1).setSelected()
|
||||
})
|
||||
@ -181,19 +182,267 @@ describe('ChangeUserRoleFormular', () => {
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
isAdmin: true,
|
||||
role: 'MODERATOR',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateIsAdmin"', () => {
|
||||
expect(wrapper.emitted('updateIsAdmin')).toEqual(
|
||||
it('emits "updateRoles" with role moderator', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
isAdmin: expect.any(Date),
|
||||
roles: ['MODERATOR'],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm role change with error', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role "ADMIN"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(2).setSelected()
|
||||
})
|
||||
|
||||
it('has "change_user_role" button enabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
describe('clicking the "change_user_role" button', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(true))
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('calls the modal', () => {
|
||||
expect(wrapper.emitted('showModal'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('confirm role change with success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
role: 'ADMIN',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateRoles" with role moderator', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm role change with error', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user has role "moderator"', () => {
|
||||
beforeEach(() => {
|
||||
apolloMutateMock.mockResolvedValue({
|
||||
data: {
|
||||
setUserRole: null,
|
||||
},
|
||||
})
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
roles: ['MODERATOR'],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
rolesToSelect = wrapper.find('select.role-select').findAll('option')
|
||||
})
|
||||
|
||||
it('has selected option set to "MODERATOR"', () => {
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('MODERATOR')
|
||||
})
|
||||
|
||||
describe('change select to', () => {
|
||||
describe('same role', () => {
|
||||
it('has "change_user_role" button disabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('does not call the API', () => {
|
||||
rolesToSelect.at(1).setSelected()
|
||||
expect(apolloMutateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role "USER"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(0).setSelected()
|
||||
})
|
||||
|
||||
it('has "change_user_role" button enabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
describe('clicking the "change_user_role" button', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(true))
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('calls the modal', () => {
|
||||
expect(wrapper.emitted('showModal'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('confirm role change with success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
role: 'USER',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateRoles"', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
roles: [],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm role change with error', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role "ADMIN"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(2).setSelected()
|
||||
})
|
||||
|
||||
it('has "change_user_role" button enabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
describe('clicking the "change_user_role" button', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(true))
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('calls the modal', () => {
|
||||
expect(wrapper.emitted('showModal'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('confirm role change with success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
role: 'ADMIN',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateRoles"', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
@ -232,7 +481,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
isAdmin: new Date(),
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -240,7 +489,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
})
|
||||
|
||||
it('has selected option set to "admin"', () => {
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('admin')
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('ADMIN')
|
||||
})
|
||||
|
||||
describe('change select to', () => {
|
||||
@ -251,11 +500,12 @@ describe('ChangeUserRoleFormular', () => {
|
||||
|
||||
it('does not call the API', () => {
|
||||
rolesToSelect.at(1).setSelected()
|
||||
// TODO: Fix this
|
||||
expect(apolloMutateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role', () => {
|
||||
describe('new role "USER"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(0).setSelected()
|
||||
})
|
||||
@ -287,19 +537,90 @@ describe('ChangeUserRoleFormular', () => {
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
isAdmin: false,
|
||||
role: 'USER',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateIsAdmin"', () => {
|
||||
expect(wrapper.emitted('updateIsAdmin')).toEqual(
|
||||
it('emits "updateRoles"', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
isAdmin: null,
|
||||
roles: [],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm role change with error', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role "MODERATOR"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(1).setSelected()
|
||||
})
|
||||
|
||||
it('has "change_user_role" button enabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
describe('clicking the "change_user_role" button', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(true))
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('calls the modal', () => {
|
||||
expect(wrapper.emitted('showModal'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('confirm role change with success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
role: 'MODERATOR',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateRoles"', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
roles: ['MODERATOR'],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
@ -328,5 +649,23 @@ describe('ChangeUserRoleFormular', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated user is MODERATOR', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$store.state.moderator.roles = ['MODERATOR']
|
||||
})
|
||||
|
||||
it('displays text with role', () => {
|
||||
expect(wrapper.text()).toBe('userRole.selectRoles.admin')
|
||||
})
|
||||
|
||||
it('has no role select', () => {
|
||||
expect(wrapper.find('select.role-select').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('has no button', () => {
|
||||
expect(wrapper.find('button.btn.btn-dange').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div class="change-user-role-formular">
|
||||
<div class="shadow p-3 mb-5 bg-white rounded">
|
||||
<div v-if="item.userId === $store.state.moderator.id" class="m-3 mb-4">
|
||||
<div v-if="!$store.state.moderator.roles.includes('ADMIN')" class="m-3 mb-4">
|
||||
{{ roles.find((role) => role.value === currentRole).text }}
|
||||
</div>
|
||||
<div v-else-if="item.userId === $store.state.moderator.id" class="m-3 mb-4">
|
||||
{{ $t('userRole.notChangeYourSelf') }}
|
||||
</div>
|
||||
<div v-else class="m-3">
|
||||
@ -25,8 +28,9 @@
|
||||
import { setUserRole } from '../graphql/setUserRole'
|
||||
|
||||
const rolesValues = {
|
||||
admin: 'admin',
|
||||
user: 'user',
|
||||
ADMIN: 'ADMIN',
|
||||
MODERATOR: 'MODERATOR',
|
||||
USER: 'USER',
|
||||
}
|
||||
|
||||
export default {
|
||||
@ -39,23 +43,30 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentRole: this.item.isAdmin ? rolesValues.admin : rolesValues.user,
|
||||
roleSelected: this.item.isAdmin ? rolesValues.admin : rolesValues.user,
|
||||
currentRole: this.getCurrentRole(),
|
||||
roleSelected: this.getCurrentRole(),
|
||||
roles: [
|
||||
{ value: rolesValues.user, text: this.$t('userRole.selectRoles.user') },
|
||||
{ value: rolesValues.admin, text: this.$t('userRole.selectRoles.admin') },
|
||||
{ value: rolesValues.USER, text: this.$t('userRole.selectRoles.user') },
|
||||
{ value: rolesValues.MODERATOR, text: this.$t('userRole.selectRoles.moderator') },
|
||||
{ value: rolesValues.ADMIN, text: this.$t('userRole.selectRoles.admin') },
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRole() {
|
||||
if (this.item.roles.length) return rolesValues[this.item.roles[0]]
|
||||
return rolesValues.USER
|
||||
},
|
||||
showModal() {
|
||||
this.$bvModal
|
||||
.msgBoxConfirm(
|
||||
this.$t('overlay.changeUserRole.question', {
|
||||
username: `${this.item.firstName} ${this.item.lastName}`,
|
||||
newRole:
|
||||
this.roleSelected === 'admin'
|
||||
this.roleSelected === rolesValues.ADMIN
|
||||
? this.$t('userRole.selectRoles.admin')
|
||||
: this.roleSelected === rolesValues.MODERATOR
|
||||
? this.$t('userRole.selectRoles.moderator')
|
||||
: this.$t('userRole.selectRoles.user'),
|
||||
}),
|
||||
{
|
||||
@ -77,25 +88,27 @@ export default {
|
||||
})
|
||||
},
|
||||
setUserRole(newRole, oldRole) {
|
||||
const role = this.roles.find((role) => {
|
||||
return role.value === newRole
|
||||
})
|
||||
const roleText = role.text
|
||||
const roleValue = role.value
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: this.item.userId,
|
||||
isAdmin: newRole === rolesValues.admin,
|
||||
role: role.value,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('updateIsAdmin', {
|
||||
this.$emit('updateRoles', {
|
||||
userId: this.item.userId,
|
||||
isAdmin: result.data.setUserRole,
|
||||
roles: roleValue === 'USER' ? [] : [roleValue],
|
||||
})
|
||||
this.toastSuccess(
|
||||
this.$t('userRole.successfullyChangedTo', {
|
||||
role:
|
||||
result.data.setUserRole !== null
|
||||
? this.$t('userRole.selectRoles.admin')
|
||||
: this.$t('userRole.selectRoles.user'),
|
||||
role: roleText,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@ -15,6 +15,7 @@ const propsData = {
|
||||
email: 'bibi@bloxberg.de',
|
||||
creation: [200, 400, 600],
|
||||
emailChecked: true,
|
||||
roles: [],
|
||||
},
|
||||
{
|
||||
userId: 2,
|
||||
@ -23,6 +24,7 @@ const propsData = {
|
||||
email: 'benjamin@bluemchen.de',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: true,
|
||||
roles: [],
|
||||
},
|
||||
{
|
||||
userId: 3,
|
||||
@ -31,6 +33,7 @@ const propsData = {
|
||||
email: 'peter@lustig.de',
|
||||
creation: [0, 0, 0],
|
||||
emailChecked: true,
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
{
|
||||
userId: 4,
|
||||
@ -39,6 +42,7 @@ const propsData = {
|
||||
email: 'new@user.ch',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: false,
|
||||
roles: [],
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
@ -68,6 +72,7 @@ const mocks = {
|
||||
moderator: {
|
||||
id: 0,
|
||||
name: 'test moderator',
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -96,14 +101,14 @@ describe('SearchUserTable', () => {
|
||||
|
||||
describe('isAdmin', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('div.change-user-role-formular').vm.$emit('updateIsAdmin', {
|
||||
await wrapper.find('div.change-user-role-formular').vm.$emit('updateRoles', {
|
||||
userId: 1,
|
||||
isAdmin: new Date(),
|
||||
roles: ['ADMIN'],
|
||||
})
|
||||
})
|
||||
|
||||
it('emits updateIsAdmin', () => {
|
||||
expect(wrapper.emitted('updateIsAdmin')).toEqual([[1, expect.any(Date)]])
|
||||
expect(wrapper.emitted('updateRoles')).toEqual([[1, ['ADMIN']]])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -79,9 +79,9 @@
|
||||
<transaction-link-list v-if="!row.item.deletedAt" :userId="row.item.userId" />
|
||||
</b-tab>
|
||||
<b-tab :title="$t('userRole.tabTitle')">
|
||||
<change-user-role-formular :item="row.item" @updateIsAdmin="updateIsAdmin" />
|
||||
<change-user-role-formular :item="row.item" @updateRoles="updateRoles" />
|
||||
</b-tab>
|
||||
<b-tab :title="$t('delete_user')">
|
||||
<b-tab v-if="$store.state.moderator.roles.includes('ADMIN')" :title="$t('delete_user')">
|
||||
<deleted-user-formular :item="row.item" @updateDeletedAt="updateDeletedAt" />
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
@ -127,8 +127,8 @@ export default {
|
||||
updateUserData(rowItem, newCreation) {
|
||||
rowItem.creation = newCreation
|
||||
},
|
||||
updateIsAdmin({ userId, isAdmin }) {
|
||||
this.$emit('updateIsAdmin', userId, isAdmin)
|
||||
updateRoles({ userId, roles }) {
|
||||
this.$emit('updateRoles', userId, roles)
|
||||
},
|
||||
updateDeletedAt({ userId, deletedAt }) {
|
||||
this.$emit('updateDeletedAt', userId, deletedAt)
|
||||
|
||||
47
admin/src/components/UserQuery.spec.js
Normal file
47
admin/src/components/UserQuery.spec.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserQuery from './UserQuery'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const propsData = {
|
||||
userId: 42,
|
||||
}
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
describe('TransactionLinkList', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(UserQuery, { mocks, localVue, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has div .input-group', () => {
|
||||
expect(wrapper.find('div .input-group').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has .test-input-criteria', () => {
|
||||
expect(wrapper.find('input.test-input-criteria').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('set value', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.find('input.test-input-criteria').setValue('Test2')
|
||||
})
|
||||
|
||||
it('emits input', () => {
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('emits input with value "Test2"', () => {
|
||||
expect(wrapper.emitted('input')).toEqual([['Test2']])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -26,7 +26,7 @@ export const searchUsers = gql`
|
||||
hasElopage
|
||||
emailConfirmationSend
|
||||
deletedAt
|
||||
isAdmin
|
||||
roles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const setUserRole = gql`
|
||||
mutation ($userId: Int!, $isAdmin: Boolean!) {
|
||||
setUserRole(userId: $userId, isAdmin: $isAdmin)
|
||||
mutation ($userId: Int!, $role: RoleNames!) {
|
||||
setUserRole(userId: $userId, role: $role)
|
||||
}
|
||||
`
|
||||
|
||||
@ -5,7 +5,7 @@ export const verifyLogin = gql`
|
||||
verifyLogin {
|
||||
firstName
|
||||
lastName
|
||||
isAdmin
|
||||
roles
|
||||
id
|
||||
language
|
||||
}
|
||||
|
||||
@ -209,6 +209,7 @@
|
||||
"selectLabel": "Rolle:",
|
||||
"selectRoles": {
|
||||
"admin": "Administrator",
|
||||
"moderator": "Moderator",
|
||||
"user": "einfacher Nutzer"
|
||||
},
|
||||
"successfullyChangedTo": "Nutzer ist jetzt „{role}“.",
|
||||
|
||||
@ -209,6 +209,7 @@
|
||||
"selectLabel": "Role:",
|
||||
"selectRoles": {
|
||||
"admin": "administrator",
|
||||
"moderator": "moderator",
|
||||
"user": "usual user"
|
||||
},
|
||||
"successfullyChangedTo": "User is now \"{role}\".",
|
||||
|
||||
@ -28,7 +28,7 @@ const mocks = {
|
||||
moderator: {
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
isAdmin: '2022-08-30T07:41:31.000Z',
|
||||
roles: ['ADMIN'],
|
||||
id: 263,
|
||||
language: 'de',
|
||||
},
|
||||
|
||||
@ -16,6 +16,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
email: 'new@user.ch',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: false,
|
||||
roles: [],
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
@ -24,6 +25,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
lastName: 'Lustig',
|
||||
email: 'peter@lustig.de',
|
||||
creation: [0, 0, 0],
|
||||
roles: ['ADMIN'],
|
||||
emailChecked: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
@ -33,6 +35,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
lastName: 'Blümchen',
|
||||
email: 'benjamin@bluemchen.de',
|
||||
creation: [1000, 1000, 1000],
|
||||
roles: [],
|
||||
emailChecked: true,
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
@ -42,6 +45,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
lastName: 'Bloxberg',
|
||||
email: 'bibi@bloxberg.de',
|
||||
creation: [200, 400, 600],
|
||||
roles: [],
|
||||
emailChecked: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
@ -212,10 +216,10 @@ describe('UserSearch', () => {
|
||||
it('updates user role to admin', async () => {
|
||||
await wrapper
|
||||
.findComponent({ name: 'SearchUserTable' })
|
||||
.vm.$emit('updateIsAdmin', userId, new Date())
|
||||
expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).isAdmin).toEqual(
|
||||
expect.any(Date),
|
||||
)
|
||||
.vm.$emit('updateRoles', userId, ['ADMIN'])
|
||||
expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).roles).toEqual([
|
||||
'ADMIN',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@ -223,8 +227,8 @@ describe('UserSearch', () => {
|
||||
it('updates user role to usual user', async () => {
|
||||
await wrapper
|
||||
.findComponent({ name: 'SearchUserTable' })
|
||||
.vm.$emit('updateIsAdmin', userId, null)
|
||||
expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).isAdmin).toEqual(null)
|
||||
.vm.$emit('updateRoles', userId, [])
|
||||
expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).roles).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
type="PageUserSearch"
|
||||
:items="searchResult"
|
||||
:fields="fields"
|
||||
@updateIsAdmin="updateIsAdmin"
|
||||
@updateRoles="updateRoles"
|
||||
@updateDeletedAt="updateDeletedAt"
|
||||
/>
|
||||
<b-pagination
|
||||
@ -101,8 +101,8 @@ export default {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
updateIsAdmin(userId, isAdmin) {
|
||||
this.searchResult.find((obj) => obj.userId === userId).isAdmin = isAdmin
|
||||
updateRoles(userId, roles) {
|
||||
this.searchResult.find((obj) => obj.userId === userId).roles = roles
|
||||
},
|
||||
updateDeletedAt(userId, deletedAt) {
|
||||
this.searchResult.find((obj) => obj.userId === userId).deletedAt = deletedAt
|
||||
|
||||
@ -13,7 +13,7 @@ const addNavigationGuards = (router, store, apollo, i18n) => {
|
||||
})
|
||||
.then((result) => {
|
||||
const moderator = result.data.verifyLogin
|
||||
if (moderator.isAdmin) {
|
||||
if (moderator.roles?.length) {
|
||||
i18n.locale = moderator.language
|
||||
store.commit('moderator', moderator)
|
||||
next({ path: '/' })
|
||||
@ -35,7 +35,7 @@ const addNavigationGuards = (router, store, apollo, i18n) => {
|
||||
!CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes
|
||||
(!store.state.token || // we do not have a token
|
||||
!store.state.moderator || // no moderator set in store
|
||||
!store.state.moderator.isAdmin) && // user is no admin
|
||||
!store.state.moderator.roles.length) && // user is no admin
|
||||
to.path !== '/not-found' && // we are not on `not-found`
|
||||
to.path !== '/logout' // we are not on `logout`
|
||||
) {
|
||||
|
||||
@ -5,7 +5,7 @@ const storeCommitMock = jest.fn()
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
isAdmin: true,
|
||||
roles: ['ADMIN'],
|
||||
language: 'de',
|
||||
},
|
||||
},
|
||||
@ -52,7 +52,10 @@ describe('navigation guards', () => {
|
||||
})
|
||||
|
||||
it('commits the moderator to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true, language: 'de' })
|
||||
expect(storeCommitMock).toBeCalledWith('moderator', {
|
||||
roles: ['ADMIN'],
|
||||
language: 'de',
|
||||
})
|
||||
})
|
||||
|
||||
it('redirects to /', () => {
|
||||
@ -60,12 +63,48 @@ describe('navigation guards', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('with valid token and not as admin', () => {
|
||||
beforeEach(() => {
|
||||
describe('with valid token and as moderator', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloQueryMock.mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
isAdmin: false,
|
||||
roles: ['MODERATOR'],
|
||||
language: 'de',
|
||||
},
|
||||
},
|
||||
})
|
||||
await navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
})
|
||||
|
||||
it('commits the token to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||
})
|
||||
|
||||
it.skip('sets the locale', () => {
|
||||
expect(i18nLocaleMock).toBeCalledWith('de')
|
||||
})
|
||||
|
||||
it('commits the moderator to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('moderator', {
|
||||
roles: ['MODERATOR'],
|
||||
language: 'de',
|
||||
})
|
||||
})
|
||||
|
||||
it('redirects to /', () => {
|
||||
expect(next).toBeCalledWith({ path: '/' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('with valid token and no roles', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
apolloQueryMock.mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
roles: [],
|
||||
language: 'de',
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -77,7 +116,7 @@ describe('navigation guards', () => {
|
||||
})
|
||||
|
||||
it('does not commit the moderator to the store', () => {
|
||||
expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false })
|
||||
expect(storeCommitMock).not.toBeCalledWith('moderator')
|
||||
})
|
||||
|
||||
it('redirects to /not-found', async () => {
|
||||
@ -128,15 +167,22 @@ describe('navigation guards', () => {
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
|
||||
it('redirects to not found with token in store and not moderator', () => {
|
||||
it('redirects to not found with token in store and not admin or moderator', () => {
|
||||
store.state.token = 'valid token'
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
|
||||
it('does not redirect with token in store and as admin', () => {
|
||||
store.state.token = 'valid token'
|
||||
store.state.moderator = { roles: ['ADMIN'] }
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
|
||||
it('does not redirect with token in store and as moderator', () => {
|
||||
store.state.token = 'valid token'
|
||||
store.state.moderator = { isAdmin: true }
|
||||
store.state.moderator = { roles: ['MODERATOR'] }
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
|
||||
@ -7,7 +7,7 @@ module.exports = {
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 89,
|
||||
lines: 90,
|
||||
},
|
||||
},
|
||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||
|
||||
3
backend/src/auth/ADMIN_RIGHTS.ts
Normal file
3
backend/src/auth/ADMIN_RIGHTS.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const ADMIN_RIGHTS = [RIGHTS.SET_USER_ROLE, RIGHTS.DELETE_USER, RIGHTS.UNDELETE_USER]
|
||||
19
backend/src/auth/MODERATOR_RIGHTS.ts
Normal file
19
backend/src/auth/MODERATOR_RIGHTS.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const MODERATOR_RIGHTS = [
|
||||
RIGHTS.SEARCH_USERS,
|
||||
RIGHTS.ADMIN_CREATE_CONTRIBUTION,
|
||||
RIGHTS.ADMIN_UPDATE_CONTRIBUTION,
|
||||
RIGHTS.ADMIN_DELETE_CONTRIBUTION,
|
||||
RIGHTS.ADMIN_LIST_CONTRIBUTIONS,
|
||||
RIGHTS.CONFIRM_CONTRIBUTION,
|
||||
RIGHTS.SEND_ACTIVATION_EMAIL,
|
||||
RIGHTS.LIST_TRANSACTION_LINKS_ADMIN,
|
||||
RIGHTS.CREATE_CONTRIBUTION_LINK,
|
||||
RIGHTS.DELETE_CONTRIBUTION_LINK,
|
||||
RIGHTS.UPDATE_CONTRIBUTION_LINK,
|
||||
RIGHTS.ADMIN_CREATE_CONTRIBUTION_MESSAGE,
|
||||
RIGHTS.ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||
RIGHTS.DENY_CONTRIBUTION,
|
||||
RIGHTS.ADMIN_OPEN_CREATIONS,
|
||||
]
|
||||
@ -1,8 +1,16 @@
|
||||
export enum RIGHTS {
|
||||
// Inalienable
|
||||
LOGIN = 'LOGIN',
|
||||
COMMUNITIES = 'COMMUNITIES',
|
||||
CREATE_USER = 'CREATE_USER',
|
||||
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||
SET_PASSWORD = 'SET_PASSWORD',
|
||||
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
|
||||
QUERY_OPT_IN = 'QUERY_OPT_IN',
|
||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||
// User
|
||||
VERIFY_LOGIN = 'VERIFY_LOGIN',
|
||||
BALANCE = 'BALANCE',
|
||||
COMMUNITIES = 'COMMUNITIES',
|
||||
LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES',
|
||||
EXIST_PID = 'EXIST_PID',
|
||||
UNSUBSCRIBE_NEWSLETTER = 'UNSUBSCRIBE_NEWSLETTER',
|
||||
@ -10,15 +18,10 @@ export enum RIGHTS {
|
||||
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
||||
SEND_COINS = 'SEND_COINS',
|
||||
LOGOUT = 'LOGOUT',
|
||||
CREATE_USER = 'CREATE_USER',
|
||||
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||
SET_PASSWORD = 'SET_PASSWORD',
|
||||
QUERY_OPT_IN = 'QUERY_OPT_IN',
|
||||
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
||||
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
||||
CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK',
|
||||
DELETE_TRANSACTION_LINK = 'DELETE_TRANSACTION_LINK',
|
||||
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
|
||||
REDEEM_TRANSACTION_LINK = 'REDEEM_TRANSACTION_LINK',
|
||||
LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS',
|
||||
GDT_BALANCE = 'GDT_BALANCE',
|
||||
@ -34,12 +37,8 @@ export enum RIGHTS {
|
||||
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||
OPEN_CREATIONS = 'OPEN_CREATIONS',
|
||||
USER = 'USER',
|
||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||
// Admin
|
||||
// Moderator
|
||||
SEARCH_USERS = 'SEARCH_USERS',
|
||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||
DELETE_USER = 'DELETE_USER',
|
||||
UNDELETE_USER = 'UNDELETE_USER',
|
||||
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
|
||||
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
|
||||
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
|
||||
@ -54,4 +53,8 @@ export enum RIGHTS {
|
||||
DENY_CONTRIBUTION = 'DENY_CONTRIBUTION',
|
||||
ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS',
|
||||
ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES = 'ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||
// Admin
|
||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||
DELETE_USER = 'DELETE_USER',
|
||||
UNDELETE_USER = 'UNDELETE_USER',
|
||||
}
|
||||
|
||||
@ -1,40 +1,24 @@
|
||||
import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS'
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
import { Role } from './Role'
|
||||
import { RoleNames } from '@/graphql/enum/RoleNames'
|
||||
|
||||
export const ROLE_UNAUTHORIZED = new Role('unauthorized', INALIENABLE_RIGHTS)
|
||||
export const ROLE_USER = new Role('user', [
|
||||
import { ADMIN_RIGHTS } from './ADMIN_RIGHTS'
|
||||
import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS'
|
||||
import { MODERATOR_RIGHTS } from './MODERATOR_RIGHTS'
|
||||
import { Role } from './Role'
|
||||
import { USER_RIGHTS } from './USER_RIGHTS'
|
||||
|
||||
export const ROLE_UNAUTHORIZED = new Role(RoleNames.UNAUTHORIZED, INALIENABLE_RIGHTS)
|
||||
export const ROLE_USER = new Role(RoleNames.USER, [...INALIENABLE_RIGHTS, ...USER_RIGHTS])
|
||||
export const ROLE_MODERATOR = new Role(RoleNames.MODERATOR, [
|
||||
...INALIENABLE_RIGHTS,
|
||||
RIGHTS.VERIFY_LOGIN,
|
||||
RIGHTS.BALANCE,
|
||||
RIGHTS.LIST_GDT_ENTRIES,
|
||||
RIGHTS.EXIST_PID,
|
||||
RIGHTS.UNSUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.SUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.TRANSACTION_LIST,
|
||||
RIGHTS.SEND_COINS,
|
||||
RIGHTS.LOGOUT,
|
||||
RIGHTS.UPDATE_USER_INFOS,
|
||||
RIGHTS.HAS_ELOPAGE,
|
||||
RIGHTS.CREATE_TRANSACTION_LINK,
|
||||
RIGHTS.DELETE_TRANSACTION_LINK,
|
||||
RIGHTS.REDEEM_TRANSACTION_LINK,
|
||||
RIGHTS.LIST_TRANSACTION_LINKS,
|
||||
RIGHTS.GDT_BALANCE,
|
||||
RIGHTS.CREATE_CONTRIBUTION,
|
||||
RIGHTS.DELETE_CONTRIBUTION,
|
||||
RIGHTS.LIST_CONTRIBUTIONS,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTIONS,
|
||||
RIGHTS.UPDATE_CONTRIBUTION,
|
||||
RIGHTS.SEARCH_ADMIN_USERS,
|
||||
RIGHTS.LIST_CONTRIBUTION_LINKS,
|
||||
RIGHTS.COMMUNITY_STATISTICS,
|
||||
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||
RIGHTS.OPEN_CREATIONS,
|
||||
RIGHTS.USER,
|
||||
...USER_RIGHTS,
|
||||
...MODERATOR_RIGHTS,
|
||||
])
|
||||
export const ROLE_ADMIN = new Role(RoleNames.ADMIN, [
|
||||
...INALIENABLE_RIGHTS,
|
||||
...USER_RIGHTS,
|
||||
...MODERATOR_RIGHTS,
|
||||
...ADMIN_RIGHTS,
|
||||
])
|
||||
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||
|
||||
// TODO from database
|
||||
export const ROLES = [ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN]
|
||||
export const ROLES = [ROLE_UNAUTHORIZED, ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN]
|
||||
|
||||
32
backend/src/auth/USER_RIGHTS.ts
Normal file
32
backend/src/auth/USER_RIGHTS.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const USER_RIGHTS = [
|
||||
RIGHTS.VERIFY_LOGIN,
|
||||
RIGHTS.BALANCE,
|
||||
RIGHTS.LIST_GDT_ENTRIES,
|
||||
RIGHTS.EXIST_PID,
|
||||
RIGHTS.UNSUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.SUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.TRANSACTION_LIST,
|
||||
RIGHTS.SEND_COINS,
|
||||
RIGHTS.LOGOUT,
|
||||
RIGHTS.UPDATE_USER_INFOS,
|
||||
RIGHTS.HAS_ELOPAGE,
|
||||
RIGHTS.CREATE_TRANSACTION_LINK,
|
||||
RIGHTS.DELETE_TRANSACTION_LINK,
|
||||
RIGHTS.REDEEM_TRANSACTION_LINK,
|
||||
RIGHTS.LIST_TRANSACTION_LINKS,
|
||||
RIGHTS.GDT_BALANCE,
|
||||
RIGHTS.CREATE_CONTRIBUTION,
|
||||
RIGHTS.DELETE_CONTRIBUTION,
|
||||
RIGHTS.LIST_CONTRIBUTIONS,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTIONS,
|
||||
RIGHTS.UPDATE_CONTRIBUTION,
|
||||
RIGHTS.SEARCH_ADMIN_USERS,
|
||||
RIGHTS.LIST_CONTRIBUTION_LINKS,
|
||||
RIGHTS.COMMUNITY_STATISTICS,
|
||||
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||
RIGHTS.OPEN_CREATIONS,
|
||||
RIGHTS.USER,
|
||||
]
|
||||
@ -12,7 +12,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0068-community_tables_public_key_length',
|
||||
DB_VERSION: '0069-add_user_roles_table',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
|
||||
13
backend/src/graphql/arg/SetUserRoleArgs.ts
Normal file
13
backend/src/graphql/arg/SetUserRoleArgs.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ArgsType, Field, Int, InputType } from 'type-graphql'
|
||||
|
||||
import { RoleNames } from '@enum/RoleNames'
|
||||
|
||||
@InputType()
|
||||
@ArgsType()
|
||||
export class SetUserRoleArgs {
|
||||
@Field(() => Int)
|
||||
userId: number
|
||||
|
||||
@Field(() => RoleNames, { nullable: true })
|
||||
role: RoleNames | null | undefined
|
||||
}
|
||||
@ -1,10 +1,12 @@
|
||||
import { User } from '@entity/User'
|
||||
import { AuthChecker } from 'type-graphql'
|
||||
|
||||
import { RoleNames } from '@enum/RoleNames'
|
||||
|
||||
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
||||
import { decode, encode } from '@/auth/JWT'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
|
||||
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN, ROLE_MODERATOR } from '@/auth/ROLES'
|
||||
import { Context } from '@/server/context'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
@ -33,10 +35,23 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
|
||||
try {
|
||||
const user = await User.findOneOrFail({
|
||||
where: { gradidoID: decoded.gradidoID },
|
||||
relations: ['emailContact'],
|
||||
withDeleted: true,
|
||||
relations: ['emailContact', 'userRoles'],
|
||||
})
|
||||
context.user = user
|
||||
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||
context.role = ROLE_USER
|
||||
if (user.userRoles?.length > 0) {
|
||||
switch (user.userRoles[0].role) {
|
||||
case RoleNames.ADMIN:
|
||||
context.role = ROLE_ADMIN
|
||||
break
|
||||
case RoleNames.MODERATOR:
|
||||
context.role = ROLE_MODERATOR
|
||||
break
|
||||
default:
|
||||
context.role = ROLE_USER
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// in case the database query fails (user deleted)
|
||||
throw new LogError('401 Unauthorized')
|
||||
|
||||
13
backend/src/graphql/enum/RoleNames.ts
Normal file
13
backend/src/graphql/enum/RoleNames.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum RoleNames {
|
||||
UNAUTHORIZED = 'UNAUTHORIZED',
|
||||
USER = 'USER',
|
||||
MODERATOR = 'MODERATOR',
|
||||
ADMIN = 'ADMIN',
|
||||
}
|
||||
|
||||
registerEnumType(RoleNames, {
|
||||
name: 'RoleNames', // this one is mandatory
|
||||
description: 'Possible role names', // this one is optional
|
||||
})
|
||||
@ -6,6 +6,7 @@ export class AdminUser {
|
||||
constructor(user: User) {
|
||||
this.firstName = user.firstName
|
||||
this.lastName = user.lastName
|
||||
this.role = user.userRoles.length > 0 ? user.userRoles[0].role : ''
|
||||
}
|
||||
|
||||
@Field(() => String)
|
||||
@ -13,6 +14,9 @@ export class AdminUser {
|
||||
|
||||
@Field(() => String)
|
||||
lastName: string
|
||||
|
||||
@Field(() => String)
|
||||
role: string
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
||||
@ -18,7 +18,7 @@ export class User {
|
||||
this.createdAt = user.createdAt
|
||||
this.language = user.language
|
||||
this.publisherId = user.publisherId
|
||||
this.isAdmin = user.isAdmin
|
||||
this.roles = user.userRoles?.map((userRole) => userRole.role) ?? []
|
||||
this.klickTipp = null
|
||||
this.hasElopage = null
|
||||
this.hideAmountGDD = user.hideAmountGDD
|
||||
@ -62,12 +62,12 @@ export class User {
|
||||
@Field(() => Int, { nullable: true })
|
||||
publisherId: number | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
isAdmin: Date | null
|
||||
|
||||
@Field(() => KlickTipp, { nullable: true })
|
||||
klickTipp: KlickTipp | null
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
hasElopage: boolean | null
|
||||
|
||||
@Field(() => [String])
|
||||
roles: string[]
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ export class UserAdmin {
|
||||
this.hasElopage = hasElopage
|
||||
this.deletedAt = user.deletedAt
|
||||
this.emailConfirmationSend = emailConfirmationSend
|
||||
this.isAdmin = user.isAdmin
|
||||
this.roles = user.userRoles?.map((userRole) => userRole.role) ?? []
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
@ -44,8 +44,8 @@ export class UserAdmin {
|
||||
@Field(() => String, { nullable: true })
|
||||
emailConfirmationSend: string | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
isAdmin: Date | null
|
||||
@Field(() => [String])
|
||||
roles: string[]
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
import { UserContact as dbUserContact } from '@entity/UserContact'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class UserContact {
|
||||
constructor(userContact: dbUserContact) {
|
||||
this.id = userContact.id
|
||||
this.type = userContact.type
|
||||
this.userId = userContact.userId
|
||||
this.email = userContact.email
|
||||
// this.emailVerificationCode = userContact.emailVerificationCode
|
||||
this.emailOptInTypeId = userContact.emailOptInTypeId
|
||||
this.emailResendCount = userContact.emailResendCount
|
||||
this.emailChecked = userContact.emailChecked
|
||||
this.phone = userContact.phone
|
||||
this.createdAt = userContact.createdAt
|
||||
this.updatedAt = userContact.updatedAt
|
||||
this.deletedAt = userContact.deletedAt
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
type: string
|
||||
|
||||
@Field(() => Int)
|
||||
userId: number
|
||||
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
// @Field(() => BigInt, { nullable: true })
|
||||
// emailVerificationCode: BigInt | null
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
emailOptInTypeId: number | null
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
emailResendCount: number | null
|
||||
|
||||
@Field(() => Boolean)
|
||||
emailChecked: boolean
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
phone: string | null
|
||||
|
||||
@Field(() => Date)
|
||||
createdAt: Date
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
updatedAt: Date | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt: Date | null
|
||||
}
|
||||
@ -9,12 +9,15 @@ import { Event as DbEvent } from '@entity/Event'
|
||||
import { TransactionLink } from '@entity/TransactionLink'
|
||||
import { User } from '@entity/User'
|
||||
import { UserContact } from '@entity/UserContact'
|
||||
import { UserRole } from '@entity/UserRole'
|
||||
import { UserInputError } from 'apollo-server-express'
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { v4 as uuidv4, validate as validateUUID, version as versionUUID } from 'uuid'
|
||||
|
||||
import { OptInType } from '@enum/OptInType'
|
||||
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
|
||||
import { RoleNames } from '@enum/RoleNames'
|
||||
import { UserContactType } from '@enum/UserContactType'
|
||||
import { ContributionLink } from '@model/ContributionLink'
|
||||
import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers'
|
||||
@ -140,7 +143,7 @@ describe('UserResolver', () => {
|
||||
describe('valid input data', () => {
|
||||
// let loginEmailOptIn: LoginEmailOptIn[]
|
||||
beforeAll(async () => {
|
||||
user = await User.find({ relations: ['emailContact'] })
|
||||
user = await User.find({ relations: ['emailContact', 'userRoles'] })
|
||||
// loginEmailOptIn = await LoginEmailOptIn.find()
|
||||
emailVerificationCode = user[0].emailContact.emailVerificationCode.toString()
|
||||
})
|
||||
@ -162,7 +165,7 @@ describe('UserResolver', () => {
|
||||
createdAt: expect.any(Date),
|
||||
// emailChecked: false,
|
||||
language: 'de',
|
||||
isAdmin: null,
|
||||
userRoles: [],
|
||||
deletedAt: null,
|
||||
publisherId: 1234,
|
||||
referrerId: null,
|
||||
@ -336,9 +339,16 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
// make Peter Lustig Admin
|
||||
const peter = await User.findOneOrFail({ where: { id: user[0].id } })
|
||||
peter.isAdmin = new Date()
|
||||
await peter.save()
|
||||
const peter = await User.findOneOrFail({
|
||||
where: { id: user[0].id },
|
||||
relations: ['userRoles'],
|
||||
})
|
||||
peter.userRoles = [] as UserRole[]
|
||||
peter.userRoles[0] = UserRole.create()
|
||||
peter.userRoles[0].createdAt = new Date()
|
||||
peter.userRoles[0].role = RoleNames.ADMIN
|
||||
peter.userRoles[0].userId = peter.id
|
||||
await peter.userRoles[0].save()
|
||||
|
||||
// date statement
|
||||
const actualDate = new Date()
|
||||
@ -353,7 +363,6 @@ describe('UserResolver', () => {
|
||||
validFrom: actualDate,
|
||||
validTo: futureDate,
|
||||
})
|
||||
|
||||
resetToken()
|
||||
result = await mutate({
|
||||
mutation: createUser,
|
||||
@ -685,13 +694,13 @@ describe('UserResolver', () => {
|
||||
firstName: 'Bibi',
|
||||
hasElopage: false,
|
||||
id: expect.any(Number),
|
||||
isAdmin: null,
|
||||
klickTipp: {
|
||||
newsletterState: false,
|
||||
},
|
||||
language: 'de',
|
||||
lastName: 'Bloxberg',
|
||||
publisherId: 1234,
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
@ -942,7 +951,7 @@ describe('UserResolver', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({ mutation: login, variables })
|
||||
user = await User.find()
|
||||
user = await User.find({ relations: ['userRoles'] })
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@ -962,7 +971,7 @@ describe('UserResolver', () => {
|
||||
},
|
||||
hasElopage: false,
|
||||
publisherId: 1234,
|
||||
isAdmin: null,
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
@ -1403,6 +1412,7 @@ describe('UserResolver', () => {
|
||||
expect.objectContaining({
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
role: RoleNames.ADMIN,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
@ -1484,13 +1494,13 @@ describe('UserResolver', () => {
|
||||
firstName: 'Bibi',
|
||||
hasElopage: false,
|
||||
id: expect.any(Number),
|
||||
isAdmin: null,
|
||||
klickTipp: {
|
||||
newsletterState: false,
|
||||
},
|
||||
language: 'de',
|
||||
lastName: 'Bloxberg',
|
||||
publisherId: 1234,
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
@ -1509,7 +1519,10 @@ describe('UserResolver', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: setUserRole, variables: { userId: 1, isAdmin: true } }),
|
||||
mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: 1, role: RoleNames.ADMIN },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
@ -1519,7 +1532,7 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
describe('without admin rights', () => {
|
||||
describe('with user rights', () => {
|
||||
beforeAll(async () => {
|
||||
user = await userFactory(testEnv, bibiBloxberg)
|
||||
await mutate({
|
||||
@ -1535,7 +1548,46 @@ describe('UserResolver', () => {
|
||||
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id + 1, isAdmin: true } }),
|
||||
mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id + 1, role: RoleNames.ADMIN },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with moderator rights', () => {
|
||||
beforeAll(async () => {
|
||||
user = await userFactory(testEnv, bibiBloxberg)
|
||||
admin = await userFactory(testEnv, peterLustig)
|
||||
|
||||
// set Moderator-Role for Peter
|
||||
const userRole = await UserRole.findOneOrFail({ where: { userId: admin.id } })
|
||||
userRole.role = RoleNames.MODERATOR
|
||||
userRole.userId = admin.id
|
||||
await UserRole.save(userRole)
|
||||
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, role: RoleNames.ADMIN },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
@ -1546,6 +1598,7 @@ describe('UserResolver', () => {
|
||||
|
||||
describe('with admin rights', () => {
|
||||
beforeAll(async () => {
|
||||
user = await userFactory(testEnv, bibiBloxberg)
|
||||
admin = await userFactory(testEnv, peterLustig)
|
||||
await mutate({
|
||||
mutation: login,
|
||||
@ -1558,11 +1611,33 @@ describe('UserResolver', () => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns user with new moderator-role', async () => {
|
||||
const result = await mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, role: RoleNames.MODERATOR },
|
||||
})
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
setUserRole: RoleNames.MODERATOR,
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('user to get a new role does not exist', () => {
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({ mutation: setUserRole, variables: { userId: admin.id + 1, isAdmin: true } }),
|
||||
mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: admin.id + 1, role: RoleNames.ADMIN },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
@ -1578,19 +1653,55 @@ describe('UserResolver', () => {
|
||||
describe('change role with success', () => {
|
||||
beforeAll(async () => {
|
||||
user = await userFactory(testEnv, bibiBloxberg)
|
||||
admin = await userFactory(testEnv, peterLustig)
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
resetToken()
|
||||
})
|
||||
|
||||
describe('user gets new role', () => {
|
||||
describe('to admin', () => {
|
||||
it('returns date string', async () => {
|
||||
it('returns admin-rolename', async () => {
|
||||
const result = await mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, isAdmin: true },
|
||||
variables: { userId: user.id, role: RoleNames.ADMIN },
|
||||
})
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
setUserRole: expect.any(String),
|
||||
setUserRole: RoleNames.ADMIN,
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the ADMIN_USER_ROLE_SET event in the database', async () => {
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.ADMIN_USER_ROLE_SET,
|
||||
affectedUserId: user.id,
|
||||
actingUserId: admin.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('to moderator', () => {
|
||||
it('returns date string', async () => {
|
||||
const result = await mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, role: RoleNames.MODERATOR },
|
||||
})
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
setUserRole: RoleNames.MODERATOR,
|
||||
},
|
||||
}),
|
||||
)
|
||||
@ -1598,19 +1709,11 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_USER_ROLE_SET event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const adminConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.ADMIN_USER_ROLE_SET,
|
||||
affectedUserId: userConatct.user.id,
|
||||
actingUserId: adminConatct.user.id,
|
||||
affectedUserId: user.id,
|
||||
actingUserId: admin.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -1619,7 +1722,7 @@ describe('UserResolver', () => {
|
||||
describe('to usual user', () => {
|
||||
it('returns null', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: false } }),
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, role: null } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
@ -1633,11 +1736,25 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
describe('change role with error', () => {
|
||||
describe('is own role', () => {
|
||||
beforeAll(async () => {
|
||||
user = await userFactory(testEnv, bibiBloxberg)
|
||||
admin = await userFactory(testEnv, peterLustig)
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
resetToken()
|
||||
})
|
||||
|
||||
describe('his own role', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({ mutation: setUserRole, variables: { userId: admin.id, isAdmin: false } }),
|
||||
mutate({ mutation: setUserRole, variables: { userId: admin.id, role: null } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('Administrator can not change his own role')],
|
||||
@ -1649,25 +1766,72 @@ describe('UserResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('to not allowed role', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, role: 'unknown rolename' },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new UserInputError(
|
||||
'Variable "$role" got invalid value "unknown rolename"; Value "unknown rolename" does not exist in "RoleNames" enum.',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user has already role to be set', () => {
|
||||
describe('to admin', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
await mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, isAdmin: true },
|
||||
variables: { userId: user.id, role: RoleNames.ADMIN },
|
||||
})
|
||||
await expect(
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: true } }),
|
||||
mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, role: RoleNames.ADMIN },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('User is already admin')],
|
||||
errors: [new GraphQLError('User already has role=')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User is already admin')
|
||||
expect(logger.error).toBeCalledWith('User already has role=', RoleNames.ADMIN)
|
||||
})
|
||||
})
|
||||
|
||||
describe('to moderator', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
await mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, role: RoleNames.MODERATOR },
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, role: RoleNames.MODERATOR },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('User already has role=')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User already has role=', RoleNames.MODERATOR)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1676,10 +1840,10 @@ describe('UserResolver', () => {
|
||||
jest.clearAllMocks()
|
||||
await mutate({
|
||||
mutation: setUserRole,
|
||||
variables: { userId: user.id, isAdmin: false },
|
||||
variables: { userId: user.id, role: null },
|
||||
})
|
||||
await expect(
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: false } }),
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, role: null } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('User is already an usual user')],
|
||||
|
||||
@ -2,11 +2,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { getConnection, IsNull, Not } from '@dbTools/typeorm'
|
||||
import { getConnection, In } from '@dbTools/typeorm'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { UserContact as DbUserContact } from '@entity/UserContact'
|
||||
import { UserRole } from '@entity/UserRole'
|
||||
import i18n from 'i18n'
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, Mutation, Int } from 'type-graphql'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
@ -14,6 +15,7 @@ import { v4 as uuidv4 } from 'uuid'
|
||||
import { CreateUserArgs } from '@arg/CreateUserArgs'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
|
||||
import { SetUserRoleArgs } from '@arg/SetUserRoleArgs'
|
||||
import { UnsecureLoginArgs } from '@arg/UnsecureLoginArgs'
|
||||
import { UpdateUserInfosArgs } from '@arg/UpdateUserInfosArgs'
|
||||
import { OptInType } from '@enum/OptInType'
|
||||
@ -66,6 +68,7 @@ import { getUserCreations } from './util/creations'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { findUsers } from './util/findUsers'
|
||||
import { getKlicktippState } from './util/getKlicktippState'
|
||||
import { setUserRole, deleteUserRole } from './util/modifyUserRole'
|
||||
import { validateAlias } from './util/validateAlias'
|
||||
|
||||
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
|
||||
@ -159,7 +162,6 @@ export class UserResolver {
|
||||
|
||||
const user = new User(dbUser)
|
||||
logger.debug(`user= ${JSON.stringify(user, null, 2)}`)
|
||||
|
||||
i18n.setLocale(user.language)
|
||||
|
||||
// Elopage Status & Stored PublisherId
|
||||
@ -353,7 +355,6 @@ export class UserResolver {
|
||||
} else {
|
||||
await EVENT_USER_REGISTER(dbUser)
|
||||
}
|
||||
|
||||
return new User(dbUser)
|
||||
}
|
||||
|
||||
@ -619,8 +620,9 @@ export class UserResolver {
|
||||
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
||||
): Promise<SearchAdminUsersResult> {
|
||||
const [users, count] = await DbUser.findAndCount({
|
||||
relations: ['userRoles'],
|
||||
where: {
|
||||
isAdmin: Not(IsNull()),
|
||||
userRoles: { role: In(['admin', 'moderator']) },
|
||||
},
|
||||
order: {
|
||||
createdAt: order,
|
||||
@ -628,13 +630,13 @@ export class UserResolver {
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
return {
|
||||
userCount: count,
|
||||
userList: users.map((user) => {
|
||||
return {
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
role: user.userRoles ? user.userRoles[0].role : '',
|
||||
}
|
||||
}),
|
||||
}
|
||||
@ -651,19 +653,9 @@ export class UserResolver {
|
||||
@Ctx() context: Context,
|
||||
): Promise<SearchUsersResult> {
|
||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||
const userFields = [
|
||||
'id',
|
||||
'firstName',
|
||||
'lastName',
|
||||
'emailId',
|
||||
'emailContact',
|
||||
'deletedAt',
|
||||
'isAdmin',
|
||||
]
|
||||
const userFields = ['id', 'firstName', 'lastName', 'emailId', 'emailContact', 'deletedAt']
|
||||
const [users, count] = await findUsers(
|
||||
userFields.map((fieldName) => {
|
||||
return 'user.' + fieldName
|
||||
}),
|
||||
userFields,
|
||||
query,
|
||||
filters ?? null,
|
||||
currentPage,
|
||||
@ -710,16 +702,16 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.SET_USER_ROLE])
|
||||
@Mutation(() => Date, { nullable: true })
|
||||
@Mutation(() => String, { nullable: true })
|
||||
async setUserRole(
|
||||
@Arg('userId', () => Int)
|
||||
userId: number,
|
||||
@Arg('isAdmin', () => Boolean)
|
||||
isAdmin: boolean,
|
||||
@Args() { userId, role }: SetUserRoleArgs,
|
||||
@Ctx()
|
||||
context: Context,
|
||||
): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ where: { id: userId } })
|
||||
): Promise<string | null> {
|
||||
const user = await DbUser.findOne({
|
||||
where: { id: userId },
|
||||
relations: ['userRoles'],
|
||||
})
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
@ -729,27 +721,17 @@ export class UserResolver {
|
||||
if (moderator.id === userId) {
|
||||
throw new LogError('Administrator can not change his own role')
|
||||
}
|
||||
// change isAdmin
|
||||
switch (user.isAdmin) {
|
||||
case null:
|
||||
if (isAdmin) {
|
||||
user.isAdmin = new Date()
|
||||
} else {
|
||||
throw new LogError('User is already an usual user')
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!isAdmin) {
|
||||
user.isAdmin = null
|
||||
} else {
|
||||
throw new LogError('User is already admin')
|
||||
}
|
||||
break
|
||||
// if user role(s) should be deleted by role=null as parameter
|
||||
if (role === null) {
|
||||
await deleteUserRole(user)
|
||||
} else if (isUserInRole(user, role)) {
|
||||
throw new LogError('User already has role=', role)
|
||||
} else {
|
||||
await setUserRole(user, role)
|
||||
}
|
||||
await user.save()
|
||||
await EVENT_ADMIN_USER_ROLE_SET(user, moderator)
|
||||
const newUser = await DbUser.findOne({ where: { id: userId } })
|
||||
return newUser ? newUser.isAdmin : null
|
||||
const newUser = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'] })
|
||||
return newUser?.userRoles ? newUser.userRoles[0].role : null
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.DELETE_USER])
|
||||
@ -842,6 +824,7 @@ export async function findUserByEmail(email: string): Promise<DbUser> {
|
||||
})
|
||||
const dbUser = dbUserContact.user
|
||||
dbUser.emailContact = dbUserContact
|
||||
dbUser.userRoles = await UserRole.find({ where: { userId: dbUser.id } })
|
||||
return dbUser
|
||||
}
|
||||
|
||||
@ -869,3 +852,14 @@ const isEmailVerificationCodeValid = (updatedAt: Date): boolean => {
|
||||
const canEmailResend = (updatedAt: Date): boolean => {
|
||||
return !isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_REQUEST_TIME)
|
||||
}
|
||||
|
||||
export function isUserInRole(user: DbUser, role: string | null | undefined): boolean {
|
||||
if (user && role) {
|
||||
for (const userRole of user.userRoles) {
|
||||
if (userRole.role === role) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -1,10 +1,24 @@
|
||||
import { getConnection, Brackets, IsNull, Not } from '@dbTools/typeorm'
|
||||
import { IsNull, Not, Like } from '@dbTools/typeorm'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
function likeQuery(searchCriteria: string) {
|
||||
return Like(`%${searchCriteria}%`)
|
||||
}
|
||||
|
||||
function emailCheckedQuery(filters: SearchUsersFilters) {
|
||||
return filters.byActivated ?? undefined
|
||||
}
|
||||
|
||||
function deletedAtQuery(filters: SearchUsersFilters | null) {
|
||||
return filters?.byDeleted !== undefined && filters?.byDeleted !== null
|
||||
? filters.byDeleted
|
||||
? Not(IsNull())
|
||||
: IsNull()
|
||||
: undefined
|
||||
}
|
||||
|
||||
export const findUsers = async (
|
||||
select: string[],
|
||||
@ -14,44 +28,51 @@ export const findUsers = async (
|
||||
pageSize: number,
|
||||
order = Order.ASC,
|
||||
): Promise<[DbUser[], number]> => {
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
try {
|
||||
await queryRunner.connect()
|
||||
const query = queryRunner.manager
|
||||
.createQueryBuilder(DbUser, 'user')
|
||||
.select(select)
|
||||
.withDeleted()
|
||||
.leftJoinAndSelect('user.emailContact', 'emailContact')
|
||||
.where(
|
||||
new Brackets((qb) => {
|
||||
qb.where(
|
||||
'user.firstName like :name or user.lastName like :lastName or emailContact.email like :email',
|
||||
{
|
||||
name: `%${searchCriteria}%`,
|
||||
lastName: `%${searchCriteria}%`,
|
||||
email: `%${searchCriteria}%`,
|
||||
},
|
||||
)
|
||||
}),
|
||||
)
|
||||
if (filters) {
|
||||
if (filters.byActivated !== null) {
|
||||
query.andWhere('emailContact.emailChecked = :value', { value: filters.byActivated })
|
||||
}
|
||||
|
||||
if (filters.byDeleted !== null) {
|
||||
query.andWhere({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() })
|
||||
}
|
||||
}
|
||||
|
||||
return await query
|
||||
.orderBy({ 'user.id': order })
|
||||
.take(pageSize)
|
||||
.skip((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
} catch (err) {
|
||||
throw new LogError('Unable to search users', err)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
const where = [
|
||||
{
|
||||
firstName: likeQuery(searchCriteria),
|
||||
deletedAt: deletedAtQuery(filters),
|
||||
emailContact: filters
|
||||
? {
|
||||
emailChecked: emailCheckedQuery(filters),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
lastName: likeQuery(searchCriteria),
|
||||
deletedAt: deletedAtQuery(filters),
|
||||
emailContact: filters
|
||||
? {
|
||||
emailChecked: emailCheckedQuery(filters),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
emailContact: {
|
||||
// ...(filters ?? emailChecked: filters.byActivated)
|
||||
emailChecked: filters ? emailCheckedQuery(filters) : undefined,
|
||||
email: likeQuery(searchCriteria),
|
||||
},
|
||||
deletedAt: deletedAtQuery(filters),
|
||||
},
|
||||
]
|
||||
const selectFind = Object.fromEntries(select.map((item) => [item, true]))
|
||||
const relations = ['emailContact', 'userRoles']
|
||||
const orderFind = {
|
||||
id: order,
|
||||
}
|
||||
const take = pageSize
|
||||
const skip = (currentPage - 1) * pageSize
|
||||
const withDeleted = true
|
||||
|
||||
const [users, count] = await DbUser.findAndCount({
|
||||
where,
|
||||
withDeleted,
|
||||
select: selectFind,
|
||||
relations,
|
||||
order: orderFind,
|
||||
take,
|
||||
skip,
|
||||
})
|
||||
return [users, count]
|
||||
}
|
||||
|
||||
29
backend/src/graphql/resolver/util/modifyUserRole.ts
Normal file
29
backend/src/graphql/resolver/util/modifyUserRole.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { UserRole } from '@entity/UserRole'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export async function setUserRole(user: DbUser, role: string | null | undefined): Promise<void> {
|
||||
// if role should be set
|
||||
if (role) {
|
||||
// in case user has still no associated userRole
|
||||
if (user.userRoles.length < 1) {
|
||||
// instanciate a userRole
|
||||
user.userRoles.push(UserRole.create())
|
||||
}
|
||||
// and initialize the userRole
|
||||
user.userRoles[0].role = role
|
||||
user.userRoles[0].userId = user.id
|
||||
await UserRole.save(user.userRoles[0])
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteUserRole(user: DbUser): Promise<void> {
|
||||
if (user.userRoles.length > 0) {
|
||||
// remove all roles of the user
|
||||
await UserRole.delete({ userId: user.id })
|
||||
user.userRoles.length = 0
|
||||
} else if (user.userRoles.length === 0) {
|
||||
throw new LogError('User is already an usual user')
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,6 @@ export const contributionLinkFactory = async (
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
|
||||
const variables = {
|
||||
amount: contributionLink.amount,
|
||||
memo: contributionLink.memo,
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
import { User } from '@entity/User'
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
|
||||
import { RoleNames } from '@enum/RoleNames'
|
||||
|
||||
import { setUserRole } from '@/graphql/resolver/util/modifyUserRole'
|
||||
import { createUser, setPassword } from '@/seeds/graphql/mutations'
|
||||
import { UserInterface } from '@/seeds/users/UserInterface'
|
||||
|
||||
@ -17,13 +20,10 @@ export const userFactory = async (
|
||||
createUser: { id },
|
||||
},
|
||||
} = await mutate({ mutation: createUser, variables: user })
|
||||
// console.log('creatUser:', { id }, { user })
|
||||
// get user from database
|
||||
let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact'] })
|
||||
// console.log('dbUser:', dbUser)
|
||||
let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact', 'userRoles'] })
|
||||
|
||||
const emailContact = dbUser.emailContact
|
||||
// console.log('emailContact:', emailContact)
|
||||
|
||||
if (user.emailChecked) {
|
||||
await mutate({
|
||||
@ -33,17 +33,22 @@ export const userFactory = async (
|
||||
}
|
||||
|
||||
// get last changes of user from database
|
||||
dbUser = await User.findOneOrFail({ where: { id } })
|
||||
dbUser = await User.findOneOrFail({ where: { id }, relations: ['userRoles'] })
|
||||
|
||||
if (user.createdAt || user.deletedAt || user.isAdmin) {
|
||||
if (user.createdAt || user.deletedAt || user.role) {
|
||||
if (user.createdAt) dbUser.createdAt = user.createdAt
|
||||
if (user.deletedAt) dbUser.deletedAt = user.deletedAt
|
||||
if (user.isAdmin) dbUser.isAdmin = new Date()
|
||||
if (user.role && (user.role === RoleNames.ADMIN || user.role === RoleNames.MODERATOR)) {
|
||||
await setUserRole(dbUser, user.role)
|
||||
}
|
||||
await dbUser.save()
|
||||
}
|
||||
|
||||
// get last changes of user from database
|
||||
// dbUser = await User.findOneOrFail({ id }, { withDeleted: true })
|
||||
|
||||
dbUser = await User.findOneOrFail({
|
||||
where: { id },
|
||||
withDeleted: true,
|
||||
relations: ['emailContact', 'userRoles'],
|
||||
})
|
||||
return dbUser
|
||||
}
|
||||
|
||||
@ -119,8 +119,8 @@ export const confirmContribution = gql`
|
||||
`
|
||||
|
||||
export const setUserRole = gql`
|
||||
mutation ($userId: Int!, $isAdmin: Boolean!) {
|
||||
setUserRole(userId: $userId, isAdmin: $isAdmin)
|
||||
mutation ($userId: Int!, $role: RoleNames) {
|
||||
setUserRole(userId: $userId, role: $role)
|
||||
}
|
||||
`
|
||||
|
||||
@ -321,7 +321,7 @@ export const login = gql`
|
||||
}
|
||||
hasElopage
|
||||
publisherId
|
||||
isAdmin
|
||||
roles
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -11,7 +11,7 @@ export const verifyLogin = gql`
|
||||
}
|
||||
hasElopage
|
||||
publisherId
|
||||
isAdmin
|
||||
roles
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -94,7 +94,7 @@ export const searchUsers = gql`
|
||||
hasElopage
|
||||
emailConfirmationSend
|
||||
deletedAt
|
||||
isAdmin
|
||||
roles
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -323,6 +323,7 @@ export const searchAdminUsers = gql`
|
||||
userList {
|
||||
firstName
|
||||
lastName
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,5 +9,5 @@ export interface UserInterface {
|
||||
language?: string
|
||||
deletedAt?: Date
|
||||
publisherId?: number
|
||||
isAdmin?: boolean
|
||||
role?: string
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { RoleNames } from '@enum/RoleNames'
|
||||
|
||||
import { UserInterface } from './UserInterface'
|
||||
|
||||
export const peterLustig: UserInterface = {
|
||||
@ -8,5 +10,5 @@ export const peterLustig: UserInterface = {
|
||||
createdAt: new Date('2020-11-25T10:48:43'),
|
||||
emailChecked: true,
|
||||
language: 'de',
|
||||
isAdmin: true,
|
||||
role: RoleNames.ADMIN,
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ const communityDbUser: dbUser = {
|
||||
createdAt: new Date(),
|
||||
// emailChecked: false,
|
||||
language: '',
|
||||
isAdmin: null,
|
||||
userRoles: [],
|
||||
publisherId: 0,
|
||||
// default password encryption type
|
||||
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,
|
||||
|
||||
120
database/entity/0069-add_user_roles_table/User.ts
Normal file
120
database/entity/0069-add_user_roles_table/User.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import {
|
||||
BaseEntity,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
} from 'typeorm'
|
||||
import { Contribution } from '../Contribution'
|
||||
import { ContributionMessage } from '../ContributionMessage'
|
||||
import { UserContact } from '../UserContact'
|
||||
import { UserRole } from './UserRole'
|
||||
|
||||
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||
export class User extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({
|
||||
name: 'gradido_id',
|
||||
length: 36,
|
||||
nullable: false,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
gradidoID: string
|
||||
|
||||
@Column({
|
||||
name: 'alias',
|
||||
length: 20,
|
||||
nullable: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
alias: string
|
||||
|
||||
@OneToOne(() => UserContact, (emailContact: UserContact) => emailContact.user)
|
||||
@JoinColumn({ name: 'email_id' })
|
||||
emailContact: UserContact
|
||||
|
||||
@Column({ name: 'email_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||
emailId: number | null
|
||||
|
||||
@Column({
|
||||
name: 'first_name',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
firstName: string
|
||||
|
||||
@Column({
|
||||
name: 'last_name',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
lastName: string
|
||||
|
||||
@Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP(3)', nullable: false })
|
||||
createdAt: Date
|
||||
|
||||
@DeleteDateColumn({ name: 'deleted_at', nullable: true })
|
||||
deletedAt: Date | null
|
||||
|
||||
@Column({ type: 'bigint', default: 0, unsigned: true })
|
||||
password: BigInt
|
||||
|
||||
@Column({
|
||||
name: 'password_encryption_type',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: false,
|
||||
default: 0,
|
||||
})
|
||||
passwordEncryptionType: number
|
||||
|
||||
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
|
||||
language: string
|
||||
|
||||
@Column({ type: 'bool', default: false })
|
||||
hideAmountGDD: boolean
|
||||
|
||||
@Column({ type: 'bool', default: false })
|
||||
hideAmountGDT: boolean
|
||||
|
||||
@OneToMany(() => UserRole, (userRole) => userRole.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
userRoles: UserRole[]
|
||||
|
||||
@Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||
referrerId?: number | null
|
||||
|
||||
@Column({
|
||||
name: 'contribution_link_id',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
contributionLinkId?: number | null
|
||||
|
||||
@Column({ name: 'publisher_id', default: 0 })
|
||||
publisherId: number
|
||||
|
||||
@OneToMany(() => Contribution, (contribution) => contribution.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
contributions?: Contribution[]
|
||||
|
||||
@OneToMany(() => ContributionMessage, (message) => message.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
messages?: ContributionMessage[]
|
||||
|
||||
@OneToMany(() => UserContact, (userContact: UserContact) => userContact.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
userContacts?: UserContact[]
|
||||
}
|
||||
24
database/entity/0069-add_user_roles_table/UserRole.ts
Normal file
24
database/entity/0069-add_user_roles_table/UserRole.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'
|
||||
import { User } from '../User'
|
||||
|
||||
@Entity('user_roles', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||
export class UserRole extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'user_id', type: 'int', unsigned: true, nullable: false })
|
||||
userId: number
|
||||
|
||||
@Column({ length: 40, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
role: string
|
||||
|
||||
@Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP(3)', nullable: false })
|
||||
createdAt: Date
|
||||
|
||||
@Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' })
|
||||
updatedAt: Date | null
|
||||
|
||||
@ManyToOne(() => User, (user) => user.userRoles)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user: User
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export { User } from './0059-add_hide_amount_to_users/User'
|
||||
export { User } from './0069-add_user_roles_table/User'
|
||||
|
||||
1
database/entity/UserRole.ts
Normal file
1
database/entity/UserRole.ts
Normal file
@ -0,0 +1 @@
|
||||
export { UserRole } from './0069-add_user_roles_table/UserRole'
|
||||
@ -11,6 +11,7 @@ import { Event } from './Event'
|
||||
import { ContributionMessage } from './ContributionMessage'
|
||||
import { Community } from './Community'
|
||||
import { FederatedCommunity } from './FederatedCommunity'
|
||||
import { UserRole } from './UserRole'
|
||||
|
||||
export const entities = [
|
||||
Community,
|
||||
@ -26,4 +27,5 @@ export const entities = [
|
||||
TransactionLink,
|
||||
User,
|
||||
UserContact,
|
||||
UserRole,
|
||||
]
|
||||
|
||||
43
database/migrations/0069-add_user_roles_table.ts
Normal file
43
database/migrations/0069-add_user_roles_table.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE user_roles (
|
||||
id int unsigned NOT NULL AUTO_INCREMENT,
|
||||
user_id int(10) unsigned NOT NULL,
|
||||
role varchar(40) NOT NULL,
|
||||
created_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at datetime(3),
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
|
||||
|
||||
// insert values from users table with users.is_admin in new user_roles table
|
||||
await queryFn(`
|
||||
INSERT INTO user_roles
|
||||
(user_id, role, created_at, updated_at)
|
||||
SELECT u.id, 'ADMIN', u.is_admin, null
|
||||
FROM users u
|
||||
WHERE u.is_admin IS NOT NULL;`)
|
||||
|
||||
// remove column is_admin from users table
|
||||
await queryFn('ALTER TABLE users DROP COLUMN is_admin;')
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
// first add column is_admin in users table
|
||||
await queryFn(
|
||||
'ALTER TABLE users ADD COLUMN is_admin datetime(3) NULL DEFAULT NULL AFTER language;',
|
||||
)
|
||||
// reconstruct the previous is_admin back from user_roles to users table
|
||||
const roles = await queryFn(
|
||||
`SELECT r.user_id, r.role, r.created_at FROM user_roles as r WHERE r.role = "ADMIN"`,
|
||||
)
|
||||
for (const id in roles) {
|
||||
const role = roles[id]
|
||||
const isAdminDate = new Date(role.created_at).toISOString().slice(0, 19).replace('T', ' ')
|
||||
await queryFn(`UPDATE users SET is_admin = "${isAdminDate}" WHERE id = "${role.user_id}"`)
|
||||
}
|
||||
|
||||
await queryFn(`DROP TABLE user_roles;`)
|
||||
}
|
||||
@ -4,7 +4,7 @@ import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0068-community_tables_public_key_length',
|
||||
DB_VERSION: '0069-add_user_roles_table',
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||
|
||||
@ -252,6 +252,13 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.19.0"
|
||||
|
||||
"@babel/runtime@^7.21.0":
|
||||
version "7.22.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
|
||||
integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3":
|
||||
version "7.20.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
|
||||
@ -672,7 +679,7 @@
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@sqltools/formatter@^1.2.2":
|
||||
"@sqltools/formatter@^1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
|
||||
integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
|
||||
@ -835,11 +842,6 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/zen-observable@0.8.3":
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3"
|
||||
integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.57.1":
|
||||
version "5.59.9"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz#2604cfaf2b306e120044f901e20c8ed926debf15"
|
||||
@ -1028,7 +1030,7 @@ anymatch@^3.0.3, anymatch@~3.1.2:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
app-root-path@^3.0.0:
|
||||
app-root-path@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86"
|
||||
integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==
|
||||
@ -1221,6 +1223,13 @@ brace-expansion@^1.1.7:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
braces@^3.0.2, braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
@ -1328,7 +1337,7 @@ chalk@^2.0.0:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
chalk@^4.0.0, chalk@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@ -1518,12 +1527,19 @@ data-urls@^2.0.0:
|
||||
whatwg-mimetype "^2.3.0"
|
||||
whatwg-url "^8.0.0"
|
||||
|
||||
date-fns@^2.29.3:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
|
||||
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.21.0"
|
||||
|
||||
date-format@^4.0.14:
|
||||
version "4.0.14"
|
||||
resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400"
|
||||
integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
@ -1680,10 +1696,10 @@ dotenv@10.0.0, dotenv@^10.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||
|
||||
dotenv@^8.2.0:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
|
||||
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
||||
dotenv@^16.0.3:
|
||||
version "16.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
||||
|
||||
electron-to-chromium@^1.4.251:
|
||||
version "1.4.284"
|
||||
@ -2297,7 +2313,7 @@ glob-parent@^6.0.2:
|
||||
dependencies:
|
||||
is-glob "^4.0.3"
|
||||
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
|
||||
@ -2309,6 +2325,17 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
|
||||
integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^5.0.1"
|
||||
once "^1.3.0"
|
||||
|
||||
globals@^11.1.0:
|
||||
version "11.12.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||
@ -2362,7 +2389,7 @@ graceful-fs@^4.2.4:
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
||||
"gradido-database@file:../database":
|
||||
version "1.21.0"
|
||||
version "1.22.3"
|
||||
dependencies:
|
||||
"@types/uuid" "^8.3.4"
|
||||
cross-env "^7.0.3"
|
||||
@ -2372,7 +2399,7 @@ graceful-fs@^4.2.4:
|
||||
mysql2 "^2.3.0"
|
||||
reflect-metadata "^0.1.13"
|
||||
ts-mysql-migrate "^1.0.2"
|
||||
typeorm "^0.2.38"
|
||||
typeorm "^0.3.16"
|
||||
uuid "^8.3.2"
|
||||
|
||||
grapheme-splitter@^1.0.4:
|
||||
@ -3205,7 +3232,7 @@ js-yaml@^3.13.1:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
js-yaml@^4.0.0, js-yaml@^4.1.0:
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
@ -3450,15 +3477,22 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^5.0.1:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.6:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||
|
||||
mkdirp@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
mkdirp@^2.1.3:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19"
|
||||
integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
@ -3940,6 +3974,11 @@ reflect-metadata@^0.1.13:
|
||||
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
|
||||
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
|
||||
|
||||
regenerator-runtime@^0.13.11:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
|
||||
|
||||
regexp-tree@~0.1.1:
|
||||
version "0.1.27"
|
||||
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd"
|
||||
@ -4072,11 +4111,6 @@ safety-catch@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/safety-catch/-/safety-catch-1.0.2.tgz#d64cbd57fd601da91c356b6ab8902f3e449a7a4b"
|
||||
integrity sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==
|
||||
|
||||
sax@>=0.6.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
saxes@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
|
||||
@ -4617,7 +4651,7 @@ tslib@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.1.0, tslib@^2.5.0:
|
||||
tslib@^2.5.0:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
|
||||
integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
|
||||
@ -4665,28 +4699,26 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typeorm@^0.2.38:
|
||||
version "0.2.45"
|
||||
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.45.tgz#e5bbb3af822dc4646bad96cfa48cd22fa4687cea"
|
||||
integrity sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA==
|
||||
typeorm@^0.3.16:
|
||||
version "0.3.17"
|
||||
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.17.tgz#a73c121a52e4fbe419b596b244777be4e4b57949"
|
||||
integrity sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==
|
||||
dependencies:
|
||||
"@sqltools/formatter" "^1.2.2"
|
||||
app-root-path "^3.0.0"
|
||||
"@sqltools/formatter" "^1.2.5"
|
||||
app-root-path "^3.1.0"
|
||||
buffer "^6.0.3"
|
||||
chalk "^4.1.0"
|
||||
chalk "^4.1.2"
|
||||
cli-highlight "^2.1.11"
|
||||
debug "^4.3.1"
|
||||
dotenv "^8.2.0"
|
||||
glob "^7.1.6"
|
||||
js-yaml "^4.0.0"
|
||||
mkdirp "^1.0.4"
|
||||
date-fns "^2.29.3"
|
||||
debug "^4.3.4"
|
||||
dotenv "^16.0.3"
|
||||
glob "^8.1.0"
|
||||
mkdirp "^2.1.3"
|
||||
reflect-metadata "^0.1.13"
|
||||
sha.js "^2.4.11"
|
||||
tslib "^2.1.0"
|
||||
uuid "^8.3.2"
|
||||
xml2js "^0.4.23"
|
||||
yargs "^17.0.1"
|
||||
zen-observable-ts "^1.0.0"
|
||||
tslib "^2.5.0"
|
||||
uuid "^9.0.0"
|
||||
yargs "^17.6.2"
|
||||
|
||||
typescript@^4.9.4:
|
||||
version "4.9.4"
|
||||
@ -4767,6 +4799,11 @@ uuid@^8.3.2:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuid@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||
@ -4895,19 +4932,6 @@ xml-name-validator@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
|
||||
|
||||
xml2js@^0.4.23:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
|
||||
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~11.0.0"
|
||||
|
||||
xmlbuilder@~11.0.0:
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
xmlchars@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
@ -4956,7 +4980,7 @@ yargs@^16.0.0, yargs@^16.2.0:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yargs@^17.0.1:
|
||||
yargs@^17.6.2:
|
||||
version "17.7.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
||||
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
||||
@ -4978,16 +5002,3 @@ yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zen-observable-ts@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83"
|
||||
integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==
|
||||
dependencies:
|
||||
"@types/zen-observable" "0.8.3"
|
||||
zen-observable "0.8.15"
|
||||
|
||||
zen-observable@0.8.15:
|
||||
version "0.8.15"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
|
||||
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
|
||||
|
||||
@ -56,7 +56,7 @@ export default defineConfig({
|
||||
}
|
||||
hasElopage
|
||||
publisherId
|
||||
isAdmin
|
||||
roles
|
||||
hideAmountGDD
|
||||
hideAmountGDT
|
||||
__typename
|
||||
|
||||
@ -11,7 +11,7 @@ Decimal.set({
|
||||
*/
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0068-community_tables_public_key_length',
|
||||
DB_VERSION: '0069-add_user_roles_table',
|
||||
// DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
|
||||
@ -14,7 +14,7 @@ describe('Sidebar', () => {
|
||||
$store: {
|
||||
state: {
|
||||
hasElopage: true,
|
||||
isAdmin: false,
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -83,7 +83,7 @@ describe('Sidebar', () => {
|
||||
|
||||
describe('for admin users', () => {
|
||||
beforeAll(() => {
|
||||
mocks.$store.state.isAdmin = true
|
||||
mocks.$store.state.roles = ['admin']
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
|
||||
@ -49,12 +49,14 @@
|
||||
</b-nav-item>
|
||||
<b-nav-item
|
||||
class="mb-3 text-light"
|
||||
v-if="$store.state.isAdmin"
|
||||
v-if="$store.state.roles && $store.state.roles.length > 0"
|
||||
@click="$emit('admin')"
|
||||
active-class="activeRoute"
|
||||
>
|
||||
<b-icon icon="shield-check" aria-hidden="true"></b-icon>
|
||||
<span class="ml-2">{{ $t('navigation.admin_area') }}</span>
|
||||
<span class="ml-2">
|
||||
{{ $t('navigation.admin_area') }}
|
||||
</span>
|
||||
</b-nav-item>
|
||||
<b-nav-item
|
||||
class="font-weight-bold"
|
||||
|
||||
@ -156,7 +156,7 @@ export const login = gql`
|
||||
}
|
||||
hasElopage
|
||||
publisherId
|
||||
isAdmin
|
||||
roles
|
||||
hideAmountGDD
|
||||
hideAmountGDT
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export const verifyLogin = gql`
|
||||
}
|
||||
hasElopage
|
||||
publisherId
|
||||
isAdmin
|
||||
roles
|
||||
hideAmountGDD
|
||||
hideAmountGDT
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ const mocks = {
|
||||
firstName: 'User',
|
||||
lastName: 'Example',
|
||||
token: 'valid-token',
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
$i18n: {
|
||||
|
||||
@ -40,8 +40,8 @@ export const mutations = {
|
||||
if (isNaN(pubId)) pubId = null
|
||||
state.publisherId = pubId
|
||||
},
|
||||
isAdmin: (state, isAdmin) => {
|
||||
state.isAdmin = !!isAdmin
|
||||
roles(state, roles) {
|
||||
state.roles = roles
|
||||
},
|
||||
hasElopage: (state, hasElopage) => {
|
||||
state.hasElopage = hasElopage
|
||||
@ -70,7 +70,7 @@ export const actions = {
|
||||
commit('newsletterState', data.klickTipp.newsletterState)
|
||||
commit('hasElopage', data.hasElopage)
|
||||
commit('publisherId', data.publisherId)
|
||||
commit('isAdmin', data.isAdmin)
|
||||
commit('roles', data.roles)
|
||||
commit('hideAmountGDD', data.hideAmountGDD)
|
||||
commit('hideAmountGDT', data.hideAmountGDT)
|
||||
commit('setDarkMode', data.darkMode)
|
||||
@ -84,7 +84,7 @@ export const actions = {
|
||||
commit('newsletterState', null)
|
||||
commit('hasElopage', false)
|
||||
commit('publisherId', null)
|
||||
commit('isAdmin', false)
|
||||
commit('roles', null)
|
||||
commit('hideAmountGDD', false)
|
||||
commit('hideAmountGDT', true)
|
||||
commit('email', '')
|
||||
@ -111,7 +111,7 @@ try {
|
||||
// username: '',
|
||||
token: null,
|
||||
tokenTime: null,
|
||||
isAdmin: false,
|
||||
roles: [],
|
||||
newsletterState: null,
|
||||
hasElopage: false,
|
||||
publisherId: null,
|
||||
|
||||
@ -29,7 +29,7 @@ const {
|
||||
username,
|
||||
newsletterState,
|
||||
publisherId,
|
||||
isAdmin,
|
||||
roles,
|
||||
hasElopage,
|
||||
hideAmountGDD,
|
||||
hideAmountGDT,
|
||||
@ -136,11 +136,11 @@ describe('Vuex store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('isAdmin', () => {
|
||||
it('sets the state of isAdmin', () => {
|
||||
const state = { isAdmin: null }
|
||||
isAdmin(state, true)
|
||||
expect(state.isAdmin).toEqual(true)
|
||||
describe('roles', () => {
|
||||
it('sets the state of roles', () => {
|
||||
const state = { roles: [] }
|
||||
roles(state, ['admin'])
|
||||
expect(state.roles).toEqual(['admin'])
|
||||
})
|
||||
})
|
||||
|
||||
@ -192,7 +192,7 @@ describe('Vuex store', () => {
|
||||
},
|
||||
hasElopage: false,
|
||||
publisherId: 1234,
|
||||
isAdmin: true,
|
||||
roles: ['admin'],
|
||||
hideAmountGDD: false,
|
||||
hideAmountGDT: true,
|
||||
}
|
||||
@ -242,9 +242,9 @@ describe('Vuex store', () => {
|
||||
expect(commit).toHaveBeenNthCalledWith(8, 'publisherId', 1234)
|
||||
})
|
||||
|
||||
it('commits isAdmin', () => {
|
||||
it('commits roles', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(9, 'isAdmin', true)
|
||||
expect(commit).toHaveBeenNthCalledWith(9, 'roles', ['admin'])
|
||||
})
|
||||
|
||||
it('commits hideAmountGDD', () => {
|
||||
@ -307,9 +307,9 @@ describe('Vuex store', () => {
|
||||
expect(commit).toHaveBeenNthCalledWith(8, 'publisherId', null)
|
||||
})
|
||||
|
||||
it('commits isAdmin', () => {
|
||||
it('commits roles', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(9, 'isAdmin', false)
|
||||
expect(commit).toHaveBeenNthCalledWith(9, 'roles', null)
|
||||
})
|
||||
|
||||
it('commits hideAmountGDD', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user