userRoles as OneToMany relation

This commit is contained in:
Claus-Peter Huebner 2023-06-28 02:47:51 +02:00
parent dcea6871ef
commit 9c9a05e64f
10 changed files with 152 additions and 89 deletions

View File

@ -33,11 +33,12 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
try {
const user = await User.findOneOrFail({
where: { gradidoID: decoded.gradidoID },
relations: ['emailContact', 'userRole'],
relations: ['emailContact', 'userRoles'],
})
console.log('isAuthorized user=', user)
context.user = user
context.role = user.userRole
? user.userRole.role === ROLE_NAMES.ROLE_NAME_ADMIN
context.role = user.userRoles
? user.userRoles[0].role === ROLE_NAMES.ROLE_NAME_ADMIN
? ROLE_ADMIN
: ROLE_MODERATOR
: ROLE_USER

View File

@ -1,7 +1,8 @@
import { ROLE_NAMES } from '@/auth/ROLES'
import { User as dbUser } from '@entity/User'
import { ObjectType, Field, Int } from 'type-graphql'
import { ROLE_NAMES } from '@/auth/ROLES'
import { KlickTipp } from './KlickTipp'
@ObjectType()
@ -19,18 +20,11 @@ export class User {
this.createdAt = user.createdAt
this.language = user.language
this.publisherId = user.publisherId
if (user.userRole) {
switch (user.userRole.role) {
case ROLE_NAMES.ROLE_NAME_ADMIN:
this.isAdmin = user.userRole.createdAt
break
case ROLE_NAMES.ROLE_NAME_MODERATOR:
this.isModerator = user.userRole.createdAt
break
default:
this.isAdmin = null
this.isModerator = null
}
if (user.userRoles) {
this.roles = [] as string[]
user.userRoles.forEach((userRole) => {
this.roles?.push(userRole.role)
})
}
this.klickTipp = null
this.hasElopage = null
@ -75,15 +69,34 @@ export class User {
@Field(() => Int, { nullable: true })
publisherId: number | null
@Field(() => Date, { nullable: true })
isAdmin: Date | null
@Field(() => Date, { nullable: true })
isModerator: Date | null
@Field(() => KlickTipp, { nullable: true })
klickTipp: KlickTipp | null
@Field(() => Boolean, { nullable: true })
hasElopage: boolean | null
@Field(() => [String], { nullable: true })
roles: string[] | null
}
export function isAdmin(user: User): boolean {
if (user.roles) {
for (const role of user.roles) {
if (role === ROLE_NAMES.ROLE_NAME_ADMIN) {
return true
}
}
}
return false
}
export function isModerator(user: User): boolean {
if (user.roles) {
for (const role of user.roles) {
if (role === ROLE_NAMES.ROLE_NAME_MODERATOR) {
return true
}
}
}
return false
}

View File

@ -2,6 +2,8 @@ import { User } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { ObjectType, Field, Int } from 'type-graphql'
import { ROLE_NAMES } from '@/auth/ROLES'
@ObjectType()
export class UserAdmin {
constructor(user: User, creation: Decimal[], hasElopage: boolean, emailConfirmationSend: string) {
@ -14,8 +16,18 @@ export class UserAdmin {
this.hasElopage = hasElopage
this.deletedAt = user.deletedAt
this.emailConfirmationSend = emailConfirmationSend
if (user.userRole) {
this.isAdmin = user.userRole?.createdAt
if (user.userRoles) {
switch (user.userRoles[0].role) {
case ROLE_NAMES.ROLE_NAME_ADMIN:
this.isAdmin = user.userRoles[0].createdAt
break
case ROLE_NAMES.ROLE_NAME_MODERATOR:
this.isModerator = user.userRoles[0].createdAt
break
default:
this.isAdmin = null
this.isModerator = null
}
}
}
@ -48,6 +60,9 @@ export class UserAdmin {
@Field(() => Date, { nullable: true })
isAdmin: Date | null
@Field(() => Date, { nullable: true })
isModerator: Date | null
}
@ObjectType()

View File

@ -9,6 +9,7 @@ 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 { ApolloServerTestClient } from 'apollo-server-testing'
import { GraphQLError } from 'graphql'
import { v4 as uuidv4, validate as validateUUID, version as versionUUID } from 'uuid'
@ -21,6 +22,7 @@ import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/help
import { logger, i18n as localization } from '@test/testSetup'
import { subscribe } from '@/apis/KlicktippController'
import { ROLE_NAMES } from '@/auth/ROLES'
import { CONFIG } from '@/config'
import {
sendAccountActivationEmail,
@ -62,8 +64,6 @@ import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { printTimeDuration } from '@/util/time'
import { objectValuesToArray } from '@/util/utilities'
import { UserRole } from '@entity/UserRole'
import { ROLE_NAMES } from '@/auth/ROLES'
jest.mock('@/emails/sendEmailVariants', () => {
const originalModule = jest.requireActual('@/emails/sendEmailVariants')
@ -142,7 +142,7 @@ describe('UserResolver', () => {
describe('valid input data', () => {
// let loginEmailOptIn: LoginEmailOptIn[]
beforeAll(async () => {
user = await User.find({ relations: ['emailContact', 'userRole'] })
user = await User.find({ relations: ['emailContact', 'userRoles'] })
// loginEmailOptIn = await LoginEmailOptIn.find()
emailVerificationCode = user[0].emailContact.emailVerificationCode.toString()
})
@ -164,7 +164,7 @@ describe('UserResolver', () => {
createdAt: expect.any(Date),
// emailChecked: false,
language: 'de',
userRole: null,
userRoles: null,
deletedAt: null,
publisherId: 1234,
referrerId: null,
@ -338,16 +338,16 @@ describe('UserResolver', () => {
// make Peter Lustig Admin
let peter = await User.findOneOrFail({
where: { id: user[0].id },
relations: ['userRole'],
relations: ['userRoles'],
})
console.log('vorher peter=', peter)
await mutate({
mutation: setUserRole,
variables: { userId: user[0].id, isAdmin: true },
variables: { userId: user[0].id, role: ROLE_NAMES.ROLE_NAME_ADMIN },
})
peter = await User.findOneOrFail({
where: { id: user[0].id },
relations: ['userRole'],
relations: ['userRoles'],
})
console.log('nachher peter=', peter)
@ -704,7 +704,7 @@ describe('UserResolver', () => {
firstName: 'Bibi',
hasElopage: false,
id: expect.any(Number),
userRole: null,
userRoles: null,
klickTipp: {
newsletterState: false,
},
@ -961,7 +961,7 @@ describe('UserResolver', () => {
beforeAll(async () => {
await mutate({ mutation: login, variables })
user = await User.find({ relations: ['userRole'] })
user = await User.find({ relations: ['userRoles'] })
})
afterAll(() => {
@ -981,7 +981,7 @@ describe('UserResolver', () => {
},
hasElopage: false,
publisherId: 1234,
userRole: null,
userRoles: null,
},
},
}),
@ -1501,7 +1501,7 @@ describe('UserResolver', () => {
firstName: 'Bibi',
hasElopage: false,
id: expect.any(Number),
userRole: null,
userRoles: null,
klickTipp: {
newsletterState: false,
},
@ -1526,7 +1526,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: ROLE_NAMES.ROLE_NAME_ADMIN },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
@ -1552,7 +1555,10 @@ 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: ROLE_NAMES.ROLE_NAME_ADMIN },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
@ -1579,7 +1585,10 @@ describe('UserResolver', () => {
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: ROLE_NAMES.ROLE_NAME_ADMIN },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('Could not find user with given ID')],
@ -1602,7 +1611,7 @@ describe('UserResolver', () => {
it('returns date string', async () => {
const result = await mutate({
mutation: setUserRole,
variables: { userId: user.id, isAdmin: true },
variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN },
})
expect(result).toEqual(
expect.objectContaining({
@ -1636,7 +1645,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: {
@ -1654,7 +1663,7 @@ describe('UserResolver', () => {
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')],
@ -1672,10 +1681,13 @@ describe('UserResolver', () => {
jest.clearAllMocks()
await mutate({
mutation: setUserRole,
variables: { userId: user.id, isAdmin: true },
variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN },
})
await expect(
mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: true } }),
mutate({
mutation: setUserRole,
variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('User is already admin')],
@ -1693,10 +1705,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

@ -706,18 +706,27 @@ 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,
@Arg('role', () => String)
role: string,
@Ctx()
context: Context,
): Promise<Date | null> {
): Promise<string | null> {
switch (role) {
case null:
case ROLE_NAMES.ROLE_NAME_ADMIN:
case ROLE_NAMES.ROLE_NAME_MODERATOR:
logger.debug('setUserRole=', role)
break
default:
throw new LogError('Not allowed to set user role=', role)
}
const user = await DbUser.findOne({
where: { id: userId },
relations: ['userRole'],
relations: ['userRoles'],
})
// user exists ?
if (!user) {
@ -728,37 +737,32 @@ export class UserResolver {
if (moderator.id === userId) {
throw new LogError('Administrator can not change his own role')
}
// change userRole
switch (user.userRole) {
case null:
if (isAdmin) {
user.userRole = UserRole.create()
user.userRole.createdAt = new Date()
user.userRole.role = ROLE_NAMES.ROLE_NAME_ADMIN
user.userRole.userId = user.id
} else {
throw new LogError('User is already an usual user')
if (isUserInRole(user, role)) {
throw new LogError('User already has role=', role)
}
// if user role should be deleted by role=null as parameter
if (role === null && user.userRoles) {
for (const usrRole of user.userRoles) {
await UserRole.delete(usrRole)
}
user.userRoles = undefined
} else {
if (!isUserInRole(user, role)) {
if (user.userRoles === undefined) {
user.userRoles = [] as UserRole[]
user.userRoles[0] = UserRole.create()
}
break
default:
if (!isAdmin) {
if (user.userRole) {
await UserRole.delete(user.userRole)
}
user.userRole = undefined
} else {
throw new LogError('User is already admin')
}
break
user.userRoles[0].createdAt = new Date()
user.userRoles[0].role = role
user.userRoles[0].userId = user.id
} else {
throw new LogError('User already is in role=', role)
}
}
await user.save()
await EVENT_ADMIN_USER_ROLE_SET(user, moderator)
const newUser = await DbUser.findOne({ id: userId })
return newUser
? newUser.userRole && newUser.userRole.role === ROLE_NAMES.ROLE_NAME_ADMIN
? newUser.userRole.createdAt
: null
: null
const newUser = await DbUser.findOne({ id: userId }, { relations: ['userRoles'] })
return newUser?.userRoles ? newUser.userRoles[0].role : null
}
@Authorized([RIGHTS.DELETE_USER])
@ -850,7 +854,7 @@ export async function findUserByEmail(email: string): Promise<DbUser> {
})
const dbUser = dbUserContact.user
dbUser.emailContact = dbUserContact
dbUser.userRole = await UserRole.findOne({ userId: dbUser.id })
dbUser.userRoles = await UserRole.find({ userId: dbUser.id })
return dbUser
}
@ -875,3 +879,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): boolean {
if (user?.userRoles) {
for (const usrRole of user.userRoles) {
if (usrRole.role === role) {
return true
}
}
}
return false
}

View File

@ -20,7 +20,7 @@ export const contributionLinkFactory = async (
mutation: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
})
console.log('contributionlinkfactory user=', user)
const variables = {
amount: contributionLink.amount,
memo: contributionLink.memo,

View File

@ -37,15 +37,20 @@ export const userFactory = async (
// get last changes of user from database
dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact', 'userRole'] })
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.userRole = UserRole.create()
dbUser.userRole.createdAt = new Date()
dbUser.userRole.role = ROLE_NAMES.ROLE_NAME_ADMIN
dbUser.userRole.userId = dbUser.id
await dbUser.userRole.save()
if (user.role) {
dbUser.userRoles = [] as UserRole[]
dbUser.userRoles[0] = UserRole.create()
dbUser.userRoles[0].createdAt = new Date()
if (user.role === ROLE_NAMES.ROLE_NAME_ADMIN) {
dbUser.userRoles[0].role = ROLE_NAMES.ROLE_NAME_ADMIN
} else if (user.role === ROLE_NAMES.ROLE_NAME_MODERATOR) {
dbUser.userRoles[0].role = ROLE_NAMES.ROLE_NAME_MODERATOR
}
dbUser.userRoles[0].userId = dbUser.id
await dbUser.userRoles[0].save()
}
await dbUser.save()
}

View File

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

View File

@ -1,3 +1,5 @@
import { ROLE_NAMES } from '@/auth/ROLES'
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: ROLE_NAMES.ROLE_NAME_ADMIN,
}

View File

@ -26,7 +26,7 @@ const communityDbUser: dbUser = {
createdAt: new Date(),
// emailChecked: false,
language: '',
userRole: undefined,
userRoles: undefined,
publisherId: 0,
// default password encryption type
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,