diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index 2ecbb6444..fc2bf8b84 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -1,26 +1,22 @@ +import { RoleNames } from '@/graphql/enum/RoleNames' + 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 enum ROLE_NAMES { - ROLE_NAME_UNAUTHORIZED = 'unauthorized', - ROLE_NAME_USER = 'user', - ROLE_NAME_MODERATOR = 'moderator', - ROLE_NAME_ADMIN = 'admin', -} -export const ROLE_UNAUTHORIZED = new Role(ROLE_NAMES.ROLE_NAME_UNAUTHORIZED, INALIENABLE_RIGHTS) -export const ROLE_USER = new Role(ROLE_NAMES.ROLE_NAME_USER, [ +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(ROLE_NAMES.ROLE_NAME_MODERATOR, [ +export const ROLE_MODERATOR = new Role(RoleNames.ROLE_NAME_MODERATOR, [ ...INALIENABLE_RIGHTS, ...USER_RIGHTS, ...MODERATOR_RIGHTS, ]) -export const ROLE_ADMIN = new Role(ROLE_NAMES.ROLE_NAME_ADMIN, [ +export const ROLE_ADMIN = new Role(RoleNames.ROLE_NAME_ADMIN, [ ...INALIENABLE_RIGHTS, ...USER_RIGHTS, ...MODERATOR_RIGHTS, diff --git a/backend/src/graphql/arg/SetUserRoleArgs.ts b/backend/src/graphql/arg/SetUserRoleArgs.ts new file mode 100644 index 000000000..709b83c43 --- /dev/null +++ b/backend/src/graphql/arg/SetUserRoleArgs.ts @@ -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 +} diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 733201776..0f0261aa9 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -4,9 +4,10 @@ import { AuthChecker } from 'type-graphql' 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_NAMES, ROLE_MODERATOR } 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' +import { RoleNames } from '@enum/RoleNames' export const isAuthorized: AuthChecker = async ({ context }, rights) => { context.role = ROLE_UNAUTHORIZED // unauthorized user @@ -40,10 +41,10 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => context.role = ROLE_USER if (user.userRoles && user.userRoles.length > 0) { switch (user.userRoles[0].role) { - case ROLE_NAMES.ROLE_NAME_ADMIN: + case RoleNames.ROLE_NAME_ADMIN: context.role = ROLE_ADMIN break - case ROLE_NAMES.ROLE_NAME_MODERATOR: + case RoleNames.ROLE_NAME_MODERATOR: context.role = ROLE_MODERATOR break default: diff --git a/backend/src/graphql/enum/RoleNames.ts b/backend/src/graphql/enum/RoleNames.ts new file mode 100644 index 000000000..67e913fd6 --- /dev/null +++ b/backend/src/graphql/enum/RoleNames.ts @@ -0,0 +1,13 @@ +import { registerEnumType } from 'type-graphql' + +export enum RoleNames { + ROLE_NAME_ADMIN = 'admin', + ROLE_NAME_UNAUTHORIZED = 'unauthorized', + ROLE_NAME_USER = 'user', + ROLE_NAME_MODERATOR = 'moderator', +} + +registerEnumType(RoleNames, { + name: 'RoleNames', // this one is mandatory + description: 'Possible role names', // this one is optional +}) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index c69585a2a..9e4c0fdf9 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -1,8 +1,6 @@ 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() @@ -72,14 +70,4 @@ export class User { @Field(() => [String]) roles: string[] - - @Field(() => Boolean) - isAdmin(): boolean { - return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) - } - - @Field(() => Boolean) - isModerator(): boolean { - return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR) - } } diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index 989ce1f8c..3063d3763 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -2,8 +2,6 @@ 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) { @@ -48,16 +46,6 @@ export class UserAdmin { @Field(() => [String]) roles: string[] - - @Field(() => Boolean) - isAdmin(): boolean { - return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) - } - - @Field(() => Boolean) - isModerator(): boolean { - return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR) - } } @ObjectType() diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 6a9ca7590..5b4160f98 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -22,7 +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 { RoleNames } from '@enum/RoleNames' import { CONFIG } from '@/config' import { sendAccountActivationEmail, @@ -56,7 +56,6 @@ import { searchUsers, user as userQuery, checkUsername, - userContact, } from '@/seeds/graphql/queries' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { bobBaumeister } from '@/seeds/users/bob-baumeister' @@ -165,7 +164,7 @@ describe('UserResolver', () => { createdAt: expect.any(Date), // emailChecked: false, language: 'de', - userRoles: [], // expect.any(Array), + userRoles: [], deletedAt: null, publisherId: 1234, referrerId: null, @@ -339,22 +338,17 @@ describe('UserResolver', () => { }) // make Peter Lustig Admin - let peter = await User.findOneOrFail({ + 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 = ROLE_NAMES.ROLE_NAME_ADMIN + peter.userRoles[0].role = RoleNames.ROLE_NAME_ADMIN peter.userRoles[0].userId = peter.id await peter.userRoles[0].save() - peter = await User.findOneOrFail({ - where: { id: user[0].id }, - relations: ['userRoles'], - }) - // date statement const actualDate = new Date() const futureDate = new Date() // Create a future day from the executed day @@ -368,7 +362,6 @@ describe('UserResolver', () => { validFrom: actualDate, validTo: futureDate, }) - resetToken() result = await mutate({ mutation: createUser, @@ -707,8 +700,6 @@ describe('UserResolver', () => { lastName: 'Bloxberg', publisherId: 1234, roles: [], - isAdmin: false, - isModerator: false, }, }, }), @@ -980,8 +971,6 @@ describe('UserResolver', () => { hasElopage: false, publisherId: 1234, roles: [], - isAdmin: false, - isModerator: false, }, }, }), @@ -997,34 +986,6 @@ describe('UserResolver', () => { }), ) }) - - it('returns usercontact object', async () => { - await expect( - query({ - query: userContact, - variables: { - userId: user[0].id, - }, - }), - ).resolves.toMatchObject({ - // expect.objectContaining({ - data: { - userContact: { - id: expect.any(Number), - type: UserContactType.USER_CONTACT_EMAIL, - userId: user[0].id, - email: 'bibi@bloxberg.de', - emailOptInTypeId: expect.any(Number), - emailResendCount: expect.any(Number), - emailChecked: expect.any(Boolean), - phone: null, - createdAt: expect.any(String), - updatedAt: expect.any(String), - deletedAt: null, - }, - }, - }) - }) }) }) }) @@ -1450,7 +1411,7 @@ describe('UserResolver', () => { expect.objectContaining({ firstName: 'Peter', lastName: 'Lustig', - role: ROLE_NAMES.ROLE_NAME_ADMIN, + role: RoleNames.ROLE_NAME_ADMIN, }), ]), }, @@ -1539,8 +1500,6 @@ describe('UserResolver', () => { lastName: 'Bloxberg', publisherId: 1234, roles: [], - isAdmin: false, - isModerator: false, }, }, }), @@ -1561,7 +1520,7 @@ describe('UserResolver', () => { await expect( mutate({ mutation: setUserRole, - variables: { userId: 1, role: ROLE_NAMES.ROLE_NAME_ADMIN }, + variables: { userId: 1, role: RoleNames.ROLE_NAME_ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1590,7 +1549,7 @@ describe('UserResolver', () => { await expect( mutate({ mutation: setUserRole, - variables: { userId: user.id + 1, role: ROLE_NAMES.ROLE_NAME_ADMIN }, + variables: { userId: user.id + 1, role: RoleNames.ROLE_NAME_ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1607,7 +1566,7 @@ describe('UserResolver', () => { // set Moderator-Role for Peter const userRole = await UserRole.findOneOrFail({ where: { userId: admin.id } }) - userRole.role = ROLE_NAMES.ROLE_NAME_MODERATOR + userRole.role = RoleNames.ROLE_NAME_MODERATOR userRole.userId = admin.id await UserRole.save(userRole) @@ -1626,7 +1585,7 @@ describe('UserResolver', () => { await expect( mutate({ mutation: setUserRole, - variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN }, + variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1654,12 +1613,12 @@ describe('UserResolver', () => { it('returns user with new moderator-role', async () => { const result = await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, }) expect(result).toEqual( expect.objectContaining({ data: { - setUserRole: ROLE_NAMES.ROLE_NAME_MODERATOR, + setUserRole: RoleNames.ROLE_NAME_MODERATOR, }, }), ) @@ -1676,7 +1635,7 @@ describe('UserResolver', () => { await expect( mutate({ mutation: setUserRole, - variables: { userId: admin.id + 1, role: ROLE_NAMES.ROLE_NAME_ADMIN }, + variables: { userId: admin.id + 1, role: RoleNames.ROLE_NAME_ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1710,12 +1669,12 @@ describe('UserResolver', () => { it('returns admin-rolename', async () => { const result = await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN }, + variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, }) expect(result).toEqual( expect.objectContaining({ data: { - setUserRole: ROLE_NAMES.ROLE_NAME_ADMIN, + setUserRole: RoleNames.ROLE_NAME_ADMIN, }, }), ) @@ -1744,12 +1703,12 @@ describe('UserResolver', () => { it('returns date string', async () => { const result = await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, }) expect(result).toEqual( expect.objectContaining({ data: { - setUserRole: ROLE_NAMES.ROLE_NAME_MODERATOR, + setUserRole: RoleNames.ROLE_NAME_MODERATOR, }, }), ) @@ -1851,12 +1810,12 @@ describe('UserResolver', () => { jest.clearAllMocks() await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN }, + variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, }) await expect( mutate({ mutation: setUserRole, - variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN }, + variables: { userId: user.id, role: RoleNames.ROLE_NAME_ADMIN }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1868,7 +1827,7 @@ describe('UserResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( 'User already has role=', - ROLE_NAMES.ROLE_NAME_ADMIN, + RoleNames.ROLE_NAME_ADMIN, ) }) }) @@ -1878,12 +1837,12 @@ describe('UserResolver', () => { jest.clearAllMocks() await mutate({ mutation: setUserRole, - variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, }) await expect( mutate({ mutation: setUserRole, - variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + variables: { userId: user.id, role: RoleNames.ROLE_NAME_MODERATOR }, }), ).resolves.toEqual( expect.objectContaining({ @@ -1895,7 +1854,7 @@ describe('UserResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( 'User already has role=', - ROLE_NAMES.ROLE_NAME_MODERATOR, + RoleNames.ROLE_NAME_MODERATOR, ) }) }) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 7f5bba32d..a0b48baac 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -24,12 +24,10 @@ import { UserContactType } from '@enum/UserContactType' import { SearchAdminUsersResult } from '@model/AdminUser' import { User } from '@model/User' import { UserAdmin, SearchUsersResult } from '@model/UserAdmin' -import { UserContact } from '@model/UserContact' import { subscribe } from '@/apis/KlicktippController' import { encode } from '@/auth/JWT' import { RIGHTS } from '@/auth/RIGHTS' -import { ROLE_NAMES } from '@/auth/ROLES' import { CONFIG } from '@/config' import { sendAccountActivationEmail, @@ -72,6 +70,9 @@ 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 => { @@ -707,22 +708,10 @@ export class UserResolver { @Authorized([RIGHTS.SET_USER_ROLE]) @Mutation(() => String, { nullable: true }) async setUserRole( - @Arg('userId', () => Int) - userId: number, - @Arg('role', () => String, { nullable: true }) - role: string | null | undefined, + @Args() { userId, role }: SetUserRoleArgs, @Ctx() context: Context, ): Promise { - 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: ['userRoles'], @@ -827,18 +816,6 @@ export class UserResolver { async user(@Arg('identifier') identifier: string): Promise { return new User(await findUserByIdentifier(identifier)) } - - @Authorized([RIGHTS.USER]) - @Query(() => UserContact) - async userContact(@Arg('userId', () => Int) userId: number): Promise { - return new UserContact( - await DbUserContact.findOneOrFail({ - where: { userId }, - withDeleted: true, - relations: ['user'], - }), - ) - } } export async function findUserByEmail(email: string): Promise { diff --git a/backend/src/seeds/factory/contributionLink.ts b/backend/src/seeds/factory/contributionLink.ts index d03d222c6..785dbe5b8 100644 --- a/backend/src/seeds/factory/contributionLink.ts +++ b/backend/src/seeds/factory/contributionLink.ts @@ -20,6 +20,7 @@ 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, @@ -32,5 +33,6 @@ 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 b87e320df..c62e034e0 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -3,7 +3,7 @@ import { User } from '@entity/User' import { ApolloServerTestClient } from 'apollo-server-testing' -import { ROLE_NAMES } from '@/auth/ROLES' +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' @@ -39,7 +39,7 @@ export const userFactory = async ( if (user.deletedAt) dbUser.deletedAt = user.deletedAt if ( user.role && - (user.role === ROLE_NAMES.ROLE_NAME_ADMIN || user.role === ROLE_NAMES.ROLE_NAME_MODERATOR) + (user.role === RoleNames.ROLE_NAME_ADMIN || user.role === RoleNames.ROLE_NAME_MODERATOR) ) { await setUserRole(dbUser, user.role) } diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 340434174..87231531f 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -119,7 +119,7 @@ export const confirmContribution = gql` ` export const setUserRole = gql` - mutation ($userId: Int!, $role: String) { + mutation ($userId: Int!, $role: RoleNames) { setUserRole(userId: $userId, role: $role) } ` @@ -322,8 +322,6 @@ export const login = gql` hasElopage publisherId roles - isAdmin - isModerator } } ` diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 16fc3aba8..fcc9f3a5f 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -12,8 +12,6 @@ export const verifyLogin = gql` hasElopage publisherId roles - isAdmin - isModerator } } ` @@ -387,20 +385,3 @@ export const user = gql` } } ` -export const userContact = gql` - query ($userId: Int!) { - userContact(userId: $userId) { - id - type - userId - email - emailOptInTypeId - emailResendCount - emailChecked - phone - createdAt - updatedAt - deletedAt - } - } -` diff --git a/backend/src/seeds/users/peter-lustig.ts b/backend/src/seeds/users/peter-lustig.ts index 1bf9711b6..973bc01b1 100644 --- a/backend/src/seeds/users/peter-lustig.ts +++ b/backend/src/seeds/users/peter-lustig.ts @@ -1,4 +1,4 @@ -import { ROLE_NAMES } from '@/auth/ROLES' +import { RoleNames } from '@enum/RoleNames' import { UserInterface } from './UserInterface' @@ -10,5 +10,5 @@ export const peterLustig: UserInterface = { createdAt: new Date('2020-11-25T10:48:43'), emailChecked: true, language: 'de', - role: ROLE_NAMES.ROLE_NAME_ADMIN, + role: RoleNames.ROLE_NAME_ADMIN, }