Merge pull request #3074 from gradido/3030-feature-role-administration-backend

feat(backend): 3030 feature role administration backend
This commit is contained in:
clauspeterhuebner 2023-07-19 01:03:27 +02:00 committed by GitHub
commit 8b2d07c0af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 735 additions and 305 deletions

View File

@ -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'],

View File

@ -0,0 +1,3 @@
import { RIGHTS } from './RIGHTS'
export const ADMIN_RIGHTS = [RIGHTS.SET_USER_ROLE, RIGHTS.DELETE_USER, RIGHTS.UNDELETE_USER]

View 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,
]

View File

@ -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',
}

View File

@ -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]

View 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,
]

View File

@ -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

View 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
}

View File

@ -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')

View 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
})

View File

@ -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()

View File

@ -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[]
}

View File

@ -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()

View File

@ -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
}

View File

@ -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')],

View File

@ -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,15 +653,7 @@ 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
@ -710,16 +704,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 +723,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 +826,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 +854,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
}

View 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')
}
}

View File

@ -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,

View File

@ -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
}

View File

@ -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
}
}
`

View File

@ -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
}
}
}

View File

@ -9,5 +9,5 @@ export interface UserInterface {
language?: string
deletedAt?: Date
publisherId?: number
isAdmin?: boolean
role?: string
}

View File

@ -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,
}

View File

@ -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,

View 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[]
}

View 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
}

View File

@ -1 +1 @@
export { User } from './0059-add_hide_amount_to_users/User'
export { User } from './0069-add_user_roles_table/User'

View File

@ -0,0 +1 @@
export { UserRole } from './0069-add_user_roles_table/UserRole'

View File

@ -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,
]

View 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;`)
}

View File

@ -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',

View File

@ -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==

View File

@ -56,7 +56,7 @@ export default defineConfig({
}
hasElopage
publisherId
isAdmin
roles
hideAmountGDD
hideAmountGDT
__typename

View File

@ -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

View File

@ -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()
})

View File

@ -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.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"

View File

@ -156,7 +156,7 @@ export const login = gql`
}
hasElopage
publisherId
isAdmin
roles
hideAmountGDD
hideAmountGDT
}

View File

@ -13,7 +13,7 @@ export const verifyLogin = gql`
}
hasElopage
publisherId
isAdmin
roles
hideAmountGDD
hideAmountGDT
}

View File

@ -47,6 +47,7 @@ const mocks = {
firstName: 'User',
lastName: 'Example',
token: 'valid-token',
roles: [],
},
},
$i18n: {

View File

@ -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,

View File

@ -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', () => {