From b14911d3147515beded27c440a11692529792c2f Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 9 Mar 2023 11:05:03 +0100 Subject: [PATCH 1/8] logout event --- backend/src/event/EVENT_LOGOUT.ts | 6 ++++++ backend/src/event/Event.ts | 1 + backend/src/event/EventType.ts | 1 + backend/src/graphql/resolver/UserResolver.ts | 13 ++++--------- 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 backend/src/event/EVENT_LOGOUT.ts diff --git a/backend/src/event/EVENT_LOGOUT.ts b/backend/src/event/EVENT_LOGOUT.ts new file mode 100644 index 000000000..1e423359c --- /dev/null +++ b/backend/src/event/EVENT_LOGOUT.ts @@ -0,0 +1,6 @@ +import { User as DbUser } from '@entity/User' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_LOGOUT = async (user: DbUser): Promise => + Event(EventType.LOGOUT, user, user).save() diff --git a/backend/src/event/Event.ts b/backend/src/event/Event.ts index cdb05748c..19fbc81cd 100644 --- a/backend/src/event/Event.ts +++ b/backend/src/event/Event.ts @@ -53,6 +53,7 @@ export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE' export { EVENT_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_CONTRIBUTION_MESSAGE_CREATE' export { EVENT_CONTRIBUTION_LINK_REDEEM } from './EVENT_CONTRIBUTION_LINK_REDEEM' export { EVENT_LOGIN } from './EVENT_LOGIN' +export { EVENT_LOGOUT } from './EVENT_LOGOUT' export { EVENT_REGISTER } from './EVENT_REGISTER' export { EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL } from './EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL' export { EVENT_SEND_CONFIRMATION_EMAIL } from './EVENT_SEND_CONFIRMATION_EMAIL' diff --git a/backend/src/event/EventType.ts b/backend/src/event/EventType.ts index 47056f05e..df4a5cc75 100644 --- a/backend/src/event/EventType.ts +++ b/backend/src/event/EventType.ts @@ -17,6 +17,7 @@ export enum EventType { CONTRIBUTION_MESSAGE_CREATE = 'CONTRIBUTION_MESSAGE_CREATE', CONTRIBUTION_LINK_REDEEM = 'CONTRIBUTION_LINK_REDEEM', LOGIN = 'LOGIN', + LOGOUT = 'LOGOUT', REGISTER = 'REGISTER', REDEEM_REGISTER = 'REDEEM_REGISTER', SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTIREGISTRATION_EMAIL', diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 2cd40938f..639f59c09 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -57,6 +57,7 @@ import { EVENT_REGISTER, EVENT_ACTIVATE_ACCOUNT, EVENT_ADMIN_SEND_CONFIRMATION_EMAIL, + EVENT_LOGOUT, } from '@/event/Event' import { getUserCreations } from './util/creations' import { isValidPassword } from '@/password/EncryptorUtils' @@ -185,15 +186,9 @@ export class UserResolver { @Authorized([RIGHTS.LOGOUT]) @Mutation(() => String) - async logout(): Promise { - // TODO: Event still missing here!! - // TODO: We dont need this anymore, but might need this in the future in oder to invalidate a valid JWT-Token. - // Furthermore this hook can be useful for tracking user behaviour (did he logout or not? Warn him if he didn't on next login) - // The functionality is fully client side - the client just needs to delete his token with the current implementation. - // we could try to force this by sending `token: null` or `token: ''` with this call. But since it bares no real security - // we should just return true for now. - logger.info('Logout...') - // remove user.pubKey from logger-context to ensure a correct filter on log-messages belonging to the same user + async logout(@Ctx() context: Context): Promise { + await EVENT_LOGOUT(getUser(context)) + // remove user from logger context logger.addContext('user', 'unknown') return true } From feeeca9fe4def82daeafba6ed3af59294019065c Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 9 Mar 2023 11:05:41 +0100 Subject: [PATCH 2/8] corrected return type --- 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 639f59c09..a65049252 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -185,7 +185,7 @@ export class UserResolver { } @Authorized([RIGHTS.LOGOUT]) - @Mutation(() => String) + @Mutation(() => Boolean) async logout(@Ctx() context: Context): Promise { await EVENT_LOGOUT(getUser(context)) // remove user from logger context From 0265834744f546f6804f8150de98f8dc5944c37a Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 9 Mar 2023 11:33:53 +0100 Subject: [PATCH 3/8] events for forgot password email and user info update --- .../src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts | 6 ++++ backend/src/event/EVENT_USER_INFO_UPDATE.ts | 6 ++++ backend/src/event/Event.ts | 1 + backend/src/event/EventType.ts | 2 ++ backend/src/graphql/resolver/UserResolver.ts | 34 +++++++++---------- 5 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 backend/src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts create mode 100644 backend/src/event/EVENT_USER_INFO_UPDATE.ts diff --git a/backend/src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts b/backend/src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts new file mode 100644 index 000000000..4160ce244 --- /dev/null +++ b/backend/src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts @@ -0,0 +1,6 @@ +import { User as DbUser } from '@entity/User' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_EMAIL_FORGOT_PASSWORD = async (user: DbUser): Promise => + Event(EventType.EMAIL_FORGOT_PASSWORD, user, user).save() diff --git a/backend/src/event/EVENT_USER_INFO_UPDATE.ts b/backend/src/event/EVENT_USER_INFO_UPDATE.ts new file mode 100644 index 000000000..681ecd473 --- /dev/null +++ b/backend/src/event/EVENT_USER_INFO_UPDATE.ts @@ -0,0 +1,6 @@ +import { User as DbUser } from '@entity/User' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_USER_INFO_UPDATE = async (user: DbUser): Promise => + Event(EventType.USER_INFO_UPDATE, user, user).save() diff --git a/backend/src/event/Event.ts b/backend/src/event/Event.ts index 19fbc81cd..60da91b70 100644 --- a/backend/src/event/Event.ts +++ b/backend/src/event/Event.ts @@ -52,6 +52,7 @@ export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE' export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE' export { EVENT_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_CONTRIBUTION_MESSAGE_CREATE' export { EVENT_CONTRIBUTION_LINK_REDEEM } from './EVENT_CONTRIBUTION_LINK_REDEEM' +export { EVENT_EMAIL_FORGOT_PASSWORD } from './EVENT_EMAIL_FORGOT_PASSWORD' export { EVENT_LOGIN } from './EVENT_LOGIN' export { EVENT_LOGOUT } from './EVENT_LOGOUT' export { EVENT_REGISTER } from './EVENT_REGISTER' diff --git a/backend/src/event/EventType.ts b/backend/src/event/EventType.ts index df4a5cc75..cc277e589 100644 --- a/backend/src/event/EventType.ts +++ b/backend/src/event/EventType.ts @@ -16,6 +16,7 @@ export enum EventType { CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE', CONTRIBUTION_MESSAGE_CREATE = 'CONTRIBUTION_MESSAGE_CREATE', CONTRIBUTION_LINK_REDEEM = 'CONTRIBUTION_LINK_REDEEM', + EMAIL_FORGOT_PASSWORD = 'EMAIL_FORGOT_PASSWORD', LOGIN = 'LOGIN', LOGOUT = 'LOGOUT', REGISTER = 'REGISTER', @@ -27,6 +28,7 @@ export enum EventType { TRANSACTION_LINK_CREATE = 'TRANSACTION_LINK_CREATE', TRANSACTION_LINK_DELETE = 'TRANSACTION_LINK_DELETE', TRANSACTION_LINK_REDEEM = 'TRANSACTION_LINK_REDEEM', + USER_INFO_UPDATE = 'USER_INFO_UPDATE', // VISIT_GRADIDO = 'VISIT_GRADIDO', // VERIFY_REDEEM = 'VERIFY_REDEEM', // INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT', diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index a65049252..196110a31 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -58,6 +58,8 @@ import { EVENT_ACTIVATE_ACCOUNT, EVENT_ADMIN_SEND_CONFIRMATION_EMAIL, EVENT_LOGOUT, + EVENT_EMAIL_FORGOT_PASSWORD, + EVENT_USER_INFO_UPDATE, } from '@/event/Event' import { getUserCreations } from './util/creations' import { isValidPassword } from '@/password/EncryptorUtils' @@ -402,6 +404,7 @@ export class UserResolver { ) } logger.info(`forgotPassword(${email}) successful...`) + await EVENT_EMAIL_FORGOT_PASSWORD(user) return true } @@ -464,8 +467,6 @@ export class UserResolver { await queryRunner.commitTransaction() logger.info('User and UserContact data written successfully...') - - await EVENT_ACTIVATE_ACCOUNT(user) } catch (e) { await queryRunner.rollbackTransaction() throw new LogError('Error on writing User and User Contact data', e) @@ -483,13 +484,9 @@ export class UserResolver { ) } catch (e) { logger.error('Error subscribing to klicktipp', e) - // TODO is this a problem? - // eslint-disable-next-line no-console - /* uncomment this, when you need the activation link on the console - console.log('Could not subscribe to klicktipp') - */ } } + await EVENT_ACTIVATE_ACCOUNT(user) return true } @@ -526,21 +523,21 @@ export class UserResolver { @Ctx() context: Context, ): Promise { logger.info(`updateUserInfos(${firstName}, ${lastName}, ${language}, ***, ***)...`) - const userEntity = getUser(context) + const user = getUser(context) if (firstName) { - userEntity.firstName = firstName + user.firstName = firstName } if (lastName) { - userEntity.lastName = lastName + user.lastName = lastName } if (language) { if (!isLanguage(language)) { throw new LogError('Given language is not a valid language', language) } - userEntity.language = language + user.language = language i18n.setLocale(language) } @@ -552,22 +549,22 @@ export class UserResolver { ) } - if (!verifyPassword(userEntity, password)) { + if (!verifyPassword(user, password)) { throw new LogError(`Old password is invalid`) } // Save new password hash and newly encrypted private key - userEntity.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID - userEntity.password = encryptPassword(userEntity, passwordNew) + user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID + user.password = encryptPassword(user, passwordNew) } // Save hideAmountGDD value if (hideAmountGDD !== undefined) { - userEntity.hideAmountGDD = hideAmountGDD + user.hideAmountGDD = hideAmountGDD } // Save hideAmountGDT value if (hideAmountGDT !== undefined) { - userEntity.hideAmountGDT = hideAmountGDT + user.hideAmountGDT = hideAmountGDT } const queryRunner = getConnection().createQueryRunner() @@ -575,7 +572,7 @@ export class UserResolver { await queryRunner.startTransaction('REPEATABLE READ') try { - await queryRunner.manager.save(userEntity).catch((error) => { + await queryRunner.manager.save(user).catch((error) => { throw new LogError('Error saving user', error) }) @@ -588,6 +585,9 @@ export class UserResolver { await queryRunner.release() } logger.info('updateUserInfos() successfully finished...') + + await EVENT_USER_INFO_UPDATE(user) + return true } From 1792d10733cedb62fccd1e5daab70b2ef6525096 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 9 Mar 2023 11:53:10 +0100 Subject: [PATCH 4/8] more events for the user resolver --- backend/src/event/EVENT_ADMIN_USER_DELETE.ts | 6 ++++++ backend/src/event/EVENT_ADMIN_USER_ROLE_SET.ts | 8 ++++++++ backend/src/event/EVENT_ADMIN_USER_UNDELETE.ts | 8 ++++++++ .../src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts | 2 +- backend/src/event/Event.ts | 4 ++++ backend/src/event/EventType.ts | 3 +++ backend/src/graphql/resolver/UserResolver.ts | 17 +++++++++++------ 7 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 backend/src/event/EVENT_ADMIN_USER_DELETE.ts create mode 100644 backend/src/event/EVENT_ADMIN_USER_ROLE_SET.ts create mode 100644 backend/src/event/EVENT_ADMIN_USER_UNDELETE.ts diff --git a/backend/src/event/EVENT_ADMIN_USER_DELETE.ts b/backend/src/event/EVENT_ADMIN_USER_DELETE.ts new file mode 100644 index 000000000..bfd5be740 --- /dev/null +++ b/backend/src/event/EVENT_ADMIN_USER_DELETE.ts @@ -0,0 +1,6 @@ +import { User as DbUser } from '@entity/User' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_ADMIN_USER_DELETE = async (user: DbUser, moderator: DbUser): Promise => + Event(EventType.ADMIN_USER_DELETE, user, moderator).save() diff --git a/backend/src/event/EVENT_ADMIN_USER_ROLE_SET.ts b/backend/src/event/EVENT_ADMIN_USER_ROLE_SET.ts new file mode 100644 index 000000000..3be825ad4 --- /dev/null +++ b/backend/src/event/EVENT_ADMIN_USER_ROLE_SET.ts @@ -0,0 +1,8 @@ +import { User as DbUser } from '@entity/User' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_ADMIN_USER_ROLE_SET = async ( + user: DbUser, + moderator: DbUser, +): Promise => Event(EventType.ADMIN_USER_ROLE_SET, user, moderator).save() diff --git a/backend/src/event/EVENT_ADMIN_USER_UNDELETE.ts b/backend/src/event/EVENT_ADMIN_USER_UNDELETE.ts new file mode 100644 index 000000000..eb861dbf1 --- /dev/null +++ b/backend/src/event/EVENT_ADMIN_USER_UNDELETE.ts @@ -0,0 +1,8 @@ +import { User as DbUser } from '@entity/User' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_ADMIN_USER_UNDELETE = async ( + user: DbUser, + moderator: DbUser, +): Promise => Event(EventType.ADMIN_USER_UNDELETE, user, moderator).save() diff --git a/backend/src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts b/backend/src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts index 4160ce244..f7e328369 100644 --- a/backend/src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts +++ b/backend/src/event/EVENT_EMAIL_FORGOT_PASSWORD.ts @@ -3,4 +3,4 @@ import { Event as DbEvent } from '@entity/Event' import { Event, EventType } from './Event' export const EVENT_EMAIL_FORGOT_PASSWORD = async (user: DbUser): Promise => - Event(EventType.EMAIL_FORGOT_PASSWORD, user, user).save() + Event(EventType.EMAIL_FORGOT_PASSWORD, user, { id: 0 } as DbUser).save() diff --git a/backend/src/event/Event.ts b/backend/src/event/Event.ts index 60da91b70..901bc33ff 100644 --- a/backend/src/event/Event.ts +++ b/backend/src/event/Event.ts @@ -47,6 +47,9 @@ export { EVENT_ADMIN_CONTRIBUTION_LINK_DELETE } from './EVENT_ADMIN_CONTRIBUTION export { EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE' export { EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE' export { EVENT_ADMIN_SEND_CONFIRMATION_EMAIL } from './EVENT_ADMIN_SEND_CONFIRMATION_EMAIL' +export { EVENT_ADMIN_USER_DELETE } from './EVENT_ADMIN_USER_DELETE' +export { EVENT_ADMIN_USER_UNDELETE } from './EVENT_ADMIN_USER_UNDELETE' +export { EVENT_ADMIN_USER_ROLE_SET } from './EVENT_ADMIN_USER_ROLE_SET' export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE' export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE' export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE' @@ -63,3 +66,4 @@ export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE' export { EVENT_TRANSACTION_LINK_CREATE } from './EVENT_TRANSACTION_LINK_CREATE' export { EVENT_TRANSACTION_LINK_DELETE } from './EVENT_TRANSACTION_LINK_DELETE' export { EVENT_TRANSACTION_LINK_REDEEM } from './EVENT_TRANSACTION_LINK_REDEEM' +export { EVENT_USER_INFO_UPDATE } from './EVENT_USER_INFO_UPDATE' diff --git a/backend/src/event/EventType.ts b/backend/src/event/EventType.ts index cc277e589..58d03c84b 100644 --- a/backend/src/event/EventType.ts +++ b/backend/src/event/EventType.ts @@ -11,6 +11,9 @@ export enum EventType { ADMIN_CONTRIBUTION_LINK_UPDATE = 'ADMIN_CONTRIBUTION_LINK_UPDATE', ADMIN_CONTRIBUTION_MESSAGE_CREATE = 'ADMIN_CONTRIBUTION_MESSAGE_CREATE', ADMIN_SEND_CONFIRMATION_EMAIL = 'ADMIN_SEND_CONFIRMATION_EMAIL', + ADMIN_USER_DELETE = 'ADMIN_USER_DELETE', + ADMIN_USER_UNDELETE = 'ADMIN_USER_UNDELETE', + ADMIN_USER_ROLE_SET = 'ADMIN_USER_ROLE_SET', CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE', CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE', CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE', diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 196110a31..f51d0fdac 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -60,6 +60,9 @@ import { EVENT_LOGOUT, EVENT_EMAIL_FORGOT_PASSWORD, EVENT_USER_INFO_UPDATE, + EVENT_USER_ROLE_SET, + EVENT_ADMIN_USER_ROLE_SET, + EVENT_ADMIN_USER_DELETE, } from '@/event/Event' import { getUserCreations } from './util/creations' import { isValidPassword } from '@/password/EncryptorUtils' @@ -585,7 +588,6 @@ export class UserResolver { await queryRunner.release() } logger.info('updateUserInfos() successfully finished...') - await EVENT_USER_INFO_UPDATE(user) return true @@ -713,8 +715,8 @@ export class UserResolver { throw new LogError('Could not find user with given ID', userId) } // administrator user changes own role? - const moderatorUser = getUser(context) - if (moderatorUser.id === userId) { + const moderator = getUser(context) + if (moderator.id === userId) { throw new LogError('Administrator can not change his own role') } // change isAdmin @@ -735,6 +737,7 @@ export class UserResolver { break } await user.save() + await EVENT_ADMIN_USER_ROLE_SET(user, moderator) const newUser = await DbUser.findOne({ id: userId }) return newUser ? newUser.isAdmin : null } @@ -751,19 +754,20 @@ export class UserResolver { throw new LogError('Could not find user with given ID', userId) } // moderator user disabled own account? - const moderatorUser = getUser(context) - if (moderatorUser.id === userId) { + const moderator = getUser(context) + if (moderator.id === userId) { throw new LogError('Moderator can not delete his own account') } // soft-delete user await user.softRemove() + await EVENT_ADMIN_USER_DELETE(user, moderator) const newUser = await DbUser.findOne({ id: userId }, { withDeleted: true }) return newUser ? newUser.deletedAt : null } @Authorized([RIGHTS.ADMIN_UNDELETE_USER]) @Mutation(() => Date, { nullable: true }) - async unDeleteUser(@Arg('userId', () => Int) userId: number): Promise { + async unDeleteUser(@Arg('userId', () => Int) userId: number, @Ctx() context: Context,): Promise { const user = await DbUser.findOne({ id: userId }, { withDeleted: true }) if (!user) { throw new LogError('Could not find user with given ID', userId) @@ -772,6 +776,7 @@ export class UserResolver { throw new LogError('User is not deleted') } await user.recover() + await EVENT_ADMIN_USER_UNDELETE(user, getUser(context)) return null } From dfbfefd73af3b6ffd7cc874ee0bd1682c23cf4da Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 9 Mar 2023 11:54:14 +0100 Subject: [PATCH 5/8] always write send confirmation event --- backend/src/graphql/resolver/UserResolver.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index f51d0fdac..1d80c650a 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -63,6 +63,7 @@ import { EVENT_USER_ROLE_SET, EVENT_ADMIN_USER_ROLE_SET, EVENT_ADMIN_USER_DELETE, + EVENT_ADMIN_USER_UNDELETE, } from '@/event/Event' import { getUserCreations } from './util/creations' import { isValidPassword } from '@/password/EncryptorUtils' @@ -767,7 +768,10 @@ export class UserResolver { @Authorized([RIGHTS.ADMIN_UNDELETE_USER]) @Mutation(() => Date, { nullable: true }) - async unDeleteUser(@Arg('userId', () => Int) userId: number, @Ctx() context: Context,): Promise { + async unDeleteUser( + @Arg('userId', () => Int) userId: number, + @Ctx() context: Context, + ): Promise { const user = await DbUser.findOne({ id: userId }, { withDeleted: true }) if (!user) { throw new LogError('Could not find user with given ID', userId) @@ -810,9 +814,8 @@ export class UserResolver { // In case EMails are disabled log the activation link for the user if (!emailSent) { logger.info(`Account confirmation link: ${activationLink}`) - } else { - await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user, getUser(context)) } + await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user, getUser(context)) return true } From ab4e1cfe1f07c898eae8f369f4cfb01a719a865a Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 9 Mar 2023 11:55:25 +0100 Subject: [PATCH 6/8] fixed imports --- backend/src/graphql/resolver/UserResolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 1d80c650a..13fb74fd5 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -60,7 +60,6 @@ import { EVENT_LOGOUT, EVENT_EMAIL_FORGOT_PASSWORD, EVENT_USER_INFO_UPDATE, - EVENT_USER_ROLE_SET, EVENT_ADMIN_USER_ROLE_SET, EVENT_ADMIN_USER_DELETE, EVENT_ADMIN_USER_UNDELETE, From e79130ea868198680bd30b9a085f52d143e61e7b Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 9 Mar 2023 12:19:53 +0100 Subject: [PATCH 7/8] test all events --- .../src/graphql/resolver/UserResolver.test.ts | 98 ++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index b382b2627..62eea9c0e 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -857,11 +857,25 @@ describe('UserResolver', () => { it('returns true', async () => { await expect(mutate({ mutation: logout })).resolves.toEqual( expect.objectContaining({ - data: { logout: 'true' }, + data: { logout: true }, errors: undefined, }), ) }) + + it('stores the LOGOUT event in the database', async () => { + const userConatct = await UserContact.findOneOrFail( + { email: 'bibi@bloxberg.de' }, + { relations: ['user'] }, + ) + expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.LOGOUT, + affectedUserId: userConatct.user.id, + actingUserId: userConatct.user.id, + }), + ) + }) }) }) @@ -1017,6 +1031,20 @@ describe('UserResolver', () => { }), }) }) + + it('stores the EMAIL_FORGOT_PASSWORD event in the database', async () => { + const userConatct = await UserContact.findOneOrFail( + { email: 'bibi@bloxberg.de' }, + { relations: ['user'] }, + ) + expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.EMAIL_FORGOT_PASSWORD, + affectedUserId: userConatct.user.id, + actingUserId: 0, + }), + ) + }) }) describe('request reset password again', () => { @@ -1141,6 +1169,20 @@ describe('UserResolver', () => { }), ) }) + + it('stores the USER_INFO_UPDATE event in the database', async () => { + const userConatct = await UserContact.findOneOrFail( + { email: 'bibi@bloxberg.de' }, + { relations: ['user'] }, + ) + expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.USER_INFO_UPDATE, + affectedUserId: userConatct.user.id, + actingUserId: userConatct.user.id, + }), + ) + }) }) describe('language is not valid', () => { @@ -1511,6 +1553,24 @@ describe('UserResolver', () => { ) expect(new Date(result.data.setUserRole)).toEqual(expect.any(Date)) }) + + it('stores the ADMIN_USER_ROLE_SET event in the database', async () => { + const userConatct = await UserContact.findOneOrFail( + { email: 'bibi@bloxberg.de' }, + { relations: ['user'] }, + ) + const adminConatct = await UserContact.findOneOrFail( + { email: 'peter@lustig.de' }, + { relations: ['user'] }, + ) + expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.ADMIN_USER_ROLE_SET, + affectedUserId: userConatct.user.id, + actingUserId: adminConatct.user.id, + }), + ) + }) }) describe('to usual user', () => { @@ -1696,6 +1756,24 @@ describe('UserResolver', () => { expect(new Date(result.data.deleteUser)).toEqual(expect.any(Date)) }) + it('stores the ADMIN_USER_DELETE event in the database', async () => { + const userConatct = await UserContact.findOneOrFail( + { email: 'bibi@bloxberg.de' }, + { relations: ['user'], withDeleted: true }, + ) + const adminConatct = await UserContact.findOneOrFail( + { email: 'peter@lustig.de' }, + { relations: ['user'] }, + ) + expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.ADMIN_USER_DELETE, + affectedUserId: userConatct.user.id, + actingUserId: adminConatct.user.id, + }), + ) + }) + describe('delete deleted user', () => { it('throws an error', async () => { jest.clearAllMocks() @@ -1971,6 +2049,24 @@ describe('UserResolver', () => { }), ) }) + + it('stores the ADMIN_USER_UNDELETE event in the database', async () => { + const userConatct = await UserContact.findOneOrFail( + { email: 'bibi@bloxberg.de' }, + { relations: ['user'] }, + ) + const adminConatct = await UserContact.findOneOrFail( + { email: 'peter@lustig.de' }, + { relations: ['user'] }, + ) + expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.ADMIN_USER_UNDELETE, + affectedUserId: userConatct.user.id, + actingUserId: adminConatct.user.id, + }), + ) + }) }) }) }) From f6071d6e078620429472b83875c4ec3222e8f638 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 28 Mar 2023 00:25:14 +0200 Subject: [PATCH 8/8] fix linting --- backend/src/graphql/resolver/UserResolver.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index b30e82f13..205f27e94 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -873,7 +873,7 @@ describe('UserResolver', () => { { email: 'bibi@bloxberg.de' }, { relations: ['user'] }, ) - expect(DbEvent.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventType.LOGOUT, affectedUserId: userConatct.user.id, @@ -1042,7 +1042,7 @@ describe('UserResolver', () => { { email: 'bibi@bloxberg.de' }, { relations: ['user'] }, ) - expect(DbEvent.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventType.EMAIL_FORGOT_PASSWORD, affectedUserId: userConatct.user.id, @@ -1180,7 +1180,7 @@ describe('UserResolver', () => { { email: 'bibi@bloxberg.de' }, { relations: ['user'] }, ) - expect(DbEvent.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventType.USER_INFO_UPDATE, affectedUserId: userConatct.user.id, @@ -1568,7 +1568,7 @@ describe('UserResolver', () => { { email: 'peter@lustig.de' }, { relations: ['user'] }, ) - expect(DbEvent.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventType.ADMIN_USER_ROLE_SET, affectedUserId: userConatct.user.id, @@ -1770,7 +1770,7 @@ describe('UserResolver', () => { { email: 'peter@lustig.de' }, { relations: ['user'] }, ) - expect(DbEvent.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventType.ADMIN_USER_DELETE, affectedUserId: userConatct.user.id, @@ -2064,7 +2064,7 @@ describe('UserResolver', () => { { email: 'peter@lustig.de' }, { relations: ['user'] }, ) - expect(DbEvent.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventType.ADMIN_USER_UNDELETE, affectedUserId: userConatct.user.id,