From abc7128e5f7e6f558b68fe645cd3e8aeb86d1025 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 14 Jul 2023 20:08:06 +0200 Subject: [PATCH] enum solution and graphql-schema-validation --- backend/src/auth/ROLES.ts | 11 +-- backend/src/graphql/arg/SetUserRoleArgs.ts | 4 +- backend/src/graphql/directive/isAuthorized.ts | 7 +- backend/src/graphql/enum/RoleNames.ts | 8 +- backend/src/graphql/model/UserContact.ts | 56 ------------- .../src/graphql/resolver/UserResolver.test.ts | 84 +++++++------------ backend/src/graphql/resolver/UserResolver.ts | 4 +- .../graphql/resolver/util/modifyUserRole.ts | 2 +- backend/src/seeds/factory/contributionLink.ts | 2 - backend/src/seeds/factory/user.ts | 6 +- backend/src/seeds/graphql/queries.ts | 2 - backend/src/seeds/users/peter-lustig.ts | 2 +- 12 files changed, 49 insertions(+), 139 deletions(-) delete mode 100644 backend/src/graphql/model/UserContact.ts diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index fc2bf8b84..7ee315cdd 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -6,17 +6,14 @@ import { MODERATOR_RIGHTS } from './MODERATOR_RIGHTS' import { Role } from './Role' import { USER_RIGHTS } from './USER_RIGHTS' -export const ROLE_UNAUTHORIZED = new Role(RoleNames.ROLE_NAME_UNAUTHORIZED, INALIENABLE_RIGHTS) -export const ROLE_USER = new Role(RoleNames.ROLE_NAME_USER, [ - ...INALIENABLE_RIGHTS, - ...USER_RIGHTS, -]) -export const ROLE_MODERATOR = new Role(RoleNames.ROLE_NAME_MODERATOR, [ +export const ROLE_UNAUTHORIZED = new Role(RoleNames.MODERATOR, INALIENABLE_RIGHTS) +export const ROLE_USER = new Role(RoleNames.USER, [...INALIENABLE_RIGHTS, ...USER_RIGHTS]) +export const ROLE_MODERATOR = new Role(RoleNames.MODERATOR, [ ...INALIENABLE_RIGHTS, ...USER_RIGHTS, ...MODERATOR_RIGHTS, ]) -export const ROLE_ADMIN = new Role(RoleNames.ROLE_NAME_ADMIN, [ +export const ROLE_ADMIN = new Role(RoleNames.ADMIN, [ ...INALIENABLE_RIGHTS, ...USER_RIGHTS, ...MODERATOR_RIGHTS, diff --git a/backend/src/graphql/arg/SetUserRoleArgs.ts b/backend/src/graphql/arg/SetUserRoleArgs.ts index 709b83c43..c076fc8cf 100644 --- a/backend/src/graphql/arg/SetUserRoleArgs.ts +++ b/backend/src/graphql/arg/SetUserRoleArgs.ts @@ -8,6 +8,6 @@ export class SetUserRoleArgs { @Field(() => Int) userId: number - @Field(() => RoleNames, { nullable: true } ) - role: RoleNames | null + @Field(() => RoleNames, { nullable: true }) + role: RoleNames | null | undefined } diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 586184f68..59309c91e 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -1,13 +1,14 @@ 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, ROLE_MODERATOR } from '@/auth/ROLES' import { Context } from '@/server/context' import { LogError } from '@/server/LogError' -import { RoleNames } from '@enum/RoleNames' export const isAuthorized: AuthChecker = async ({ context }, rights) => { context.role = ROLE_UNAUTHORIZED // unauthorized user @@ -41,10 +42,10 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => context.role = ROLE_USER if (user.userRoles?.length > 0) { switch (user.userRoles[0].role) { - case RoleNames.ROLE_NAME_ADMIN: + case RoleNames.ADMIN: context.role = ROLE_ADMIN break - case RoleNames.ROLE_NAME_MODERATOR: + case RoleNames.MODERATOR: context.role = ROLE_MODERATOR break default: diff --git a/backend/src/graphql/enum/RoleNames.ts b/backend/src/graphql/enum/RoleNames.ts index 67e913fd6..c4a9b25cc 100644 --- a/backend/src/graphql/enum/RoleNames.ts +++ b/backend/src/graphql/enum/RoleNames.ts @@ -1,10 +1,10 @@ import { registerEnumType } from 'type-graphql' export enum RoleNames { - ROLE_NAME_ADMIN = 'admin', - ROLE_NAME_UNAUTHORIZED = 'unauthorized', - ROLE_NAME_USER = 'user', - ROLE_NAME_MODERATOR = 'moderator', + UNAUTHORIZED = 'UNAUTHORIZED', + USER = 'USER', + MODERATOR = 'MODERATOR', + ADMIN = 'ADMIN', } registerEnumType(RoleNames, { diff --git a/backend/src/graphql/model/UserContact.ts b/backend/src/graphql/model/UserContact.ts deleted file mode 100644 index 4a6ed47b6..000000000 --- a/backend/src/graphql/model/UserContact.ts +++ /dev/null @@ -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 -} diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 5b4160f98..171af7cf5 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -10,19 +10,20 @@ 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' import { logger, i18n as localization } from '@test/testSetup' import { subscribe } from '@/apis/KlicktippController' -import { RoleNames } from '@enum/RoleNames' import { CONFIG } from '@/config' import { sendAccountActivationEmail, @@ -345,7 +346,7 @@ describe('UserResolver', () => { peter.userRoles = [] as UserRole[] peter.userRoles[0] = UserRole.create() 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 await peter.userRoles[0].save() @@ -1411,7 +1412,7 @@ describe('UserResolver', () => { expect.objectContaining({ firstName: 'Peter', lastName: 'Lustig', - role: RoleNames.ROLE_NAME_ADMIN, + role: RoleNames.ADMIN, }), ]), }, @@ -1520,7 +1521,7 @@ describe('UserResolver', () => { await expect( mutate({ mutation: setUserRole, - variables: { userId: 1, role: RoleNames.ROLE_NAME_ADMIN }, + variables: { userId: 1, role: RoleNames.ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1549,7 +1550,7 @@ describe('UserResolver', () => { await expect( mutate({ mutation: setUserRole, - variables: { userId: user.id + 1, role: RoleNames.ROLE_NAME_ADMIN }, + variables: { userId: user.id + 1, role: RoleNames.ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1566,7 +1567,7 @@ describe('UserResolver', () => { // set Moderator-Role for Peter const userRole = await UserRole.findOneOrFail({ where: { userId: admin.id } }) - userRole.role = RoleNames.ROLE_NAME_MODERATOR + userRole.role = RoleNames.MODERATOR userRole.userId = admin.id await UserRole.save(userRole) @@ -1585,7 +1586,7 @@ describe('UserResolver', () => { await expect( mutate({ mutation: setUserRole, - variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, + variables: { userId: user.id, role: RoleNames.ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1613,12 +1614,12 @@ describe('UserResolver', () => { it('returns user with new moderator-role', async () => { const result = await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, + variables: { userId: user.id, role: RoleNames.MODERATOR }, }) expect(result).toEqual( expect.objectContaining({ data: { - setUserRole: RoleNames.ROLE_NAME_MODERATOR, + setUserRole: RoleNames.MODERATOR, }, }), ) @@ -1635,7 +1636,7 @@ describe('UserResolver', () => { await expect( mutate({ mutation: setUserRole, - variables: { userId: admin.id + 1, role: RoleNames.ROLE_NAME_ADMIN }, + variables: { userId: admin.id + 1, role: RoleNames.ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1669,31 +1670,23 @@ describe('UserResolver', () => { it('returns admin-rolename', async () => { const result = await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, + variables: { userId: user.id, role: RoleNames.ADMIN }, }) expect(result).toEqual( expect.objectContaining({ data: { - setUserRole: RoleNames.ROLE_NAME_ADMIN, + setUserRole: RoleNames.ADMIN, }, }), ) }) 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( expect.objectContaining({ type: EventType.ADMIN_USER_ROLE_SET, - affectedUserId: userContact.user.id, - actingUserId: adminContact.user.id, + affectedUserId: user.id, + actingUserId: admin.id, }), ) }) @@ -1703,12 +1696,12 @@ describe('UserResolver', () => { it('returns date string', async () => { const result = await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, + variables: { userId: user.id, role: RoleNames.MODERATOR }, }) expect(result).toEqual( expect.objectContaining({ 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 () => { - 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( expect.objectContaining({ type: EventType.ADMIN_USER_ROLE_SET, - affectedUserId: userContact.user.id, - actingUserId: adminContact.user.id, + affectedUserId: user.id, + actingUserId: admin.id, }), ) }) @@ -1791,17 +1776,14 @@ describe('UserResolver', () => { }), ).resolves.toEqual( 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', () => { @@ -1810,12 +1792,12 @@ describe('UserResolver', () => { jest.clearAllMocks() await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, + variables: { userId: user.id, role: RoleNames.ADMIN }, }) await expect( mutate({ mutation: setUserRole, - variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, + variables: { userId: user.id, role: RoleNames.ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1825,10 +1807,7 @@ describe('UserResolver', () => { }) it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith( - 'User already has role=', - RoleNames.ROLE_NAME_ADMIN, - ) + expect(logger.error).toBeCalledWith('User already has role=', RoleNames.ADMIN) }) }) @@ -1837,12 +1816,12 @@ describe('UserResolver', () => { jest.clearAllMocks() await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, + variables: { userId: user.id, role: RoleNames.MODERATOR }, }) await expect( mutate({ mutation: setUserRole, - variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, + variables: { userId: user.id, role: RoleNames.MODERATOR }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1852,10 +1831,7 @@ describe('UserResolver', () => { }) it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith( - 'User already has role=', - RoleNames.ROLE_NAME_MODERATOR, - ) + expect(logger.error).toBeCalledWith('User already has role=', RoleNames.MODERATOR) }) }) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index a0b48baac..3c707bf0f 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -15,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' @@ -70,9 +71,6 @@ import { getKlicktippState } from './util/getKlicktippState' import { setUserRole, deleteUserRole } from './util/modifyUserRole' import { validateAlias } from './util/validateAlias' -import { RoleNames } from '@enum/RoleNames' -import { SetUserRoleArgs } from '@arg/SetUserRoleArgs' - const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const DEFAULT_LANGUAGE = 'de' const isLanguage = (language: string): boolean => { diff --git a/backend/src/graphql/resolver/util/modifyUserRole.ts b/backend/src/graphql/resolver/util/modifyUserRole.ts index bcd140c07..5acc71f93 100644 --- a/backend/src/graphql/resolver/util/modifyUserRole.ts +++ b/backend/src/graphql/resolver/util/modifyUserRole.ts @@ -3,7 +3,7 @@ import { UserRole } from '@entity/UserRole' import { LogError } from '@/server/LogError' -export async function setUserRole(user: DbUser, role: string | null): Promise { +export async function setUserRole(user: DbUser, role: string | null | undefined): Promise { // if role should be set if (role) { // in case user has still no associated userRole diff --git a/backend/src/seeds/factory/contributionLink.ts b/backend/src/seeds/factory/contributionLink.ts index 785dbe5b8..d03d222c6 100644 --- a/backend/src/seeds/factory/contributionLink.ts +++ b/backend/src/seeds/factory/contributionLink.ts @@ -20,7 +20,6 @@ export const contributionLinkFactory = async ( mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - console.log('user=', user) const variables = { amount: contributionLink.amount, memo: contributionLink.memo, @@ -33,6 +32,5 @@ export const contributionLinkFactory = async ( } const result = await mutate({ mutation: createContributionLink, variables }) - console.log('link...', result) return result.data.createContributionLink } diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index c62e034e0..3fa5591a2 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -4,6 +4,7 @@ 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' @@ -37,10 +38,7 @@ export const userFactory = async ( if (user.createdAt || user.deletedAt || user.role) { if (user.createdAt) dbUser.createdAt = user.createdAt if (user.deletedAt) dbUser.deletedAt = user.deletedAt - if ( - user.role && - (user.role === RoleNames.ROLE_NAME_ADMIN || user.role === RoleNames.ROLE_NAME_MODERATOR) - ) { + if (user.role && (user.role === RoleNames.ADMIN || user.role === RoleNames.MODERATOR)) { await setUserRole(dbUser, user.role) } await dbUser.save() diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index fcc9f3a5f..f016102a2 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -95,8 +95,6 @@ export const searchUsers = gql` emailConfirmationSend deletedAt roles - isAdmin - isModerator } } } diff --git a/backend/src/seeds/users/peter-lustig.ts b/backend/src/seeds/users/peter-lustig.ts index 973bc01b1..ff1fe065e 100644 --- a/backend/src/seeds/users/peter-lustig.ts +++ b/backend/src/seeds/users/peter-lustig.ts @@ -10,5 +10,5 @@ export const peterLustig: UserInterface = { createdAt: new Date('2020-11-25T10:48:43'), emailChecked: true, language: 'de', - role: RoleNames.ROLE_NAME_ADMIN, + role: RoleNames.ADMIN, }