From 523d24682c018d2afa9533fbb4101ce1f9fdaf1f Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 14 Jun 2023 02:00:24 +0200 Subject: [PATCH 01/72] new table user_roles --- .../migrations/0067-add_user_roles_table.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 database/migrations/0067-add_user_roles_table.ts diff --git a/database/migrations/0067-add_user_roles_table.ts b/database/migrations/0067-add_user_roles_table.ts new file mode 100644 index 000000000..8a6692d07 --- /dev/null +++ b/database/migrations/0067-add_user_roles_table.ts @@ -0,0 +1,27 @@ +/* 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>) { + 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; + `) + + // merge values from login_email_opt_in table with users.email in new user_contacts 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;`) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn(`DROP TABLE user_roles;`) +} From 30e94be1ec4b066305813f7321102001be5e5e88 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 14 Jun 2023 02:01:10 +0200 Subject: [PATCH 02/72] concerned entities --- .../entity/0067-add_user_roles_table/User.ts | 120 ++++++++++++++++++ .../0067-add_user_roles_table/UserRole.ts | 19 +++ database/entity/User.ts | 2 +- database/entity/UserRole.ts | 1 + 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 database/entity/0067-add_user_roles_table/User.ts create mode 100644 database/entity/0067-add_user_roles_table/UserRole.ts create mode 100644 database/entity/UserRole.ts diff --git a/database/entity/0067-add_user_roles_table/User.ts b/database/entity/0067-add_user_roles_table/User.ts new file mode 100644 index 000000000..57e6e75db --- /dev/null +++ b/database/entity/0067-add_user_roles_table/User.ts @@ -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', 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 + + @OneToOne(() => UserRole, (role: UserRole) => role.userId) + @JoinColumn({ name: 'user_id' }) + userRole: 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[] +} diff --git a/database/entity/0067-add_user_roles_table/UserRole.ts b/database/entity/0067-add_user_roles_table/UserRole.ts new file mode 100644 index 000000000..eb68b6e85 --- /dev/null +++ b/database/entity/0067-add_user_roles_table/UserRole.ts @@ -0,0 +1,19 @@ +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm' + +@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', nullable: false }) + createdAt: Date + + @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' }) + updatedAt: Date | null +} diff --git a/database/entity/User.ts b/database/entity/User.ts index fbb65204c..71c694303 100644 --- a/database/entity/User.ts +++ b/database/entity/User.ts @@ -1 +1 @@ -export { User } from './0059-add_hide_amount_to_users/User' +export { User } from './0067-add_user_roles_table/User' diff --git a/database/entity/UserRole.ts b/database/entity/UserRole.ts new file mode 100644 index 000000000..a6dda6c54 --- /dev/null +++ b/database/entity/UserRole.ts @@ -0,0 +1 @@ +export { UserContact } from './0067-add_user_roles_table/UserRole' From d2ab850cf645d59e58feb1fccaf2a01cbe433647 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 14 Jun 2023 02:01:32 +0200 Subject: [PATCH 03/72] all RIGHTS --- backend/src/auth/RIGHTS.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index b3627ff7a..53c963e57 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -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', @@ -53,4 +52,8 @@ export enum RIGHTS { ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE', DENY_CONTRIBUTION = 'DENY_CONTRIBUTION', ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS', + // Admin + SET_USER_ROLE = 'SET_USER_ROLE', + DELETE_USER = 'DELETE_USER', + UNDELETE_USER = 'UNDELETE_USER', } From f9b4970b21f30a11f87077ca6828383f2ab00d0d Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 14 Jun 2023 02:01:59 +0200 Subject: [PATCH 04/72] all ROLES --- backend/src/auth/ROLES.ts | 46 ++++++++++++--------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index bc868a199..69a7ea52b 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -1,40 +1,22 @@ +import { ADMIN_RIGHTS } from './ADMIN_RIGHTS' import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS' -import { RIGHTS } from './RIGHTS' +import { MODERATOR_RIGHTS } from './MODERATOR_RIGHTS' import { Role } from './Role' +import { USER_RIGHTS } from './USER_RIGHTS' export const ROLE_UNAUTHORIZED = new Role('unauthorized', INALIENABLE_RIGHTS) -export const ROLE_USER = new Role('user', [ +export const ROLE_USER = new Role('user', [...INALIENABLE_RIGHTS, ...USER_RIGHTS]) +export const ROLE_MODERATOR = new Role('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('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] From 078b314def6b4bdabe65478734af7be5d4eeaf3f Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 14 Jun 2023 02:02:20 +0200 Subject: [PATCH 05/72] specific RIGHTS --- backend/src/auth/ADMIN_RIGHTS.ts | 3 +++ backend/src/auth/MODERATOR_RIGHTS.ts | 18 ++++++++++++++++ backend/src/auth/USER_RIGHTS.ts | 32 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 backend/src/auth/ADMIN_RIGHTS.ts create mode 100644 backend/src/auth/MODERATOR_RIGHTS.ts create mode 100644 backend/src/auth/USER_RIGHTS.ts diff --git a/backend/src/auth/ADMIN_RIGHTS.ts b/backend/src/auth/ADMIN_RIGHTS.ts new file mode 100644 index 000000000..b81ff51d6 --- /dev/null +++ b/backend/src/auth/ADMIN_RIGHTS.ts @@ -0,0 +1,3 @@ +import { RIGHTS } from './RIGHTS' + +export const ADMIN_RIGHTS = [RIGHTS.SET_USER_ROLE, RIGHTS.DELETE_USER, RIGHTS.UNDELETE_USER] diff --git a/backend/src/auth/MODERATOR_RIGHTS.ts b/backend/src/auth/MODERATOR_RIGHTS.ts new file mode 100644 index 000000000..7c892b903 --- /dev/null +++ b/backend/src/auth/MODERATOR_RIGHTS.ts @@ -0,0 +1,18 @@ +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.DENY_CONTRIBUTION, + RIGHTS.ADMIN_OPEN_CREATIONS, +] diff --git a/backend/src/auth/USER_RIGHTS.ts b/backend/src/auth/USER_RIGHTS.ts new file mode 100644 index 000000000..9bf9fee93 --- /dev/null +++ b/backend/src/auth/USER_RIGHTS.ts @@ -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, +] From 092fd7ea676e45392c2844e7480ae247330bde71 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 14 Jun 2023 02:09:29 +0200 Subject: [PATCH 06/72] syntax error --- database/migrations/0067-add_user_roles_table.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/database/migrations/0067-add_user_roles_table.ts b/database/migrations/0067-add_user_roles_table.ts index 8a6692d07..215ee2705 100644 --- a/database/migrations/0067-add_user_roles_table.ts +++ b/database/migrations/0067-add_user_roles_table.ts @@ -9,9 +9,8 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis 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; - `) + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`) // merge values from login_email_opt_in table with users.email in new user_contacts table await queryFn(` From 63dd8587b4069a450a737c8e5954c738f4bf929c Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 21 Jun 2023 01:59:22 +0200 Subject: [PATCH 07/72] define ROLE_NAMES enum --- backend/src/auth/ROLES.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index 69a7ea52b..2ecbb6444 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -4,14 +4,23 @@ import { MODERATOR_RIGHTS } from './MODERATOR_RIGHTS' import { Role } from './Role' import { USER_RIGHTS } from './USER_RIGHTS' -export const ROLE_UNAUTHORIZED = new Role('unauthorized', INALIENABLE_RIGHTS) -export const ROLE_USER = new Role('user', [...INALIENABLE_RIGHTS, ...USER_RIGHTS]) -export const ROLE_MODERATOR = new Role('moderator', [ +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, [ + ...INALIENABLE_RIGHTS, + ...USER_RIGHTS, +]) +export const ROLE_MODERATOR = new Role(ROLE_NAMES.ROLE_NAME_MODERATOR, [ ...INALIENABLE_RIGHTS, ...USER_RIGHTS, ...MODERATOR_RIGHTS, ]) -export const ROLE_ADMIN = new Role('admin', [ +export const ROLE_ADMIN = new Role(ROLE_NAMES.ROLE_NAME_ADMIN, [ ...INALIENABLE_RIGHTS, ...USER_RIGHTS, ...MODERATOR_RIGHTS, From 5226245b86da1368bda778785a9a9a33a1e37577 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 21 Jun 2023 02:02:00 +0200 Subject: [PATCH 08/72] new entity UserRole --- database/entity/UserRole.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/entity/UserRole.ts b/database/entity/UserRole.ts index a6dda6c54..3d2b9b6bf 100644 --- a/database/entity/UserRole.ts +++ b/database/entity/UserRole.ts @@ -1 +1 @@ -export { UserContact } from './0067-add_user_roles_table/UserRole' +export { UserRole } from './0067-add_user_roles_table/UserRole' From d1d9f6c0509253412f97337086a3b7d09c3a0dac Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 21 Jun 2023 02:05:01 +0200 Subject: [PATCH 09/72] add_user_roles migration and entity --- .../entity/0067-add_user_roles_table/User.ts | 2 +- .../migrations/0067-add_user_roles_table.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/database/entity/0067-add_user_roles_table/User.ts b/database/entity/0067-add_user_roles_table/User.ts index 57e6e75db..f53b397a1 100644 --- a/database/entity/0067-add_user_roles_table/User.ts +++ b/database/entity/0067-add_user_roles_table/User.ts @@ -89,7 +89,7 @@ export class User extends BaseEntity { @OneToOne(() => UserRole, (role: UserRole) => role.userId) @JoinColumn({ name: 'user_id' }) - userRole: UserRole + userRole?: UserRole @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) referrerId?: number | null diff --git a/database/migrations/0067-add_user_roles_table.ts b/database/migrations/0067-add_user_roles_table.ts index 215ee2705..8b2970d39 100644 --- a/database/migrations/0067-add_user_roles_table.ts +++ b/database/migrations/0067-add_user_roles_table.ts @@ -12,15 +12,32 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`) - // merge values from login_email_opt_in table with users.email in new user_contacts table + // 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>) { + // 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;`) } From 44687e6fc36878ae144dbeac640251c2e2d0d690 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 21 Jun 2023 02:08:11 +0200 Subject: [PATCH 10/72] adapt existing isAdmin treatment --- backend/src/graphql/directive/isAuthorized.ts | 10 ++++--- backend/src/graphql/model/User.ts | 4 ++- backend/src/graphql/model/UserAdmin.ts | 4 ++- backend/src/graphql/resolver/UserResolver.ts | 27 ++++++++++++++----- backend/src/util/communityUser.ts | 2 +- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index e41f41151..94aa60ffc 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -4,7 +4,7 @@ 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 } from '@/auth/ROLES' +import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN, ROLE_NAMES, ROLE_MODERATOR } from '@/auth/ROLES' import { Context } from '@/server/context' import { LogError } from '@/server/LogError' @@ -33,10 +33,14 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => try { const user = await User.findOneOrFail({ where: { gradidoID: decoded.gradidoID }, - relations: ['emailContact'], + relations: ['emailContact', 'userRole'], }) context.user = user - context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER + context.role = user.userRole + ? user.userRole.role === ROLE_NAMES.ROLE_NAME_ADMIN + ? ROLE_ADMIN + : ROLE_MODERATOR + : ROLE_USER } catch { // in case the database query fails (user deleted) throw new LogError('401 Unauthorized') diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 5abbdadb7..496f263d9 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -18,7 +18,9 @@ export class User { this.createdAt = user.createdAt this.language = user.language this.publisherId = user.publisherId - this.isAdmin = user.isAdmin + if (user.userRole) { + this.isAdmin = user.userRole.createdAt + } this.klickTipp = null this.hasElopage = null this.hideAmountGDD = user.hideAmountGDD diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index 3e7210874..ff1011468 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -14,7 +14,9 @@ export class UserAdmin { this.hasElopage = hasElopage this.deletedAt = user.deletedAt this.emailConfirmationSend = emailConfirmationSend - this.isAdmin = user.isAdmin + if (user.userRole) { + this.isAdmin = user.userRole?.createdAt + } } @Field(() => Int) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index cbfd9b5c5..a2ae333a9 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -7,6 +7,7 @@ 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, @@ -38,6 +39,7 @@ import { UserRepository } from '@repository/User' 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, @@ -713,7 +715,10 @@ export class UserResolver { @Ctx() context: Context, ): Promise { - const user = await DbUser.findOne({ id: userId }) + const user = await DbUser.findOne({ + where: { id: userId }, + relations: ['userRole'], + }) // user exists ? if (!user) { throw new LogError('Could not find user with given ID', userId) @@ -723,18 +728,24 @@ export class UserResolver { if (moderator.id === userId) { throw new LogError('Administrator can not change his own role') } - // change isAdmin - switch (user.isAdmin) { + // change userRole + switch (user.userRole) { case null: if (isAdmin) { - user.isAdmin = new Date() + 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') } break default: if (!isAdmin) { - user.isAdmin = null + if (user.userRole) { + await UserRole.delete(user.userRole) + } + user.userRole = undefined } else { throw new LogError('User is already admin') } @@ -743,7 +754,11 @@ export class UserResolver { await user.save() await EVENT_ADMIN_USER_ROLE_SET(user, moderator) const newUser = await DbUser.findOne({ id: userId }) - return newUser ? newUser.isAdmin : null + return newUser + ? newUser.userRole && newUser.userRole.role === ROLE_NAMES.ROLE_NAME_ADMIN + ? newUser.userRole.createdAt + : null + : null } @Authorized([RIGHTS.DELETE_USER]) diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index f96c33470..a8fab134f 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -26,7 +26,7 @@ const communityDbUser: dbUser = { createdAt: new Date(), // emailChecked: false, language: '', - isAdmin: null, + userRole: undefined, publisherId: 0, // default password encryption type passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD, From a3b60faa505f2453e1d79d64044cc4db73a0daba Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 22 Jun 2023 01:27:45 +0200 Subject: [PATCH 11/72] change/correct relation user:user_role --- backend/src/config/index.ts | 2 +- .../src/graphql/resolver/UserResolver.test.ts | 29 +++++++++++++------ backend/src/seeds/factory/user.ts | 12 ++++++-- .../entity/0067-add_user_roles_table/User.ts | 4 +-- .../0067-add_user_roles_table/UserRole.ts | 7 ++++- database/entity/index.ts | 2 ++ 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 8d1ed8ae6..60da283f3 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -12,7 +12,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0066-x-community-sendcoins-transactions_table', + DB_VERSION: '0067-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 diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 25787490f..7d8a57942 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -62,6 +62,8 @@ 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') @@ -162,7 +164,7 @@ describe('UserResolver', () => { createdAt: expect.any(Date), // emailChecked: false, language: 'de', - isAdmin: null, + userRole: null, deletedAt: null, publisherId: 1234, referrerId: null, @@ -334,8 +336,17 @@ describe('UserResolver', () => { }) // make Peter Lustig Admin - const peter = await User.findOneOrFail({ id: user[0].id }) - peter.isAdmin = new Date() + const peter = await User.findOneOrFail({ + where: { id: user[0].id }, + relations: ['userRole'], + }) + if (peter.userRole == null) { + peter.userRole = UserRole.create() + } + peter.userRole.createdAt = new Date() + peter.userRole.role = ROLE_NAMES.ROLE_NAME_ADMIN + peter.userRole.userId = peter.id + await peter.userRole.save() await peter.save() // date statement @@ -679,7 +690,7 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - isAdmin: null, + userRole: null, klickTipp: { newsletterState: false, }, @@ -936,7 +947,7 @@ describe('UserResolver', () => { beforeAll(async () => { await mutate({ mutation: login, variables }) - user = await User.find() + user = await User.find({ relations: ['userRole'] }) }) afterAll(() => { @@ -956,7 +967,7 @@ describe('UserResolver', () => { }, hasElopage: false, publisherId: 1234, - isAdmin: null, + userRole: null, }, }, }), @@ -1476,7 +1487,7 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - isAdmin: null, + userRole: null, klickTipp: { newsletterState: false, }, @@ -1594,7 +1605,7 @@ describe('UserResolver', () => { { email: 'bibi@bloxberg.de' }, { relations: ['user'] }, ) - const adminConatct = await UserContact.findOneOrFail( + const adminContact = await UserContact.findOneOrFail( { email: 'peter@lustig.de' }, { relations: ['user'] }, ) @@ -1602,7 +1613,7 @@ describe('UserResolver', () => { expect.objectContaining({ type: EventType.ADMIN_USER_ROLE_SET, affectedUserId: userConatct.user.id, - actingUserId: adminConatct.user.id, + actingUserId: adminContact.user.id, }), ) }) diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 13a274911..06271eccd 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/unbound-method */ import { User } from '@entity/User' +import { UserRole } from '@entity/UserRole' import { ApolloServerTestClient } from 'apollo-server-testing' +import { ROLE_NAMES } from '@/auth/ROLES' import { createUser, setPassword } from '@/seeds/graphql/mutations' import { UserInterface } from '@/seeds/users/UserInterface' @@ -19,7 +21,7 @@ export const userFactory = async ( } = await mutate({ mutation: createUser, variables: user }) // console.log('creatUser:', { id }, { user }) // get user from database - let dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact'] }) + let dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact', 'userRole'] }) // console.log('dbUser:', dbUser) const emailContact = dbUser.emailContact @@ -38,7 +40,13 @@ export const userFactory = async ( if (user.createdAt || user.deletedAt || user.isAdmin) { if (user.createdAt) dbUser.createdAt = user.createdAt if (user.deletedAt) dbUser.deletedAt = user.deletedAt - if (user.isAdmin) dbUser.isAdmin = new Date() + 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() + } await dbUser.save() } diff --git a/database/entity/0067-add_user_roles_table/User.ts b/database/entity/0067-add_user_roles_table/User.ts index f53b397a1..07c723683 100644 --- a/database/entity/0067-add_user_roles_table/User.ts +++ b/database/entity/0067-add_user_roles_table/User.ts @@ -87,8 +87,8 @@ export class User extends BaseEntity { @Column({ type: 'bool', default: false }) hideAmountGDT: boolean - @OneToOne(() => UserRole, (role: UserRole) => role.userId) - @JoinColumn({ name: 'user_id' }) + @OneToOne(() => UserRole, (userRole) => userRole.userId) + @JoinColumn({ name: 'id' }) userRole?: UserRole @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) diff --git a/database/entity/0067-add_user_roles_table/UserRole.ts b/database/entity/0067-add_user_roles_table/UserRole.ts index eb68b6e85..eeca3faa1 100644 --- a/database/entity/0067-add_user_roles_table/UserRole.ts +++ b/database/entity/0067-add_user_roles_table/UserRole.ts @@ -1,4 +1,5 @@ -import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm' +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm' +import { User } from '../User' @Entity('user_roles', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) export class UserRole extends BaseEntity { @@ -16,4 +17,8 @@ export class UserRole extends BaseEntity { @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' }) updatedAt: Date | null + + @OneToOne(() => User, (user) => user.id) + @JoinColumn({ name: 'user_id' }) + userRole?: UserRole } diff --git a/database/entity/index.ts b/database/entity/index.ts index d44029754..b27ac4d61 100644 --- a/database/entity/index.ts +++ b/database/entity/index.ts @@ -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, ] From 61a9f0a045c57d3a89702fde9f0c0e9945ee56a6 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 27 Jun 2023 02:36:19 +0200 Subject: [PATCH 12/72] change entity relations --- database/entity/0067-add_user_roles_table/User.ts | 2 +- database/entity/0067-add_user_roles_table/UserRole.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/database/entity/0067-add_user_roles_table/User.ts b/database/entity/0067-add_user_roles_table/User.ts index 07c723683..2812c2db4 100644 --- a/database/entity/0067-add_user_roles_table/User.ts +++ b/database/entity/0067-add_user_roles_table/User.ts @@ -87,7 +87,7 @@ export class User extends BaseEntity { @Column({ type: 'bool', default: false }) hideAmountGDT: boolean - @OneToOne(() => UserRole, (userRole) => userRole.userId) + @OneToOne(() => UserRole, (userRole: UserRole) => userRole.userId) @JoinColumn({ name: 'id' }) userRole?: UserRole diff --git a/database/entity/0067-add_user_roles_table/UserRole.ts b/database/entity/0067-add_user_roles_table/UserRole.ts index eeca3faa1..eb68b6e85 100644 --- a/database/entity/0067-add_user_roles_table/UserRole.ts +++ b/database/entity/0067-add_user_roles_table/UserRole.ts @@ -1,5 +1,4 @@ -import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm' -import { User } from '../User' +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm' @Entity('user_roles', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) export class UserRole extends BaseEntity { @@ -17,8 +16,4 @@ export class UserRole extends BaseEntity { @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' }) updatedAt: Date | null - - @OneToOne(() => User, (user) => user.id) - @JoinColumn({ name: 'user_id' }) - userRole?: UserRole } From 35eef52aef77ec653ab9eab642d35240732aed07 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 27 Jun 2023 02:39:01 +0200 Subject: [PATCH 13/72] add "isModerator" in graphql model --- backend/src/graphql/model/User.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 496f263d9..3558a0840 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -1,3 +1,4 @@ +import { ROLE_NAMES } from '@/auth/ROLES' import { User as dbUser } from '@entity/User' import { ObjectType, Field, Int } from 'type-graphql' @@ -19,7 +20,17 @@ export class User { this.language = user.language this.publisherId = user.publisherId if (user.userRole) { - this.isAdmin = user.userRole.createdAt + 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 + } } this.klickTipp = null this.hasElopage = null @@ -67,6 +78,9 @@ export class User { @Field(() => Date, { nullable: true }) isAdmin: Date | null + @Field(() => Date, { nullable: true }) + isModerator: Date | null + @Field(() => KlickTipp, { nullable: true }) klickTipp: KlickTipp | null From 37b2e659f05d2e3d0b24fae72c49cb169544c71e Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 27 Jun 2023 02:42:42 +0200 Subject: [PATCH 14/72] add userRole relations --- backend/src/seeds/factory/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 06271eccd..74c6aea26 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -35,7 +35,7 @@ export const userFactory = async ( } // get last changes of user from database - dbUser = await User.findOneOrFail({ id }) + dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact', 'userRole'] }) if (user.createdAt || user.deletedAt || user.isAdmin) { if (user.createdAt) dbUser.createdAt = user.createdAt From 2ca9af9cbf2d579b47f5645e9c9b84ad2a6cf693 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 27 Jun 2023 02:44:27 +0200 Subject: [PATCH 15/72] load userRole in findUserByEmail() --- backend/src/graphql/resolver/UserResolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index a2ae333a9..6124f1a0d 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -850,6 +850,7 @@ export async function findUserByEmail(email: string): Promise { }) const dbUser = dbUserContact.user dbUser.emailContact = dbUserContact + dbUser.userRole = await UserRole.findOne({ userId: dbUser.id }) return dbUser } From e2790a268ebf7edef028a1958227869c38538326 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 27 Jun 2023 02:53:17 +0200 Subject: [PATCH 16/72] modify tests, but still not working --- .../src/graphql/resolver/UserResolver.test.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 7d8a57942..9927af91e 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -142,7 +142,7 @@ describe('UserResolver', () => { describe('valid input data', () => { // let loginEmailOptIn: LoginEmailOptIn[] beforeAll(async () => { - user = await User.find({ relations: ['emailContact'] }) + user = await User.find({ relations: ['emailContact', 'userRole'] }) // loginEmailOptIn = await LoginEmailOptIn.find() emailVerificationCode = user[0].emailContact.emailVerificationCode.toString() }) @@ -336,10 +336,22 @@ describe('UserResolver', () => { }) // make Peter Lustig Admin - const peter = await User.findOneOrFail({ + let peter = await User.findOneOrFail({ where: { id: user[0].id }, relations: ['userRole'], }) + console.log('vorher peter=', peter) + await mutate({ + mutation: setUserRole, + variables: { userId: user[0].id, isAdmin: true }, + }) + peter = await User.findOneOrFail({ + where: { id: user[0].id }, + relations: ['userRole'], + }) + console.log('nachher peter=', peter) + + /* if (peter.userRole == null) { peter.userRole = UserRole.create() } @@ -347,7 +359,9 @@ describe('UserResolver', () => { peter.userRole.role = ROLE_NAMES.ROLE_NAME_ADMIN peter.userRole.userId = peter.id await peter.userRole.save() - await peter.save() + // await peter.save() + console.log('user peter=', peter) + */ // date statement const actualDate = new Date() From dcea6871efa0fe9071ada07b51a99f0cf3d84d95 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 28 Jun 2023 02:47:11 +0200 Subject: [PATCH 17/72] userRoles as OneToMany relation --- database/entity/0067-add_user_roles_table/User.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/entity/0067-add_user_roles_table/User.ts b/database/entity/0067-add_user_roles_table/User.ts index 2812c2db4..ba75084ec 100644 --- a/database/entity/0067-add_user_roles_table/User.ts +++ b/database/entity/0067-add_user_roles_table/User.ts @@ -87,9 +87,9 @@ export class User extends BaseEntity { @Column({ type: 'bool', default: false }) hideAmountGDT: boolean - @OneToOne(() => UserRole, (userRole: UserRole) => userRole.userId) + @OneToMany(() => UserRole, (userRole: UserRole) => userRole.userId) @JoinColumn({ name: 'id' }) - userRole?: UserRole + userRoles?: UserRole[] @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) referrerId?: number | null From 9c9a05e64f5619e293190e0d77d09478a6b4568a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 28 Jun 2023 02:47:51 +0200 Subject: [PATCH 18/72] userRoles as OneToMany relation --- backend/src/graphql/directive/isAuthorized.ts | 7 +- backend/src/graphql/model/User.ts | 51 +++++++----- backend/src/graphql/model/UserAdmin.ts | 19 ++++- .../src/graphql/resolver/UserResolver.test.ts | 54 ++++++++----- backend/src/graphql/resolver/UserResolver.ts | 81 +++++++++++-------- backend/src/seeds/factory/contributionLink.ts | 2 +- backend/src/seeds/factory/user.ts | 19 +++-- backend/src/seeds/users/UserInterface.ts | 2 +- backend/src/seeds/users/peter-lustig.ts | 4 +- backend/src/util/communityUser.ts | 2 +- 10 files changed, 152 insertions(+), 89 deletions(-) diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 94aa60ffc..f1e26df4a 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -33,11 +33,12 @@ export const isAuthorized: AuthChecker = 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 diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 3558a0840..0a4993d28 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -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 } diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index ff1011468..d12b629fa 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -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() diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 9927af91e..c37e0f48e 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -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')], diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 6124f1a0d..274b3fa1d 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -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 { + ): 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: ['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 { }) 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 +} diff --git a/backend/src/seeds/factory/contributionLink.ts b/backend/src/seeds/factory/contributionLink.ts index 51e970a5c..cbbe02ec8 100644 --- a/backend/src/seeds/factory/contributionLink.ts +++ b/backend/src/seeds/factory/contributionLink.ts @@ -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, diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 74c6aea26..fb84787df 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -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() } diff --git a/backend/src/seeds/users/UserInterface.ts b/backend/src/seeds/users/UserInterface.ts index 08aa5d19d..5f441c009 100644 --- a/backend/src/seeds/users/UserInterface.ts +++ b/backend/src/seeds/users/UserInterface.ts @@ -8,5 +8,5 @@ export interface UserInterface { language?: string deletedAt?: Date publisherId?: number - isAdmin?: boolean + role?: string } diff --git a/backend/src/seeds/users/peter-lustig.ts b/backend/src/seeds/users/peter-lustig.ts index 0cdc67829..1bf9711b6 100644 --- a/backend/src/seeds/users/peter-lustig.ts +++ b/backend/src/seeds/users/peter-lustig.ts @@ -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, } diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index a8fab134f..d7c46df36 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -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, From ddee5707f51a46d82219c85d6c1d787e80ec4eee Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 29 Jun 2023 00:00:12 +0200 Subject: [PATCH 19/72] further tests adapted --- backend/src/graphql/model/UserAdmin.ts | 42 +++++++++++------- .../src/graphql/resolver/UserResolver.test.ts | 44 ++++++++----------- backend/src/graphql/resolver/UserResolver.ts | 7 ++- backend/src/seeds/factory/user.ts | 4 +- backend/src/seeds/graphql/mutations.ts | 6 +-- backend/src/seeds/graphql/queries.ts | 4 +- .../entity/0067-add_user_roles_table/User.ts | 4 +- .../0067-add_user_roles_table/UserRole.ts | 7 ++- e2e-tests/cypress.config.ts | 2 +- 9 files changed, 67 insertions(+), 53 deletions(-) diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index d12b629fa..8e7526a61 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -17,17 +17,10 @@ export class UserAdmin { this.deletedAt = user.deletedAt this.emailConfirmationSend = emailConfirmationSend 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 - } + this.roles = [] as string[] + user.userRoles.forEach((userRole) => { + this.roles?.push(userRole.role) + }) } } @@ -58,11 +51,30 @@ export class UserAdmin { @Field(() => String, { nullable: true }) emailConfirmationSend: string | null - @Field(() => Date, { nullable: true }) - isAdmin: Date | null + @Field(() => [String], { nullable: true }) + roles: string[] | null +} - @Field(() => Date, { nullable: true }) - isModerator: Date | null +export function isAdmin(user: UserAdmin): 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: UserAdmin): boolean { + if (user.roles) { + for (const role of user.roles) { + if (role === ROLE_NAMES.ROLE_NAME_MODERATOR) { + return true + } + } + } + return false } @ObjectType() diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index c37e0f48e..ac71afbf3 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -106,7 +106,7 @@ beforeAll(async () => { }) afterAll(async () => { - await cleanDB() + // await cleanDB() await con.close() }) @@ -130,7 +130,7 @@ describe('UserResolver', () => { }) afterAll(async () => { - await cleanDB() + // await cleanDB() }) it('returns success', () => { @@ -164,7 +164,7 @@ describe('UserResolver', () => { createdAt: expect.any(Date), // emailChecked: false, language: 'de', - userRoles: null, + userRoles: expect.any(Array), deletedAt: null, publisherId: 1234, referrerId: null, @@ -340,28 +340,17 @@ describe('UserResolver', () => { where: { id: user[0].id }, relations: ['userRoles'], }) - console.log('vorher peter=', peter) - await mutate({ - mutation: setUserRole, - variables: { userId: user[0].id, role: ROLE_NAMES.ROLE_NAME_ADMIN }, - }) + 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].userId = peter.id + await peter.userRoles[0].save() + peter = await User.findOneOrFail({ where: { id: user[0].id }, relations: ['userRoles'], }) - console.log('nachher peter=', peter) - - /* - if (peter.userRole == null) { - peter.userRole = UserRole.create() - } - peter.userRole.createdAt = new Date() - peter.userRole.role = ROLE_NAMES.ROLE_NAME_ADMIN - peter.userRole.userId = peter.id - await peter.userRole.save() - // await peter.save() - console.log('user peter=', peter) - */ // date statement const actualDate = new Date() @@ -443,13 +432,15 @@ describe('UserResolver', () => { beforeAll(async () => { await userFactory(testEnv, peterLustig) await userFactory(testEnv, bobBaumeister) - await mutate({ mutation: login, variables: bobData }) + const loginResult = await mutate({ mutation: login, variables: bobData }) + console.log('login result=', loginResult) // create contribution as user bob contribution = await mutate({ mutation: createContribution, variables: { amount: 1000, memo: 'testing', creationDate: new Date().toISOString() }, }) + console.log('createContribution contribution=', contribution) // login as admin await mutate({ mutation: login, variables: peterData }) @@ -459,6 +450,7 @@ describe('UserResolver', () => { mutation: confirmContribution, variables: { id: contribution.data.createContribution.id }, }) + console.log('confirmContribution contribution=', contribution) // login as user bob bob = await mutate({ mutation: login, variables: bobData }) @@ -471,6 +463,7 @@ describe('UserResolver', () => { }) transactionLink = await TransactionLink.findOneOrFail() + console.log('transactionLink=', transactionLink) resetToken() @@ -483,6 +476,7 @@ describe('UserResolver', () => { redeemCode: transactionLink.code, }, }) + console.log('newUser=', newUser) }) it('sets the referrer id to bob baumeister id', async () => { @@ -704,7 +698,7 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - userRoles: null, + roles: expect.any(Array), klickTipp: { newsletterState: false, }, @@ -981,7 +975,7 @@ describe('UserResolver', () => { }, hasElopage: false, publisherId: 1234, - userRoles: null, + roles: expect.any(Array), }, }, }), @@ -1501,7 +1495,7 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - userRoles: null, + roles: expect.any(Array), klickTipp: { newsletterState: false, }, diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 274b3fa1d..b100eb785 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -619,7 +619,7 @@ export class UserResolver { const [users, count] = await userRepository.findAndCount({ where: { - isAdmin: Not(IsNull()), + userRoles: Not(IsNull()), }, order: { createdAt: order, @@ -655,7 +655,7 @@ export class UserResolver { 'emailId', 'emailContact', 'deletedAt', - 'isAdmin', + 'userRoles', ] const [users, count] = await userRepository.findBySearchCriteriaPagedFiltered( userFields.map((fieldName) => { @@ -728,6 +728,7 @@ export class UserResolver { where: { id: userId }, relations: ['userRoles'], }) + console.log('setUserRole user=', user) // user exists ? if (!user) { throw new LogError('Could not find user with given ID', userId) @@ -759,9 +760,11 @@ export class UserResolver { throw new LogError('User already is in role=', role) } } + console.log('setUserRole before save user=', user) await user.save() await EVENT_ADMIN_USER_ROLE_SET(user, moderator) const newUser = await DbUser.findOne({ id: userId }, { relations: ['userRoles'] }) + console.log('setUserRole newUser=', user) return newUser?.userRoles ? newUser.userRoles[0].role : null } diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index fb84787df..e55f8e8d6 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -21,7 +21,7 @@ export const userFactory = async ( } = await mutate({ mutation: createUser, variables: user }) // console.log('creatUser:', { id }, { user }) // get user from database - let dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact', 'userRole'] }) + let dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact', 'userRoles'] }) // console.log('dbUser:', dbUser) const emailContact = dbUser.emailContact @@ -35,7 +35,7 @@ export const userFactory = async ( } // get last changes of user from database - dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact', 'userRole'] }) + dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact', 'userRoles'] }) if (user.createdAt || user.deletedAt || user.role) { if (user.createdAt) dbUser.createdAt = user.createdAt diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 22e0b1b09..06b2caef5 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -117,8 +117,8 @@ export const confirmContribution = gql` ` export const setUserRole = gql` - mutation ($userId: Int!, $isAdmin: Boolean!) { - setUserRole(userId: $userId, isAdmin: $isAdmin) + mutation ($userId: Int!, $role: String!) { + setUserRole(userId: $userId, role: $role) } ` @@ -315,7 +315,7 @@ export const login = gql` } hasElopage publisherId - isAdmin + roles } } ` diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index ce7efbfc3..6a9dcc601 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -11,7 +11,7 @@ export const verifyLogin = gql` } hasElopage publisherId - isAdmin + roles } } ` @@ -87,7 +87,7 @@ export const searchUsers = gql` hasElopage emailConfirmationSend deletedAt - isAdmin + roles } } } diff --git a/database/entity/0067-add_user_roles_table/User.ts b/database/entity/0067-add_user_roles_table/User.ts index ba75084ec..666d9dacc 100644 --- a/database/entity/0067-add_user_roles_table/User.ts +++ b/database/entity/0067-add_user_roles_table/User.ts @@ -87,8 +87,8 @@ export class User extends BaseEntity { @Column({ type: 'bool', default: false }) hideAmountGDT: boolean - @OneToMany(() => UserRole, (userRole: UserRole) => userRole.userId) - @JoinColumn({ name: 'id' }) + @OneToMany(() => UserRole, (userRole) => userRole.user) + @JoinColumn({ name: 'user_id' }) userRoles?: UserRole[] @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) diff --git a/database/entity/0067-add_user_roles_table/UserRole.ts b/database/entity/0067-add_user_roles_table/UserRole.ts index eb68b6e85..4734de8d9 100644 --- a/database/entity/0067-add_user_roles_table/UserRole.ts +++ b/database/entity/0067-add_user_roles_table/UserRole.ts @@ -1,4 +1,5 @@ -import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm' +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 { @@ -16,4 +17,8 @@ export class UserRole extends BaseEntity { @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' }) updatedAt: Date | null + + @ManyToOne(() => User, (user) => user.userRoles) + @JoinColumn({ name: 'user_id' }) + user: User } diff --git a/e2e-tests/cypress.config.ts b/e2e-tests/cypress.config.ts index e26259626..3c1485a90 100644 --- a/e2e-tests/cypress.config.ts +++ b/e2e-tests/cypress.config.ts @@ -68,7 +68,7 @@ export default defineConfig({ } hasElopage publisherId - isAdmin + roles hideAmountGDD hideAmountGDT __typename From 9830afb91cb453ed5cf004418c199b4e6b17cd01 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 29 Jun 2023 00:00:35 +0200 Subject: [PATCH 20/72] further tests adapted --- backend/src/graphql/directive/isAuthorized.ts | 18 ++++++++++++------ backend/src/seeds/factory/contributionLink.ts | 1 - backend/src/typeorm/repository/User.ts | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index f1e26df4a..660806d3d 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -35,14 +35,20 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => where: { gradidoID: decoded.gradidoID }, relations: ['emailContact', 'userRoles'], }) - console.log('isAuthorized user=', user) + // console.log('isAuthorized user=', user) context.user = user - context.role = user.userRoles - ? user.userRoles[0].role === ROLE_NAMES.ROLE_NAME_ADMIN - ? ROLE_ADMIN - : ROLE_MODERATOR - : ROLE_USER + if (user.userRoles && user.userRoles.length > 0) { + if (user.userRoles[0].role === ROLE_NAMES.ROLE_NAME_ADMIN) { + context.role = ROLE_ADMIN + } else if (user.userRoles[0].role === ROLE_NAMES.ROLE_NAME_MODERATOR) { + context.role = ROLE_MODERATOR + } + } else { + context.role = ROLE_USER + } + // console.log('context.role=', context.role) } catch { + // console.log('401 Unauthorized for decoded', decoded) // in case the database query fails (user deleted) throw new LogError('401 Unauthorized') } diff --git a/backend/src/seeds/factory/contributionLink.ts b/backend/src/seeds/factory/contributionLink.ts index cbbe02ec8..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('contributionlinkfactory user=', user) const variables = { amount: contributionLink.amount, memo: contributionLink.memo, diff --git a/backend/src/typeorm/repository/User.ts b/backend/src/typeorm/repository/User.ts index 53273102d..3c09f0fc7 100644 --- a/backend/src/typeorm/repository/User.ts +++ b/backend/src/typeorm/repository/User.ts @@ -16,6 +16,7 @@ export class UserRepository extends Repository { .select(select) .withDeleted() .leftJoinAndSelect('user.emailContact', 'emailContact') + .leftJoinAndSelect('user.userRoles', 'userRoles') .where( new Brackets((qb) => { qb.where( From 6189857ed47beb6f669e5255cb8b06b2ae916a68 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 30 Jun 2023 03:10:14 +0200 Subject: [PATCH 21/72] solve some more testcases, but still problems with searchAdminUser and some more... --- .../src/graphql/resolver/UserResolver.test.ts | 2 +- backend/src/graphql/resolver/UserResolver.ts | 9 ++++++--- backend/src/typeorm/repository/User.ts | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index ac71afbf3..3c3be360f 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -1404,7 +1404,7 @@ describe('UserResolver', () => { }) }) - it('finds peter@lustig.de', async () => { + it.only('finds peter@lustig.de', async () => { await expect(mutate({ mutation: searchAdminUsers })).resolves.toEqual( expect.objectContaining({ data: { diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index b100eb785..e9db0ebc7 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeorm' +import { getConnection, getCustomRepository, In, IsNull, Not } from '@dbTools/typeorm' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' import { User as DbUser } from '@entity/User' @@ -76,6 +76,7 @@ import { FULL_CREATION_AVAILABLE } from './const/const' import { getUserCreations } from './util/creations' import { findUserByIdentifier } from './util/findUserByIdentifier' import { validateAlias } from './util/validateAlias' +import { ArrayMinSize } from 'class-validator' // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-commonjs const random = require('random-bigint') @@ -616,10 +617,12 @@ export class UserResolver { { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated, ): Promise { const userRepository = getCustomRepository(UserRepository) + console.log('test') const [users, count] = await userRepository.findAndCount({ + relations: ['userRoles'], where: { - userRoles: Not(IsNull()), + userRoles: { role: In(['admin', 'moderator']) }, }, order: { createdAt: order, @@ -627,7 +630,7 @@ export class UserResolver { skip: (currentPage - 1) * pageSize, take: pageSize, }) - + console.log('users=', users) return { userCount: count, userList: users.map((user) => { diff --git a/backend/src/typeorm/repository/User.ts b/backend/src/typeorm/repository/User.ts index 3c09f0fc7..673772996 100644 --- a/backend/src/typeorm/repository/User.ts +++ b/backend/src/typeorm/repository/User.ts @@ -1,6 +1,7 @@ import { Brackets, EntityRepository, IsNull, Not, Repository } from '@dbTools/typeorm' import { User as DbUser } from '@entity/User' +import { ROLE_NAMES } from '@/auth/ROLES' import { SearchUsersFilters } from '@/graphql/arg/SearchUsersFilters' @EntityRepository(DbUser) @@ -12,11 +13,21 @@ export class UserRepository extends Repository { currentPage: number, pageSize: number, ): Promise<[DbUser[], number]> { + const selectAttr = [ + 'user.id AS user_id', + 'user.email_id AS user_email_id', + 'user.first_name AS user_first_name', + 'user.last_name AS user_last_name', + 'user.deleted_at AS user_deleted_at', + 'count', + ] const query = this.createQueryBuilder('user') - .select(select) + .select(selectAttr) .withDeleted() .leftJoinAndSelect('user.emailContact', 'emailContact') - .leftJoinAndSelect('user.userRoles', 'userRoles') + .leftJoinAndSelect('user.userRoles', 'userRoles', 'userRoles.role in :rolenames', { + rolenames: [ROLE_NAMES.ROLE_NAME_ADMIN, ROLE_NAMES.ROLE_NAME_MODERATOR], + }) .where( new Brackets((qb) => { qb.where( @@ -45,7 +56,7 @@ export class UserRepository extends Repository { query.andWhere({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() }) } } - + console.log('query=', query.getQueryAndParameters()) return query .take(pageSize) .skip((currentPage - 1) * pageSize) From faf86f95b9855a9d175d742ed0eeecd661e8cd84 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 30 Jun 2023 23:23:35 +0200 Subject: [PATCH 22/72] further solved tests --- .../src/graphql/resolver/UserResolver.test.ts | 24 ++++------ backend/src/graphql/resolver/UserResolver.ts | 47 +++++++++---------- backend/src/seeds/graphql/mutations.ts | 2 +- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 8def5daeb..6117d96cd 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -434,15 +434,13 @@ describe('UserResolver', () => { beforeAll(async () => { await userFactory(testEnv, peterLustig) await userFactory(testEnv, bobBaumeister) - const loginResult = await mutate({ mutation: login, variables: bobData }) - console.log('login result=', loginResult) + await mutate({ mutation: login, variables: bobData }) // create contribution as user bob contribution = await mutate({ mutation: createContribution, variables: { amount: 1000, memo: 'testing', creationDate: new Date().toISOString() }, }) - console.log('createContribution contribution=', contribution) // login as admin await mutate({ mutation: login, variables: peterData }) @@ -452,7 +450,6 @@ describe('UserResolver', () => { mutation: confirmContribution, variables: { id: contribution.data.createContribution.id }, }) - console.log('confirmContribution contribution=', contribution) // login as user bob bob = await mutate({ mutation: login, variables: bobData }) @@ -477,7 +474,6 @@ describe('UserResolver', () => { redeemCode: transactionLink.code, }, }) - console.log('newUser=', newUser) }) it('sets the referrer id to bob baumeister id', async () => { @@ -1411,7 +1407,7 @@ describe('UserResolver', () => { }) }) - it.only('finds peter@lustig.de', async () => { + it('finds peter@lustig.de', async () => { await expect(mutate({ mutation: searchAdminUsers })).resolves.toEqual( expect.objectContaining({ data: { @@ -1625,14 +1621,14 @@ describe('UserResolver', () => { }) it('stores the ADMIN_USER_ROLE_SET event in the database', async () => { - const userContact = await UserContact.findOneOrFail( - { email: 'bibi@bloxberg.de' }, - { relations: ['user'] }, - ) - const adminContact = await UserContact.findOneOrFail( - { email: 'peter@lustig.de' }, - { relations: ['user'] }, - ) + 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, diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index faa3a37b2..17fde6775 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -717,8 +717,8 @@ export class UserResolver { async setUserRole( @Arg('userId', () => Int) userId: number, - @Arg('role', () => String) - role: string, + @Arg('role', () => String, { nullable: true }) + role: string | null, @Ctx() context: Context, ): Promise { @@ -735,7 +735,6 @@ export class UserResolver { where: { id: userId }, relations: ['userRoles'], }) - console.log('setUserRole user=', user) // user exists ? if (!user) { throw new LogError('Could not find user with given ID', userId) @@ -745,33 +744,29 @@ export class UserResolver { if (moderator.id === userId) { throw new LogError('Administrator can not change his own role') } - if (isUserInRole(user, role)) { + // if user role(s) should be deleted by role=null as parameter + if (role === null && user.userRoles && user.userRoles.length > 0) { + await UserRole.delete({ userId: user.id }) + user.userRoles = undefined + } else 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) + if (role) { + if (user.userRoles === undefined) { + user.userRoles = [] as UserRole[] } - user.userRoles = undefined - } else { - if (!isUserInRole(user, role)) { - if (user.userRoles === undefined) { - user.userRoles = [] as UserRole[] - user.userRoles[0] = UserRole.create() - } - 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) + if (user.userRoles.length < 1) { + user.userRoles.push(UserRole.create()) } + user.userRoles[0].createdAt = new Date() + user.userRoles[0].role = role + user.userRoles[0].userId = user.id + await UserRole.save(user.userRoles[0]) } - console.log('setUserRole before save user=', user) - await user.save() + // await user.save() await EVENT_ADMIN_USER_ROLE_SET(user, moderator) - const newUser = await DbUser.findOne({ id: userId }, { relations: ['userRoles'] }) - console.log('setUserRole newUser=', user) + const newUser = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'] }) + console.log('setUserRole newUser=', newUser) return newUser?.userRoles ? newUser.userRoles[0].role : null } @@ -865,7 +860,7 @@ export async function findUserByEmail(email: string): Promise { }) const dbUser = dbUserContact.user dbUser.emailContact = dbUserContact - dbUser.userRoles = await UserRole.find({ userId: dbUser.id }) + dbUser.userRoles = await UserRole.find({ where: { userId: dbUser.id } }) return dbUser } @@ -894,7 +889,7 @@ const canEmailResend = (updatedAt: Date): boolean => { return !isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_REQUEST_TIME) } -export function isUserInRole(user: DbUser, role: string): boolean { +export function isUserInRole(user: DbUser, role: string | null): boolean { if (user?.userRoles) { for (const usrRole of user.userRoles) { if (usrRole.role === role) { diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 2f1af8772..296e7c890 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: String) { setUserRole(userId: $userId, role: $role) } ` From 4ccf687a6313312b124657b3352ec12c29402782 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 30 Jun 2023 23:29:57 +0200 Subject: [PATCH 23/72] further solved tests --- backend/src/graphql/resolver/UserResolver.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 6117d96cd..1c2bdc178 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -1687,13 +1687,16 @@ describe('UserResolver', () => { }), ).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=', + ROLE_NAMES.ROLE_NAME_ADMIN, + ) }) }) From 4e9ed0726fb386f74c11b810f5e8b770e4aac7d2 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 3 Jul 2023 16:26:16 +0200 Subject: [PATCH 24/72] all tests solved --- backend/src/graphql/resolver/UserResolver.ts | 22 +++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 17fde6775..4a9a8ef4e 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -654,15 +654,7 @@ export class UserResolver { @Ctx() context: Context, ): Promise { const clientTimezoneOffset = getClientTimezoneOffset(context) - const userFields = [ - 'id', - 'firstName', - 'lastName', - 'emailId', - 'emailContact', - 'deletedAt', - 'userRoles', - ] + const userFields = ['id', 'firstName', 'lastName', 'emailId', 'emailContact', 'deletedAt'] const [users, count] = await findUsers( userFields.map((fieldName) => { return 'user.' + fieldName @@ -745,12 +737,18 @@ export class UserResolver { throw new LogError('Administrator can not change his own role') } // if user role(s) should be deleted by role=null as parameter - if (role === null && user.userRoles && user.userRoles.length > 0) { - await UserRole.delete({ userId: user.id }) - user.userRoles = undefined + if (role === null && user.userRoles) { + 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') + } } else if (isUserInRole(user, role)) { throw new LogError('User already has role=', role) } + // if role shoud be set if (role) { if (user.userRoles === undefined) { user.userRoles = [] as UserRole[] From 2cb8b45fcfefa8e6f6a4ba658c311e975bda5dca Mon Sep 17 00:00:00 2001 From: clauspeterhuebner <86960882+clauspeterhuebner@users.noreply.github.com> Date: Mon, 3 Jul 2023 16:40:21 +0200 Subject: [PATCH 25/72] Update backend/src/graphql/directive/isAuthorized.ts Co-authored-by: Hannes Heine --- backend/src/graphql/directive/isAuthorized.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 660806d3d..20efb0cb9 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -37,14 +37,18 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => }) // console.log('isAuthorized user=', user) context.user = user + context.role = ROLE_USER if (user.userRoles && user.userRoles.length > 0) { - if (user.userRoles[0].role === ROLE_NAMES.ROLE_NAME_ADMIN) { - context.role = ROLE_ADMIN - } else if (user.userRoles[0].role === ROLE_NAMES.ROLE_NAME_MODERATOR) { - context.role = ROLE_MODERATOR + switch (user.userRoles[0].role) { + case ROLE_NAMES.ROLE_NAME_ADMIN: + context.role = ROLE_ADMIN + break + case ROLE_NAMES.ROLE_NAME_MODERATOR: + context.role = ROLE_MODERATOR + break + default: + context.role = ROLE_USER } - } else { - context.role = ROLE_USER } // console.log('context.role=', context.role) } catch { From 567d2399f704db5b37f746c8b2acfc5216501e24 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 3 Jul 2023 16:56:04 +0200 Subject: [PATCH 26/72] rework PR-comments --- backend/src/graphql/resolver/UserResolver.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 1c2bdc178..84e2370e4 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -106,7 +106,7 @@ beforeAll(async () => { }) afterAll(async () => { - // await cleanDB() + await cleanDB() await con.close() }) @@ -130,7 +130,7 @@ describe('UserResolver', () => { }) afterAll(async () => { - // await cleanDB() + await cleanDB() }) it('returns success', () => { From c324acc5b9d77858aa63fdfd2ca99dc61c737a1d Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 3 Jul 2023 17:10:26 +0200 Subject: [PATCH 27/72] linting --- backend/src/graphql/resolver/UserResolver.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 4a9a8ef4e..787ea2081 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -import { getConnection, In, 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' @@ -69,7 +69,6 @@ import { findUserByIdentifier } from './util/findUserByIdentifier' import { findUsers } from './util/findUsers' import { getKlicktippState } from './util/getKlicktippState' import { validateAlias } from './util/validateAlias' -import { ArrayMinSize } from 'class-validator' const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const DEFAULT_LANGUAGE = 'de' @@ -631,7 +630,6 @@ export class UserResolver { skip: (currentPage - 1) * pageSize, take: pageSize, }) - console.log('users=', users) return { userCount: count, userList: users.map((user) => { @@ -710,7 +708,7 @@ export class UserResolver { @Arg('userId', () => Int) userId: number, @Arg('role', () => String, { nullable: true }) - role: string | null, + role: string | null | undefined, @Ctx() context: Context, ): Promise { @@ -764,7 +762,6 @@ export class UserResolver { // await user.save() await EVENT_ADMIN_USER_ROLE_SET(user, moderator) const newUser = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'] }) - console.log('setUserRole newUser=', newUser) return newUser?.userRoles ? newUser.userRoles[0].role : null } From e01823459131be39b43717b9e722082a63ffa52f Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 3 Jul 2023 18:43:49 +0200 Subject: [PATCH 28/72] repair isAuthorized and other user access and findings --- backend/src/auth/MODERATOR_RIGHTS.ts | 1 + backend/src/graphql/directive/isAuthorized.ts | 1 + backend/src/graphql/resolver/UserResolver.ts | 1 + backend/src/seeds/factory/user.ts | 6 +++++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/auth/MODERATOR_RIGHTS.ts b/backend/src/auth/MODERATOR_RIGHTS.ts index 7c892b903..1ff689de6 100644 --- a/backend/src/auth/MODERATOR_RIGHTS.ts +++ b/backend/src/auth/MODERATOR_RIGHTS.ts @@ -13,6 +13,7 @@ export const MODERATOR_RIGHTS = [ 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, ] diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 20efb0cb9..fcee2d19e 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -33,6 +33,7 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => try { const user = await User.findOneOrFail({ where: { gradidoID: decoded.gradidoID }, + withDeleted: true, relations: ['emailContact', 'userRoles'], }) // console.log('isAuthorized user=', user) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 787ea2081..f96389f77 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -134,6 +134,7 @@ export class UserResolver { logger.info(`login with ${email}, ***, ${publisherId} ...`) email = email.trim().toLowerCase() const dbUser = await findUserByEmail(email) + // console.log('login dbUser=', dbUser) if (dbUser.deletedAt) { throw new LogError('This user was permanently deleted. Contact support for questions', dbUser) } diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 82b44c6d9..721360b8a 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -56,7 +56,11 @@ export const userFactory = async ( } // 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 } From d4bfaaa52c1e1385a4f7339303651afcb95685fe Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 4 Jul 2023 14:48:51 +0200 Subject: [PATCH 29/72] rework PR-comment --- backend/src/graphql/model/User.ts | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 0a4993d28..551567a13 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -77,26 +77,20 @@ export class User { @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 - } + @Field(() => Boolean) + isAdmin(): boolean { + if (this.roles) { + return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) } + return false } - 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 - } + @Field(() => Boolean) + isModerator(): boolean { + if (this.roles) { + return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR) } + return false } - return false } From 329d8b5507720768a91f39554b8f37c34c01d956 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 6 Jul 2023 03:13:10 +0200 Subject: [PATCH 30/72] add Field role --- backend/src/graphql/model/AdminUser.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/graphql/model/AdminUser.ts b/backend/src/graphql/model/AdminUser.ts index 92a22b7f1..b3f82d76d 100644 --- a/backend/src/graphql/model/AdminUser.ts +++ b/backend/src/graphql/model/AdminUser.ts @@ -6,6 +6,7 @@ export class AdminUser { constructor(user: User) { this.firstName = user.firstName this.lastName = user.lastName + this.role = user.userRoles ? user.userRoles[0].role : '' } @Field(() => String) @@ -13,6 +14,9 @@ export class AdminUser { @Field(() => String) lastName: string + + @Field(() => String) + role: string } @ObjectType() From b19b68a8eac22e300338266759faa6e9e691dc4d Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 6 Jul 2023 03:14:18 +0200 Subject: [PATCH 31/72] login returns isAdmin and isModerator --- backend/src/seeds/graphql/mutations.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 296e7c890..340434174 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -322,6 +322,8 @@ export const login = gql` hasElopage publisherId roles + isAdmin + isModerator } } ` From 6c0a0b8bc2a179faf13514aaf151b79866c9831e Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 6 Jul 2023 03:15:53 +0200 Subject: [PATCH 32/72] verifyLogin und searchUsers return isAdmin and isModerator; searchAdminUser returns role --- backend/src/seeds/graphql/queries.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index de003aa8d..73e20b535 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -12,6 +12,8 @@ export const verifyLogin = gql` hasElopage publisherId roles + isAdmin + isModerator } } ` @@ -95,6 +97,8 @@ export const searchUsers = gql` emailConfirmationSend deletedAt roles + isAdmin + isModerator } } } @@ -323,6 +327,7 @@ export const searchAdminUsers = gql` userList { firstName lastName + role } } } From e20916fdad96844658bf18cbffdc4d7af600f655 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 6 Jul 2023 03:19:16 +0200 Subject: [PATCH 33/72] reduce complexity of role-setting --- backend/src/seeds/factory/user.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 721360b8a..bbf884bcc 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -40,15 +40,14 @@ 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) { + if ( + user.role && + (user.role === ROLE_NAMES.ROLE_NAME_ADMIN || user.role === ROLE_NAMES.ROLE_NAME_MODERATOR) + ) { 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].role = user.role dbUser.userRoles[0].userId = dbUser.id await dbUser.userRoles[0].save() } @@ -61,6 +60,5 @@ export const userFactory = async ( withDeleted: true, relations: ['emailContact', 'userRoles'], }) - return dbUser } From b3234acffce431ca2023665d64addabff30cb54d Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 6 Jul 2023 03:19:42 +0200 Subject: [PATCH 34/72] reduce complexitiy of role-setting --- backend/src/seeds/factory/user.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index bbf884bcc..47d20f47c 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -19,10 +19,10 @@ export const userFactory = async ( createUser: { id }, }, } = await mutate({ mutation: createUser, variables: user }) - // console.log('creatUser:', { id }, { user }) + console.log('creatUser:', { id }, { user }) // get user from database let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact', 'userRoles'] }) - // console.log('dbUser:', dbUser) + console.log('dbUser:', dbUser) const emailContact = dbUser.emailContact // console.log('emailContact:', emailContact) From 0fca31799b26a94c5bd8dd06144d35336ead600a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 6 Jul 2023 03:21:51 +0200 Subject: [PATCH 35/72] searchAdminUsers with relations userRoles --- backend/src/graphql/resolver/UserResolver.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index f96389f77..efad48330 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -162,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 @@ -356,7 +355,7 @@ export class UserResolver { } else { await EVENT_USER_REGISTER(dbUser) } - + console.log('createUser dbUser=', dbUser) return new User(dbUser) } @@ -622,6 +621,7 @@ export class UserResolver { { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated, ): Promise { const [users, count] = await DbUser.findAndCount({ + relations: ['userRoles'], where: { userRoles: { role: In(['admin', 'moderator']) }, }, @@ -637,6 +637,7 @@ export class UserResolver { return { firstName: user.firstName, lastName: user.lastName, + role: user.userRoles ? user.userRoles[0].role : '', } }), } @@ -722,33 +723,44 @@ export class UserResolver { default: throw new LogError('Not allowed to set user role=', role) } + console.log('1') const user = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'], }) + console.log('2') // user exists ? if (!user) { throw new LogError('Could not find user with given ID', userId) } + console.log('3') // administrator user changes own role? const moderator = getUser(context) + console.log('4') if (moderator.id === userId) { throw new LogError('Administrator can not change his own role') } + console.log('5') // if user role(s) should be deleted by role=null as parameter if (role === null && user.userRoles) { + console.log('6') if (user.userRoles.length > 0) { + console.log('7') // remove all roles of the user await UserRole.delete({ userId: user.id }) + console.log('8') user.userRoles.length = 0 } else if (user.userRoles.length === 0) { + console.log('9') throw new LogError('User is already an usual user') } } else if (isUserInRole(user, role)) { + console.log('10') throw new LogError('User already has role=', role) } // if role shoud be set if (role) { + console.log('11 ', role) if (user.userRoles === undefined) { user.userRoles = [] as UserRole[] } @@ -762,7 +774,9 @@ export class UserResolver { } // await user.save() await EVENT_ADMIN_USER_ROLE_SET(user, moderator) + console.log('12 ') const newUser = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'] }) + console.log('13 ', newUser) return newUser?.userRoles ? newUser.userRoles[0].role : null } From bbcb97abfbb1a80918cc331cb9cbca93fc3ad448 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 7 Jul 2023 17:55:52 +0200 Subject: [PATCH 36/72] additional tests for userRoles and userContact --- backend/src/graphql/model/UserAdmin.ts | 37 ++- .../src/graphql/resolver/UserResolver.test.ts | 215 +++++++++++++++++- backend/src/graphql/resolver/UserResolver.ts | 28 +-- backend/src/seeds/factory/user.ts | 4 +- backend/src/seeds/graphql/queries.ts | 17 ++ .../0068-add_user_roles_table/UserRole.ts | 4 +- 6 files changed, 259 insertions(+), 46 deletions(-) diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index 8e7526a61..59a0d871f 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -53,29 +53,24 @@ export class UserAdmin { @Field(() => [String], { nullable: true }) roles: string[] | null + + @Field(() => Boolean) + isAdmin(): boolean { + if (this.roles) { + return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) + } + return false + } + + @Field(() => Boolean) + isModerator(): boolean { + if (this.roles) { + return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR) + } + return false + } } -export function isAdmin(user: UserAdmin): 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: UserAdmin): boolean { - if (user.roles) { - for (const role of user.roles) { - if (role === ROLE_NAMES.ROLE_NAME_MODERATOR) { - return true - } - } - } - return false -} @ObjectType() export class SearchUsersResult { diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 84e2370e4..48fbfe3b7 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -56,6 +56,7 @@ 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' @@ -699,13 +700,15 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - roles: expect.any(Array), klickTipp: { newsletterState: false, }, language: 'de', lastName: 'Bloxberg', publisherId: 1234, + roles: [], + isAdmin: false, + isModerator: false, }, }, }), @@ -976,7 +979,9 @@ describe('UserResolver', () => { }, hasElopage: false, publisherId: 1234, - roles: expect.any(Array), + roles: [], + isAdmin: false, + isModerator: false, }, }, }), @@ -992,6 +997,35 @@ describe('UserResolver', () => { }), ) }) + + it('returns usercontact object', async () => { + await expect( + query({ + query: userContact, + variables: { + userId: user[0].id, + }, + }), + ).resolves.toEqual( + 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(Date), + updatedAt: expect.any(Date), + deletedAt: null, + }, + }, + }), + ) + }) }) }) }) @@ -1417,6 +1451,7 @@ describe('UserResolver', () => { expect.objectContaining({ firstName: 'Peter', lastName: 'Lustig', + role: ROLE_NAMES.ROLE_NAME_ADMIN, }), ]), }, @@ -1498,13 +1533,15 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - roles: expect.any(Array), klickTipp: { newsletterState: false, }, language: 'de', lastName: 'Bloxberg', publisherId: 1234, + roles: [], + isAdmin: false, + isModerator: false, }, }, }), @@ -1536,7 +1573,7 @@ describe('UserResolver', () => { }) describe('authenticated', () => { - describe('without admin rights', () => { + describe('with user rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) await mutate({ @@ -1564,8 +1601,45 @@ describe('UserResolver', () => { }) }) + 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 = ROLE_NAMES.ROLE_NAME_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: ROLE_NAMES.ROLE_NAME_ADMIN }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + describe('with admin rights', () => { beforeAll(async () => { + user = await userFactory(testEnv, bibiBloxberg) admin = await userFactory(testEnv, peterLustig) await mutate({ mutation: login, @@ -1578,7 +1652,26 @@ describe('UserResolver', () => { resetToken() }) + it('returns user with new moderator-role', async () => { + const result = await mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + }) + expect(result).toEqual( + expect.objectContaining({ + data: { + setUserRole: ROLE_NAMES.ROLE_NAME_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( @@ -1601,11 +1694,21 @@ 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, role: ROLE_NAMES.ROLE_NAME_ADMIN }, @@ -1613,7 +1716,41 @@ describe('UserResolver', () => { expect(result).toEqual( expect.objectContaining({ data: { - setUserRole: expect.any(String), + setUserRole: ROLE_NAMES.ROLE_NAME_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, + }), + ) + }) + }) + + describe('to moderator', () => { + it('returns date string', async () => { + const result = await mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + }) + expect(result).toEqual( + expect.objectContaining({ + data: { + setUserRole: ROLE_NAMES.ROLE_NAME_MODERATOR, }, }), ) @@ -1656,7 +1793,21 @@ 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( @@ -1672,6 +1823,29 @@ 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 GraphQLError('Not allowed to set user role=')], + }), + ) + }) + + 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('to admin', () => { it('throws an error', async () => { @@ -1700,6 +1874,33 @@ describe('UserResolver', () => { }) }) + describe('to moderator', () => { + it('throws an error', async () => { + jest.clearAllMocks() + await mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + }) + await expect( + mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_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=', + ROLE_NAMES.ROLE_NAME_MODERATOR, + ) + }) + }) + describe('to usual user', () => { it('throws an error', async () => { jest.clearAllMocks() diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index efad48330..17872ef70 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -69,6 +69,7 @@ import { findUserByIdentifier } from './util/findUserByIdentifier' import { findUsers } from './util/findUsers' import { getKlicktippState } from './util/getKlicktippState' import { validateAlias } from './util/validateAlias' +import { UserContact } from '../model/UserContact' const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const DEFAULT_LANGUAGE = 'de' @@ -223,6 +224,7 @@ export class UserResolver { // check if user with email still exists? email = email.trim().toLowerCase() if (await checkEmailExists(email)) { + // console.log('email still exists! email', email) const foundUser = await findUserByEmail(email) logger.info('DbUser.findOne', email, foundUser) @@ -355,7 +357,6 @@ export class UserResolver { } else { await EVENT_USER_REGISTER(dbUser) } - console.log('createUser dbUser=', dbUser) return new User(dbUser) } @@ -723,44 +724,33 @@ export class UserResolver { default: throw new LogError('Not allowed to set user role=', role) } - console.log('1') const user = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'], }) - console.log('2') // user exists ? if (!user) { throw new LogError('Could not find user with given ID', userId) } - console.log('3') // administrator user changes own role? const moderator = getUser(context) - console.log('4') if (moderator.id === userId) { throw new LogError('Administrator can not change his own role') } - console.log('5') // if user role(s) should be deleted by role=null as parameter if (role === null && user.userRoles) { - console.log('6') if (user.userRoles.length > 0) { - console.log('7') // remove all roles of the user await UserRole.delete({ userId: user.id }) - console.log('8') user.userRoles.length = 0 } else if (user.userRoles.length === 0) { - console.log('9') throw new LogError('User is already an usual user') } } else if (isUserInRole(user, role)) { - console.log('10') throw new LogError('User already has role=', role) } // if role shoud be set if (role) { - console.log('11 ', role) if (user.userRoles === undefined) { user.userRoles = [] as UserRole[] } @@ -774,9 +764,7 @@ export class UserResolver { } // await user.save() await EVENT_ADMIN_USER_ROLE_SET(user, moderator) - console.log('12 ') const newUser = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'] }) - console.log('13 ', newUser) return newUser?.userRoles ? newUser.userRoles[0].role : null } @@ -858,6 +846,18 @@ 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/user.ts b/backend/src/seeds/factory/user.ts index 47d20f47c..4d1d59f14 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -19,10 +19,10 @@ export const userFactory = async ( createUser: { id }, }, } = await mutate({ mutation: createUser, variables: user }) - console.log('creatUser:', { id }, { user }) + // console.log('after creatUser:', { id }, { user }) // get user from database let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact', 'userRoles'] }) - console.log('dbUser:', dbUser) + // console.log('dbUser:', dbUser) const emailContact = dbUser.emailContact // console.log('emailContact:', emailContact) diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 73e20b535..16fc3aba8 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -387,3 +387,20 @@ 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/database/entity/0068-add_user_roles_table/UserRole.ts b/database/entity/0068-add_user_roles_table/UserRole.ts index 4734de8d9..0de0e27a2 100644 --- a/database/entity/0068-add_user_roles_table/UserRole.ts +++ b/database/entity/0068-add_user_roles_table/UserRole.ts @@ -18,7 +18,7 @@ export class UserRole extends BaseEntity { @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' }) updatedAt: Date | null - @ManyToOne(() => User, (user) => user.userRoles) + @ManyToOne(() => User, (user) => user.userRoles, { nullable: true }) @JoinColumn({ name: 'user_id' }) - user: User + user: User | null } From 1ae685412f94475a8c8a9f9cf7ec8ed6e59090d4 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 7 Jul 2023 18:06:14 +0200 Subject: [PATCH 37/72] changes after master-merge --- .../User.ts | 0 .../UserRole.ts | 0 database/entity/User.ts | 2 +- database/entity/UserRole.ts | 2 +- ..._table.ts => 0069-add_user_roles_table.ts} | 0 dht-node/src/config/index.ts | 2 +- dht-node/yarn.lock | 153 ++++++++++-------- federation/src/config/index.ts | 2 +- 8 files changed, 86 insertions(+), 75 deletions(-) rename database/entity/{0068-add_user_roles_table => 0069-add_user_roles_table}/User.ts (100%) rename database/entity/{0068-add_user_roles_table => 0069-add_user_roles_table}/UserRole.ts (100%) rename database/migrations/{0068-add_user_roles_table.ts => 0069-add_user_roles_table.ts} (100%) diff --git a/database/entity/0068-add_user_roles_table/User.ts b/database/entity/0069-add_user_roles_table/User.ts similarity index 100% rename from database/entity/0068-add_user_roles_table/User.ts rename to database/entity/0069-add_user_roles_table/User.ts diff --git a/database/entity/0068-add_user_roles_table/UserRole.ts b/database/entity/0069-add_user_roles_table/UserRole.ts similarity index 100% rename from database/entity/0068-add_user_roles_table/UserRole.ts rename to database/entity/0069-add_user_roles_table/UserRole.ts diff --git a/database/entity/User.ts b/database/entity/User.ts index 16365cae2..eb66bdee9 100644 --- a/database/entity/User.ts +++ b/database/entity/User.ts @@ -1 +1 @@ -export { User } from './0068-add_user_roles_table/User' +export { User } from './0069-add_user_roles_table/User' diff --git a/database/entity/UserRole.ts b/database/entity/UserRole.ts index 857ddf5a1..1ef9a08b2 100644 --- a/database/entity/UserRole.ts +++ b/database/entity/UserRole.ts @@ -1 +1 @@ -export { UserRole } from './0068-add_user_roles_table/UserRole' +export { UserRole } from './0069-add_user_roles_table/UserRole' diff --git a/database/migrations/0068-add_user_roles_table.ts b/database/migrations/0069-add_user_roles_table.ts similarity index 100% rename from database/migrations/0068-add_user_roles_table.ts rename to database/migrations/0069-add_user_roles_table.ts diff --git a/dht-node/src/config/index.ts b/dht-node/src/config/index.ts index 6f32daaa8..03048b624 100644 --- a/dht-node/src/config/index.ts +++ b/dht-node/src/config/index.ts @@ -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', diff --git a/dht-node/yarn.lock b/dht-node/yarn.lock index f339fd59e..5f42f71a7 100644 --- a/dht-node/yarn.lock +++ b/dht-node/yarn.lock @@ -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== diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index 8b0f52816..72da74aaa 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -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 From abd59b8ad7dc3e1ddfaf22e26f630bfcae5e8c35 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 7 Jul 2023 18:10:22 +0200 Subject: [PATCH 38/72] increase coverage to 90 --- backend/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/jest.config.js b/backend/jest.config.js index 81ebbec55..d282f8361 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -7,7 +7,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 89, + lines: 90, }, }, setupFiles: ['/test/testSetup.ts'], From 301fa1b702a6656f104276d42c59b8310247d47e Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 7 Jul 2023 19:03:28 +0200 Subject: [PATCH 39/72] eeeeeendlich hat ers geschluckt... grrrrrrrr --- backend/src/graphql/model/UserAdmin.ts | 1 - .../src/graphql/resolver/UserResolver.test.ts | 35 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index 59a0d871f..b5dba89b9 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -71,7 +71,6 @@ export class UserAdmin { } } - @ObjectType() export class SearchUsersResult { @Field(() => Int) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 48fbfe3b7..e9d1f1d36 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -1006,25 +1006,24 @@ describe('UserResolver', () => { userId: user[0].id, }, }), - ).resolves.toEqual( - 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(Date), - updatedAt: expect.any(Date), - deletedAt: null, - }, + ).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, }, - }), - ) + }, + }) }) }) }) From 3f7852e5d8071b572bc1d6f9bade4ea23066ebbb Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 7 Jul 2023 19:41:09 +0200 Subject: [PATCH 40/72] linting --- backend/src/graphql/resolver/UserResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 17872ef70..859d90a57 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -24,6 +24,7 @@ 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' @@ -69,7 +70,6 @@ import { findUserByIdentifier } from './util/findUserByIdentifier' import { findUsers } from './util/findUsers' import { getKlicktippState } from './util/getKlicktippState' import { validateAlias } from './util/validateAlias' -import { UserContact } from '../model/UserContact' const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const DEFAULT_LANGUAGE = 'de' From 4beae51475409f70634793d6d1ad4770171f0e57 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 11 Jul 2023 18:50:51 +0200 Subject: [PATCH 41/72] rework PR-comments --- backend/src/graphql/directive/isAuthorized.ts | 3 -- backend/src/graphql/model/AdminUser.ts | 2 +- backend/src/graphql/model/User.ts | 21 +++-------- backend/src/graphql/model/UserAdmin.ts | 21 +++-------- .../src/graphql/resolver/UserResolver.test.ts | 2 +- backend/src/graphql/resolver/UserResolver.ts | 36 +++++-------------- .../graphql/resolver/util/modifyUserRole.ts | 30 ++++++++++++++++ backend/src/seeds/factory/user.ts | 9 ++--- backend/src/util/communityUser.ts | 2 +- .../entity/0069-add_user_roles_table/User.ts | 2 +- .../0069-add_user_roles_table/UserRole.ts | 4 +-- 11 files changed, 57 insertions(+), 75 deletions(-) create mode 100644 backend/src/graphql/resolver/util/modifyUserRole.ts diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index fcee2d19e..733201776 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -36,7 +36,6 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => withDeleted: true, relations: ['emailContact', 'userRoles'], }) - // console.log('isAuthorized user=', user) context.user = user context.role = ROLE_USER if (user.userRoles && user.userRoles.length > 0) { @@ -51,9 +50,7 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => context.role = ROLE_USER } } - // console.log('context.role=', context.role) } catch { - // console.log('401 Unauthorized for decoded', decoded) // in case the database query fails (user deleted) throw new LogError('401 Unauthorized') } diff --git a/backend/src/graphql/model/AdminUser.ts b/backend/src/graphql/model/AdminUser.ts index b3f82d76d..d849762bd 100644 --- a/backend/src/graphql/model/AdminUser.ts +++ b/backend/src/graphql/model/AdminUser.ts @@ -6,7 +6,7 @@ export class AdminUser { constructor(user: User) { this.firstName = user.firstName this.lastName = user.lastName - this.role = user.userRoles ? user.userRoles[0].role : '' + this.role = user.userRoles.length > 0 ? user.userRoles[0].role : '' } @Field(() => String) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 551567a13..c69585a2a 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -20,12 +20,7 @@ export class User { this.createdAt = user.createdAt this.language = user.language this.publisherId = user.publisherId - if (user.userRoles) { - this.roles = [] as string[] - user.userRoles.forEach((userRole) => { - this.roles?.push(userRole.role) - }) - } + this.roles = user.userRoles?.map((userRole) => userRole.role) ?? [] this.klickTipp = null this.hasElopage = null this.hideAmountGDD = user.hideAmountGDD @@ -75,22 +70,16 @@ export class User { @Field(() => Boolean, { nullable: true }) hasElopage: boolean | null - @Field(() => [String], { nullable: true }) - roles: string[] | null + @Field(() => [String]) + roles: string[] @Field(() => Boolean) isAdmin(): boolean { - if (this.roles) { - return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) - } - return false + return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) } @Field(() => Boolean) isModerator(): boolean { - if (this.roles) { - return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR) - } - return false + 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 b5dba89b9..989ce1f8c 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -16,12 +16,7 @@ export class UserAdmin { this.hasElopage = hasElopage this.deletedAt = user.deletedAt this.emailConfirmationSend = emailConfirmationSend - if (user.userRoles) { - this.roles = [] as string[] - user.userRoles.forEach((userRole) => { - this.roles?.push(userRole.role) - }) - } + this.roles = user.userRoles?.map((userRole) => userRole.role) ?? [] } @Field(() => Int) @@ -51,23 +46,17 @@ export class UserAdmin { @Field(() => String, { nullable: true }) emailConfirmationSend: string | null - @Field(() => [String], { nullable: true }) - roles: string[] | null + @Field(() => [String]) + roles: string[] @Field(() => Boolean) isAdmin(): boolean { - if (this.roles) { - return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) - } - return false + return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) } @Field(() => Boolean) isModerator(): boolean { - if (this.roles) { - return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR) - } - return false + return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR) } } diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index e9d1f1d36..6a9ca7590 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -165,7 +165,7 @@ describe('UserResolver', () => { createdAt: expect.any(Date), // emailChecked: false, language: 'de', - userRoles: expect.any(Array), + userRoles: [], // expect.any(Array), deletedAt: null, publisherId: 1234, referrerId: null, diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 859d90a57..f59b5b704 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -69,6 +69,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'] @@ -135,7 +136,6 @@ export class UserResolver { logger.info(`login with ${email}, ***, ${publisherId} ...`) email = email.trim().toLowerCase() const dbUser = await findUserByEmail(email) - // console.log('login dbUser=', dbUser) if (dbUser.deletedAt) { throw new LogError('This user was permanently deleted. Contact support for questions', dbUser) } @@ -738,31 +738,13 @@ export class UserResolver { throw new LogError('Administrator can not change his own role') } // if user role(s) should be deleted by role=null as parameter - if (role === null && user.userRoles) { - 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') - } + if (role === null) { + await deleteUserRole(user) } else if (isUserInRole(user, role)) { throw new LogError('User already has role=', role) + } else { + await setUserRole(user, role) } - // if role shoud be set - if (role) { - if (user.userRoles === undefined) { - user.userRoles = [] as UserRole[] - } - if (user.userRoles.length < 1) { - user.userRoles.push(UserRole.create()) - } - user.userRoles[0].createdAt = new Date() - user.userRoles[0].role = role - user.userRoles[0].userId = user.id - await UserRole.save(user.userRoles[0]) - } - // await user.save() await EVENT_ADMIN_USER_ROLE_SET(user, moderator) const newUser = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'] }) return newUser?.userRoles ? newUser.userRoles[0].role : null @@ -899,10 +881,10 @@ const canEmailResend = (updatedAt: Date): boolean => { return !isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_REQUEST_TIME) } -export function isUserInRole(user: DbUser, role: string | null): boolean { - if (user?.userRoles) { - for (const usrRole of user.userRoles) { - if (usrRole.role === role) { +export function isUserInRole(user: DbUser, role: string): boolean { + if (user && role) { + for (const userRole of user.userRoles) { + if (userRole.role === role) { return true } } diff --git a/backend/src/graphql/resolver/util/modifyUserRole.ts b/backend/src/graphql/resolver/util/modifyUserRole.ts new file mode 100644 index 000000000..bcd140c07 --- /dev/null +++ b/backend/src/graphql/resolver/util/modifyUserRole.ts @@ -0,0 +1,30 @@ +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): Promise { + // 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].createdAt = new Date() + user.userRoles[0].role = role + user.userRoles[0].userId = user.id + await UserRole.save(user.userRoles[0]) + } +} + +export async function deleteUserRole(user: DbUser): Promise { + 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') + } +} diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 4d1d59f14..af6b1c3b9 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/unbound-method */ import { User } from '@entity/User' -import { UserRole } from '@entity/UserRole' import { ApolloServerTestClient } from 'apollo-server-testing' import { ROLE_NAMES } from '@/auth/ROLES' +import { setUserRole } from '@/graphql/resolver/util/modifyUserRole' import { createUser, setPassword } from '@/seeds/graphql/mutations' import { UserInterface } from '@/seeds/users/UserInterface' @@ -44,12 +44,7 @@ export const userFactory = async ( user.role && (user.role === ROLE_NAMES.ROLE_NAME_ADMIN || user.role === ROLE_NAMES.ROLE_NAME_MODERATOR) ) { - dbUser.userRoles = [] as UserRole[] - dbUser.userRoles[0] = UserRole.create() - dbUser.userRoles[0].createdAt = new Date() - dbUser.userRoles[0].role = user.role - dbUser.userRoles[0].userId = dbUser.id - await dbUser.userRoles[0].save() + await setUserRole(dbUser, user.role) } await dbUser.save() } diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index d7c46df36..422c836df 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -26,7 +26,7 @@ const communityDbUser: dbUser = { createdAt: new Date(), // emailChecked: false, language: '', - userRoles: undefined, + userRoles: [], publisherId: 0, // default password encryption type passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD, diff --git a/database/entity/0069-add_user_roles_table/User.ts b/database/entity/0069-add_user_roles_table/User.ts index 666d9dacc..2236fd099 100644 --- a/database/entity/0069-add_user_roles_table/User.ts +++ b/database/entity/0069-add_user_roles_table/User.ts @@ -89,7 +89,7 @@ export class User extends BaseEntity { @OneToMany(() => UserRole, (userRole) => userRole.user) @JoinColumn({ name: 'user_id' }) - userRoles?: UserRole[] + userRoles: UserRole[] @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) referrerId?: number | null diff --git a/database/entity/0069-add_user_roles_table/UserRole.ts b/database/entity/0069-add_user_roles_table/UserRole.ts index 0de0e27a2..4734de8d9 100644 --- a/database/entity/0069-add_user_roles_table/UserRole.ts +++ b/database/entity/0069-add_user_roles_table/UserRole.ts @@ -18,7 +18,7 @@ export class UserRole extends BaseEntity { @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' }) updatedAt: Date | null - @ManyToOne(() => User, (user) => user.userRoles, { nullable: true }) + @ManyToOne(() => User, (user) => user.userRoles) @JoinColumn({ name: 'user_id' }) - user: User | null + user: User } From 928091029a47f456c5cb3dd2b100b602a77e3dda Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 11 Jul 2023 19:35:58 +0200 Subject: [PATCH 42/72] remove console.logs --- backend/src/graphql/resolver/UserResolver.ts | 1 - backend/src/seeds/factory/user.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index f59b5b704..7f5bba32d 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -224,7 +224,6 @@ export class UserResolver { // check if user with email still exists? email = email.trim().toLowerCase() if (await checkEmailExists(email)) { - // console.log('email still exists! email', email) const foundUser = await findUserByEmail(email) logger.info('DbUser.findOne', email, foundUser) diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index af6b1c3b9..b87e320df 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -19,13 +19,10 @@ export const userFactory = async ( createUser: { id }, }, } = await mutate({ mutation: createUser, variables: user }) - // console.log('after creatUser:', { id }, { user }) // get user from database let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact', 'userRoles'] }) - // console.log('dbUser:', dbUser) const emailContact = dbUser.emailContact - // console.log('emailContact:', emailContact) if (user.emailChecked) { await mutate({ From 47bf651ba3ba2009e409e2d9bf24f82f9493df9a Mon Sep 17 00:00:00 2001 From: clauspeterhuebner <86960882+clauspeterhuebner@users.noreply.github.com> Date: Wed, 12 Jul 2023 22:53:38 +0200 Subject: [PATCH 43/72] Update backend/src/graphql/directive/isAuthorized.ts good hint, many thx... Co-authored-by: Moriz Wahl --- backend/src/graphql/directive/isAuthorized.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 733201776..04579a3f9 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -38,7 +38,7 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => }) context.user = user context.role = ROLE_USER - if (user.userRoles && user.userRoles.length > 0) { + if (user.userRoles?.length > 0) { switch (user.userRoles[0].role) { case ROLE_NAMES.ROLE_NAME_ADMIN: context.role = ROLE_ADMIN From 1456d4a74abc553b3ef466211ed616606fdcb70f Mon Sep 17 00:00:00 2001 From: clauspeterhuebner <86960882+clauspeterhuebner@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:34:33 +0200 Subject: [PATCH 44/72] Update backend/src/graphql/resolver/UserResolver.test.ts Co-authored-by: Moriz Wahl --- backend/src/graphql/resolver/UserResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 6a9ca7590..9925e96b3 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -165,7 +165,7 @@ describe('UserResolver', () => { createdAt: expect.any(Date), // emailChecked: false, language: 'de', - userRoles: [], // expect.any(Array), + userRoles: [], deletedAt: null, publisherId: 1234, referrerId: null, From f00c5cd9cbf4ca6fe6597fd3d445f3ff77d19c75 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner <86960882+clauspeterhuebner@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:36:12 +0200 Subject: [PATCH 45/72] Update backend/src/graphql/resolver/UserResolver.test.ts Co-authored-by: Moriz Wahl --- backend/src/graphql/resolver/UserResolver.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 9925e96b3..20565dad8 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -350,10 +350,6 @@ describe('UserResolver', () => { 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() From 46151006b932c9c550f8270e0fd8a450cece025b Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 13 Jul 2023 21:10:56 +0200 Subject: [PATCH 46/72] pure Verzweiflung... --- backend/src/auth/ROLES.ts | 16 ++-- backend/src/graphql/arg/SetUserRoleArgs.ts | 13 +++ backend/src/graphql/directive/isAuthorized.ts | 7 +- backend/src/graphql/enum/RoleNames.ts | 13 +++ backend/src/graphql/model/User.ts | 12 --- backend/src/graphql/model/UserAdmin.ts | 12 --- .../src/graphql/resolver/UserResolver.test.ts | 85 +++++-------------- backend/src/graphql/resolver/UserResolver.ts | 31 +------ backend/src/seeds/factory/contributionLink.ts | 2 + backend/src/seeds/factory/user.ts | 4 +- backend/src/seeds/graphql/mutations.ts | 4 +- backend/src/seeds/graphql/queries.ts | 19 ----- backend/src/seeds/users/peter-lustig.ts | 4 +- 13 files changed, 69 insertions(+), 153 deletions(-) create mode 100644 backend/src/graphql/arg/SetUserRoleArgs.ts create mode 100644 backend/src/graphql/enum/RoleNames.ts 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, } From abc7128e5f7e6f558b68fe645cd3e8aeb86d1025 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 14 Jul 2023 20:08:06 +0200 Subject: [PATCH 47/72] 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, } From 36e410a2634788e8df649abcd1dce6e091dc5cc6 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 14 Jul 2023 20:10:41 +0200 Subject: [PATCH 48/72] enum as null | undefined --- backend/src/graphql/resolver/UserResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 3c707bf0f..7b64b548e 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -855,7 +855,7 @@ const canEmailResend = (updatedAt: Date): boolean => { return !isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_REQUEST_TIME) } -export function isUserInRole(user: DbUser, role: string): boolean { +export function isUserInRole(user: DbUser, role: string | null | undefined): boolean { if (user && role) { for (const userRole of user.userRoles) { if (userRole.role === role) { From ef16fd8e312e230819c41ae71cc08877dcf06dfb Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 18 Jul 2023 13:53:18 +0200 Subject: [PATCH 49/72] add frontend part for roles --- frontend/src/components/Menu/Sidebar.spec.js | 4 ++-- frontend/src/components/Menu/Sidebar.vue | 6 ++++-- frontend/src/graphql/mutations.js | 2 +- frontend/src/graphql/queries.js | 2 +- frontend/src/store/store.js | 10 ++++----- frontend/src/store/store.test.js | 22 ++++++++++---------- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/Menu/Sidebar.spec.js b/frontend/src/components/Menu/Sidebar.spec.js index a978be0bb..23c855557 100644 --- a/frontend/src/components/Menu/Sidebar.spec.js +++ b/frontend/src/components/Menu/Sidebar.spec.js @@ -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() }) diff --git a/frontend/src/components/Menu/Sidebar.vue b/frontend/src/components/Menu/Sidebar.vue index b43afd9f9..73eea1015 100644 --- a/frontend/src/components/Menu/Sidebar.vue +++ b/frontend/src/components/Menu/Sidebar.vue @@ -49,12 +49,14 @@ - {{ $t('navigation.admin_area') }} + + {{ $t('navigation.admin_area') }} + { - 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, diff --git a/frontend/src/store/store.test.js b/frontend/src/store/store.test.js index e72b984f7..6a765cc3f 100644 --- a/frontend/src/store/store.test.js +++ b/frontend/src/store/store.test.js @@ -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', () => { From 2d1bf3c5102f1a044de3c07162d18682baf08660 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 18 Jul 2023 14:10:42 +0200 Subject: [PATCH 50/72] add roles to store --- frontend/src/layouts/DashboardLayout.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/layouts/DashboardLayout.spec.js b/frontend/src/layouts/DashboardLayout.spec.js index a2a666591..3f19aca4e 100644 --- a/frontend/src/layouts/DashboardLayout.spec.js +++ b/frontend/src/layouts/DashboardLayout.spec.js @@ -47,6 +47,7 @@ const mocks = { firstName: 'User', lastName: 'Example', token: 'valid-token', + roles: [], }, }, $i18n: { From 1b853c44e417d2c4aeafc790d1edcca368be9268 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 18 Jul 2023 16:06:42 +0200 Subject: [PATCH 51/72] set correct rolename for ROLE_UNAUTHORIZED --- backend/src/auth/ROLES.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index 7ee315cdd..15ba7b263 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -6,7 +6,7 @@ import { MODERATOR_RIGHTS } from './MODERATOR_RIGHTS' import { Role } from './Role' import { USER_RIGHTS } from './USER_RIGHTS' -export const ROLE_UNAUTHORIZED = new Role(RoleNames.MODERATOR, INALIENABLE_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, From 4b04f81f02f86d36a578668bcbf213a836319759 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 18 Jul 2023 16:32:59 +0200 Subject: [PATCH 52/72] add default value on field createdAt like it is in migration --- backend/src/graphql/resolver/util/modifyUserRole.ts | 1 - database/entity/0069-add_user_roles_table/User.ts | 2 +- database/entity/0069-add_user_roles_table/UserRole.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/util/modifyUserRole.ts b/backend/src/graphql/resolver/util/modifyUserRole.ts index 5acc71f93..f9af28a22 100644 --- a/backend/src/graphql/resolver/util/modifyUserRole.ts +++ b/backend/src/graphql/resolver/util/modifyUserRole.ts @@ -12,7 +12,6 @@ export async function setUserRole(user: DbUser, role: string | null | undefined) user.userRoles.push(UserRole.create()) } // and initialize the userRole - user.userRoles[0].createdAt = new Date() user.userRoles[0].role = role user.userRoles[0].userId = user.id await UserRole.save(user.userRoles[0]) diff --git a/database/entity/0069-add_user_roles_table/User.ts b/database/entity/0069-add_user_roles_table/User.ts index 2236fd099..a49bb4b87 100644 --- a/database/entity/0069-add_user_roles_table/User.ts +++ b/database/entity/0069-add_user_roles_table/User.ts @@ -60,7 +60,7 @@ export class User extends BaseEntity { }) lastName: string - @Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP', nullable: false }) + @Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP(3)', nullable: false }) createdAt: Date @DeleteDateColumn({ name: 'deleted_at', nullable: true }) diff --git a/database/entity/0069-add_user_roles_table/UserRole.ts b/database/entity/0069-add_user_roles_table/UserRole.ts index 4734de8d9..118753b20 100644 --- a/database/entity/0069-add_user_roles_table/UserRole.ts +++ b/database/entity/0069-add_user_roles_table/UserRole.ts @@ -12,7 +12,7 @@ export class UserRole extends BaseEntity { @Column({ length: 40, nullable: false, collation: 'utf8mb4_unicode_ci' }) role: string - @Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP', nullable: false }) + @Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP(3)', nullable: false }) createdAt: Date @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' }) From bed2b86ea58de86452465ec924973612de98b325 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 11:09:39 +0200 Subject: [PATCH 53/72] Test guards with roles array for verfiyLogin. --- admin/src/router/guards.test.js | 61 +++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/admin/src/router/guards.test.js b/admin/src/router/guards.test.js index da4dd5969..19a1881ef 100644 --- a/admin/src/router/guards.test.js +++ b/admin/src/router/guards.test.js @@ -5,7 +5,7 @@ const storeCommitMock = jest.fn() const apolloQueryMock = jest.fn().mockResolvedValue({ data: { verifyLogin: { - isAdmin: true, + roles: ['ADMIN'], language: 'de', }, }, @@ -52,7 +52,12 @@ describe('navigation guards', () => { }) it('commits the moderator to the store', () => { - expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true, language: 'de' }) + expect(storeCommitMock).toBeCalledWith('moderator', { + roles: ['ADMIN'], + isAdmin: true, + isModerator: false, + language: 'de', + }) }) it('redirects to /', () => { @@ -60,12 +65,49 @@ describe('navigation guards', () => { }) }) - describe('with valid token and not as admin', () => { + describe('with valid token and as moderator', () => { + beforeEach(async () => { + jest.clearAllMocks() + apolloQueryMock.mockResolvedValue({ + data: { + verifyLogin: { + roles: ['MODERATOR'], + language: 'de', + }, + }, + }) + await navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next) + }) + + it('commits the token to the store', () => { + expect(storeCommitMock).toBeCalledWith('token', 'valid-token') + }) + + it.skip('sets the locale', () => { + expect(i18nLocaleMock).toBeCalledWith('de') + }) + + it('commits the moderator to the store', () => { + expect(storeCommitMock).toBeCalledWith('moderator', { + roles: ['MODERATOR'], + isAdmin: false, + isModerator: true, + language: 'de', + }) + }) + + it('redirects to /', () => { + expect(next).toBeCalledWith({ path: '/' }) + }) + }) + + describe('with valid token and no roles', () => { beforeEach(() => { apolloQueryMock.mockResolvedValue({ data: { verifyLogin: { - isAdmin: false, + roles: [], + language: 'de', }, }, }) @@ -128,17 +170,24 @@ describe('navigation guards', () => { expect(next).toBeCalledWith({ path: '/not-found' }) }) - it('redirects to not found with token in store and not moderator', () => { + it('redirects to not found with token in store and not admin or moderator', () => { store.state.token = 'valid token' navGuard({ path: '/' }, {}, next) expect(next).toBeCalledWith({ path: '/not-found' }) }) - it('does not redirect with token in store and as moderator', () => { + it('does not redirect with token in store and as admin', () => { store.state.token = 'valid token' store.state.moderator = { isAdmin: true } navGuard({ path: '/' }, {}, next) expect(next).toBeCalledWith() }) + + it('does not redirect with token in store and as moderator', () => { + store.state.token = 'valid token' + store.state.moderator = { isModerator: true } + navGuard({ path: '/' }, {}, next) + expect(next).toBeCalledWith() + }) }) }) From 1a4c42ea266a041c892089d036b7cbeceeffc45b Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 11:10:12 +0200 Subject: [PATCH 54/72] Add roles check to the guards verifyLogin. --- admin/src/router/guards.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/admin/src/router/guards.js b/admin/src/router/guards.js index dd61e8657..3780c6ee1 100644 --- a/admin/src/router/guards.js +++ b/admin/src/router/guards.js @@ -13,8 +13,10 @@ const addNavigationGuards = (router, store, apollo, i18n) => { }) .then((result) => { const moderator = result.data.verifyLogin - if (moderator.isAdmin) { + if (moderator.roles.includes('ADMIN', 0) || moderator.roles.includes('MODERATOR', 0)) { i18n.locale = moderator.language + moderator.isAdmin = moderator.roles.includes('ADMIN', 0) + moderator.isModerator = moderator.roles.includes('MODERATOR', 0) store.commit('moderator', moderator) next({ path: '/' }) } else { @@ -35,7 +37,7 @@ const addNavigationGuards = (router, store, apollo, i18n) => { !CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes (!store.state.token || // we do not have a token !store.state.moderator || // no moderator set in store - !store.state.moderator.isAdmin) && // user is no admin + !(store.state.moderator.isAdmin || store.state.moderator.isModerator)) && // user is no admin to.path !== '/not-found' && // we are not on `not-found` to.path !== '/logout' // we are not on `logout` ) { From 9f00b4e73a267cb664faaab60f70bae9715a3978 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 11:11:13 +0200 Subject: [PATCH 55/72] Get roles property of verifyLogin query. --- admin/src/graphql/verifyLogin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/src/graphql/verifyLogin.js b/admin/src/graphql/verifyLogin.js index ea08bb5b1..ce9e96ffc 100644 --- a/admin/src/graphql/verifyLogin.js +++ b/admin/src/graphql/verifyLogin.js @@ -5,7 +5,7 @@ export const verifyLogin = gql` verifyLogin { firstName lastName - isAdmin + roles id language } From 489a043aa73dd588d4c04e49618dd7eab6f577c9 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 16:09:58 +0200 Subject: [PATCH 56/72] Refactor backend findUsers query to find user roles. --- backend/src/graphql/resolver/UserResolver.ts | 4 +- .../src/graphql/resolver/util/findUsers.ts | 103 +++++++++++------- 2 files changed, 63 insertions(+), 44 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 7b64b548e..6408b9dda 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -655,9 +655,7 @@ export class UserResolver { const clientTimezoneOffset = getClientTimezoneOffset(context) const userFields = ['id', 'firstName', 'lastName', 'emailId', 'emailContact', 'deletedAt'] const [users, count] = await findUsers( - userFields.map((fieldName) => { - return 'user.' + fieldName - }), + userFields, query, filters ?? null, currentPage, diff --git a/backend/src/graphql/resolver/util/findUsers.ts b/backend/src/graphql/resolver/util/findUsers.ts index d01afb904..a7d7d719c 100644 --- a/backend/src/graphql/resolver/util/findUsers.ts +++ b/backend/src/graphql/resolver/util/findUsers.ts @@ -1,10 +1,24 @@ -import { getConnection, Brackets, IsNull, Not } from '@dbTools/typeorm' +import { IsNull, Not, Like } from '@dbTools/typeorm' import { User as DbUser } from '@entity/User' import { SearchUsersFilters } from '@arg/SearchUsersFilters' import { Order } from '@enum/Order' -import { LogError } from '@/server/LogError' +function likeQuery(searchCriteria: string) { + return Like(`%${searchCriteria}%`) +} + +function emailCheckedQuery(filters: SearchUsersFilters) { + return filters.byActivated ?? undefined +} + +function deletedAtQuery(filters: SearchUsersFilters | null) { + return filters?.byDeleted !== undefined && filters?.byDeleted !== null + ? filters.byDeleted + ? Not(IsNull()) + : IsNull() + : undefined +} export const findUsers = async ( select: string[], @@ -14,44 +28,51 @@ export const findUsers = async ( pageSize: number, order = Order.ASC, ): Promise<[DbUser[], number]> => { - const queryRunner = getConnection().createQueryRunner() - try { - await queryRunner.connect() - const query = queryRunner.manager - .createQueryBuilder(DbUser, 'user') - .select(select) - .withDeleted() - .leftJoinAndSelect('user.emailContact', 'emailContact') - .where( - new Brackets((qb) => { - qb.where( - 'user.firstName like :name or user.lastName like :lastName or emailContact.email like :email', - { - name: `%${searchCriteria}%`, - lastName: `%${searchCriteria}%`, - email: `%${searchCriteria}%`, - }, - ) - }), - ) - if (filters) { - if (filters.byActivated !== null) { - query.andWhere('emailContact.emailChecked = :value', { value: filters.byActivated }) - } - - if (filters.byDeleted !== null) { - query.andWhere({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() }) - } - } - - return await query - .orderBy({ 'user.id': order }) - .take(pageSize) - .skip((currentPage - 1) * pageSize) - .getManyAndCount() - } catch (err) { - throw new LogError('Unable to search users', err) - } finally { - await queryRunner.release() + const where = [ + { + firstName: likeQuery(searchCriteria), + deletedAt: deletedAtQuery(filters), + emailContact: filters + ? { + emailChecked: emailCheckedQuery(filters), + } + : undefined, + }, + { + lastName: likeQuery(searchCriteria), + deletedAt: deletedAtQuery(filters), + emailContact: filters + ? { + emailChecked: emailCheckedQuery(filters), + } + : undefined, + }, + { + emailContact: { + // ...(filters ?? emailChecked: filters.byActivated) + emailChecked: filters ? emailCheckedQuery(filters) : undefined, + email: likeQuery(searchCriteria), + }, + deletedAt: deletedAtQuery(filters), + }, + ] + const selectFind = Object.fromEntries(select.map((item) => [item, true])) + const relations = ['emailContact', 'userRoles'] + const orderFind = { + id: order, } + const take = pageSize + const skip = (currentPage - 1) * pageSize + const withDeleted = true + + const [users, count] = await DbUser.findAndCount({ + where, + withDeleted, + select: selectFind, + relations, + order: orderFind, + take, + skip, + }) + return [users, count] } From 06033775e748cb2272172423d8878ad8acfe6028 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 16:42:03 +0200 Subject: [PATCH 57/72] Change user query to get roles array & setUserRole mutation. --- admin/src/graphql/searchUsers.js | 3 ++- admin/src/graphql/setUserRole.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/admin/src/graphql/searchUsers.js b/admin/src/graphql/searchUsers.js index 15fb8da32..315b2e1aa 100644 --- a/admin/src/graphql/searchUsers.js +++ b/admin/src/graphql/searchUsers.js @@ -26,7 +26,8 @@ export const searchUsers = gql` hasElopage emailConfirmationSend deletedAt - isAdmin + # isAdmin + roles } } } diff --git a/admin/src/graphql/setUserRole.js b/admin/src/graphql/setUserRole.js index 8cdcab396..8df86da6b 100644 --- a/admin/src/graphql/setUserRole.js +++ b/admin/src/graphql/setUserRole.js @@ -1,7 +1,7 @@ import gql from 'graphql-tag' export const setUserRole = gql` - mutation ($userId: Int!, $isAdmin: Boolean!) { - setUserRole(userId: $userId, isAdmin: $isAdmin) + mutation ($userId: Int!, $role: RoleNames!) { + setUserRole(userId: $userId, role: $role) } ` From 037178bbe1c0d3dbed06306caff1301fe95495b8 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 16:42:32 +0200 Subject: [PATCH 58/72] Add moderator object. --- admin/src/locales/de.json | 1 + admin/src/locales/en.json | 1 + 2 files changed, 2 insertions(+) diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 1f5e41fbe..d4209ce83 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -209,6 +209,7 @@ "selectLabel": "Rolle:", "selectRoles": { "admin": "Administrator", + "moderator": "Moderator", "user": "einfacher Nutzer" }, "successfullyChangedTo": "Nutzer ist jetzt „{role}“.", diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index 99c438365..35aacfa69 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -209,6 +209,7 @@ "selectLabel": "Role:", "selectRoles": { "admin": "administrator", + "moderator": "moderator", "user": "usual user" }, "successfullyChangedTo": "User is now \"{role}\".", From 8934e9205b8d03811ba2b50353b37e70bb4e83ed Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 16:43:26 +0200 Subject: [PATCH 59/72] Change of roles added to CreationConfirm. --- admin/src/pages/CreationConfirm.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/admin/src/pages/CreationConfirm.spec.js b/admin/src/pages/CreationConfirm.spec.js index 9e1ddb4a4..008ffb0bc 100644 --- a/admin/src/pages/CreationConfirm.spec.js +++ b/admin/src/pages/CreationConfirm.spec.js @@ -28,7 +28,9 @@ const mocks = { moderator: { firstName: 'Peter', lastName: 'Lustig', - isAdmin: '2022-08-30T07:41:31.000Z', + isAdmin: true, + isModerator: false, + roles: ['ADMIN'], id: 263, language: 'de', }, From adae41d2d0c74e18e3f7af807407c6e77b196e82 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 17:35:45 +0200 Subject: [PATCH 60/72] Add new roles to mock objects. --- admin/src/components/Tables/SearchUserTable.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/admin/src/components/Tables/SearchUserTable.spec.js b/admin/src/components/Tables/SearchUserTable.spec.js index 20eeb9dad..d38520327 100644 --- a/admin/src/components/Tables/SearchUserTable.spec.js +++ b/admin/src/components/Tables/SearchUserTable.spec.js @@ -15,6 +15,7 @@ const propsData = { email: 'bibi@bloxberg.de', creation: [200, 400, 600], emailChecked: true, + roles: [], }, { userId: 2, @@ -23,6 +24,7 @@ const propsData = { email: 'benjamin@bluemchen.de', creation: [1000, 1000, 1000], emailChecked: true, + roles: [], }, { userId: 3, @@ -31,6 +33,7 @@ const propsData = { email: 'peter@lustig.de', creation: [0, 0, 0], emailChecked: true, + roles: ['ADMIN'], }, { userId: 4, @@ -39,6 +42,7 @@ const propsData = { email: 'new@user.ch', creation: [1000, 1000, 1000], emailChecked: false, + roles: [], }, ], fields: [ From e0b84b52a013dcfceb78df0bd5ce77419edca829 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 17:36:21 +0200 Subject: [PATCH 61/72] Add moderator role selection of roles. --- .../components/ChangeUserRoleFormular.spec.js | 350 +++++++++++++++++- .../src/components/ChangeUserRoleFormular.vue | 33 +- 2 files changed, 364 insertions(+), 19 deletions(-) diff --git a/admin/src/components/ChangeUserRoleFormular.spec.js b/admin/src/components/ChangeUserRoleFormular.spec.js index 381d2ce43..21df772e3 100644 --- a/admin/src/components/ChangeUserRoleFormular.spec.js +++ b/admin/src/components/ChangeUserRoleFormular.spec.js @@ -21,6 +21,7 @@ const mocks = { moderator: { id: 0, name: 'test moderator', + roles: ['ADMIN'], }, }, }, @@ -45,7 +46,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - isAdmin: null, + roles: ['USER'], }, } wrapper = Wrapper() @@ -61,7 +62,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 0, - isAdmin: null, + roles: ['USER'], }, } wrapper = Wrapper() @@ -88,7 +89,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - isAdmin: null, + roles: ['USER'], }, } wrapper = Wrapper() @@ -126,7 +127,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - isAdmin: null, + roles: ['USER'], }, } wrapper = Wrapper() @@ -149,7 +150,7 @@ describe('ChangeUserRoleFormular', () => { }) }) - describe('new role', () => { + describe('new role "MODERATOR"', () => { beforeEach(() => { rolesToSelect.at(1).setSelected() }) @@ -181,7 +182,190 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - isAdmin: true, + // isAdmin: true, + role: 'moderator', + }, + }), + ) + }) + + it('emits "updateIsAdmin" with role moderator', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + // isAdmin: true, + role: 'moderator', + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + }) + + describe('new role "ADMIN"', () => { + beforeEach(() => { + rolesToSelect.at(2).setSelected() + }) + + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, + ) + }) + + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + // isAdmin: true, + role: 'admin', + }, + }), + ) + }) + + it('emits "updateIsAdmin" with role moderator', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + // isAdmin: true, + role: 'admin', + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + }) + }) + }) + + describe('user has role "moderator"', () => { + beforeEach(() => { + apolloMutateMock.mockResolvedValue({ + data: { + setUserRole: null, + }, + }) + propsData = { + item: { + userId: 1, + // isAdmin: new Date(), + roles: ['MODERATOR'], + }, + } + wrapper = Wrapper() + rolesToSelect = wrapper.find('select.role-select').findAll('option') + }) + + it('has selected option set to "moderator"', () => { + expect(wrapper.find('select.role-select').element.value).toBe('moderator') + }) + + describe('change select to', () => { + describe('same role', () => { + it('has "change_user_role" button disabled', () => { + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(true) + }) + + it('does not call the API', () => { + rolesToSelect.at(1).setSelected() + // TODO: Fix this + expect(apolloMutateMock).not.toHaveBeenCalled() + }) + }) + + describe('new role "USER"', () => { + beforeEach(() => { + rolesToSelect.at(0).setSelected() + }) + + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, + ) + }) + + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + role: 'user', }, }), ) @@ -193,7 +377,78 @@ describe('ChangeUserRoleFormular', () => { expect.arrayContaining([ { userId: 1, - isAdmin: expect.any(Date), + role: 'user', + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + }) + + describe('new role "ADMIN"', () => { + beforeEach(() => { + rolesToSelect.at(2).setSelected() + }) + + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, + ) + }) + + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + role: 'admin', + }, + }), + ) + }) + + it('emits "updateIsAdmin"', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + role: 'admin', }, ]), ]), @@ -232,7 +487,8 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - isAdmin: new Date(), + // isAdmin: new Date(), + roles: ['ADMIN'], }, } wrapper = Wrapper() @@ -251,11 +507,12 @@ describe('ChangeUserRoleFormular', () => { it('does not call the API', () => { rolesToSelect.at(1).setSelected() + // TODO: Fix this expect(apolloMutateMock).not.toHaveBeenCalled() }) }) - describe('new role', () => { + describe('new role "USER"', () => { beforeEach(() => { rolesToSelect.at(0).setSelected() }) @@ -287,7 +544,7 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - isAdmin: false, + role: 'user', }, }), ) @@ -299,7 +556,78 @@ describe('ChangeUserRoleFormular', () => { expect.arrayContaining([ { userId: 1, - isAdmin: null, + role: 'user', + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + }) + + describe('new role "MODERATOR"', () => { + beforeEach(() => { + rolesToSelect.at(1).setSelected() + }) + + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, + ) + }) + + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + role: 'moderator', + }, + }), + ) + }) + + it('emits "updateIsAdmin"', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + role: 'moderator', }, ]), ]), diff --git a/admin/src/components/ChangeUserRoleFormular.vue b/admin/src/components/ChangeUserRoleFormular.vue index 677a12f56..06cb9d946 100644 --- a/admin/src/components/ChangeUserRoleFormular.vue +++ b/admin/src/components/ChangeUserRoleFormular.vue @@ -26,6 +26,7 @@ import { setUserRole } from '../graphql/setUserRole' const rolesValues = { admin: 'admin', + moderator: 'moderator', user: 'user', } @@ -39,15 +40,24 @@ export default { }, data() { return { - currentRole: this.item.isAdmin ? rolesValues.admin : rolesValues.user, - roleSelected: this.item.isAdmin ? rolesValues.admin : rolesValues.user, + // currentRole: this.item.isAdmin ? rolesValues.admin : rolesValues.user, + currentRole: this.newFunction(), + // roleSelected: this.item.isAdmin ? rolesValues.admin : rolesValues.user, + roleSelected: this.newFunction(), roles: [ { value: rolesValues.user, text: this.$t('userRole.selectRoles.user') }, + { value: rolesValues.moderator, text: this.$t('userRole.selectRoles.moderator') }, { value: rolesValues.admin, text: this.$t('userRole.selectRoles.admin') }, ], } }, methods: { + newFunction() { + let userRole = rolesValues.user + if (this.item.roles.includes('ADMIN', 0)) userRole = rolesValues.admin + else if (this.item.roles.includes('MODERATOR', 0)) userRole = rolesValues.moderator + return userRole + }, showModal() { this.$bvModal .msgBoxConfirm( @@ -77,25 +87,32 @@ export default { }) }, setUserRole(newRole, oldRole) { + let role + switch (newRole) { + case rolesValues.admin: + case rolesValues.moderator: + case rolesValues.user: + role = newRole + break + default: + role = 'USER' + } this.$apollo .mutate({ mutation: setUserRole, variables: { userId: this.item.userId, - isAdmin: newRole === rolesValues.admin, + role, }, }) .then((result) => { this.$emit('updateIsAdmin', { userId: this.item.userId, - isAdmin: result.data.setUserRole, + role, }) this.toastSuccess( this.$t('userRole.successfullyChangedTo', { - role: - result.data.setUserRole !== null - ? this.$t('userRole.selectRoles.admin') - : this.$t('userRole.selectRoles.user'), + role: role.text, }), ) }) From 557ffed4ef93cd098bc31c95bc93174218fa1b3a Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 18:57:44 +0200 Subject: [PATCH 62/72] Add flushPromises. --- admin/package.json | 1 + admin/yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/admin/package.json b/admin/package.json index 8350e146a..039ddd02f 100644 --- a/admin/package.json +++ b/admin/package.json @@ -36,6 +36,7 @@ "date-fns": "^2.29.3", "dotenv-webpack": "^7.0.3", "express": "^4.17.1", + "flush-promises": "^1.0.2", "graphql": "^15.6.1", "identity-obj-proxy": "^3.0.0", "jest": "26.6.3", diff --git a/admin/yarn.lock b/admin/yarn.lock index c270b80bf..6f9a999ce 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -6423,6 +6423,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== +flush-promises@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flush-promises/-/flush-promises-1.0.2.tgz#4948fd58f15281fed79cbafc86293d5bb09b2ced" + integrity sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" From 19ba385e46790876f9dbc84b6917b60e7d993ee6 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 23 Jul 2023 18:58:12 +0200 Subject: [PATCH 63/72] Test UserQuery to get jest line check. --- admin/src/components/UserQuery.spec.js | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 admin/src/components/UserQuery.spec.js diff --git a/admin/src/components/UserQuery.spec.js b/admin/src/components/UserQuery.spec.js new file mode 100644 index 000000000..2d6a6c554 --- /dev/null +++ b/admin/src/components/UserQuery.spec.js @@ -0,0 +1,50 @@ +import { mount } from '@vue/test-utils' +import UserQuery from './UserQuery' +import flushPromises from 'flush-promises' + +const localVue = global.localVue + +const propsData = { + userId: 42, +} +const mocks = { + $t: jest.fn((t) => t), +} +describe('TransactionLinkList', () => { + let wrapper + + const Wrapper = () => { + return mount(UserQuery, { mocks, localVue, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('has div .input-group', () => { + expect(wrapper.find('div .input-group').exists()).toBe(true) + }) + + it('has .test-input-criteria', () => { + expect(wrapper.find('input.test-input-criteria').exists()).toBe(true) + }) + + describe('has', () => { + beforeEach(async () => { + await wrapper.find('input.test-input-criteria').setValue('Test2') + await wrapper.find('input.test-input-criteria').trigger('blur') + await flushPromises() + await wrapper.vm.$nextTick() + }) + + it('emits input', () => { + expect(wrapper.emitted('input')).toBeTruthy() + }) + + it('emits input with value "Test2"', () => { + expect(wrapper.emitted('input')).toEqual([['Test2']]) + }) + }) + }) +}) From 03ae59627d46c77fc2430368b844e82a946d9883 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 24 Jul 2023 10:00:15 +0200 Subject: [PATCH 64/72] Fix emit and role as uppercase --- .../components/ChangeUserRoleFormular.spec.js | 30 ++++++++----------- .../src/components/ChangeUserRoleFormular.vue | 25 +++++++--------- .../components/Tables/SearchUserTable.spec.js | 5 ++-- .../src/components/Tables/SearchUserTable.vue | 4 +-- admin/src/pages/UserSearch.spec.js | 18 +++++++---- admin/src/pages/UserSearch.vue | 4 +-- 6 files changed, 42 insertions(+), 44 deletions(-) diff --git a/admin/src/components/ChangeUserRoleFormular.spec.js b/admin/src/components/ChangeUserRoleFormular.spec.js index 21df772e3..51a558d60 100644 --- a/admin/src/components/ChangeUserRoleFormular.spec.js +++ b/admin/src/components/ChangeUserRoleFormular.spec.js @@ -182,8 +182,7 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - // isAdmin: true, - role: 'moderator', + role: 'MODERATOR', }, }), ) @@ -195,8 +194,7 @@ describe('ChangeUserRoleFormular', () => { expect.arrayContaining([ { userId: 1, - // isAdmin: true, - role: 'moderator', + role: 'MODERATOR', }, ]), ]), @@ -255,8 +253,7 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - // isAdmin: true, - role: 'admin', + role: 'ADMIN', }, }), ) @@ -268,8 +265,7 @@ describe('ChangeUserRoleFormular', () => { expect.arrayContaining([ { userId: 1, - // isAdmin: true, - role: 'admin', + role: 'ADMIN', }, ]), ]), @@ -308,7 +304,6 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - // isAdmin: new Date(), roles: ['MODERATOR'], }, } @@ -365,7 +360,7 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - role: 'user', + role: 'USER', }, }), ) @@ -377,7 +372,7 @@ describe('ChangeUserRoleFormular', () => { expect.arrayContaining([ { userId: 1, - role: 'user', + role: 'USER', }, ]), ]), @@ -436,7 +431,7 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - role: 'admin', + role: 'ADMIN', }, }), ) @@ -448,7 +443,7 @@ describe('ChangeUserRoleFormular', () => { expect.arrayContaining([ { userId: 1, - role: 'admin', + role: 'ADMIN', }, ]), ]), @@ -487,7 +482,6 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - // isAdmin: new Date(), roles: ['ADMIN'], }, } @@ -544,7 +538,7 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - role: 'user', + role: 'USER', }, }), ) @@ -556,7 +550,7 @@ describe('ChangeUserRoleFormular', () => { expect.arrayContaining([ { userId: 1, - role: 'user', + role: 'USER', }, ]), ]), @@ -615,7 +609,7 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - role: 'moderator', + role: 'MODERATOR', }, }), ) @@ -627,7 +621,7 @@ describe('ChangeUserRoleFormular', () => { expect.arrayContaining([ { userId: 1, - role: 'moderator', + role: 'MODERATOR', }, ]), ]), diff --git a/admin/src/components/ChangeUserRoleFormular.vue b/admin/src/components/ChangeUserRoleFormular.vue index 06cb9d946..840410931 100644 --- a/admin/src/components/ChangeUserRoleFormular.vue +++ b/admin/src/components/ChangeUserRoleFormular.vue @@ -64,8 +64,10 @@ export default { this.$t('overlay.changeUserRole.question', { username: `${this.item.firstName} ${this.item.lastName}`, newRole: - this.roleSelected === 'admin' + this.roleSelected === rolesValues.admin ? this.$t('userRole.selectRoles.admin') + : this.roleSelected === rolesValues.moderator + ? this.$t('userRole.selectRoles.moderator') : this.$t('userRole.selectRoles.user'), }), { @@ -87,32 +89,27 @@ export default { }) }, setUserRole(newRole, oldRole) { - let role - switch (newRole) { - case rolesValues.admin: - case rolesValues.moderator: - case rolesValues.user: - role = newRole - break - default: - role = 'USER' - } + const role = this.roles.find((role) => { + return role.value === newRole + }) + const roleText = role.text + const roleValue = role.value.toUpperCase() this.$apollo .mutate({ mutation: setUserRole, variables: { userId: this.item.userId, - role, + role: role.value.toUpperCase(), }, }) .then((result) => { this.$emit('updateIsAdmin', { userId: this.item.userId, - role, + role: roleValue, }) this.toastSuccess( this.$t('userRole.successfullyChangedTo', { - role: role.text, + role: roleText, }), ) }) diff --git a/admin/src/components/Tables/SearchUserTable.spec.js b/admin/src/components/Tables/SearchUserTable.spec.js index d38520327..a8037422f 100644 --- a/admin/src/components/Tables/SearchUserTable.spec.js +++ b/admin/src/components/Tables/SearchUserTable.spec.js @@ -102,12 +102,13 @@ describe('SearchUserTable', () => { beforeEach(async () => { await wrapper.find('div.change-user-role-formular').vm.$emit('updateIsAdmin', { userId: 1, - isAdmin: new Date(), + // isAdmin: new Date(), + role: 'ADMIN', }) }) it('emits updateIsAdmin', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual([[1, expect.any(Date)]]) + expect(wrapper.emitted('updateIsAdmin')).toEqual([[1, 'ADMIN']]) }) }) diff --git a/admin/src/components/Tables/SearchUserTable.vue b/admin/src/components/Tables/SearchUserTable.vue index 25d807c83..32f917e0e 100644 --- a/admin/src/components/Tables/SearchUserTable.vue +++ b/admin/src/components/Tables/SearchUserTable.vue @@ -127,8 +127,8 @@ export default { updateUserData(rowItem, newCreation) { rowItem.creation = newCreation }, - updateIsAdmin({ userId, isAdmin }) { - this.$emit('updateIsAdmin', userId, isAdmin) + updateIsAdmin({ userId, role }) { + this.$emit('updateIsAdmin', userId, role) }, updateDeletedAt({ userId, deletedAt }) { this.$emit('updateDeletedAt', userId, deletedAt) diff --git a/admin/src/pages/UserSearch.spec.js b/admin/src/pages/UserSearch.spec.js index 7979ed6d0..a35486720 100644 --- a/admin/src/pages/UserSearch.spec.js +++ b/admin/src/pages/UserSearch.spec.js @@ -16,6 +16,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({ email: 'new@user.ch', creation: [1000, 1000, 1000], emailChecked: false, + roles: [], deletedAt: null, }, { @@ -24,6 +25,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({ lastName: 'Lustig', email: 'peter@lustig.de', creation: [0, 0, 0], + roles: ['ADMIN'], emailChecked: true, deletedAt: null, }, @@ -33,6 +35,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({ lastName: 'Blümchen', email: 'benjamin@bluemchen.de', creation: [1000, 1000, 1000], + roles: ['USER'], emailChecked: true, deletedAt: new Date(), }, @@ -42,6 +45,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({ lastName: 'Bloxberg', email: 'bibi@bloxberg.de', creation: [200, 400, 600], + roles: ['USER'], emailChecked: true, deletedAt: null, }, @@ -212,10 +216,10 @@ describe('UserSearch', () => { it('updates user role to admin', async () => { await wrapper .findComponent({ name: 'SearchUserTable' }) - .vm.$emit('updateIsAdmin', userId, new Date()) - expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).isAdmin).toEqual( - expect.any(Date), - ) + .vm.$emit('updateIsAdmin', userId, 'ADMIN') + expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).roles).toEqual([ + 'ADMIN', + ]) }) }) @@ -223,8 +227,10 @@ describe('UserSearch', () => { it('updates user role to usual user', async () => { await wrapper .findComponent({ name: 'SearchUserTable' }) - .vm.$emit('updateIsAdmin', userId, null) - expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).isAdmin).toEqual(null) + .vm.$emit('updateIsAdmin', userId, 'USER') + expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).roles).toEqual([ + 'USER', + ]) }) }) }) diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue index 95b9a6833..4f6c74720 100644 --- a/admin/src/pages/UserSearch.vue +++ b/admin/src/pages/UserSearch.vue @@ -101,8 +101,8 @@ export default { this.toastError(error.message) }) }, - updateIsAdmin(userId, isAdmin) { - this.searchResult.find((obj) => obj.userId === userId).isAdmin = isAdmin + updateIsAdmin(userId, role) { + this.searchResult.find((obj) => obj.userId === userId).roles = [role] }, updateDeletedAt(userId, deletedAt) { this.searchResult.find((obj) => obj.userId === userId).deletedAt = deletedAt From fdf40a6ec79ecd88964a2d20cd19afcbd51d4704 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 24 Jul 2023 14:46:55 +0200 Subject: [PATCH 65/72] remove flush promises --- admin/package.json | 1 - admin/src/components/UserQuery.spec.js | 7 ++----- admin/yarn.lock | 5 ----- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/admin/package.json b/admin/package.json index 039ddd02f..8350e146a 100644 --- a/admin/package.json +++ b/admin/package.json @@ -36,7 +36,6 @@ "date-fns": "^2.29.3", "dotenv-webpack": "^7.0.3", "express": "^4.17.1", - "flush-promises": "^1.0.2", "graphql": "^15.6.1", "identity-obj-proxy": "^3.0.0", "jest": "26.6.3", diff --git a/admin/src/components/UserQuery.spec.js b/admin/src/components/UserQuery.spec.js index 2d6a6c554..92fdd2413 100644 --- a/admin/src/components/UserQuery.spec.js +++ b/admin/src/components/UserQuery.spec.js @@ -1,6 +1,5 @@ import { mount } from '@vue/test-utils' import UserQuery from './UserQuery' -import flushPromises from 'flush-promises' const localVue = global.localVue @@ -30,12 +29,10 @@ describe('TransactionLinkList', () => { expect(wrapper.find('input.test-input-criteria').exists()).toBe(true) }) - describe('has', () => { + describe('set value', () => { beforeEach(async () => { + jest.clearAllMocks() await wrapper.find('input.test-input-criteria').setValue('Test2') - await wrapper.find('input.test-input-criteria').trigger('blur') - await flushPromises() - await wrapper.vm.$nextTick() }) it('emits input', () => { diff --git a/admin/yarn.lock b/admin/yarn.lock index 6f9a999ce..c270b80bf 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -6423,11 +6423,6 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== -flush-promises@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flush-promises/-/flush-promises-1.0.2.tgz#4948fd58f15281fed79cbafc86293d5bb09b2ced" - integrity sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA== - flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" From d82a994eebc93d72c2aa831db6254b50bf0a5a78 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 24 Jul 2023 14:58:43 +0200 Subject: [PATCH 66/72] remove isAdmin from guards, simplify logic --- admin/src/graphql/searchUsers.js | 1 - admin/src/router/guards.js | 6 ++---- admin/src/router/guards.test.js | 11 ++++------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/admin/src/graphql/searchUsers.js b/admin/src/graphql/searchUsers.js index 315b2e1aa..9f82452c3 100644 --- a/admin/src/graphql/searchUsers.js +++ b/admin/src/graphql/searchUsers.js @@ -26,7 +26,6 @@ export const searchUsers = gql` hasElopage emailConfirmationSend deletedAt - # isAdmin roles } } diff --git a/admin/src/router/guards.js b/admin/src/router/guards.js index 3780c6ee1..79e381478 100644 --- a/admin/src/router/guards.js +++ b/admin/src/router/guards.js @@ -13,10 +13,8 @@ const addNavigationGuards = (router, store, apollo, i18n) => { }) .then((result) => { const moderator = result.data.verifyLogin - if (moderator.roles.includes('ADMIN', 0) || moderator.roles.includes('MODERATOR', 0)) { + if (moderator.roles?.length) { i18n.locale = moderator.language - moderator.isAdmin = moderator.roles.includes('ADMIN', 0) - moderator.isModerator = moderator.roles.includes('MODERATOR', 0) store.commit('moderator', moderator) next({ path: '/' }) } else { @@ -37,7 +35,7 @@ const addNavigationGuards = (router, store, apollo, i18n) => { !CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes (!store.state.token || // we do not have a token !store.state.moderator || // no moderator set in store - !(store.state.moderator.isAdmin || store.state.moderator.isModerator)) && // user is no admin + !store.state.moderator.roles.length) && // user is no admin to.path !== '/not-found' && // we are not on `not-found` to.path !== '/logout' // we are not on `logout` ) { diff --git a/admin/src/router/guards.test.js b/admin/src/router/guards.test.js index 19a1881ef..6d51fda6d 100644 --- a/admin/src/router/guards.test.js +++ b/admin/src/router/guards.test.js @@ -54,8 +54,6 @@ describe('navigation guards', () => { it('commits the moderator to the store', () => { expect(storeCommitMock).toBeCalledWith('moderator', { roles: ['ADMIN'], - isAdmin: true, - isModerator: false, language: 'de', }) }) @@ -90,8 +88,6 @@ describe('navigation guards', () => { it('commits the moderator to the store', () => { expect(storeCommitMock).toBeCalledWith('moderator', { roles: ['MODERATOR'], - isAdmin: false, - isModerator: true, language: 'de', }) }) @@ -103,6 +99,7 @@ describe('navigation guards', () => { describe('with valid token and no roles', () => { beforeEach(() => { + jest.clearAllMocks() apolloQueryMock.mockResolvedValue({ data: { verifyLogin: { @@ -119,7 +116,7 @@ describe('navigation guards', () => { }) it('does not commit the moderator to the store', () => { - expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false }) + expect(storeCommitMock).not.toBeCalledWith('moderator') }) it('redirects to /not-found', async () => { @@ -178,14 +175,14 @@ describe('navigation guards', () => { it('does not redirect with token in store and as admin', () => { store.state.token = 'valid token' - store.state.moderator = { isAdmin: true } + store.state.moderator = { roles: ['ADMIN'] } navGuard({ path: '/' }, {}, next) expect(next).toBeCalledWith() }) it('does not redirect with token in store and as moderator', () => { store.state.token = 'valid token' - store.state.moderator = { isModerator: true } + store.state.moderator = { roles: ['MODERATOR'] } navGuard({ path: '/' }, {}, next) expect(next).toBeCalledWith() }) From a1d12c3c682b8250608302c1f804dbc23f87f7b1 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 24 Jul 2023 15:40:05 +0200 Subject: [PATCH 67/72] do not pass USER role by update role. Simplify logic --- .../components/ChangeUserRoleFormular.spec.js | 54 +++++++++---------- .../src/components/ChangeUserRoleFormular.vue | 43 ++++++++------- .../components/Tables/SearchUserTable.spec.js | 7 ++- .../src/components/Tables/SearchUserTable.vue | 6 +-- admin/src/pages/CreationConfirm.spec.js | 2 - admin/src/pages/UserSearch.spec.js | 12 ++--- admin/src/pages/UserSearch.vue | 6 +-- 7 files changed, 62 insertions(+), 68 deletions(-) diff --git a/admin/src/components/ChangeUserRoleFormular.spec.js b/admin/src/components/ChangeUserRoleFormular.spec.js index 51a558d60..42f859733 100644 --- a/admin/src/components/ChangeUserRoleFormular.spec.js +++ b/admin/src/components/ChangeUserRoleFormular.spec.js @@ -46,7 +46,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - roles: ['USER'], + roles: [], }, } wrapper = Wrapper() @@ -62,7 +62,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 0, - roles: ['USER'], + roles: ['ADMIN'], }, } wrapper = Wrapper() @@ -89,7 +89,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - roles: ['USER'], + roles: [], }, } wrapper = Wrapper() @@ -121,7 +121,7 @@ describe('ChangeUserRoleFormular', () => { beforeEach(() => { apolloMutateMock.mockResolvedValue({ data: { - setUserRole: new Date(), + setUserRole: 'ADMIN', }, }) propsData = { @@ -135,7 +135,7 @@ describe('ChangeUserRoleFormular', () => { }) it('has selected option set to "usual user"', () => { - expect(wrapper.find('select.role-select').element.value).toBe('user') + expect(wrapper.find('select.role-select').element.value).toBe('USER') }) describe('change select to', () => { @@ -188,13 +188,13 @@ describe('ChangeUserRoleFormular', () => { ) }) - it('emits "updateIsAdmin" with role moderator', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( + it('emits "updateRoles" with role moderator', () => { + expect(wrapper.emitted('updateRoles')).toEqual( expect.arrayContaining([ expect.arrayContaining([ { userId: 1, - role: 'MODERATOR', + roles: ['MODERATOR'], }, ]), ]), @@ -259,13 +259,13 @@ describe('ChangeUserRoleFormular', () => { ) }) - it('emits "updateIsAdmin" with role moderator', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( + it('emits "updateRoles" with role moderator', () => { + expect(wrapper.emitted('updateRoles')).toEqual( expect.arrayContaining([ expect.arrayContaining([ { userId: 1, - role: 'ADMIN', + roles: ['ADMIN'], }, ]), ]), @@ -296,6 +296,7 @@ describe('ChangeUserRoleFormular', () => { describe('user has role "moderator"', () => { beforeEach(() => { + jest.clearAllMocks() apolloMutateMock.mockResolvedValue({ data: { setUserRole: null, @@ -311,8 +312,8 @@ describe('ChangeUserRoleFormular', () => { rolesToSelect = wrapper.find('select.role-select').findAll('option') }) - it('has selected option set to "moderator"', () => { - expect(wrapper.find('select.role-select').element.value).toBe('moderator') + it('has selected option set to "MODERATOR"', () => { + expect(wrapper.find('select.role-select').element.value).toBe('MODERATOR') }) describe('change select to', () => { @@ -323,7 +324,6 @@ describe('ChangeUserRoleFormular', () => { it('does not call the API', () => { rolesToSelect.at(1).setSelected() - // TODO: Fix this expect(apolloMutateMock).not.toHaveBeenCalled() }) }) @@ -366,13 +366,13 @@ describe('ChangeUserRoleFormular', () => { ) }) - it('emits "updateIsAdmin"', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( + it('emits "updateRoles"', () => { + expect(wrapper.emitted('updateRoles')).toEqual( expect.arrayContaining([ expect.arrayContaining([ { userId: 1, - role: 'USER', + roles: [], }, ]), ]), @@ -437,13 +437,13 @@ describe('ChangeUserRoleFormular', () => { ) }) - it('emits "updateIsAdmin"', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( + it('emits "updateRoles"', () => { + expect(wrapper.emitted('updateRoles')).toEqual( expect.arrayContaining([ expect.arrayContaining([ { userId: 1, - role: 'ADMIN', + roles: ['ADMIN'], }, ]), ]), @@ -490,7 +490,7 @@ describe('ChangeUserRoleFormular', () => { }) it('has selected option set to "admin"', () => { - expect(wrapper.find('select.role-select').element.value).toBe('admin') + expect(wrapper.find('select.role-select').element.value).toBe('ADMIN') }) describe('change select to', () => { @@ -544,13 +544,13 @@ describe('ChangeUserRoleFormular', () => { ) }) - it('emits "updateIsAdmin"', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( + it('emits "updateRoles"', () => { + expect(wrapper.emitted('updateRoles')).toEqual( expect.arrayContaining([ expect.arrayContaining([ { userId: 1, - role: 'USER', + roles: [], }, ]), ]), @@ -615,13 +615,13 @@ describe('ChangeUserRoleFormular', () => { ) }) - it('emits "updateIsAdmin"', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( + it('emits "updateRoles"', () => { + expect(wrapper.emitted('updateRoles')).toEqual( expect.arrayContaining([ expect.arrayContaining([ { userId: 1, - role: 'MODERATOR', + roles: ['MODERATOR'], }, ]), ]), diff --git a/admin/src/components/ChangeUserRoleFormular.vue b/admin/src/components/ChangeUserRoleFormular.vue index 840410931..7f048d0e2 100644 --- a/admin/src/components/ChangeUserRoleFormular.vue +++ b/admin/src/components/ChangeUserRoleFormular.vue @@ -1,7 +1,10 @@