enum solution and graphql-schema-validation

This commit is contained in:
Claus-Peter Huebner 2023-07-14 20:08:06 +02:00
parent 03f60fa4d4
commit abc7128e5f
12 changed files with 49 additions and 139 deletions

View File

@ -6,17 +6,14 @@ import { MODERATOR_RIGHTS } from './MODERATOR_RIGHTS'
import { Role } from './Role' import { Role } from './Role'
import { USER_RIGHTS } from './USER_RIGHTS' import { USER_RIGHTS } from './USER_RIGHTS'
export const ROLE_UNAUTHORIZED = new Role(RoleNames.ROLE_NAME_UNAUTHORIZED, INALIENABLE_RIGHTS) export const ROLE_UNAUTHORIZED = new Role(RoleNames.MODERATOR, INALIENABLE_RIGHTS)
export const ROLE_USER = new Role(RoleNames.ROLE_NAME_USER, [ export const ROLE_USER = new Role(RoleNames.USER, [...INALIENABLE_RIGHTS, ...USER_RIGHTS])
...INALIENABLE_RIGHTS, export const ROLE_MODERATOR = new Role(RoleNames.MODERATOR, [
...USER_RIGHTS,
])
export const ROLE_MODERATOR = new Role(RoleNames.ROLE_NAME_MODERATOR, [
...INALIENABLE_RIGHTS, ...INALIENABLE_RIGHTS,
...USER_RIGHTS, ...USER_RIGHTS,
...MODERATOR_RIGHTS, ...MODERATOR_RIGHTS,
]) ])
export const ROLE_ADMIN = new Role(RoleNames.ROLE_NAME_ADMIN, [ export const ROLE_ADMIN = new Role(RoleNames.ADMIN, [
...INALIENABLE_RIGHTS, ...INALIENABLE_RIGHTS,
...USER_RIGHTS, ...USER_RIGHTS,
...MODERATOR_RIGHTS, ...MODERATOR_RIGHTS,

View File

@ -8,6 +8,6 @@ export class SetUserRoleArgs {
@Field(() => Int) @Field(() => Int)
userId: number userId: number
@Field(() => RoleNames, { nullable: true } ) @Field(() => RoleNames, { nullable: true })
role: RoleNames | null role: RoleNames | null | undefined
} }

View File

@ -1,13 +1,14 @@
import { User } from '@entity/User' import { User } from '@entity/User'
import { AuthChecker } from 'type-graphql' import { AuthChecker } from 'type-graphql'
import { RoleNames } from '@enum/RoleNames'
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS' import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
import { decode, encode } from '@/auth/JWT' import { decode, encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN, ROLE_MODERATOR } from '@/auth/ROLES' import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN, ROLE_MODERATOR } from '@/auth/ROLES'
import { Context } from '@/server/context' import { Context } from '@/server/context'
import { LogError } from '@/server/LogError' import { LogError } from '@/server/LogError'
import { RoleNames } from '@enum/RoleNames'
export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) => { export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) => {
context.role = ROLE_UNAUTHORIZED // unauthorized user context.role = ROLE_UNAUTHORIZED // unauthorized user
@ -41,10 +42,10 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
context.role = ROLE_USER context.role = ROLE_USER
if (user.userRoles?.length > 0) { if (user.userRoles?.length > 0) {
switch (user.userRoles[0].role) { switch (user.userRoles[0].role) {
case RoleNames.ROLE_NAME_ADMIN: case RoleNames.ADMIN:
context.role = ROLE_ADMIN context.role = ROLE_ADMIN
break break
case RoleNames.ROLE_NAME_MODERATOR: case RoleNames.MODERATOR:
context.role = ROLE_MODERATOR context.role = ROLE_MODERATOR
break break
default: default:

View File

@ -1,10 +1,10 @@
import { registerEnumType } from 'type-graphql' import { registerEnumType } from 'type-graphql'
export enum RoleNames { export enum RoleNames {
ROLE_NAME_ADMIN = 'admin', UNAUTHORIZED = 'UNAUTHORIZED',
ROLE_NAME_UNAUTHORIZED = 'unauthorized', USER = 'USER',
ROLE_NAME_USER = 'user', MODERATOR = 'MODERATOR',
ROLE_NAME_MODERATOR = 'moderator', ADMIN = 'ADMIN',
} }
registerEnumType(RoleNames, { registerEnumType(RoleNames, {

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

@ -10,19 +10,20 @@ import { TransactionLink } from '@entity/TransactionLink'
import { User } from '@entity/User' import { User } from '@entity/User'
import { UserContact } from '@entity/UserContact' import { UserContact } from '@entity/UserContact'
import { UserRole } from '@entity/UserRole' import { UserRole } from '@entity/UserRole'
import { UserInputError } from 'apollo-server-express'
import { ApolloServerTestClient } from 'apollo-server-testing' import { ApolloServerTestClient } from 'apollo-server-testing'
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { v4 as uuidv4, validate as validateUUID, version as versionUUID } from 'uuid' import { v4 as uuidv4, validate as validateUUID, version as versionUUID } from 'uuid'
import { OptInType } from '@enum/OptInType' import { OptInType } from '@enum/OptInType'
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType' import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
import { RoleNames } from '@enum/RoleNames'
import { UserContactType } from '@enum/UserContactType' import { UserContactType } from '@enum/UserContactType'
import { ContributionLink } from '@model/ContributionLink' import { ContributionLink } from '@model/ContributionLink'
import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers' import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup' import { logger, i18n as localization } from '@test/testSetup'
import { subscribe } from '@/apis/KlicktippController' import { subscribe } from '@/apis/KlicktippController'
import { RoleNames } from '@enum/RoleNames'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { import {
sendAccountActivationEmail, sendAccountActivationEmail,
@ -345,7 +346,7 @@ describe('UserResolver', () => {
peter.userRoles = [] as UserRole[] peter.userRoles = [] as UserRole[]
peter.userRoles[0] = UserRole.create() peter.userRoles[0] = UserRole.create()
peter.userRoles[0].createdAt = new Date() peter.userRoles[0].createdAt = new Date()
peter.userRoles[0].role = RoleNames.ROLE_NAME_ADMIN peter.userRoles[0].role = RoleNames.ADMIN
peter.userRoles[0].userId = peter.id peter.userRoles[0].userId = peter.id
await peter.userRoles[0].save() await peter.userRoles[0].save()
@ -1411,7 +1412,7 @@ describe('UserResolver', () => {
expect.objectContaining({ expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
role: RoleNames.ROLE_NAME_ADMIN, role: RoleNames.ADMIN,
}), }),
]), ]),
}, },
@ -1520,7 +1521,7 @@ describe('UserResolver', () => {
await expect( await expect(
mutate({ mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: 1, role: RoleNames.ROLE_NAME_ADMIN }, variables: { userId: 1, role: RoleNames.ADMIN },
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
@ -1549,7 +1550,7 @@ describe('UserResolver', () => {
await expect( await expect(
mutate({ mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id + 1, role: RoleNames.ROLE_NAME_ADMIN }, variables: { userId: user.id + 1, role: RoleNames.ADMIN },
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
@ -1566,7 +1567,7 @@ describe('UserResolver', () => {
// set Moderator-Role for Peter // set Moderator-Role for Peter
const userRole = await UserRole.findOneOrFail({ where: { userId: admin.id } }) const userRole = await UserRole.findOneOrFail({ where: { userId: admin.id } })
userRole.role = RoleNames.ROLE_NAME_MODERATOR userRole.role = RoleNames.MODERATOR
userRole.userId = admin.id userRole.userId = admin.id
await UserRole.save(userRole) await UserRole.save(userRole)
@ -1585,7 +1586,7 @@ describe('UserResolver', () => {
await expect( await expect(
mutate({ mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, variables: { userId: user.id, role: RoleNames.ADMIN },
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
@ -1613,12 +1614,12 @@ describe('UserResolver', () => {
it('returns user with new moderator-role', async () => { it('returns user with new moderator-role', async () => {
const result = await mutate({ const result = await mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, variables: { userId: user.id, role: RoleNames.MODERATOR },
}) })
expect(result).toEqual( expect(result).toEqual(
expect.objectContaining({ expect.objectContaining({
data: { data: {
setUserRole: RoleNames.ROLE_NAME_MODERATOR, setUserRole: RoleNames.MODERATOR,
}, },
}), }),
) )
@ -1635,7 +1636,7 @@ describe('UserResolver', () => {
await expect( await expect(
mutate({ mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: admin.id + 1, role: RoleNames.ROLE_NAME_ADMIN }, variables: { userId: admin.id + 1, role: RoleNames.ADMIN },
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
@ -1669,31 +1670,23 @@ describe('UserResolver', () => {
it('returns admin-rolename', async () => { it('returns admin-rolename', async () => {
const result = await mutate({ const result = await mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, variables: { userId: user.id, role: RoleNames.ADMIN },
}) })
expect(result).toEqual( expect(result).toEqual(
expect.objectContaining({ expect.objectContaining({
data: { data: {
setUserRole: RoleNames.ROLE_NAME_ADMIN, setUserRole: RoleNames.ADMIN,
}, },
}), }),
) )
}) })
it('stores the ADMIN_USER_ROLE_SET event in the database', async () => { it('stores the ADMIN_USER_ROLE_SET event in the database', async () => {
const userContact = await UserContact.findOneOrFail({
where: { email: 'bibi@bloxberg.de' },
relations: ['user'],
})
const adminContact = await UserContact.findOneOrFail({
where: { email: 'peter@lustig.de' },
relations: ['user'],
})
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventType.ADMIN_USER_ROLE_SET, type: EventType.ADMIN_USER_ROLE_SET,
affectedUserId: userContact.user.id, affectedUserId: user.id,
actingUserId: adminContact.user.id, actingUserId: admin.id,
}), }),
) )
}) })
@ -1703,12 +1696,12 @@ describe('UserResolver', () => {
it('returns date string', async () => { it('returns date string', async () => {
const result = await mutate({ const result = await mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, variables: { userId: user.id, role: RoleNames.MODERATOR },
}) })
expect(result).toEqual( expect(result).toEqual(
expect.objectContaining({ expect.objectContaining({
data: { data: {
setUserRole: RoleNames.ROLE_NAME_MODERATOR, setUserRole: RoleNames.MODERATOR,
}, },
}), }),
) )
@ -1716,19 +1709,11 @@ describe('UserResolver', () => {
}) })
it('stores the ADMIN_USER_ROLE_SET event in the database', async () => { it('stores the ADMIN_USER_ROLE_SET event in the database', async () => {
const userContact = await UserContact.findOneOrFail({
where: { email: 'bibi@bloxberg.de' },
relations: ['user'],
})
const adminContact = await UserContact.findOneOrFail({
where: { email: 'peter@lustig.de' },
relations: ['user'],
})
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventType.ADMIN_USER_ROLE_SET, type: EventType.ADMIN_USER_ROLE_SET,
affectedUserId: userContact.user.id, affectedUserId: user.id,
actingUserId: adminContact.user.id, actingUserId: admin.id,
}), }),
) )
}) })
@ -1791,17 +1776,14 @@ describe('UserResolver', () => {
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
errors: [new GraphQLError('Not allowed to set user role=')], errors: [
new UserInputError(
'Variable "$role" got invalid value "unknown rolename"; Value "unknown rolename" does not exist in "RoleNames" enum.',
),
],
}), }),
) )
}) })
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'Not allowed to set user role=',
'unknown rolename',
)
})
}) })
describe('user has already role to be set', () => { describe('user has already role to be set', () => {
@ -1810,12 +1792,12 @@ describe('UserResolver', () => {
jest.clearAllMocks() jest.clearAllMocks()
await mutate({ await mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, variables: { userId: user.id, role: RoleNames.ADMIN },
}) })
await expect( await expect(
mutate({ mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, variables: { userId: user.id, role: RoleNames.ADMIN },
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
@ -1825,10 +1807,7 @@ describe('UserResolver', () => {
}) })
it('logs the error thrown', () => { it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith( expect(logger.error).toBeCalledWith('User already has role=', RoleNames.ADMIN)
'User already has role=',
RoleNames.ROLE_NAME_ADMIN,
)
}) })
}) })
@ -1837,12 +1816,12 @@ describe('UserResolver', () => {
jest.clearAllMocks() jest.clearAllMocks()
await mutate({ await mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, variables: { userId: user.id, role: RoleNames.MODERATOR },
}) })
await expect( await expect(
mutate({ mutate({
mutation: setUserRole, mutation: setUserRole,
variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, variables: { userId: user.id, role: RoleNames.MODERATOR },
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
@ -1852,10 +1831,7 @@ describe('UserResolver', () => {
}) })
it('logs the error thrown', () => { it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith( expect(logger.error).toBeCalledWith('User already has role=', RoleNames.MODERATOR)
'User already has role=',
RoleNames.ROLE_NAME_MODERATOR,
)
}) })
}) })

View File

@ -15,6 +15,7 @@ import { v4 as uuidv4 } from 'uuid'
import { CreateUserArgs } from '@arg/CreateUserArgs' import { CreateUserArgs } from '@arg/CreateUserArgs'
import { Paginated } from '@arg/Paginated' import { Paginated } from '@arg/Paginated'
import { SearchUsersFilters } from '@arg/SearchUsersFilters' import { SearchUsersFilters } from '@arg/SearchUsersFilters'
import { SetUserRoleArgs } from '@arg/SetUserRoleArgs'
import { UnsecureLoginArgs } from '@arg/UnsecureLoginArgs' import { UnsecureLoginArgs } from '@arg/UnsecureLoginArgs'
import { UpdateUserInfosArgs } from '@arg/UpdateUserInfosArgs' import { UpdateUserInfosArgs } from '@arg/UpdateUserInfosArgs'
import { OptInType } from '@enum/OptInType' import { OptInType } from '@enum/OptInType'
@ -70,9 +71,6 @@ import { getKlicktippState } from './util/getKlicktippState'
import { setUserRole, deleteUserRole } from './util/modifyUserRole' import { setUserRole, deleteUserRole } from './util/modifyUserRole'
import { validateAlias } from './util/validateAlias' import { validateAlias } from './util/validateAlias'
import { RoleNames } from '@enum/RoleNames'
import { SetUserRoleArgs } from '@arg/SetUserRoleArgs'
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
const DEFAULT_LANGUAGE = 'de' const DEFAULT_LANGUAGE = 'de'
const isLanguage = (language: string): boolean => { const isLanguage = (language: string): boolean => {

View File

@ -3,7 +3,7 @@ import { UserRole } from '@entity/UserRole'
import { LogError } from '@/server/LogError' import { LogError } from '@/server/LogError'
export async function setUserRole(user: DbUser, role: string | null): Promise<void> { export async function setUserRole(user: DbUser, role: string | null | undefined): Promise<void> {
// if role should be set // if role should be set
if (role) { if (role) {
// in case user has still no associated userRole // in case user has still no associated userRole

View File

@ -20,7 +20,6 @@ export const contributionLinkFactory = async (
mutation: login, mutation: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
}) })
console.log('user=', user)
const variables = { const variables = {
amount: contributionLink.amount, amount: contributionLink.amount,
memo: contributionLink.memo, memo: contributionLink.memo,
@ -33,6 +32,5 @@ export const contributionLinkFactory = async (
} }
const result = await mutate({ mutation: createContributionLink, variables }) const result = await mutate({ mutation: createContributionLink, variables })
console.log('link...', result)
return result.data.createContributionLink return result.data.createContributionLink
} }

View File

@ -4,6 +4,7 @@ import { User } from '@entity/User'
import { ApolloServerTestClient } from 'apollo-server-testing' import { ApolloServerTestClient } from 'apollo-server-testing'
import { RoleNames } from '@enum/RoleNames' import { RoleNames } from '@enum/RoleNames'
import { setUserRole } from '@/graphql/resolver/util/modifyUserRole' import { setUserRole } from '@/graphql/resolver/util/modifyUserRole'
import { createUser, setPassword } from '@/seeds/graphql/mutations' import { createUser, setPassword } from '@/seeds/graphql/mutations'
import { UserInterface } from '@/seeds/users/UserInterface' import { UserInterface } from '@/seeds/users/UserInterface'
@ -37,10 +38,7 @@ export const userFactory = async (
if (user.createdAt || user.deletedAt || user.role) { if (user.createdAt || user.deletedAt || user.role) {
if (user.createdAt) dbUser.createdAt = user.createdAt if (user.createdAt) dbUser.createdAt = user.createdAt
if (user.deletedAt) dbUser.deletedAt = user.deletedAt if (user.deletedAt) dbUser.deletedAt = user.deletedAt
if ( if (user.role && (user.role === RoleNames.ADMIN || user.role === RoleNames.MODERATOR)) {
user.role &&
(user.role === RoleNames.ROLE_NAME_ADMIN || user.role === RoleNames.ROLE_NAME_MODERATOR)
) {
await setUserRole(dbUser, user.role) await setUserRole(dbUser, user.role)
} }
await dbUser.save() await dbUser.save()

View File

@ -95,8 +95,6 @@ export const searchUsers = gql`
emailConfirmationSend emailConfirmationSend
deletedAt deletedAt
roles roles
isAdmin
isModerator
} }
} }
} }

View File

@ -10,5 +10,5 @@ export const peterLustig: UserInterface = {
createdAt: new Date('2020-11-25T10:48:43'), createdAt: new Date('2020-11-25T10:48:43'),
emailChecked: true, emailChecked: true,
language: 'de', language: 'de',
role: RoleNames.ROLE_NAME_ADMIN, role: RoleNames.ADMIN,
} }