From fba79dba5f0d63dc9c8f89c000dd6531e06bc9fd Mon Sep 17 00:00:00 2001 From: Jeff Stirn <52271945+jfstirn@users.noreply.github.com> Date: Tue, 19 Dec 2023 20:00:34 +0100 Subject: [PATCH] User Access module implementation with the microsoft identity framework. --- .gitignore | 7 + .../CompanyName.MyMeetings.API.csproj | 14 +- .../CompanyName.MyMeetings.API.xml | 114 ++++++ .../HasPermissionAuthorizationHandler.cs | 4 +- .../PermissionAuthorizationHandler.cs | 61 ++++ .../ExecutionContextAccessor.cs | 3 + .../Extensions/ConfigurationExtensions.cs | 15 + .../Extensions/SwaggerExtensions.cs | 17 + .../AuthenticatedUserController.cs | 12 +- .../{ => IdentityServer}/EmailsController.cs | 6 +- .../RegisterNewUserRequest.cs | 2 +- .../ResourceOwnerPasswordValidator.cs | 6 +- .../UserAccessAutofacModule.cs | 6 +- .../UserRegistrationsController.cs | 8 +- .../ApplicationController.cs | 55 +++ .../AuthenticationController.cs | 233 ++++++++++++ .../AuthenticationMappingProfile.cs | 16 + .../Authentication/IdentityController.cs | 61 ++++ .../AuthenticationRequestValidator.cs | 14 + .../ResetPasswordRequestValidator.cs | 18 + .../Authorization/AuthorizationController.cs | 40 ++ .../AuthorizationMappingProfile.cs | 15 + .../MicrosoftIdentity/Results/ApiResult.cs | 110 ++++++ .../Results/ApiResultStatus.cs | 29 ++ .../MicrosoftIdentity/Results/ErrorMapper.cs | 64 ++++ .../Results/ResultExtensions.cs | 141 +++++++ .../Roles/RoleMappingProfile.cs | 16 + .../Roles/RolesController.cs | 130 +++++++ .../Validators/AddRoleRequestValidator.cs | 14 + .../Validators/RenameRoleRequestValidator.cs | 13 + .../SetRolePermissionsRequestValidator.cs | 13 + .../UserAccessAutofacModule.cs | 16 + .../Users/UserMappingProfile.cs | 18 + .../Users/UserRegistrationsController.cs | 49 +++ .../Users/UsersController.cs | 221 +++++++++++ .../ConfirmEmailAddressRequestValidator.cs | 14 + .../CreateUserAccountRequestValidator.cs | 14 + .../SetUserPermissionsRequestValidator.cs | 13 + .../SetUserRolesRequestValidator.cs | 13 + .../MicrosoftIdentity/UsersPermissions.cs | 28 ++ src/API/CompanyName.MyMeetings.API/Startup.cs | 112 +++++- .../appsettings.json | 47 ++- .../CompanyName.MyMeetings.Contracts.csproj | 9 + .../Results/ErrorMessage.cs | 14 + .../Results/Result.cs | 48 +++ .../Results/ResultOfT.cs | 19 + .../Authentication/AuthenticationRequest.cs | 8 + .../Authentication/AuthenticationResultDto.cs | 16 + .../Authentication/ResetPasswordRequest.cs | 17 + .../V1/Users/Authentication/TokenRequest.cs | 8 + .../V1/Users/Authentication/TokenResultDto.cs | 8 + .../V1/Users/Authorization/PermissionDto.cs | 10 + .../V1/Users/Identity/UserAccountDto.cs | 18 + .../V1/Users/Identity/UserPermissionDto.cs | 6 + .../V1/Users/Roles/AddRoleRequest.cs | 8 + .../V1/Users/Roles/PermissionDto.cs | 10 + .../V1/Users/Roles/RenameRoleRequest.cs | 6 + .../V1/Users/Roles/RoleDto.cs | 8 + .../Users/Roles/SetRolePermissionsRequest.cs | 6 + .../RegisterNewUserRequest.cs | 16 + .../Users/Users/ConfirmEmailAddressRequest.cs | 8 + .../Users/Users/CreateUserAccountRequest.cs | 19 + .../V1/Users/Users/PermissionDto.cs | 10 + .../V1/Users/Users/RoleDto.cs | 8 + .../Users/Users/SetUserPermissionsRequest.cs | 6 + .../V1/Users/Users/SetUserRolesRequest.cs | 6 + .../Users/Users/UpdateUserAccountRequest.cs | 10 + .../V1/Users/Users/UserAccountDto.cs | 71 ++++ .../Data/IDatabaseConfiguration.cs | 6 + .../Application/IExecutionContextAccessor.cs | 2 + src/BuildingBlocks/Domain/Entity.cs | 2 +- src/BuildingBlocks/Domain/IEntity.cs | 12 + .../Infrastructure/DatabaseConfiguration.cs | 13 + .../StronglyTypedIdValueConverterSelector.cs | 2 +- src/CompanyName.MyMeetings.sln | 75 +++- .../CompanyName.MyMeetings.Database.sqlproj | 23 ++ .../Scripts/CreateStructure.sql | 345 ++++++++++++++++++ .../Scripts/Seeds/0002_SeedPermissions.sql | 91 +++++ .../Scripts/Seeds/0003_SeedRoles.sql | 60 +++ .../Scripts/Seeds/0004_SeedUsers.sql | 48 +++ .../Structure/Security/Schemas.sql | 3 + .../usersmi/Tables/InboxMessages.sql | 10 + .../usersmi/Tables/InternalCommands.sql | 11 + .../usersmi/Tables/OutboxMessages.sql | 10 + .../Structure/usersmi/Tables/Permission.sql | 8 + .../Structure/usersmi/Tables/Role.sql | 11 + .../Structure/usersmi/Tables/RoleClaim.sql | 10 + .../Structure/usersmi/Tables/User.sql | 25 ++ .../Structure/usersmi/Tables/UserClaim.sql | 10 + .../Structure/usersmi/Tables/UserLogin.sql | 10 + .../usersmi/Tables/UserRefreshToken.sql | 13 + .../usersmi/Tables/UserRegistrations.sql | 15 + .../Structure/usersmi/Tables/UserRole.sql | 9 + .../Structure/usersmi/Tables/UserToken.sql | 10 + .../usersmi/Views/v_UserPermissions.sql | 10 + .../usersmi/Views/v_UserRegistrations.sql | 12 + .../Structure/usersmi/Views/v_UserRoles.sql | 8 + .../Structure/usersmi/Views/v_Users.sql | 11 + src/Directory.Build.props | 3 +- src/Directory.Build.targets | 1 + src/Directory.Packages.props | 5 +- ....Modules.Administration.Application.csproj | 2 +- ...ewUserRegisteredIntegrationEventHandler.cs | 2 +- .../EventsBus/EventsBusStartup.cs | 2 +- .../SeedWork/ExecutionContextMock.cs | 2 + ...etings.Modules.Meetings.Application.csproj | 4 +- ...ewUserRegisteredIntegrationEventHandler.cs | 2 +- .../EventsBus/EventsBusStartup.cs | 2 +- .../SeedWork/ExecutionContextMock.cs | 2 + ...etings.Modules.Payments.Application.csproj | 2 +- ...ewUserRegisteredIntegrationEventHandler.cs | 2 +- .../EventsBus/EventsBusStartup.cs | 2 +- .../SeedWork/ExecutionContextMock.cs | 2 + .../Authenticate/AuthenticateCommand.cs | 4 +- .../AuthenticateCommandHandler.cs | 6 +- .../AuthenticateCommandValidator.cs | 2 +- .../Authenticate/AuthenticationResult.cs | 2 +- .../Authentication/Authenticate/UserDto.cs | 2 +- .../Authentication/PasswordManager.cs | 2 +- .../GetAuthenticatedUserPermissionsQuery.cs | 6 +- ...uthenticatedUserPermissionsQueryHandler.cs | 6 +- .../GetUserPermissionsQuery.cs | 4 +- .../GetUserPermissionsQueryHandler.cs | 4 +- .../GetUserPermissions/UserPermissionDto.cs | 2 +- ...s.Modules.UserAccessIS.Application.csproj} | 3 - .../Configuration/Commands/ICommandHandler.cs | 4 +- .../Commands/ICommandsScheduler.cs | 4 +- .../Commands/InternalCommandBase.cs | 4 +- .../Configuration/Queries/IQueryHandler.cs | 4 +- .../Application/Contracts/CommandBase.cs | 2 +- .../Application/Contracts/CustomClaimTypes.cs | 2 +- .../Application/Contracts/ICommand.cs | 2 +- .../Application/Contracts/IQuery.cs | 2 +- .../Contracts/IRecurringCommand.cs | 2 +- .../Contracts/IUserAccessModule.cs | 2 +- .../Application/Contracts/QueryBase.cs | 2 +- .../UserAccess/Application/Contracts/Roles.cs | 2 +- .../UserAccess/Application/Emails/EmailDto.cs | 2 +- .../Application/Emails/GetAllEmailsQuery.cs | 4 +- .../Emails/GetAllEmailsQueryHandler.cs | 4 +- .../IdentityServer/IdentityServerConfig.cs | 4 +- .../IdentityServer/ProfileService.cs | 4 +- .../ConfirmUserRegistrationCommand.cs | 4 +- .../ConfirmUserRegistrationCommandHandler.cs | 6 +- .../UserRegistrationConfirmedHandler.cs | 8 +- .../GetUserRegistrationQuery.cs | 4 +- .../GetUserRegistrationQueryHandler.cs | 4 +- .../UserRegistrationDto.cs | 2 +- ...gisteredEnqueueEmailConfirmationHandler.cs | 6 +- .../NewUserRegisteredNotification.cs | 4 +- .../NewUserRegisteredPublishEventHandler.cs | 4 +- .../RegisterNewUser/RegisterNewUserCommand.cs | 4 +- .../RegisterNewUserCommandHandler.cs | 8 +- ...serRegistrationConfirmationEmailCommand.cs | 6 +- ...strationConfirmationEmailCommandHandler.cs | 4 +- .../UserRegistrations/UsersCounter.cs | 4 +- .../Users/AddAdminUser/AddAdminUserCommand.cs | 4 +- .../AddAdminUserCommandHandler.cs | 8 +- .../GetAuthenticatedUserQuery.cs | 6 +- .../GetAuthenticatedUserQueryHandler.cs | 6 +- .../Application/Users/GetUser/GetUserQuery.cs | 4 +- .../Users/GetUser/GetUserQueryHandler.cs | 4 +- .../Application/Users/GetUser/UserDto.cs | 2 +- ...etings.Modules.UserAccessIS.Domain.csproj} | 0 .../Events/NewUserRegisteredDomainEvent.cs | 2 +- .../UserRegistrationConfirmedDomainEvent.cs | 2 +- .../UserRegistrationExpiredDomainEvent.cs | 2 +- .../IUserRegistrationRepository.cs | 2 +- .../Domain/UserRegistrations/IUsersCounter.cs | 2 +- ...eatedWhenRegistrationIsNotConfirmedRule.cs | 2 +- .../Rules/UserLoginMustBeUniqueRule.cs | 2 +- ...ionCannotBeConfirmedAfterExpirationRule.cs | 2 +- ...rationCannotBeConfirmedMoreThanOnceRule.cs | 2 +- ...strationCannotBeExpiredMoreThanOnceRule.cs | 2 +- .../UserRegistrations/UserRegistration.cs | 8 +- .../UserRegistrations/UserRegistrationId.cs | 2 +- .../UserRegistrationStatus.cs | 2 +- .../Users/Events/UserCreatedDomainEvent.cs | 2 +- .../Domain/Users/IUserRepository.cs | 2 +- src/Modules/UserAccess/Domain/Users/User.cs | 6 +- src/Modules/UserAccess/Domain/Users/UserId.cs | 2 +- .../UserAccess/Domain/Users/UserRole.cs | 2 +- ...odules.UserAccessIS.Infrastructure.csproj} | 0 .../Configuration/AllConstructorFinder.cs | 2 +- .../Configuration/Assemblies.cs | 4 +- .../DataAccess/DataAccessModule.cs | 2 +- .../Configuration/Domain/DomainModule.cs | 6 +- .../Configuration/Email/EmailModule.cs | 2 +- .../EventsBus/EventsBusModule.cs | 2 +- .../EventsBus/EventsBusStartup.cs | 2 +- .../IntegrationEventGenericHandler.cs | 2 +- .../Configuration/Logging/LoggingModule.cs | 2 +- .../Configuration/Mediation/MediatorModule.cs | 4 +- .../Processing/CommandsExecutor.cs | 4 +- .../Processing/IRecurringCommand.cs | 2 +- .../Processing/Inbox/InboxMessageDto.cs | 2 +- .../Processing/Inbox/ProcessInboxCommand.cs | 6 +- .../Inbox/ProcessInboxCommandHandler.cs | 4 +- .../Processing/Inbox/ProcessInboxJob.cs | 2 +- .../InternalCommands/CommandsScheduler.cs | 6 +- .../ProcessInternalCommandsCommand.cs | 6 +- .../ProcessInternalCommandsCommandHandler.cs | 4 +- .../ProcessInternalCommandsJob.cs | 2 +- .../LoggingCommandHandlerDecorator.cs | 11 +- ...oggingCommandHandlerWithResultDecorator.cs | 6 +- .../Processing/Outbox/OutboxMessageDto.cs | 2 +- .../Processing/Outbox/OutboxModule.cs | 4 +- .../Processing/Outbox/ProcessOutboxCommand.cs | 6 +- .../Outbox/ProcessOutboxCommandHandler.cs | 4 +- .../Processing/Outbox/ProcessOutboxJob.cs | 2 +- .../Processing/ProcessingModule.cs | 6 +- .../UnitOfWorkCommandHandlerDecorator.cs | 6 +- ...OfWorkCommandHandlerWithResultDecorator.cs | 6 +- .../ValidationCommandHandlerDecorator.cs | 6 +- ...dationCommandHandlerWithResultDecorator.cs | 6 +- .../Configuration/Quartz/QuartzModule.cs | 2 +- .../Configuration/Quartz/QuartzStartup.cs | 8 +- .../Quartz/SerilogLogProvider.cs | 2 +- .../Security/AesDataProtector.cs | 2 +- .../Configuration/Security/IDataProtector.cs | 2 +- .../Configuration/Security/SecurityModule.cs | 2 +- .../UserAccessCompositionRoot.cs | 2 +- .../Configuration/UserAccessStartup.cs | 24 +- ...UserRegistrationEntityTypeConfiguration.cs | 4 +- .../UserRegistrationRepository.cs | 4 +- .../Users/UserEntityTypeConfiguration.cs | 4 +- .../Domain/Users/UserRepository.cs | 4 +- .../InternalCommandEntityTypeConfiguration.cs | 2 +- .../Infrastructure/Outbox/OutboxAccessor.cs | 2 +- .../OutboxMessageEntityTypeConfiguration.cs | 2 +- .../Infrastructure/UserAccessContext.cs | 14 +- .../Infrastructure/UserAccessModule.cs | 8 +- ...les.UserAccessIS.IntegrationEvents.csproj} | 0 .../NewUserRegisteredIntegrationEvent.cs | 2 +- .../ArchTests/Application/ApplicationTests.cs | 10 +- ...ngs.Modules.UserAccessIS.ArchTests.csproj} | 0 .../Tests/ArchTests/Domain/DomainTests.cs | 4 +- .../Tests/ArchTests/Module/LayersTests.cs | 4 +- .../Tests/ArchTests/SeedWork/TestBase.cs | 8 +- .../Tests/IntegrationTests/AssemblyInfo.cs | 2 +- ...ules.UserAccessIS.IntegrationTests.csproj} | 0 .../SeedWork/ExecutionContextMock.cs | 4 +- .../SeedWork/OutboxMessagesHelper.cs | 6 +- .../IntegrationTests/SeedWork/TestBase.cs | 8 +- .../ConfirmUserRegistrationTests.cs | 12 +- ...dUserRegistrationConfirmationEmailTests.cs | 8 +- .../UserRegistrationSampleData.cs | 2 +- .../UserRegistrationTests.cs | 8 +- .../IntegrationTests/Users/CreateUserTests.cs | 12 +- ...ules.UserAccessIS.Domain.UnitTests.csproj} | 0 .../SeedWork/DomainEventsTestHelper.cs | 2 +- .../Tests/UnitTests/SeedWork/TestBase.cs | 2 +- .../UserRegistrationTests.cs | 12 +- .../Login/AccountLoginCommand.cs | 16 + .../Login/AccountLoginCommandHandler.cs | 162 ++++++++ .../Login/AccountLoginCommandValidator.cs | 12 + .../Login/AccountTwoFactorLoginCommand.cs | 19 + .../AccountTwoFactorLoginCommandHandler.cs | 57 +++ .../Login/AuthenticationResult.cs | 62 ++++ .../External/ExternalAccountLoginCommand.cs | 22 ++ .../ExternalAccountLoginCommandHandler.cs | 76 ++++ .../Authentication/Login/UserDto.cs | 16 + .../RefreshToken/RefreshTokenCommand.cs | 17 + .../RefreshTokenCommandHandler.cs | 22 ++ .../Authentication/RefreshToken/TokenDto.cs | 3 + .../ForgotPasswordLinkResult.cs | 17 + .../RequestForgotPasswordLinkCommand.cs | 13 + ...RequestForgotPasswordLinkCommandHandler.cs | 41 +++ .../ResetPassword/ResetPasswordCommand.cs | 20 + .../ResetPasswordCommandHandler.cs | 40 ++ .../ByUserId/GetPermissionsQuery.cs | 14 + .../ByUserId/GetPermissionsQueryHandler.cs | 84 +++++ .../Directory/GetPermissionsQuery.cs | 8 + .../Directory/GetPermissionsQueryHandler.cs | 31 ++ .../GetPermissions/PermissionDto.cs | 10 + .../GetUserRoles/GetUserRolesQuery.cs | 14 + .../GetUserRoles/GetUserRolesQueryHandler.cs | 44 +++ .../Authorization/GetUserRoles/RoleDto.cs | 8 + ...gs.Modules.UserAccessMI.Application.csproj | 11 + .../Configuration/Commands/ICommandHandler.cs | 15 + .../Commands/ICommandsScheduler.cs | 10 + .../Commands/InternalCommandBase.cs | 28 ++ .../Configuration/Queries/IQueryHandler.cs | 10 + .../Configuration/Results/IResult.cs | 26 ++ .../Configuration/Results/IResultOfT.cs | 8 + .../Configuration/Results/RespultOfT.cs | 42 +++ .../Configuration/Results/Result.cs | 117 ++++++ .../Configuration/Results/ResultStatus.cs | 24 ++ .../Contracts/ApplicationPermissions.cs | 6 + .../Application/Contracts/CommandBase.cs | 31 ++ .../Application/Contracts/CustomClaimTypes.cs | 15 + .../Application/Contracts/ICommand.cs | 13 + .../Application/Contracts/IQuery.cs | 7 + .../Contracts/IRecurringCommand.cs | 5 + .../Contracts/ITokenClaimsService.cs | 20 + .../Contracts/IUserAccessModule.cs | 10 + .../Application/Contracts/QueryBase.cs | 16 + .../Application/Contracts/Roles.cs | 8 + .../Application/CustomValidators.cs | 110 ++++++ .../GetUserAccount/GetUserAccountQuery.cs | 14 + .../GetUserAccountQueryHandler.cs | 37 ++ .../Identity/GetUserAccount/UserAccountDto.cs | 18 + .../GetUserPermissionsQuery.cs | 14 + .../GetUserPermissionsQueryHandler.cs | 29 ++ .../GetUserPermissions/UserPermissionDto.cs | 6 + .../Application/IdentityHelpers.cs | 12 + .../Roles/CreateRole/CreateRoleCommand.cs | 17 + .../CreateRole/CreateRoleCommandHandler.cs | 39 ++ .../Roles/DeleteRole/DeleteRoleCommand.cs | 14 + .../DeleteRole/DeleteRoleCommandHandler.cs | 29 ++ .../GetRolePermissionsQuery.cs | 14 + .../GetRolePermissionsQueryHandler.cs | 64 ++++ .../Roles/GetRolePermissions/PermissionDto.cs | 10 + .../Roles/GetRoles/ById/GetRolesQuery.cs | 14 + .../GetRoles/ById/GetRolesQueryHandler.cs | 35 ++ .../Roles/GetRoles/Directory/GetRolesQuery.cs | 8 + .../Directory/GetRolesQueryHandler.cs | 28 ++ .../Application/Roles/GetRoles/RoleDto.cs | 8 + .../Roles/RenameRole/RenameRoleCommand.cs | 17 + .../RenameRole/RenameRoleCommandHander.cs | 36 ++ .../SetRolePermissionsCommand.cs | 17 + .../SetRolePermissionsCommandHandler.cs | 61 ++++ .../GetAuthenticatorKeyQuery.cs | 14 + .../GetAuthenticatorKeyQueryHandler.cs | 46 +++ .../RegisterAuthenticatorCommand.cs | 17 + .../RegisterAuthenticatorCommandHandler.cs | 35 ++ .../ChangeEmailAddressCommand.cs | 20 + .../ChangeEmailAddressCommandHandler.cs | 47 +++ .../ChangePassword/ChangePasswordCommand.cs | 20 + .../ChangePasswordCommandHandler.cs | 42 +++ .../ConfirmEmailAddressCommand.cs | 17 + .../ConfirmEmailAddressCommandHandler.cs | 34 ++ .../CreateUserAccountCommand.cs | 29 ++ .../CreateUserAccountCommandHandler.cs | 67 ++++ .../ById/GetUserAccountsQuery.cs | 14 + .../ById/GetUserAccountsQueryHandler.cs | 47 +++ .../Directory/GetUserAccountsQuery.cs | 8 + .../Directory/GetUserAccountsQueryHandler.cs | 41 +++ .../GetUserAccounts/UserAccountDto.cs | 83 +++++ .../RequestChangeEmailAddressCommand.cs | 18 + ...RequestChangeEmailAddressCommandHandler.cs | 31 ++ .../RequestChangeEmailAddressResult.cs | 18 + .../SetUserPermissionsCommand.cs | 17 + .../SetUserPermissionsCommandHandler.cs | 48 +++ .../SetUserRoles/SetUserRolesCommand.cs | 17 + .../SetUserRolesCommandHandler.cs | 66 ++++ .../UnlockUserAccountCommand.cs | 14 + .../UnlockUserAccountCommandHandler.cs | 34 ++ .../UpdateUserAccountCommand.cs | 23 ++ .../UpdateUserAccountCommandHandler.cs | 38 ++ .../ConfirmUserRegistrationCommand.cs | 14 + .../ConfirmUserRegistrationCommandHandler.cs | 30 ++ .../UserRegistrationConfirmedHandler.cs | 48 +++ .../GetUserRegistrationQuery.cs | 15 + .../GetUserRegistrationQueryHandler.cs | 48 +++ .../UserRegistrationDto.cs | 18 + ...gisteredEnqueueEmailConfirmationHandler.cs | 24 ++ .../NewUserRegisteredNotification.cs | 14 + .../NewUserRegisteredPublishEventHandler.cs | 28 ++ .../RegisterNewUser/RegisterNewUserCommand.cs | 35 ++ .../RegisterNewUserCommandHandler.cs | 42 +++ ...serRegistrationConfirmationEmailCommand.cs | 27 ++ ...strationConfirmationEmailCommandHandler.cs | 28 ++ .../UserRegistrations/UsersCounter.cs | 31 ++ ...eetings.Modules.UserAccessMI.Domain.csproj | 12 + .../Domain/ErrorHandling/Error.cs | 125 +++++++ .../Domain/ErrorHandling/ErrorExtensions.cs | 71 ++++ .../Domain/ErrorHandling/Errors.cs | 125 +++++++ .../Domain/IUserRefreshTokenRepository.cs | 10 + .../UserAccessMI/Domain/UserRefreshToken.cs | 43 +++ .../UserAccessMI/Domain/UserRefreshTokenId.cs | 11 + .../Events/NewUserRegisteredDomainEvent.cs | 42 +++ .../UserRegistrationConfirmedDomainEvent.cs | 13 + .../UserRegistrationExpiredDomainEvent.cs | 13 + .../IUserRegistrationRepository.cs | 8 + .../Domain/UserRegistrations/IUsersCounter.cs | 6 + ...eatedWhenRegistrationIsNotConfirmedRule.cs | 17 + .../Rules/UserLoginMustBeUniqueRule.cs | 19 + ...ionCannotBeConfirmedAfterExpirationRule.cs | 17 + ...rationCannotBeConfirmedMoreThanOnceRule.cs | 17 + ...strationCannotBeExpiredMoreThanOnceRule.cs | 17 + .../UserRegistrations/UserRegistration.cs | 113 ++++++ .../UserRegistrations/UserRegistrationId.cs | 11 + .../UserRegistrationStatus.cs | 20 + .../Domain/Users/ApplicationUser.cs | 76 ++++ .../Users/Events/UserCreatedDomainEvent.cs | 13 + src/Modules/UserAccessMI/Domain/Users/Role.cs | 16 + .../UserAccessMI/Domain/Users/UserRole.cs | 22 ++ ...Modules.UserAccessMI.Infrastructure.csproj | 16 + .../Configuration/AllConstructorFinder.cs | 20 + .../Configuration/Assemblies.cs | 9 + .../DataAccess/DataAccessModule.cs | 41 +++ .../Configuration/Domain/DomainModule.cs | 16 + .../Configuration/Email/EmailModule.cs | 34 ++ .../EventsBus/EventsBusModule.cs | 28 ++ .../EventsBus/EventsBusStartup.cs | 30 ++ .../IntegrationEventGenericHandler.cs | 38 ++ .../Configuration/IUserAccessConfiguration.cs | 30 ++ .../DoesNotContainPasswordValidator.cs | 29 ++ .../EmailConfirmationTokenProvider.cs | 21 ++ .../EmailConfirmationTokenProviderOptions.cs | 7 + .../Configuration/Identity/IdentityModule.cs | 47 +++ .../Configuration/Logging/LoggingModule.cs | 21 ++ .../Configuration/Mediation/MediatorModule.cs | 92 +++++ .../Processing/CommandsExecutor.cs | 26 ++ .../Processing/IRecurringCommand.cs | 5 + .../Processing/Inbox/InboxMessageDto.cs | 10 + .../Processing/Inbox/ProcessInboxCommand.cs | 5 + .../Inbox/ProcessInboxCommandHandler.cs | 62 ++++ .../Processing/Inbox/ProcessInboxJob.cs | 12 + .../InternalCommands/CommandsScheduler.cs | 56 +++ .../ProcessInternalCommandsCommand.cs | 5 + .../ProcessInternalCommandsCommandHandler.cs | 82 +++++ .../ProcessInternalCommandsJob.cs | 12 + .../LoggingCommandHandlerDecorator.cs | 89 +++++ ...oggingCommandHandlerWithResultDecorator.cs | 87 +++++ .../Processing/Outbox/OutboxMessageDto.cs | 10 + .../Processing/Outbox/OutboxModule.cs | 59 +++ .../Processing/Outbox/ProcessOutboxCommand.cs | 5 + .../Outbox/ProcessOutboxCommandHandler.cs | 84 +++++ .../Processing/Outbox/ProcessOutboxJob.cs | 12 + .../Processing/ProcessingModule.cs | 68 ++++ .../UnitOfWorkCommandHandlerDecorator.cs | 41 +++ ...OfWorkCommandHandlerWithResultDecorator.cs | 43 +++ .../ValidationCommandHandlerDecorator.cs | 37 ++ ...dationCommandHandlerWithResultDecorator.cs | 38 ++ .../Configuration/Quartz/QuartzModule.cs | 13 + .../Configuration/Quartz/QuartzStartup.cs | 111 ++++++ .../Quartz/SerilogLogProvider.cs | 67 ++++ .../Security/AesDataProtector.cs | 63 ++++ .../Configuration/Security/IDataProtector.cs | 8 + .../Configuration/Security/SecurityModule.cs | 20 + .../Configuration/Services/ServicesModule.cs | 25 ++ .../UserAccessCompositionRoot.cs | 23 ++ .../Configuration/UserAccessConfiguration.cs | 15 + .../Configuration/UserAccessStartup.cs | 93 +++++ .../ApplicationUserEntityTypeConfiguration.cs | 17 + ...dentityRoleClaimEntityTypeConfiguration.cs | 13 + ...dentityUserClaimEntityTypeConfiguration.cs | 13 + ...dentityUserLoginEntityTypeConfiguration.cs | 13 + ...IdentityUserRoleEntityTypeConfiguration.cs | 13 + ...dentityUserTokenEntityTypeConfiguration.cs | 13 + .../RoleEntityTypeConfiguration.cs | 13 + ...UserRefreshTokenEntityTypeConfiguration.cs | 22 ++ ...UserRegistrationEntityTypeConfiguration.cs | 29 ++ .../UserRefreshTokenRepository.cs | 31 ++ .../UserRegistrationRepository.cs | 24 ++ .../InternalCommandEntityTypeConfiguration.cs | 16 + .../Infrastructure/Outbox/OutboxAccessor.cs | 23 ++ .../OutboxMessageEntityTypeConfiguration.cs | 16 + .../IdentityTokenService/CustomClaimTypes.cs | 12 + .../IdentityTokenClaimService.cs | 186 ++++++++++ .../Infrastructure/UserAccessContext.cs | 90 +++++ .../Infrastructure/UserAccessModule.cs | 30 ++ ...ules.UserAccessMI.IntegrationEvents.csproj | 9 + .../NewUserRegisteredIntegrationEvent.cs | 29 ++ src/Tests/ArchTests/Modules/ModuleTests.cs | 6 +- .../SeedWork/ExecutionContextMock.cs | 2 + src/Tests/SUT/Helpers/UsersFactory.cs | 8 +- .../SUT/SeedWork/ExecutionContextMock.cs | 2 + src/Tests/SUT/SeedWork/TestBase.cs | 6 +- 461 files changed, 9718 insertions(+), 379 deletions(-) create mode 100644 src/API/CompanyName.MyMeetings.API/CompanyName.MyMeetings.API.xml create mode 100644 src/API/CompanyName.MyMeetings.API/Configuration/Authorization/PermissionAuthorizationHandler.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Configuration/Extensions/ConfigurationExtensions.cs rename src/API/CompanyName.MyMeetings.API/Modules/UserAccess/{ => IdentityServer}/AuthenticatedUserController.cs (69%) rename src/API/CompanyName.MyMeetings.API/Modules/UserAccess/{ => IdentityServer}/EmailsController.cs (77%) rename src/API/CompanyName.MyMeetings.API/Modules/UserAccess/{ => IdentityServer}/RegisterNewUserRequest.cs (81%) rename src/API/CompanyName.MyMeetings.API/Modules/UserAccess/{ => IdentityServer}/ResourceOwnerPasswordValidator.cs (82%) rename src/API/CompanyName.MyMeetings.API/Modules/UserAccess/{ => IdentityServer}/UserAccessAutofacModule.cs (59%) rename src/API/CompanyName.MyMeetings.API/Modules/UserAccess/{ => IdentityServer}/UserRegistrationsController.cs (80%) create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/ApplicationController.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/AuthenticationController.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/AuthenticationMappingProfile.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/IdentityController.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/Validators/AuthenticationRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/Validators/ResetPasswordRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authorization/AuthorizationController.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authorization/AuthorizationMappingProfile.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ApiResult.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ApiResultStatus.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ErrorMapper.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ResultExtensions.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/RoleMappingProfile.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/RolesController.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/AddRoleRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/RenameRoleRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/SetRolePermissionsRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/UserAccessAutofacModule.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UserMappingProfile.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UserRegistrationsController.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UsersController.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/ConfirmEmailAddressRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/CreateUserAccountRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/SetUserPermissionsRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/SetUserRolesRequestValidator.cs create mode 100644 src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/UsersPermissions.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/CompanyName.MyMeetings.Contracts.csproj create mode 100644 src/API/CompanyName.MyMeetings.Contracts/Results/ErrorMessage.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/Results/Result.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/Results/ResultOfT.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/AuthenticationRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/AuthenticationResultDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/ResetPasswordRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/TokenRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/TokenResultDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authorization/PermissionDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Identity/UserAccountDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Identity/UserPermissionDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/AddRoleRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/PermissionDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/RenameRoleRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/RoleDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/SetRolePermissionsRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/UserRegistrations/RegisterNewUserRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/ConfirmEmailAddressRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/CreateUserAccountRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/PermissionDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/RoleDto.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/SetUserPermissionsRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/SetUserRolesRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/UpdateUserAccountRequest.cs create mode 100644 src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/UserAccountDto.cs create mode 100644 src/BuildingBlocks/Application/Data/IDatabaseConfiguration.cs create mode 100644 src/BuildingBlocks/Domain/IEntity.cs create mode 100644 src/BuildingBlocks/Infrastructure/DatabaseConfiguration.cs create mode 100644 src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0002_SeedPermissions.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0003_SeedRoles.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0004_SeedUsers.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/InboxMessages.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/InternalCommands.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/OutboxMessages.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/Permission.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/Role.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/RoleClaim.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/User.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserClaim.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserLogin.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRefreshToken.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRegistrations.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRole.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserToken.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserPermissions.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserRegistrations.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserRoles.sql create mode 100644 src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_Users.sql rename src/Modules/UserAccess/Application/{CompanyName.MyMeetings.Modules.UserAccess.Application.csproj => CompanyName.MyMeetings.Modules.UserAccessIS.Application.csproj} (68%) rename src/Modules/UserAccess/Domain/{CompanyName.MyMeetings.Modules.UserAccess.Domain.csproj => CompanyName.MyMeetings.Modules.UserAccessIS.Domain.csproj} (100%) rename src/Modules/UserAccess/Infrastructure/{CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.csproj => CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.csproj} (100%) rename src/Modules/UserAccess/IntegrationEvents/{CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents.csproj => CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationEvents.csproj} (100%) rename src/Modules/UserAccess/Tests/ArchTests/{CompanyName.MyMeetings.Modules.UserAccess.ArchTests.csproj => CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.csproj} (100%) rename src/Modules/UserAccess/Tests/IntegrationTests/{CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.csproj => CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.csproj} (100%) rename src/Modules/UserAccess/Tests/UnitTests/{CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests.csproj => CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests.csproj} (100%) create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommandValidator.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/AccountTwoFactorLoginCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/AccountTwoFactorLoginCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/AuthenticationResult.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/External/ExternalAccountLoginCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/External/ExternalAccountLoginCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/Login/UserDto.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/RefreshToken/RefreshTokenCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/RefreshToken/RefreshTokenCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/RefreshToken/TokenDto.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/ForgotPasswordLinkResult.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/RequestForgotPasswordLinkCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/RequestForgotPasswordLinkCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/ResetPassword/ResetPasswordCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Authentication/ResetPassword/ResetPasswordCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authorization/GetPermissions/ByUserId/GetPermissionsQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Authorization/GetPermissions/ByUserId/GetPermissionsQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authorization/GetPermissions/Directory/GetPermissionsQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Authorization/GetPermissions/Directory/GetPermissionsQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authorization/GetPermissions/PermissionDto.cs create mode 100644 src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/GetUserRolesQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/GetUserRolesQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/RoleDto.cs create mode 100644 src/Modules/UserAccessMI/Application/CompanyName.MyMeetings.Modules.UserAccessMI.Application.csproj create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Commands/ICommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Commands/ICommandsScheduler.cs create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Commands/InternalCommandBase.cs create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Queries/IQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Results/IResult.cs create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Results/IResultOfT.cs create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Results/RespultOfT.cs create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Results/Result.cs create mode 100644 src/Modules/UserAccessMI/Application/Configuration/Results/ResultStatus.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/ApplicationPermissions.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/CommandBase.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/CustomClaimTypes.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/ICommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/IQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/IRecurringCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/ITokenClaimsService.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/IUserAccessModule.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/QueryBase.cs create mode 100644 src/Modules/UserAccessMI/Application/Contracts/Roles.cs create mode 100644 src/Modules/UserAccessMI/Application/CustomValidators.cs create mode 100644 src/Modules/UserAccessMI/Application/Identity/GetUserAccount/GetUserAccountQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Identity/GetUserAccount/GetUserAccountQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Identity/GetUserAccount/UserAccountDto.cs create mode 100644 src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/GetUserPermissionsQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/GetUserPermissionsQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/UserPermissionDto.cs create mode 100644 src/Modules/UserAccessMI/Application/IdentityHelpers.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/CreateRole/CreateRoleCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/CreateRole/CreateRoleCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/DeleteRole/DeleteRoleCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/DeleteRole/DeleteRoleCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/GetRolePermissionsQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/GetRolePermissionsQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/PermissionDto.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/GetRoles/ById/GetRolesQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/GetRoles/ById/GetRolesQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/GetRoles/Directory/GetRolesQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/GetRoles/Directory/GetRolesQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/GetRoles/RoleDto.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/RenameRole/RenameRoleCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/RenameRole/RenameRoleCommandHander.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/SetRolePermissions/SetRolePermissionsCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/Roles/SetRolePermissions/SetRolePermissionsCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/GetAuthenticatorKey/GetAuthenticatorKeyQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/GetAuthenticatorKey/GetAuthenticatorKeyQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/RegisterAuthenticator/RegisterAuthenticatorCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/RegisterAuthenticator/RegisterAuthenticatorCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/ChangeEmailAddress/ChangeEmailAddressCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/ChangeEmailAddress/ChangeEmailAddressCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/ChangePassword/ChangePasswordCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/ChangePassword/ChangePasswordCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/ConfirmEmailAddress/ConfirmEmailAddressCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/ConfirmEmailAddress/ConfirmEmailAddressCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/CreateUserAccount/CreateUserAccountCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/CreateUserAccount/CreateUserAccountCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/ById/GetUserAccountsQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/ById/GetUserAccountsQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/Directory/GetUserAccountsQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/Directory/GetUserAccountsQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/UserAccountDto.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressResult.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/SetUserPermissions/SetUserPermissionsCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/SetUserPermissions/SetUserPermissionsCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/SetUserRoles/SetUserRolesCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/SetUserRoles/SetUserRolesCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/UnlockUserAccount/UnlockUserAccountCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/UnlockUserAccount/UnlockUserAccountCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/UpdateUserAccount/UpdateUserAccountCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserAccounts/UpdateUserAccount/UpdateUserAccountCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/UserRegistrationConfirmedHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQuery.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQueryHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/UserRegistrationDto.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredEnqueueEmailConfirmationHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredNotification.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredPublishEventHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommand.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Application/UserRegistrations/UsersCounter.cs create mode 100644 src/Modules/UserAccessMI/Domain/CompanyName.MyMeetings.Modules.UserAccessMI.Domain.csproj create mode 100644 src/Modules/UserAccessMI/Domain/ErrorHandling/Error.cs create mode 100644 src/Modules/UserAccessMI/Domain/ErrorHandling/ErrorExtensions.cs create mode 100644 src/Modules/UserAccessMI/Domain/ErrorHandling/Errors.cs create mode 100644 src/Modules/UserAccessMI/Domain/IUserRefreshTokenRepository.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRefreshToken.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRefreshTokenId.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/Events/NewUserRegisteredDomainEvent.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/Events/UserRegistrationConfirmedDomainEvent.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/Events/UserRegistrationExpiredDomainEvent.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/IUserRegistrationRepository.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/IUsersCounter.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserLoginMustBeUniqueRule.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedAfterExpirationRule.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedMoreThanOnceRule.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeExpiredMoreThanOnceRule.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistration.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistrationId.cs create mode 100644 src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistrationStatus.cs create mode 100644 src/Modules/UserAccessMI/Domain/Users/ApplicationUser.cs create mode 100644 src/Modules/UserAccessMI/Domain/Users/Events/UserCreatedDomainEvent.cs create mode 100644 src/Modules/UserAccessMI/Domain/Users/Role.cs create mode 100644 src/Modules/UserAccessMI/Domain/Users/UserRole.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.csproj create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/AllConstructorFinder.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Assemblies.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/DataAccess/DataAccessModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Domain/DomainModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Email/EmailModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/EventsBusModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/IntegrationEventGenericHandler.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/IUserAccessConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/DoesNotContainPasswordValidator.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/EmailConfirmationTokenProvider.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/EmailConfirmationTokenProviderOptions.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/IdentityModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Logging/LoggingModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Mediation/MediatorModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/CommandsExecutor.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/IRecurringCommand.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/InboxMessageDto.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommand.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxJob.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/CommandsScheduler.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommand.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsJob.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/LoggingCommandHandlerDecorator.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/LoggingCommandHandlerWithResultDecorator.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/OutboxMessageDto.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/OutboxModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommand.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommandHandler.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxJob.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ProcessingModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerDecorator.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ValidationCommandHandlerWithResultDecorator.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/QuartzModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/QuartzStartup.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/SerilogLogProvider.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Security/AesDataProtector.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Security/IDataProtector.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Security/SecurityModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/Services/ServicesModule.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessCompositionRoot.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessStartup.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/ApplicationUserEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityRoleClaimEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserClaimEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserLoginEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserRoleEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserTokenEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/RoleEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/UserRefreshTokenEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/UserRegistrationEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Repositories/UserRefreshTokenRepository.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Domain/Repositories/UserRegistrationRepository.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/InternalCommands/InternalCommandEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Outbox/OutboxAccessor.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Outbox/OutboxMessageEntityTypeConfiguration.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Services/IdentityTokenService/CustomClaimTypes.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/Services/IdentityTokenService/IdentityTokenClaimService.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/UserAccessContext.cs create mode 100644 src/Modules/UserAccessMI/Infrastructure/UserAccessModule.cs create mode 100644 src/Modules/UserAccessMI/IntegrationEvents/CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents.csproj create mode 100644 src/Modules/UserAccessMI/IntegrationEvents/NewUserRegisteredIntegrationEvent.cs diff --git a/.gitignore b/.gitignore index 0a74300d6..a7f6012f1 100644 --- a/.gitignore +++ b/.gitignore @@ -275,3 +275,10 @@ UploadedFiles #Nuke working directory .nuke-working-directory /src/API/CompanyName.MyMeetings.API/tempkey.jwk + + +#Ignore appsettings +**/appsettings.Development.json + +# CodeRush personal settings +**/.cr/personal diff --git a/src/API/CompanyName.MyMeetings.API/CompanyName.MyMeetings.API.csproj b/src/API/CompanyName.MyMeetings.API/CompanyName.MyMeetings.API.csproj index 340f1f0bf..fda3cf00a 100644 --- a/src/API/CompanyName.MyMeetings.API/CompanyName.MyMeetings.API.csproj +++ b/src/API/CompanyName.MyMeetings.API/CompanyName.MyMeetings.API.csproj @@ -5,7 +5,15 @@ Linux ..\.. - - - + + + .\CompanyName.MyMeetings.API.xml + 1701;1702;1591 + + + + + + + diff --git a/src/API/CompanyName.MyMeetings.API/CompanyName.MyMeetings.API.xml b/src/API/CompanyName.MyMeetings.API/CompanyName.MyMeetings.API.xml new file mode 100644 index 000000000..0d5dae6ef --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/CompanyName.MyMeetings.API.xml @@ -0,0 +1,114 @@ + + + + CompanyName.MyMeetings.API + + + + + Custom base class for API Controllers + + Decorating the controller class with the ApiController attribute unleashes more magic power the framework offers. + This automates the model state checks and the model state IsValid property doesn't need to be checked manually. + Further more the [FromBody] attribute isn't needed anymore since the controller now automatically infers the binding source for the incoming data. + + + + + User login. + + Authentication attributes. + ApiResult. + + + + User login. + + User generated token. + ApiResult. + + + + Send forgot password link. + + Email address of the user. + ApiResult. + + + + Reset password. + + Reset password attributes. + ApiResult. + + + + Successful result. + + + + + General error result. + + + + + Not found error result. + + + + + Invalid error result. + + + + + Forbidden error result. + + + + + Convert an Result to a ApiResult. + + The Result to convert. + ApiResult. + + + + Convert an Result to a ApiResult. + + The value type being returned. + The Result to convert. + The value being returned. + ApiResult. + + + + Convert an Result to a ApiResult. + + The value being returned. + The Result to convert. + ApiResult. + + + + Gets the user directory. + + List of users. + + + + Get the authenticator key for the current logged in user. + Use this key to register an new account in an authenticator app. + + The authenticator key. + + + + Enable two-step authentication for the current logged in user by providing the code generated by the authenticator app. + + One-Time Password Code. + Result. + + + diff --git a/src/API/CompanyName.MyMeetings.API/Configuration/Authorization/HasPermissionAuthorizationHandler.cs b/src/API/CompanyName.MyMeetings.API/Configuration/Authorization/HasPermissionAuthorizationHandler.cs index 82b1de0b3..ed5166636 100644 --- a/src/API/CompanyName.MyMeetings.API/Configuration/Authorization/HasPermissionAuthorizationHandler.cs +++ b/src/API/CompanyName.MyMeetings.API/Configuration/Authorization/HasPermissionAuthorizationHandler.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Application; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetUserPermissions; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetUserPermissions; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using Microsoft.AspNetCore.Authorization; namespace CompanyName.MyMeetings.API.Configuration.Authorization diff --git a/src/API/CompanyName.MyMeetings.API/Configuration/Authorization/PermissionAuthorizationHandler.cs b/src/API/CompanyName.MyMeetings.API/Configuration/Authorization/PermissionAuthorizationHandler.cs new file mode 100644 index 000000000..2a8748193 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Configuration/Authorization/PermissionAuthorizationHandler.cs @@ -0,0 +1,61 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions.ByUserId; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using Microsoft.AspNetCore.Authorization; + +namespace CompanyName.MyMeetings.API.Configuration.Authorization; + +internal class PermissionAuthorizationHandler : AttributeAuthorizationHandler +{ + private readonly IUserAccessModule _userAccessModule; + private readonly IExecutionContextAccessor _executionContextAccessor; + + public PermissionAuthorizationHandler( + IUserAccessModule userAccessModule, + IExecutionContextAccessor executionContextAccessor) + { + _userAccessModule = userAccessModule; + _executionContextAccessor = executionContextAccessor; + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, HasPermissionAuthorizationRequirement requirement, HasPermissionAttribute attribute) + { + if (!_executionContextAccessor.IsAuthenticated) + { + context.Fail(); + return; + } + + var userId = _executionContextAccessor.UserId; + var response = await _userAccessModule.ExecuteQueryAsync(new GetPermissionsQuery(userId)); + if (response.HasError) + { + context.Fail(); + return; + } + + var permissions = response.Value ?? Enumerable.Empty(); + + // Short circuit if the user owns the administrator privilege. + if (permissions.Any(x => x.Code.Equals(ApplicationPermissions.Administrator))) + { + context.Succeed(requirement); + return; + } + + // Check if the user owns the necessary rights. + if (!IsAuthorized(attribute.Name, permissions)) + { + context.Fail(); + return; + } + + context.Succeed(requirement); + } + + private bool IsAuthorized(string permission, IEnumerable permissions) + { + return permissions.Any(x => x.Code == permission); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Configuration/ExecutionContext/ExecutionContextAccessor.cs b/src/API/CompanyName.MyMeetings.API/Configuration/ExecutionContext/ExecutionContextAccessor.cs index 207dbdeac..a316800cd 100644 --- a/src/API/CompanyName.MyMeetings.API/Configuration/ExecutionContext/ExecutionContextAccessor.cs +++ b/src/API/CompanyName.MyMeetings.API/Configuration/ExecutionContext/ExecutionContextAccessor.cs @@ -15,6 +15,7 @@ public Guid UserId { get { + // nameidentifier if (_httpContextAccessor .HttpContext? .User? @@ -46,5 +47,7 @@ public Guid CorrelationId } public bool IsAvailable => _httpContextAccessor.HttpContext != null; + + public bool IsAuthenticated => IsAvailable && (_httpContextAccessor.HttpContext.User.Identity?.IsAuthenticated ?? false); } } \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Configuration/Extensions/ConfigurationExtensions.cs b/src/API/CompanyName.MyMeetings.API/Configuration/Extensions/ConfigurationExtensions.cs new file mode 100644 index 000000000..41360fc75 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Configuration/Extensions/ConfigurationExtensions.cs @@ -0,0 +1,15 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; + +namespace CompanyName.MyMeetings.API.Configuration.Extensions +{ + internal static class ConfigurationExtensions + { + public static IUserAccessConfiguration GetUserAccessConfiguration(this IConfiguration configuration) + { + ArgumentNullException.ThrowIfNull(configuration, nameof(configuration)); + + var userManagementSection = configuration.GetSection("Modules:UserAccess"); + return userManagementSection.Get(); + } + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Configuration/Extensions/SwaggerExtensions.cs b/src/API/CompanyName.MyMeetings.API/Configuration/Extensions/SwaggerExtensions.cs index 17dbce681..98a2c39b5 100644 --- a/src/API/CompanyName.MyMeetings.API/Configuration/Extensions/SwaggerExtensions.cs +++ b/src/API/CompanyName.MyMeetings.API/Configuration/Extensions/SwaggerExtensions.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.OpenApi.Models; namespace CompanyName.MyMeetings.API.Configuration.Extensions @@ -16,6 +17,22 @@ internal static IServiceCollection AddSwaggerDocumentation(this IServiceCollecti Description = "MyMeetings API for modular monolith .NET application." }); options.CustomSchemaIds(t => t.ToString()); + options.TagActionsBy(api => + { + if (api.GroupName != null) + { + return new[] { api.GroupName }; + } + + var controllerActionDescriptor = api.ActionDescriptor as ControllerActionDescriptor; + if (controllerActionDescriptor != null) + { + return new[] { controllerActionDescriptor.ControllerName }; + } + + throw new InvalidOperationException("Unable to determine tag for endpoint."); + }); + options.DocInclusionPredicate((name, api) => true); var baseDirectory = AppDomain.CurrentDomain.BaseDirectory; var commentsFileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/AuthenticatedUserController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/AuthenticatedUserController.cs similarity index 69% rename from src/API/CompanyName.MyMeetings.API/Modules/UserAccess/AuthenticatedUserController.cs rename to src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/AuthenticatedUserController.cs index 881cc033b..470c55b1c 100644 --- a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/AuthenticatedUserController.cs +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/AuthenticatedUserController.cs @@ -1,12 +1,12 @@ using CompanyName.MyMeetings.API.Configuration.Authorization; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetAuthenticatedUserPermissions; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetUserPermissions; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetAuthenticatedUser; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetAuthenticatedUserPermissions; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetUserPermissions; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetAuthenticatedUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetUser; using Microsoft.AspNetCore.Mvc; -namespace CompanyName.MyMeetings.API.Modules.UserAccess +namespace CompanyName.MyMeetings.API.Modules.UserAccess.IdentityServer { [Route("api/userAccess/authenticatedUser")] [ApiController] diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/EmailsController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/EmailsController.cs similarity index 77% rename from src/API/CompanyName.MyMeetings.API/Modules/UserAccess/EmailsController.cs rename to src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/EmailsController.cs index ee75f4d2d..2d67ba3ad 100644 --- a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/EmailsController.cs +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/EmailsController.cs @@ -1,10 +1,10 @@ using CompanyName.MyMeetings.API.Configuration.Authorization; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Emails; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Emails; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace CompanyName.MyMeetings.API.Modules.UserAccess +namespace CompanyName.MyMeetings.API.Modules.UserAccess.IdentityServer { [Route("api/userAccess/emails")] [ApiController] diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/RegisterNewUserRequest.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/RegisterNewUserRequest.cs similarity index 81% rename from src/API/CompanyName.MyMeetings.API/Modules/UserAccess/RegisterNewUserRequest.cs rename to src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/RegisterNewUserRequest.cs index 53fd0d6d1..7711e4a71 100644 --- a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/RegisterNewUserRequest.cs +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/RegisterNewUserRequest.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.API.Modules.UserAccess +namespace CompanyName.MyMeetings.API.Modules.UserAccess.IdentityServer { public class RegisterNewUserRequest { diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/ResourceOwnerPasswordValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/ResourceOwnerPasswordValidator.cs similarity index 82% rename from src/API/CompanyName.MyMeetings.API/Modules/UserAccess/ResourceOwnerPasswordValidator.cs rename to src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/ResourceOwnerPasswordValidator.cs index 4a5b7a9de..d1b263010 100644 --- a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/ResourceOwnerPasswordValidator.cs +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/ResourceOwnerPasswordValidator.cs @@ -1,9 +1,9 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication.Authenticate; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication.Authenticate; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using IdentityServer4.Models; using IdentityServer4.Validation; -namespace CompanyName.MyMeetings.API.Modules.UserAccess +namespace CompanyName.MyMeetings.API.Modules.UserAccess.IdentityServer { public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/UserAccessAutofacModule.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/UserAccessAutofacModule.cs similarity index 59% rename from src/API/CompanyName.MyMeetings.API/Modules/UserAccess/UserAccessAutofacModule.cs rename to src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/UserAccessAutofacModule.cs index 4f846e7da..54772e839 100644 --- a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/UserAccessAutofacModule.cs +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/UserAccessAutofacModule.cs @@ -1,8 +1,8 @@ using Autofac; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure; -namespace CompanyName.MyMeetings.API.Modules.UserAccess +namespace CompanyName.MyMeetings.API.Modules.UserAccess.IdentityServer { public class UserAccessAutofacModule : Module { diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/UserRegistrationsController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/UserRegistrationsController.cs similarity index 80% rename from src/API/CompanyName.MyMeetings.API/Modules/UserAccess/UserRegistrationsController.cs rename to src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/UserRegistrationsController.cs index 01d1411c9..550cbaa0f 100644 --- a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/UserRegistrationsController.cs +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/IdentityServer/UserRegistrationsController.cs @@ -1,11 +1,11 @@ using CompanyName.MyMeetings.API.Configuration.Authorization; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.ConfirmUserRegistration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.ConfirmUserRegistration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace CompanyName.MyMeetings.API.Modules.UserAccess +namespace CompanyName.MyMeetings.API.Modules.UserAccess.IdentityServer { [Route("userAccess/[controller]")] [ApiController] diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/ApplicationController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/ApplicationController.cs new file mode 100644 index 000000000..ac50db793 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/ApplicationController.cs @@ -0,0 +1,55 @@ +using CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using Microsoft.AspNetCore.Mvc; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity; + +/// +/// Custom base class for API Controllers +/// +/// Decorating the controller class with the ApiController attribute unleashes more magic power the framework offers. +/// This automates the model state checks and the model state IsValid property doesn't need to be checked manually. +/// Further more the [FromBody] attribute isn't needed anymore since the controller now automatically infers the binding source for the incoming data. +/// +[ApiController] +[Route("api/[controller]")] +public class ApplicationController : ControllerBase +{ + protected new IActionResult Ok(object result = null) + { + return ApiResult.Ok(result); + } + + /* + protected IActionResult Ok(string successMessage, object result = null) + { + return ApiResult.Ok(result, successMessage); + } + */ + + protected IActionResult NotFound(Error error, string invalidField = null) + { + return ApiResult.NotFound(error, invalidField); + } + + protected IActionResult Error(Error error, string invalidField = null) + { + return ApiResult.Error(error, invalidField); + } + + protected IActionResult FromResponse(Result response) + { + return response.ToApiResult(); + } + + protected IActionResult FromResult(CSharpFunctionalExtensions.Result result) + { + if (result.IsSuccess) + { + return Ok(); + } + + return Error(result.Error); + } +} diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/AuthenticationController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/AuthenticationController.cs new file mode 100644 index 000000000..ad846a2f3 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/AuthenticationController.cs @@ -0,0 +1,233 @@ +using System.Security.Claims; +using CompanyName.MyMeetings.API.Configuration.Authorization; +using CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Contracts.V1.Users.Authentication; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login.External; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.RefreshToken; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.RequestForgotPasswordLink; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.ResetPassword; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Authentication; + +[AllowAnonymous] +[Route("api/userAccess/authentication")] +[ApiExplorerSettings(GroupName = "Authentication")] +public class AuthenticationController : ApplicationController +{ + private readonly IUserAccessModule _userAccessModule; + private readonly IExecutionContextAccessor _executionContextAccessor; + + public AuthenticationController(IUserAccessModule userAccessModule, IExecutionContextAccessor executionContextAccessor) + { + _userAccessModule = userAccessModule; + _executionContextAccessor = executionContextAccessor; + } + + /// + /// User login. + /// + /// Authentication attributes. + /// ApiResult. + [HttpPost("login")] + [NoPermissionRequired] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + public async Task Login(AuthenticationRequest request) + { + AuthenticationResultDto result = null; + + var response = await _userAccessModule.ExecuteCommandAsync(new AccountLoginCommand(request.UserName, request.Password)); + if (response != null) + { + if (response.RequiresTwoFactor) + { + await HttpContext.SignInAsync(IdentityConstants.TwoFactorUserIdScheme, response.ClaimsPrincipal); + + result = new AuthenticationResultDto() + { + RequiresTwoFactor = true + }; + } + else if (response.IsAuthenticated) + { + await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, response.ClaimsPrincipal); + + result = new AuthenticationResultDto() + { + UserName = response.User.UserName, + AccessToken = response.AccessToken, + RefreshToken = response.RefreshToken + }; + } + + return response.ToApiResult(result); + } + + return Error(Errors.General.InvalidRequest()); + } + + /// + /// User login. + /// + /// User generated token. + /// ApiResult. + [HttpPost("two-factor-login")] + [NoPermissionRequired] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + public async Task TwoFactorLogin(string token) + { + var result = await HttpContext.AuthenticateAsync(IdentityConstants.TwoFactorUserIdScheme); + if (result != null) + { + if (!result.Succeeded) + { + return Error(Errors.Authentication.LoginRequestExpired()); + } + + var response = await _userAccessModule.ExecuteCommandAsync(new AccountTwoFactorLoginCommand( + Guid.Parse(result.Principal.FindFirstValue("sub")), + result.Principal.FindFirstValue("amr"), + token)); + + if (response != null) + { + if (response.IsAuthenticated) + { + // Clean up the cookie + await HttpContext.SignOutAsync(IdentityConstants.TwoFactorUserIdScheme); + await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, response.ClaimsPrincipal); + + AuthenticationResultDto authenticationResult = new AuthenticationResultDto() + { + UserName = response.User.UserName, + AccessToken = response.AccessToken, + RefreshToken = response.RefreshToken + }; + return Ok(authenticationResult); + } + + return Error(Errors.Authentication.InvalidToken()); + } + } + + return Error(Errors.General.InvalidRequest()); + } + + /// + /// Send forgot password link. + /// + /// Email address of the user. + /// ApiResult. + [HttpPost("request-forgot-password-link")] + [NoPermissionRequired] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + public async Task RequestForgotPasswordLink(string emailAddress) + { + var response = await _userAccessModule.ExecuteCommandAsync(new RequestForgotPasswordLinkCommand(emailAddress)); + return response.ToApiResult(response.Token); + } + + /// + /// Reset password. + /// + /// Reset password attributes. + /// ApiResult. + [HttpPost("reset-password")] + [NoPermissionRequired] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + public async Task ResetPassword(ResetPasswordRequest resetPassword) + { + var response = await _userAccessModule.ExecuteCommandAsync(new ResetPasswordCommand(resetPassword.Token, resetPassword.EmailAddress, resetPassword.Password)); + return response.ToApiResult(); + } + + [HttpGet("external-login")] + [NoPermissionRequired] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public IActionResult ExternalLogin(string provider) + { + var properties = new AuthenticationProperties + { + RedirectUri = Url.Action(nameof(ExternalLoginCallback)), + Items = { { "scheme", provider } } + }; + return Challenge(properties, provider); + } + + [HttpGet("external-login-callback")] + [NoPermissionRequired] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + public async Task ExternalLoginCallback() + { + var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme); + if (result != null) + { + // We really need the external user identifier + var externalUserId = result.Principal.FindFirstValue("sub") + ?? result.Principal.FindFirstValue(ClaimTypes.NameIdentifier) + ?? throw new Exception("Cannot find external user id"); + + // Get the provider from the authentication properties which is available from the scheme item + var provider = result.Properties.Items["scheme"]; + + var emailAddress = result.Principal.FindFirstValue("email") + ?? result.Principal.FindFirstValue(ClaimTypes.Email); + + // Once we have all this we can go ahead an call the external login command + var response = await _userAccessModule.ExecuteCommandAsync(new ExternalAccountLoginCommand(provider, externalUserId, emailAddress, false)); + + if (response != null) + { + if (response.IsAuthenticated) + { + // Clean up the cookie + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + + await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, response.ClaimsPrincipal); + + var authenticationResult = new AuthenticationResultDto() + { + UserName = response.User.UserName, + AccessToken = response.AccessToken, + RefreshToken = response.RefreshToken + }; + return response.ToApiResult(authenticationResult); + } + + return Error(Errors.Authentication.InvalidToken()); + } + } + + return Error(Errors.General.InvalidRequest()); + } + + [HttpPost("refresh-token")] + [NoPermissionRequired] + public async Task RefreshToken(TokenRequest tokenRequest) + { + var response = await _userAccessModule.ExecuteCommandAsync(new RefreshTokenCommand(tokenRequest.AccessToken, tokenRequest.RefreshToken)); + if (response.HasError) + { + return FromResponse(response); + } + + var tokenResult = new TokenResultDto() + { + AccessToken = response.Value!.AccessToken, + RefreshToken = response.Value!.RefreshToken + }; + return response.ToApiResult(tokenResult); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/AuthenticationMappingProfile.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/AuthenticationMappingProfile.cs new file mode 100644 index 000000000..051458d65 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/AuthenticationMappingProfile.cs @@ -0,0 +1,16 @@ +using AutoMapper; +using CompanyName.MyMeetings.Contracts.V1.Users.Identity; +using Application = CompanyName.MyMeetings.Modules.UserAccessMI.Application; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Authentication; + +internal class AuthenticationMappingProfile : Profile +{ + public AuthenticationMappingProfile() + { + AllowNullCollections = true; + + CreateMap(); + CreateMap(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/IdentityController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/IdentityController.cs new file mode 100644 index 000000000..6c365fc31 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/IdentityController.cs @@ -0,0 +1,61 @@ +using AutoMapper; +using CompanyName.MyMeetings.API.Configuration.Authorization; +using CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Identity.GetUserAccount; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Identity.GetUserPermissions; +using Microsoft.AspNetCore.Mvc; +using IdentityContracts = CompanyName.MyMeetings.Contracts.V1.Users.Identity; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Authentication; + +[Route("api/userAccess/identity")] +[ApiExplorerSettings(GroupName = "Authenticated User")] +public class IdentityController : ApplicationController +{ + private readonly IMapper _mapper; + private readonly IUserAccessModule _userAccessModule; + private readonly IExecutionContextAccessor _executionContextAccessor; + + public IdentityController(IUserAccessModule userAccessModule, IExecutionContextAccessor executionContextAccessor, IMapper mapper) + { + _mapper = mapper; + _userAccessModule = userAccessModule; + _executionContextAccessor = executionContextAccessor; + } + + [HttpGet("user-account")] + [HasPermission(UsersPermissions.GetUserAccounts)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + public async Task GetUserAccount() + { + var result = await _userAccessModule.ExecuteQueryAsync(new GetUserAccountQuery(_executionContextAccessor.UserId)); + if (result.HasError) + { + return FromResponse(result); + } + + var userAccount = _mapper.Map(result.Value); + return result.ToApiResult(userAccount); + } + + [HttpGet("permissions")] + [HasPermission(UsersPermissions.GetUserAccounts)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + public async Task GetAuthenticatedUserPermissions() + { + var result = await _userAccessModule.ExecuteQueryAsync(new GetUserPermissionsQuery(_executionContextAccessor.UserId)); + if (result.HasError) + { + return FromResponse(result); + } + + var permissions = _mapper.Map>(result.Value); + return result.ToApiResult(permissions); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/Validators/AuthenticationRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/Validators/AuthenticationRequestValidator.cs new file mode 100644 index 000000000..e21ae38eb --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/Validators/AuthenticationRequestValidator.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Authentication; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Authentication.Validators; + +internal class AuthenticationRequestValidator : AbstractValidator +{ + public AuthenticationRequestValidator() + { + RuleFor(x => x.UserName).CustomNotEmpty(); + RuleFor(x => x.Password).CustomNotEmpty(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/Validators/ResetPasswordRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/Validators/ResetPasswordRequestValidator.cs new file mode 100644 index 000000000..a96b6946f --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authentication/Validators/ResetPasswordRequestValidator.cs @@ -0,0 +1,18 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Authentication; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Authentication.Validators; + +internal class ResetPasswordRequestValidator : AbstractValidator +{ + public ResetPasswordRequestValidator() + { + RuleFor(x => x.Token).CustomNotEmpty(); + RuleFor(x => x.EmailAddress).CustomEmailAddress(); + RuleFor(x => x.Password).CustomNotEmpty(); + RuleFor(x => x.ConfirmPassword).CustomNotEmpty(); + + RuleFor(x => x.ConfirmPassword).CustomEqual(x => x.Password); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authorization/AuthorizationController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authorization/AuthorizationController.cs new file mode 100644 index 000000000..c0217f2fb --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authorization/AuthorizationController.cs @@ -0,0 +1,40 @@ +using AutoMapper; +using CompanyName.MyMeetings.API.Configuration.Authorization; +using CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using Microsoft.AspNetCore.Mvc; +using AuthorizationApplication = CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions; +using AuthorizationContracts = CompanyName.MyMeetings.Contracts.V1.Users.Authorization; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Authorization; + +[Route("api/userAccess/authorization")] +[ApiExplorerSettings(GroupName = "User Access")] +public class AuthorizationController : ApplicationController +{ + private readonly IMapper _mapper; + private readonly IUserAccessModule _userAccessModule; + + public AuthorizationController(IUserAccessModule userAccessModule, IMapper mapper) + { + _mapper = mapper; + _userAccessModule = userAccessModule; + } + + [HttpGet("permissions")] + [NoPermissionRequired] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + public async Task GetPermissionDirectory() + { + var response = await _userAccessModule.ExecuteQueryAsync(new AuthorizationApplication.Directory.GetPermissionsQuery()); + if (response.HasError) + { + return FromResponse(response); + } + + var permissions = _mapper.Map>(response.Value); + return response.ToApiResult(permissions); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authorization/AuthorizationMappingProfile.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authorization/AuthorizationMappingProfile.cs new file mode 100644 index 000000000..cce6bbd96 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Authorization/AuthorizationMappingProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using CompanyName.MyMeetings.Contracts.V1.Users.Authorization; +using Application = CompanyName.MyMeetings.Modules.UserAccessMI.Application; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Authorization; + +public class AuthorizationMappingProfile : Profile +{ + public AuthorizationMappingProfile() + { + AllowNullCollections = true; + + CreateMap(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ApiResult.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ApiResult.cs new file mode 100644 index 000000000..f532ea07a --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ApiResult.cs @@ -0,0 +1,110 @@ +using System.Net; +using CompanyName.MyMeetings.Contracts.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using Microsoft.AspNetCore.Mvc; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; + +public class ApiResult : IActionResult +{ + private readonly Result _result; + private readonly int _statusCode; + + public ApiResult(Result result, HttpStatusCode statusCode) + { + _result = result; + _statusCode = (int)statusCode; + } + + public Guid? CorrelationId { get; set; } + + public ApiResultStatus Status => MapStatus(_statusCode); + + public Task ExecuteResultAsync(ActionContext context) + { + var objectResult = new ObjectResult(_result) + { + StatusCode = _statusCode + }; + + return objectResult.ExecuteResultAsync(context); + } + + /* + public static ApiResult Ok(string successMessage = null) + => new ApiResult(Result.Ok(successMessage), HttpStatusCode.OK); + */ + + public static ApiResult Ok() + => new ApiResult(Result.Ok(), HttpStatusCode.OK); + + public static ApiResult Ok(object result = null) + => new ApiResult(Result.Ok(result), HttpStatusCode.OK); + + /* + public static ApiResult Ok(object result = null, string successMessage = null) + => new ApiResult(Result.Ok(result, successMessage), HttpStatusCode.OK); + */ + + public static ApiResult Error(Error error, string fieldName = null) + => new ApiResult(Result.Error(error.ToErrorMessages(), fieldName), HttpStatusCode.BadRequest); + + public static ApiResult Error() + => Error(Errors.General.InvalidRequest()); + + public static ApiResult Error(Error error) + => Error(new Dictionary>() { { string.Empty, new[] { error } } }); + + public static ApiResult Error(IEnumerable errors) + => Error(new Dictionary>() { { string.Empty, errors } }); + + public static ApiResult Error(IDictionary> errors) + => new ApiResult(Result.Error(errors.ToErrorMessages()), HttpStatusCode.BadRequest); + + public static ApiResult NotFound(Error error) + => NotFound(new Dictionary>() { { string.Empty, new[] { error } } }); + + public static ApiResult NotFound(Error error, string fieldName = null) + => NotFound(new Dictionary>() { { fieldName ?? string.Empty, new[] { error } } }); + + public static ApiResult NotFound(IEnumerable errors) + => NotFound(new Dictionary>() { { string.Empty, errors } }); + + public static ApiResult NotFound(IDictionary> errors) + => new ApiResult(Result.Error(errors.ToErrorMessages()), HttpStatusCode.NotFound); + + public static ApiResult Forbidden(string errorMessage) + => Forbidden(Errors.Authentication.NotAuthorized(errorMessage)); + + public static ApiResult Forbidden(Error error) + => Forbidden(new Dictionary>() { { string.Empty, new[] { error } } }); + + public static ApiResult Forbidden(IEnumerable errors) + => Forbidden(new Dictionary>() { { string.Empty, errors } }); + + public static ApiResult Forbidden(IDictionary> errors) + => new ApiResult(Result.Error(errors.ToErrorMessages()), HttpStatusCode.Forbidden); + + private ApiResultStatus MapStatus(int status) + { + switch (status) + { + case (int)HttpStatusCode.InternalServerError: + return ApiResultStatus.Error; + + case (int)HttpStatusCode.Forbidden: + return ApiResultStatus.Forbidden; + + case (int)HttpStatusCode.BadRequest: + return ApiResultStatus.Invalid; + + case (int)HttpStatusCode.NotFound: + return ApiResultStatus.NotFound; + + case (int)HttpStatusCode.OK: + return ApiResultStatus.Ok; + } + + return ApiResultStatus.Error; + } +} diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ApiResultStatus.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ApiResultStatus.cs new file mode 100644 index 000000000..487edb9d6 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ApiResultStatus.cs @@ -0,0 +1,29 @@ +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; + +public enum ApiResultStatus +{ + /// + /// Successful result. + /// + Ok, + + /// + /// General error result. + /// + Error, + + /// + /// Not found error result. + /// + NotFound, + + /// + /// Invalid error result. + /// + Invalid, + + /// + /// Forbidden error result. + /// + Forbidden +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ErrorMapper.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ErrorMapper.cs new file mode 100644 index 000000000..f0a70891f --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ErrorMapper.cs @@ -0,0 +1,64 @@ +using CompanyName.MyMeetings.Contracts.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; + +internal static class ErrorMapper +{ + public static IDictionary> ToErrorMessages(this IDictionary> errors) + { + var errorMessages = new Dictionary>(); + foreach (var keyValue in errors) + { + errorMessages.Add(keyValue.Key, keyValue.Value.ToErrorMessages()); + } + + return errorMessages; + } + + public static IEnumerable ToErrorMessages(this IEnumerable errors) + { + var errorMessages = new List(); + foreach (var error in errors) + { + errorMessages.AddRange(error.ToErrorMessages()); + } + + return errorMessages; + } + + public static IEnumerable ToErrorMessages(this Error error, List errorMessages = null) + { + if (error is null) + { + throw new ArgumentNullException(nameof(error)); + } + + errorMessages ??= new List(); + + if (!string.IsNullOrEmpty(error.Code + error.Message)) + { + errorMessages.Add(error.ToErrorMessage()); + } + + if (error.Errors is not null) + { + foreach (var subError in error.Errors) + { + subError.ToErrorMessages(errorMessages); + } + } + + return errorMessages; + } + + private static ErrorMessage ToErrorMessage(this Error error) + { + if (error is null) + { + throw new ArgumentNullException(nameof(error)); + } + + return new ErrorMessage(error.Code, error.Message); + } +} diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ResultExtensions.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ResultExtensions.cs new file mode 100644 index 000000000..07d9f6011 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Results/ResultExtensions.cs @@ -0,0 +1,141 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; + +internal static class ResultExtensions +{ + /// + /// Convert an Result to a ApiResult. + /// + /// The Result to convert. + /// ApiResult. + public static ApiResult ToApiResult(this MyMeetings.Modules.UserAccessMI.Application.Configuration.Results.IResult result) + { + if (result == null) + { + return ApiResult.Error(); + } + + // Create the result + ApiResult apiResult = null; + + // Translate the response + if (result.Status == ResultStatus.Forbidden) + { + apiResult = ApiResult.Forbidden("Not authorized."); + } + + if (result.Status == ResultStatus.NotFound) + { + apiResult = ApiResult.NotFound(result.Errors); + } + + if (result.Status == ResultStatus.Error) + { + apiResult = ApiResult.Error(result.Errors); + } + + if (result.Status == ResultStatus.Ok) + { + apiResult = ApiResult.Ok(); + } + + if (apiResult is null) + { + throw new InvalidOperationException($"The Result with status '{result.Status}' cannot be translated to an equivalent ApiResult."); + } + + return apiResult; + } + + /// + /// Convert an Result to a ApiResult. + /// + /// The value type being returned. + /// The Result to convert. + /// The value being returned. + /// ApiResult. + public static ApiResult ToApiResult(this MyMeetings.Modules.UserAccessMI.Application.Configuration.Results.IResult result, T value) + { + if (result == null) + { + return ApiResult.Error(); + } + + // Create the result + ApiResult apiResult = null; + + // Translate the response + if (result.Status == ResultStatus.Forbidden) + { + apiResult = ApiResult.Forbidden("Not authorized."); + } + + if (result.Status == ResultStatus.NotFound) + { + apiResult = ApiResult.NotFound(result.Errors); + } + + if (result.Status == ResultStatus.Error) + { + apiResult = ApiResult.Error(result.Errors); + } + + if (result.Status == ResultStatus.Ok) + { + apiResult = ApiResult.Ok(value); + } + + if (apiResult is null) + { + throw new InvalidOperationException($"The Result with status '{result.Status}' cannot be translated to an equivalent ApiResult."); + } + + return apiResult; + } + + /// + /// Convert an Result to a ApiResult. + /// + /// The value being returned. + /// The Result to convert. + /// ApiResult. + public static ApiResult ToApiResult(this IResult result) + { + if (result == null) + { + return ApiResult.Error(); + } + + // Create the result + ApiResult apiResult = null; + + // Translate the response + if (result.Status == ResultStatus.Forbidden) + { + apiResult = ApiResult.Forbidden("Not authorized."); + } + + if (result.Status == ResultStatus.NotFound) + { + apiResult = ApiResult.NotFound(result.Errors); + } + + if (result.Status == ResultStatus.Error) + { + apiResult = ApiResult.Error(result.Errors); + } + + if (result.Status == ResultStatus.Ok) + { + apiResult = ApiResult.Ok(result.Value); + } + + if (apiResult is null) + { + throw new InvalidOperationException($"The Result with status '{result.Status}' cannot be translated to an equivalent ApiResult."); + } + + return apiResult; + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/RoleMappingProfile.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/RoleMappingProfile.cs new file mode 100644 index 000000000..54d37038e --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/RoleMappingProfile.cs @@ -0,0 +1,16 @@ +using AutoMapper; +using CompanyName.MyMeetings.Contracts.V1.Users.Roles; +using Application = CompanyName.MyMeetings.Modules.UserAccessMI.Application; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Roles; + +internal class RoleMappingProfile : Profile +{ + public RoleMappingProfile() + { + AllowNullCollections = true; + + CreateMap(); + CreateMap(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/RolesController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/RolesController.cs new file mode 100644 index 000000000..02bce7a3f --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/RolesController.cs @@ -0,0 +1,130 @@ +using AutoMapper; +using CompanyName.MyMeetings.API.Configuration.Authorization; +using CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.CreateRole; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.DeleteRole; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRolePermissions; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.RenameRole; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.SetRolePermissions; +using Microsoft.AspNetCore.Mvc; +using RolesApplication = CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRoles; +using UserRoleContracts = CompanyName.MyMeetings.Contracts.V1.Users.Roles; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Roles; + +[Route("api/userAccess/roles")] +[ApiExplorerSettings(GroupName = "User Access")] +public class RolesController : ApplicationController +{ + private readonly IMapper _mapper; + private readonly IUserAccessModule _userAccessModule; + + public RolesController(IUserAccessModule userAccessModule, IMapper mapper) + { + _mapper = mapper; + _userAccessModule = userAccessModule; + } + + [HttpGet] + [HasPermission(UsersPermissions.GetRoles)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task GetRoleDirectory() + { + var response = await _userAccessModule.ExecuteQueryAsync(new RolesApplication.Directory.GetRolesQuery()); + if (response.HasError) + { + return FromResponse(response); + } + + var userRoles = _mapper.Map>(response.Value); + return response.ToApiResult(userRoles); + } + + [HttpGet("{roleId}")] + [HasPermission(UsersPermissions.GetRoles)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task GetRole(Guid roleId) + { + var response = await _userAccessModule.ExecuteQueryAsync(new RolesApplication.ById.GetRolesQuery(roleId)); + if (response.HasError) + { + return FromResponse(response); + } + + var userRoles = _mapper.Map(response.Value); + return response.ToApiResult(userRoles); + } + + [HttpPost] + [HasPermission(UsersPermissions.AddRole)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task AddRole([FromBody] UserRoleContracts.AddRoleRequest request) + { + var response = await _userAccessModule.ExecuteCommandAsync(new CreateRoleCommand(request.Name, request.Permissions)); + return response.ToApiResult(); + } + + [HttpPatch("{roleId}/rename")] + [HasPermission(UsersPermissions.RenameRole)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task RenameRole(Guid roleId, [FromBody] UserRoleContracts.RenameRoleRequest request) + { + var response = await _userAccessModule.ExecuteCommandAsync(new RenameRoleCommand(roleId, request.Name)); + return response.ToApiResult(); + } + + [HttpDelete("{roleId}")] + [HasPermission(UsersPermissions.DeleteRole)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task DeleteRole(Guid roleId) + { + var response = await _userAccessModule.ExecuteCommandAsync(new DeleteRoleCommand(roleId)); + return response.ToApiResult(); + } + + [HttpGet("{roleId}/permissions")] + [HasPermission(UsersPermissions.GetRolePermissions)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task GetRolePermissions(Guid roleId) + { + var response = await _userAccessModule.ExecuteQueryAsync(new GetRolePermissionsQuery(roleId)); + if (response.HasError) + { + return FromResponse(response); + } + + var permissions = _mapper.Map>(response.Value); + return response.ToApiResult(permissions); + } + + [HttpPatch("{roleId}/permissions")] + [HasPermission(UsersPermissions.SetRolePermissions)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task SetRolePermissions(Guid roleId, [FromBody] UserRoleContracts.SetRolePermissionsRequest request) + { + var response = await _userAccessModule.ExecuteCommandAsync(new SetRolePermissionsCommand(roleId, request.Permissions)); + return response.ToApiResult(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/AddRoleRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/AddRoleRequestValidator.cs new file mode 100644 index 000000000..3e3c1a955 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/AddRoleRequestValidator.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Roles; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Roles.Validators; + +internal class AddRoleRequestValidator : AbstractValidator +{ + public AddRoleRequestValidator() + { + RuleFor(x => x.Name).CustomNotEmpty(); + RuleFor(x => x.Permissions).CustomNotEmpty(); + } +} diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/RenameRoleRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/RenameRoleRequestValidator.cs new file mode 100644 index 000000000..153f6c742 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/RenameRoleRequestValidator.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Roles; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Roles.Validators; + +internal class RenameRoleRequestValidator : AbstractValidator +{ + public RenameRoleRequestValidator() + { + RuleFor(x => x.Name).CustomNotEmpty(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/SetRolePermissionsRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/SetRolePermissionsRequestValidator.cs new file mode 100644 index 000000000..98b06e4b3 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Roles/Validators/SetRolePermissionsRequestValidator.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Roles; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Roles.Validators; + +internal class SetRolePermissionsRequestValidator : AbstractValidator +{ + public SetRolePermissionsRequestValidator() + { + RuleFor(x => x.Permissions).CustomNotEmpty(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/UserAccessAutofacModule.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/UserAccessAutofacModule.cs new file mode 100644 index 000000000..30bd78f23 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/UserAccessAutofacModule.cs @@ -0,0 +1,16 @@ +using Autofac; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity +{ + public class UserAccessAutofacModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + } + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UserMappingProfile.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UserMappingProfile.cs new file mode 100644 index 000000000..fa824aedf --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UserMappingProfile.cs @@ -0,0 +1,18 @@ +using AutoMapper; +using CompanyName.MyMeetings.Contracts.V1.Users.Users; +using Application = CompanyName.MyMeetings.Modules.UserAccessMI.Application; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Users; + +internal class UserMappingProfile : Profile +{ + public UserMappingProfile() + { + AllowNullCollections = true; + + CreateMap(); + + CreateMap(); + CreateMap(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UserRegistrationsController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UserRegistrationsController.cs new file mode 100644 index 000000000..02e48b5a7 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UserRegistrationsController.cs @@ -0,0 +1,49 @@ +using CompanyName.MyMeetings.API.Configuration.Authorization; +using CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; +using CompanyName.MyMeetings.Contracts.V1.Users.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.ConfirmUserRegistration; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.RegisterNewUser; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Users; + +[Route("userAccess/[controller]")] +[ApiExplorerSettings(GroupName = "User Access")] +public class UserRegistrationsController : ApplicationController +{ + private readonly IUserAccessModule _userAccessModule; + + public UserRegistrationsController(IUserAccessModule userAccessModule) + { + _userAccessModule = userAccessModule; + } + + [NoPermissionRequired] + [AllowAnonymous] + [HttpPost("")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task RegisterNewUser(RegisterNewUserRequest request) + { + var result = await _userAccessModule.ExecuteCommandAsync(new RegisterNewUserCommand( + request.Login, + request.Password, + request.Email, + request.FirstName, + request.LastName, + request.ConfirmLink)); + + return result.ToApiResult(); + } + + [NoPermissionRequired] + [AllowAnonymous] + [HttpPatch("{userRegistrationId}/confirm")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task ConfirmRegistration(Guid userRegistrationId) + { + var result = await _userAccessModule.ExecuteCommandAsync(new ConfirmUserRegistrationCommand(userRegistrationId)); + return result.ToApiResult(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UsersController.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UsersController.cs new file mode 100644 index 000000000..c2810cf5c --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/UsersController.cs @@ -0,0 +1,221 @@ +using AutoMapper; +using CompanyName.MyMeetings.API.Configuration.Authorization; +using CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions.ByUserId; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetUserRoles; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.AuthenticatorRegistration.GetAuthenticatorKey; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.AuthenticatorRegistration.RegisterAuthenticator; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.ConfirmEmailAddress; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.CreateUserAccount; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.SetUserPermissions; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.SetUserRoles; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.UnlockUserAccount; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.UpdateUserAccount; +using Microsoft.AspNetCore.Mvc; +using UserContracts = CompanyName.MyMeetings.Contracts.V1.Users.Users; +using UsersApplication = CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.GetUserAccounts; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Users; + +[Route("api/userAccess/Users")] +[ApiExplorerSettings(GroupName = "User Access")] +public class UsersController : ApplicationController +{ + private readonly IMapper _mapper; + private readonly IUserAccessModule _userAccessModule; + private readonly IExecutionContextAccessor _executionContextAccessor; + + public UsersController(IUserAccessModule userAccessModule, IExecutionContextAccessor executionContextAccessor, IMapper mapper) + { + _mapper = mapper; + _userAccessModule = userAccessModule; + _executionContextAccessor = executionContextAccessor; + } + + /// + /// Gets the user directory. + /// + /// List of users. + [HttpGet] + [HasPermission(UsersPermissions.GetUsers)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task GetUserAccountDirectory() + { + var response = await _userAccessModule.ExecuteQueryAsync(new UsersApplication.Directory.GetUserAccountsQuery()); + if (response.HasError) + { + return FromResponse(response); + } + + var users = _mapper.Map>(response.Value); + return response.ToApiResult(users); + } + + [HttpGet("{userId}")] + [HasPermission(UsersPermissions.GetUsers)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task GetUserAccount(Guid userId) + { + var response = await _userAccessModule.ExecuteQueryAsync(new UsersApplication.ById.GetUserAccountsQuery(userId)); + if (response.HasError) + { + return FromResponse(response); + } + + var user = _mapper.Map(response.Value); + return response.ToApiResult(user); + } + + [HttpPost] + [HasPermission(UsersPermissions.CreateUserAccount)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task CreateUserAccount(UserContracts.CreateUserAccountRequest request) + { + var response = await _userAccessModule.ExecuteCommandAsync( + new CreateUserAccountCommand( + request.UserName, request.Password, request.Name, request.FirstName, request.LastName, request.EmailAddress)); + + return response.ToApiResult(); + } + + [HttpPut("{userId}")] + [HasPermission(UsersPermissions.UpdateUserAccount)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task UpdateUserAccount(Guid userId, UserContracts.UpdateUserAccountRequest request) + { + var response = await _userAccessModule.ExecuteCommandAsync(new UpdateUserAccountCommand(userId, request.Name, request.FirstName, request.LastName)); + return response.ToApiResult(); + } + + [HttpPatch("{userId}/unlock")] + [HasPermission(UsersPermissions.UnlockUserAccount)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task UnlockUserAccount(Guid userId) + { + var response = await _userAccessModule.ExecuteCommandAsync(new UnlockUserAccountCommand(userId)); + return response.ToApiResult(); + } + + [HttpPost("confirm-email-address")] + [HasPermission(UsersPermissions.ConfirmEmailAddress)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task ConfirmEmailAddress(UserContracts.ConfirmEmailAddressRequest confirmEmailAddress) + { + var response = await _userAccessModule.ExecuteCommandAsync(new ConfirmEmailAddressCommand(confirmEmailAddress.EmailAddress, confirmEmailAddress.Token)); + return response.ToApiResult(); + } + + /// + /// Get the authenticator key for the current logged in user. + /// Use this key to register an new account in an authenticator app. + /// + /// The authenticator key. + [HttpGet("authenticator-key")] + [HasPermission(UsersPermissions.GetAuthenticatorKey)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task GetAuthenticatorKey() + { + var response = await _userAccessModule.ExecuteQueryAsync(new GetAuthenticatorKeyQuery(_executionContextAccessor.UserId)); + return response.ToApiResult(); + } + + /// + /// Enable two-step authentication for the current logged in user by providing the code generated by the authenticator app. + /// + /// One-Time Password Code. + /// Result. + [HttpPost("register-authenticator")] + [HasPermission(UsersPermissions.RegisterAuthenticator)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task RegisterAuthenticator(string otpCode) + { + var response = await _userAccessModule.ExecuteCommandAsync(new RegisterAuthenticatorCommand(_executionContextAccessor.UserId, otpCode)); + return response.ToApiResult(); + } + + [HttpGet("{userId}/roles")] + [HasPermission(UsersPermissions.GetUserRoles)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task GetUserRoles(Guid userId) + { + var response = await _userAccessModule.ExecuteQueryAsync(new GetUserRolesQuery(userId)); + if (response.HasError) + { + return FromResponse(response); + } + + var roles = _mapper.Map>(response.Value); + return response.ToApiResult(roles); + } + + [HttpPatch("{userId}/roles")] + [HasPermission(UsersPermissions.SetUserRoles)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task SetUserRoles(Guid userId, UserContracts.SetUserRolesRequest request) + { + var response = await _userAccessModule.ExecuteCommandAsync(new SetUserRolesCommand(userId, request.RoleIds)); + return response.ToApiResult(); + } + + [HttpGet("{userId}/permissions")] + [HasPermission(UsersPermissions.GetUserPermissions)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task GetUserPermissions(Guid userId) + { + var response = await _userAccessModule.ExecuteQueryAsync(new GetPermissionsQuery(userId)); + if (response.HasError) + { + return FromResponse(response); + } + + var permissions = _mapper.Map>(response.Value); + return response.ToApiResult(permissions); + } + + [HttpPatch("{userId}/permissions")] + [HasPermission(UsersPermissions.SetUserPermissions)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(ApiResult), StatusCodes.Status403Forbidden)] + public async Task SetUserPermissions(Guid userId, [FromBody] UserContracts.SetUserPermissionsRequest request) + { + var response = await _userAccessModule.ExecuteCommandAsync(new SetUserPermissionsCommand(userId, request.Permissions)); + return response.ToApiResult(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/ConfirmEmailAddressRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/ConfirmEmailAddressRequestValidator.cs new file mode 100644 index 000000000..51e05e4fe --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/ConfirmEmailAddressRequestValidator.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Users; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Users.Validators; + +internal class ConfirmEmailAddressRequestValidator : AbstractValidator +{ + public ConfirmEmailAddressRequestValidator() + { + RuleFor(x => x.Token).CustomNotEmpty(); + RuleFor(x => x.EmailAddress).CustomEmailAddress(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/CreateUserAccountRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/CreateUserAccountRequestValidator.cs new file mode 100644 index 000000000..acf381824 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/CreateUserAccountRequestValidator.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Users; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Users.Validators; + +internal class CreateUserAccountRequestValidator : AbstractValidator +{ + public CreateUserAccountRequestValidator() + { + RuleFor(x => x.UserName).CustomNotEmpty(); + RuleFor(x => x.EmailAddress).CustomEmailAddress(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/SetUserPermissionsRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/SetUserPermissionsRequestValidator.cs new file mode 100644 index 000000000..0bf60337d --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/SetUserPermissionsRequestValidator.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Users; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Users.Validators; + +internal class SetUserPermissionsRequestValidator : AbstractValidator +{ + public SetUserPermissionsRequestValidator() + { + RuleFor(x => x.Permissions).CustomNotNull(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/SetUserRolesRequestValidator.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/SetUserRolesRequestValidator.cs new file mode 100644 index 000000000..46bc3ede7 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/Users/Validators/SetUserRolesRequestValidator.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.Contracts.V1.Users.Users; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application; +using FluentValidation; + +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Users.Validators; + +internal class SetUserRolesRequestValidator : AbstractValidator +{ + public SetUserRolesRequestValidator() + { + RuleFor(x => x.RoleIds).CustomNotNull(); + } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/UsersPermissions.cs b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/UsersPermissions.cs new file mode 100644 index 000000000..39a15eb76 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.API/Modules/UserAccess/MicrosoftIdentity/UsersPermissions.cs @@ -0,0 +1,28 @@ +namespace CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity; + +internal class UsersPermissions +{ + // Identity + public const string GetUserAccounts = "Users.GetUserAccounts"; + + // Users + public const string GetUsers = "Users.GetUsers"; + public const string CreateUserAccount = "Users.CreateUserAccount"; + public const string UpdateUserAccount = "Users.UpdateUserAccount"; + public const string UnlockUserAccount = "Users.UnlockUserAccount"; + public const string ConfirmEmailAddress = "Users.ConfirmEmailAddress"; + public const string GetAuthenticatorKey = "Users.GetAuthenticatorKey"; + public const string RegisterAuthenticator = "Users.RegisterAuthenticator"; + public const string GetUserRoles = "Users.GetUserRoles"; + public const string SetUserRoles = "Users.SetUserRoles"; + public const string GetUserPermissions = "Users.GetUserPermissions"; + public const string SetUserPermissions = "Users.SetUserPermissions"; + + // Roles + public const string GetRoles = "Users.GetRoles"; + public const string AddRole = "Users.AddRole"; + public const string RenameRole = "Users.RenameRole"; + public const string DeleteRole = "Users.DeleteRole"; + public const string GetRolePermissions = "Users.GetRolePermissions"; + public const string SetRolePermissions = "Users.SetRolePermissions"; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.API/Startup.cs b/src/API/CompanyName.MyMeetings.API/Startup.cs index c514e745d..5a0f154a2 100644 --- a/src/API/CompanyName.MyMeetings.API/Startup.cs +++ b/src/API/CompanyName.MyMeetings.API/Startup.cs @@ -1,4 +1,7 @@ -using Autofac; +using System.IdentityModel.Tokens.Jwt; +using System.Reflection; +using System.Text.Json; +using Autofac; using Autofac.Extensions.DependencyInjection; using CompanyName.MyMeetings.API.Configuration.Authorization; using CompanyName.MyMeetings.API.Configuration.ExecutionContext; @@ -7,22 +10,29 @@ using CompanyName.MyMeetings.API.Modules.Administration; using CompanyName.MyMeetings.API.Modules.Meetings; using CompanyName.MyMeetings.API.Modules.Payments; -using CompanyName.MyMeetings.API.Modules.UserAccess; +using CompanyName.MyMeetings.API.Modules.UserAccess.MicrosoftIdentity.Results; using CompanyName.MyMeetings.BuildingBlocks.Application; using CompanyName.MyMeetings.BuildingBlocks.Domain; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Emails; +using CompanyName.MyMeetings.Contracts.Results; using CompanyName.MyMeetings.Modules.Administration.Infrastructure.Configuration; using CompanyName.MyMeetings.Modules.Meetings.Infrastructure.Configuration; using CompanyName.MyMeetings.Modules.Payments.Infrastructure.Configuration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.IdentityServer; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.IdentityServer; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; using Hellang.Middleware.ProblemDetails; using IdentityServer4.AccessTokenValidation; using IdentityServer4.Validation; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.Tokens; using Serilog; using Serilog.Formatting.Compact; using ILogger = Serilog.ILogger; +using UserAccess = CompanyName.MyMeetings.API.Modules.UserAccess; +using UserAccessISConfiguration = CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration; namespace CompanyName.MyMeetings.API { @@ -44,7 +54,7 @@ public Startup(IWebHostEnvironment env) .AddEnvironmentVariables("Meetings_") .Build(); - _loggerForApi.Information("Connection string:" + _configuration[MeetingsConnectionString]); + _loggerForApi.Information("Connection string:" + _configuration["ConnectionStrings:" + MeetingsConnectionString]); AuthorizationChecker.CheckAllEndpoints(); } @@ -55,11 +65,73 @@ public void ConfigureServices(IServiceCollection services) services.AddSwaggerDocumentation(); - ConfigureIdentityServer(services); - + // ConfigureIdentityServer(services); services.AddSingleton(); services.AddSingleton(); + services.AddAuthentication() + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, config => + { + config.RequireHttpsMetadata = false; + config.SaveToken = true; + + var userAccessConfiguration = _configuration.GetUserAccessConfiguration(); + config.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = userAccessConfiguration.GetIssuerSigningKey(), + ValidIssuer = userAccessConfiguration.GetValidIssuer(), + ValidateIssuer = userAccessConfiguration.ShouldValidateIssuer(), + ValidAudience = userAccessConfiguration.GetValidAudience(), + ValidateAudience = userAccessConfiguration.ShouldValidateAudience(), + ValidateLifetime = true, + RequireExpirationTime = true, + + // Clock skew compensates for server time drift. + ClockSkew = TimeSpan.FromMinutes(5) + }; + + config.Events = new JwtBearerEvents + { + OnChallenge = context => + { + context.Response.ContentType = "application/json"; + context.Response.OnStarting(() => + { + var error = Errors.Authentication.NotAuthorized(); + string result = JsonSerializer.Serialize(Result.Error(error.ToErrorMessages())); + return context.Response.WriteAsync(result); + }); + return Task.CompletedTask; + }, + OnTokenValidated = context => + { + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) + { + context.Response.Headers.Append("Token-Expired", "true"); + } + + return Task.CompletedTask; + } + }; + }) + .AddCookie(IdentityConstants.ApplicationScheme) + .AddCookie(IdentityConstants.TwoFactorUserIdScheme); + /* + Add additional external authentication providers (don't forget to reference the required packages) + Package: Microsoft.AspNetCore.Authentication.Google + Google portal (APIs und Services) for configuration: https://console.cloud.google.com/apis/library + .AddGoogle(googleOptions => + { + googleOptions.ClientId = configuration["Authentication:Google:ClientId"]; + googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"]; + }); + */ + services.AddProblemDetails(x => { x.Map(ex => new InvalidCommandProblemDetails(ex)); @@ -75,15 +147,18 @@ public void ConfigureServices(IServiceCollection services) }); }); - services.AddScoped(); + // services.AddScoped(); + services.AddScoped(); + services.AddAutoMapper(Assembly.GetExecutingAssembly()); } public void ConfigureContainer(ContainerBuilder containerBuilder) { containerBuilder.RegisterModule(new MeetingsAutofacModule()); containerBuilder.RegisterModule(new AdministrationAutofacModule()); - containerBuilder.RegisterModule(new UserAccessAutofacModule()); + containerBuilder.RegisterModule(new UserAccess.IdentityServer.UserAccessAutofacModule()); containerBuilder.RegisterModule(new PaymentsAutofacModule()); + containerBuilder.RegisterModule(new UserAccess.MicrosoftIdentity.UserAccessAutofacModule()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -100,8 +175,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IService app.UseSwaggerDocumentation(); - app.UseIdentityServer(); - + // app.UseIdentityServer(); if (env.IsDevelopment()) { app.UseProblemDetails(); @@ -116,6 +190,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IService app.UseRouting(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + // app.UseAuthentication(); app.UseAuthorization(); @@ -148,7 +224,7 @@ private void ConfigureIdentityServer(IServiceCollection services) .AddProfileService() .AddDeveloperSigningCredential(); - services.AddTransient(); + services.AddTransient(); services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, x => @@ -165,31 +241,33 @@ private void InitializeModules(ILifetimeScope container) var executionContextAccessor = new ExecutionContextAccessor(httpContextAccessor); var emailsConfiguration = new EmailsConfiguration(_configuration["EmailsConfiguration:FromEmail"]); + var userAccessConfiguration = _configuration.GetUserAccessConfiguration(); MeetingsStartup.Initialize( - _configuration[MeetingsConnectionString], + _configuration["ConnectionStrings:" + MeetingsConnectionString], executionContextAccessor, _logger, emailsConfiguration, null); AdministrationStartup.Initialize( - _configuration[MeetingsConnectionString], + _configuration["ConnectionStrings:" + MeetingsConnectionString], executionContextAccessor, _logger, null); UserAccessStartup.Initialize( - _configuration[MeetingsConnectionString], + _configuration["ConnectionStrings:" + MeetingsConnectionString], executionContextAccessor, _logger, emailsConfiguration, _configuration["Security:TextEncryptionKey"], null, - null); + null, + userAccessConfiguration); PaymentsStartup.Initialize( - _configuration[MeetingsConnectionString], + _configuration["ConnectionStrings:" + MeetingsConnectionString], executionContextAccessor, _logger, emailsConfiguration, diff --git a/src/API/CompanyName.MyMeetings.API/appsettings.json b/src/API/CompanyName.MyMeetings.API/appsettings.json index 8b580c672..185f35b43 100644 --- a/src/API/CompanyName.MyMeetings.API/appsettings.json +++ b/src/API/CompanyName.MyMeetings.API/appsettings.json @@ -1,19 +1,38 @@ { - "Logging": { - "LogLevel": { - "Default": "Trace", - "System": "Information", - "Microsoft": "None" - } + "Logging": { + "LogLevel": { + "Default": "Trace", + "System": "Information", + "Microsoft": "None" + } + + }, + "AllowedHosts": "*", + + "EmailsConfiguration": { + "FromEmail": "no-reply@mymeetings.com" + }, + "Security": { + /* NOTE! This is sensitive data and should be stored in secure way (not here). Added only for demo purpose. */ + "TextEncryptionKey": "E546C8DF278CK5990069B522" + }, + + "ConnectionStrings": { + "MeetingsConnectionString": "" + }, - }, - "AllowedHosts": "*", + "Modules": { - "EmailsConfiguration": { - "FromEmail": "no-reply@mymeetings.com" - }, - "Security": { - /* NOTE! This is sensitive data and should be stored in secure way (not here). Added only for demo purpose. */ - "TextEncryptionKey": "E546C8DF278CK5990069B522" + "UserAccess": { + "Security": { + /* + NOTE! This is sensitive data and should be stored in secure way (not here). Added only for demo purpose. + TODO: Generate new key (helper: https://www.browserling.com/tools/random-string) + */ + "JwtSecretKey": "eiURlJpBCvFkZXzQrWKVhAWhHcbfefCPcqUrrbKOTDrkJOxLEOjuAmKiRMcKNKC", + "JwtIssuer": "api://mymeetings.com/api", + "JwtAudience": "api://mymeetings.com/api" + } } + } } diff --git a/src/API/CompanyName.MyMeetings.Contracts/CompanyName.MyMeetings.Contracts.csproj b/src/API/CompanyName.MyMeetings.Contracts/CompanyName.MyMeetings.Contracts.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/CompanyName.MyMeetings.Contracts.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/src/API/CompanyName.MyMeetings.Contracts/Results/ErrorMessage.cs b/src/API/CompanyName.MyMeetings.Contracts/Results/ErrorMessage.cs new file mode 100644 index 000000000..0c86d81e0 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/Results/ErrorMessage.cs @@ -0,0 +1,14 @@ +namespace CompanyName.MyMeetings.Contracts.Results; + +public struct ErrorMessage +{ + public ErrorMessage(string code, string? message) + { + Code = code; + Message = message; + } + + public string Code { get; set; } + + public string? Message { get; set; } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/Results/Result.cs b/src/API/CompanyName.MyMeetings.Contracts/Results/Result.cs new file mode 100644 index 000000000..0cf77273f --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/Results/Result.cs @@ -0,0 +1,48 @@ +namespace CompanyName.MyMeetings.Contracts.Results; + +public class Result +{ + public object? Value { get; set; } + + public DateTime TimeGenerated { get; set; } + + public IDictionary>? Errors { get; set; } + + public Result() + { + } + + internal Result(object? value, IDictionary>? errors) + { + Value = value; + Errors = errors; + TimeGenerated = DateTime.UtcNow; + } + + public static Result Ok() + => Ok(null); + + public static Result Ok(object? value) + => new Result(value, null); + + public static Result Error(ErrorMessage error, string? invalidField = null) + => Error(new Dictionary>() { { invalidField ?? string.Empty, new[] { error } } }); + + public static Result Error(IDictionary> errors) + => new Result(null, errors); + + public static Result Error(IEnumerable errors, string? invalidField = null) + => new Result(null, new Dictionary>() { { invalidField ?? string.Empty, errors } }); + + public static Result Ok() + where T : notnull + => new Result(default, null); + + public static Result Ok(T? value) + where T : notnull + => new Result(value, null); + + public static Result Error(IDictionary> errors) + where T : notnull + => new Result(default, errors); +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/Results/ResultOfT.cs b/src/API/CompanyName.MyMeetings.Contracts/Results/ResultOfT.cs new file mode 100644 index 000000000..5eb330da0 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/Results/ResultOfT.cs @@ -0,0 +1,19 @@ +namespace CompanyName.MyMeetings.Contracts.Results; + +#pragma warning disable SA1649 // File name should match first type name +public class Result : Result +#pragma warning restore SA1649 // File name should match first type name + where T : notnull +{ + public Result() + : base() + { + } + + internal Result(T? value, IDictionary>? errors) + : base(value, errors) + { + } + + public new T? Value { get; set; } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/AuthenticationRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/AuthenticationRequest.cs new file mode 100644 index 000000000..6e0959b7b --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/AuthenticationRequest.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Authentication; + +public class AuthenticationRequest +{ + public string UserName { get; set; } = null!; + + public string Password { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/AuthenticationResultDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/AuthenticationResultDto.cs new file mode 100644 index 000000000..07efc486c --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/AuthenticationResultDto.cs @@ -0,0 +1,16 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Authentication; + +public class AuthenticationResultDto +{ + public string? AccessToken { get; set; } + + public string? RefreshToken { get; set; } + + public string? UserName { get; set; } + + public bool IsLockedOut { get; set; } = false; + + public bool IsNotAllowed { get; set; } = false; + + public bool RequiresTwoFactor { get; set; } = false; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/ResetPasswordRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/ResetPasswordRequest.cs new file mode 100644 index 000000000..2d0e3aaee --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/ResetPasswordRequest.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace CompanyName.MyMeetings.Contracts.V1.Users.Authentication; + +public class ResetPasswordRequest +{ + public string Token { get; set; } = null!; + + public string EmailAddress { get; set; } = null!; + + [DataType(DataType.Password)] + public string Password { get; set; } = null!; + + [Compare(nameof(Password))] + [DataType(DataType.Password)] + public string ConfirmPassword { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/TokenRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/TokenRequest.cs new file mode 100644 index 000000000..74c89ed61 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/TokenRequest.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Authentication; + +public class TokenRequest +{ + public string AccessToken { get; set; } = null!; + + public string RefreshToken { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/TokenResultDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/TokenResultDto.cs new file mode 100644 index 000000000..271c1c79b --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authentication/TokenResultDto.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Authentication; + +public class TokenResultDto +{ + public string AccessToken { get; set; } = null!; + + public string RefreshToken { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authorization/PermissionDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authorization/PermissionDto.cs new file mode 100644 index 000000000..1d7c9fbc1 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Authorization/PermissionDto.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Authorization; + +public class PermissionDto +{ + public string Code { get; set; } = null!; + + public string Name { get; set; } = null!; + + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Identity/UserAccountDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Identity/UserAccountDto.cs new file mode 100644 index 000000000..999015e10 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Identity/UserAccountDto.cs @@ -0,0 +1,18 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Identity; + +public class UserAccountDto +{ + public Guid Id { get; set; } + + public bool IsActive { get; set; } + + public string UserName { get; set; } = null!; + + public string? Name { get; set; } + + public string? FirstName { get; set; } + + public string? LastName { get; set; } + + public string? EmailAddress { get; set; } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Identity/UserPermissionDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Identity/UserPermissionDto.cs new file mode 100644 index 000000000..148e21703 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Identity/UserPermissionDto.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Identity; + +public class UserPermissionDto +{ + public string Code { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/AddRoleRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/AddRoleRequest.cs new file mode 100644 index 000000000..4ae6488f0 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/AddRoleRequest.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Roles; + +public class AddRoleRequest +{ + public string Name { get; set; } = null!; + + public string[] Permissions { get; set; } = null!; +} diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/PermissionDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/PermissionDto.cs new file mode 100644 index 000000000..7e3584724 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/PermissionDto.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Roles; + +public class PermissionDto +{ + public string Code { get; set; } = null!; + + public string Name { get; set; } = null!; + + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/RenameRoleRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/RenameRoleRequest.cs new file mode 100644 index 000000000..79bbc6b8a --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/RenameRoleRequest.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Roles; + +public class RenameRoleRequest +{ + public string Name { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/RoleDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/RoleDto.cs new file mode 100644 index 000000000..5093b9dfa --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/RoleDto.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Roles; + +public class RoleDto +{ + public Guid Id { get; set; } + + public string Name { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/SetRolePermissionsRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/SetRolePermissionsRequest.cs new file mode 100644 index 000000000..c156b8608 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Roles/SetRolePermissionsRequest.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Roles; + +public class SetRolePermissionsRequest +{ + public string[] Permissions { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/UserRegistrations/RegisterNewUserRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/UserRegistrations/RegisterNewUserRequest.cs new file mode 100644 index 000000000..ee4bf6681 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/UserRegistrations/RegisterNewUserRequest.cs @@ -0,0 +1,16 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.UserRegistrations; + +public class RegisterNewUserRequest +{ + public string Login { get; set; } = null!; + + public string Password { get; set; } = null!; + + public string Email { get; set; } = null!; + + public string FirstName { get; set; } = null!; + + public string LastName { get; set; } = null!; + + public string ConfirmLink { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/ConfirmEmailAddressRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/ConfirmEmailAddressRequest.cs new file mode 100644 index 000000000..405d91cb4 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/ConfirmEmailAddressRequest.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Users; + +public class ConfirmEmailAddressRequest +{ + public string Token { get; set; } = null!; + + public string EmailAddress { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/CreateUserAccountRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/CreateUserAccountRequest.cs new file mode 100644 index 000000000..b568333c6 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/CreateUserAccountRequest.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace CompanyName.MyMeetings.Contracts.V1.Users.Users; + +public class CreateUserAccountRequest +{ + public string UserName { get; set; } = null!; + + [DataType(DataType.Password)] + public string? Password { get; set; } + + public string? Name { get; set; } + + public string? FirstName { get; set; } + + public string? LastName { get; set; } + + public string EmailAddress { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/PermissionDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/PermissionDto.cs new file mode 100644 index 000000000..e35adb513 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/PermissionDto.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Users; + +public class PermissionDto +{ + public string Code { get; set; } = null!; + + public string Name { get; set; } = null!; + + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/RoleDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/RoleDto.cs new file mode 100644 index 000000000..a70b3a445 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/RoleDto.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Users; + +public class RoleDto +{ + public Guid Id { get; set; } + + public string Name { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/SetUserPermissionsRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/SetUserPermissionsRequest.cs new file mode 100644 index 000000000..ef73d21b2 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/SetUserPermissionsRequest.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Users; + +public class SetUserPermissionsRequest +{ + public string[] Permissions { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/SetUserRolesRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/SetUserRolesRequest.cs new file mode 100644 index 000000000..bfbb17d41 --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/SetUserRolesRequest.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Users; + +public class SetUserRolesRequest +{ + public Guid[] RoleIds { get; set; } = null!; +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/UpdateUserAccountRequest.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/UpdateUserAccountRequest.cs new file mode 100644 index 000000000..c316b046a --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/UpdateUserAccountRequest.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Users; + +public class UpdateUserAccountRequest +{ + public string? Name { get; set; } + + public string? FirstName { get; set; } + + public string? LastName { get; set; } +} \ No newline at end of file diff --git a/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/UserAccountDto.cs b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/UserAccountDto.cs new file mode 100644 index 000000000..a659e996f --- /dev/null +++ b/src/API/CompanyName.MyMeetings.Contracts/V1/Users/Users/UserAccountDto.cs @@ -0,0 +1,71 @@ +namespace CompanyName.MyMeetings.Contracts.V1.Users.Users; + +public class UserAccountDto +{ + public Guid Id { get; init; } + + public string? Name { get; init; } + + public string? FirstName { get; init; } + + public string? LastName { get; init; } + + /// + /// Gets or sets the date and time, in UTC, when any user lockout ends. + /// A value in the past means the user is not locked out. + /// + public virtual DateTimeOffset? LockoutEnd { get; init; } + + /// + /// Gets or sets a flag indicating if two factor authentication is enabled for this user. + /// True if 2fa is enabled, otherwise false. + /// + public virtual bool TwoFactorEnabled { get; init; } + + /// + /// Gets or sets a flag indicating if a user has confirmed their telephone address. + /// True if the telephone number has been confirmed, otherwise false. + /// + public virtual bool PhoneNumberConfirmed { get; init; } + + /// + /// Gets or sets a telephone number for the user. + /// + public virtual string? PhoneNumber { get; init; } + + /// + /// Gets or sets a flag indicating if a user has confirmed their email address. + /// True if the email address has been confirmed, otherwise false. + /// + public virtual bool EmailConfirmed { get; init; } + + /// + /// Gets or sets the normalized email address for this user. + /// + public virtual string NormalizedEmail { get; init; } = null!; + + /// + /// Gets or sets the email address for this user. + /// + public virtual string Email { get; init; } = null!; + + /// + /// Gets or sets the normalized user name for this user. + /// + public virtual string NormalizedUserName { get; init; } = null!; + + /// + /// Gets or sets the login for this user. + /// + public virtual string UserName { get; init; } = null!; + + /// + /// True if the user could be locked out, otherwise false. + /// + public virtual bool LockoutEnabled { get; init; } + + /// + /// Gets or sets the number of failed login attempts for the current user. + /// + public virtual int AccessFailedCount { get; init; } +} \ No newline at end of file diff --git a/src/BuildingBlocks/Application/Data/IDatabaseConfiguration.cs b/src/BuildingBlocks/Application/Data/IDatabaseConfiguration.cs new file mode 100644 index 000000000..964420940 --- /dev/null +++ b/src/BuildingBlocks/Application/Data/IDatabaseConfiguration.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.BuildingBlocks.Application.Data; + +public interface IDatabaseConfiguration +{ + string ConnectionString { get; } +} \ No newline at end of file diff --git a/src/BuildingBlocks/Application/IExecutionContextAccessor.cs b/src/BuildingBlocks/Application/IExecutionContextAccessor.cs index c534528b6..fe5d788ca 100644 --- a/src/BuildingBlocks/Application/IExecutionContextAccessor.cs +++ b/src/BuildingBlocks/Application/IExecutionContextAccessor.cs @@ -7,5 +7,7 @@ public interface IExecutionContextAccessor Guid CorrelationId { get; } bool IsAvailable { get; } + + bool IsAuthenticated { get; } } } \ No newline at end of file diff --git a/src/BuildingBlocks/Domain/Entity.cs b/src/BuildingBlocks/Domain/Entity.cs index bfc9c7db1..84c92ce72 100644 --- a/src/BuildingBlocks/Domain/Entity.cs +++ b/src/BuildingBlocks/Domain/Entity.cs @@ -1,6 +1,6 @@ namespace CompanyName.MyMeetings.BuildingBlocks.Domain { - public abstract class Entity + public abstract class Entity : IEntity { private List _domainEvents; diff --git a/src/BuildingBlocks/Domain/IEntity.cs b/src/BuildingBlocks/Domain/IEntity.cs new file mode 100644 index 000000000..aa0e009cb --- /dev/null +++ b/src/BuildingBlocks/Domain/IEntity.cs @@ -0,0 +1,12 @@ +namespace CompanyName.MyMeetings.BuildingBlocks.Domain +{ + public interface IEntity + { + void ClearDomainEvents(); + + /// + /// Domain events occurred. + /// + IReadOnlyCollection DomainEvents { get; } + } +} diff --git a/src/BuildingBlocks/Infrastructure/DatabaseConfiguration.cs b/src/BuildingBlocks/Infrastructure/DatabaseConfiguration.cs new file mode 100644 index 000000000..d94438067 --- /dev/null +++ b/src/BuildingBlocks/Infrastructure/DatabaseConfiguration.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; + +namespace CompanyName.MyMeetings.BuildingBlocks.Infrastructure; + +public class DatabaseConfiguration : IDatabaseConfiguration +{ + public DatabaseConfiguration(string connectionString) + { + ConnectionString = connectionString; + } + + public string ConnectionString { get; } +} \ No newline at end of file diff --git a/src/BuildingBlocks/Infrastructure/StronglyTypedIdValueConverterSelector.cs b/src/BuildingBlocks/Infrastructure/StronglyTypedIdValueConverterSelector.cs index bd06a9221..70b4bbd18 100644 --- a/src/BuildingBlocks/Infrastructure/StronglyTypedIdValueConverterSelector.cs +++ b/src/BuildingBlocks/Infrastructure/StronglyTypedIdValueConverterSelector.cs @@ -5,7 +5,7 @@ namespace CompanyName.MyMeetings.BuildingBlocks.Infrastructure { /// - /// Based on https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-4/ + /// Based on https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-4/. /// public class StronglyTypedIdValueConverterSelector : ValueConverterSelector { diff --git a/src/CompanyName.MyMeetings.sln b/src/CompanyName.MyMeetings.sln index 5e68c9248..a025009fe 100644 --- a/src/CompanyName.MyMeetings.sln +++ b/src/CompanyName.MyMeetings.sln @@ -15,7 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UserAccess", "UserAccess", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "API", "API", "{BC9DDFD1-FB81-4996-812A-68BEBCA33A97}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccess.Application", "Modules\UserAccess\Application\CompanyName.MyMeetings.Modules.UserAccess.Application.csproj", "{F34C6504-590B-480A-A239-F230CDFF8CED}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessIS.Application", "Modules\UserAccess\Application\CompanyName.MyMeetings.Modules.UserAccessIS.Application.csproj", "{F34C6504-590B-480A-A239-F230CDFF8CED}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.Meetings.Application", "Modules\Meetings\Application\CompanyName.MyMeetings.Modules.Meetings.Application.csproj", "{5C2C1630-8A7A-451F-81A8-8547C939F5FD}" EndProject @@ -41,11 +41,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Database", "Database", "{C7 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.Administration.IntegrationEvents", "Modules\Administration\IntegrationEvents\CompanyName.MyMeetings.Modules.Administration.IntegrationEvents.csproj", "{396817BD-94A7-4559-A7F1-2FAB8009BCF8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccess.Infrastructure", "Modules\UserAccess\Infrastructure\CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.csproj", "{55E1C531-2B9B-49B1-BDFE-9DE322E7FE00}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure", "Modules\UserAccess\Infrastructure\CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.csproj", "{55E1C531-2B9B-49B1-BDFE-9DE322E7FE00}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccess.Domain", "Modules\UserAccess\Domain\CompanyName.MyMeetings.Modules.UserAccess.Domain.csproj", "{F364B0C4-1882-46A1-9B08-22587BEF05A2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessIS.Domain", "Modules\UserAccess\Domain\CompanyName.MyMeetings.Modules.UserAccessIS.Domain.csproj", "{F364B0C4-1882-46A1-9B08-22587BEF05A2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents", "Modules\UserAccess\IntegrationEvents\CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents.csproj", "{0C31EA31-6A10-47D0-82E5-6D224E1CE532}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationEvents", "Modules\UserAccess\IntegrationEvents\CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationEvents.csproj", "{0C31EA31-6A10-47D0-82E5-6D224E1CE532}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Payments", "Payments", "{13E4D721-2E96-497E-9657-503D09468F9F}" EndProject @@ -63,7 +63,7 @@ Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "CompanyName.MyMeetings.Data EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0FF699EF-8156-43CB-8D18-8EA28F30E9EE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests", "Modules\UserAccess\Tests\UnitTests\CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests.csproj", "{0BC12804-A858-427B-88E2-F9CDE9E97986}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests", "Modules\UserAccess\Tests\UnitTests\CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests.csproj", "{0BC12804-A858-427B-88E2-F9CDE9E97986}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{53E4F002-E708-45F7-8444-19EB8977B5C9}" EndProject @@ -87,9 +87,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.Payments.ArchTests", "Modules\Payments\Tests\ArchTests\CompanyName.MyMeetings.Modules.Payments.ArchTests.csproj", "{1885C71E-1624-4673-B0BC-BF4035CFFE72}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccess.ArchTests", "Modules\UserAccess\Tests\ArchTests\CompanyName.MyMeetings.Modules.UserAccess.ArchTests.csproj", "{F8EE61DA-0E8B-4074-A0AF-433244EC1FB8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests", "Modules\UserAccess\Tests\ArchTests\CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.csproj", "{F8EE61DA-0E8B-4074-A0AF-433244EC1FB8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests", "Modules\UserAccess\Tests\IntegrationTests\CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.csproj", "{396A6C3F-74BA-4D85-8BF3-1182F9DE9D95}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests", "Modules\UserAccess\Tests\IntegrationTests\CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.csproj", "{396A6C3F-74BA-4D85-8BF3-1182F9DE9D95}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.Payments.IntegrationTests", "Modules\Payments\Tests\IntegrationTests\CompanyName.MyMeetings.Modules.Payments.IntegrationTests.csproj", "{B448FDC3-5F85-47EE-9F4A-2654E8CC67E1}" EndProject @@ -136,6 +136,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RequestExamples", "RequestE EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.SUT", "Tests\SUT\CompanyName.MyMeetings.SUT.csproj", "{1853847F-9988-43A1-B3E1-DDBE4B2F3365}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IdentityServer", "IdentityServer", "{1761E09E-402B-4273-BDE5-0A8D9BD2BAC0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MicrosoftIdentity", "MicrosoftIdentity", "{10AFBD87-F071-47A5-B3E6-F7B4C63E1207}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessMI.Domain", "Modules\UserAccessMI\Domain\CompanyName.MyMeetings.Modules.UserAccessMI.Domain.csproj", "{44FD1665-B9E0-4B6C-BAE2-043E8EB9DCEB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure", "Modules\UserAccessMI\Infrastructure\CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.csproj", "{D98E656A-90DB-449D-9E37-19E315B6C13F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessMI.Application", "Modules\UserAccessMI\Application\CompanyName.MyMeetings.Modules.UserAccessMI.Application.csproj", "{738C7BA8-036F-4B73-B90E-6D26F076D53C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents", "Modules\UserAccessMI\IntegrationEvents\CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents.csproj", "{CDD3A192-432E-478D-AE93-A16707FCF801}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompanyName.MyMeetings.Contracts", "API\CompanyName.MyMeetings.Contracts\CompanyName.MyMeetings.Contracts.csproj", "{45821070-B75C-458C-A25E-2EDD86E47152}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -385,6 +399,36 @@ Global {1853847F-9988-43A1-B3E1-DDBE4B2F3365}.Production|Any CPU.Build.0 = Debug|Any CPU {1853847F-9988-43A1-B3E1-DDBE4B2F3365}.Release|Any CPU.ActiveCfg = Release|Any CPU {1853847F-9988-43A1-B3E1-DDBE4B2F3365}.Release|Any CPU.Build.0 = Release|Any CPU + {44FD1665-B9E0-4B6C-BAE2-043E8EB9DCEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44FD1665-B9E0-4B6C-BAE2-043E8EB9DCEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44FD1665-B9E0-4B6C-BAE2-043E8EB9DCEB}.Production|Any CPU.ActiveCfg = Production|Any CPU + {44FD1665-B9E0-4B6C-BAE2-043E8EB9DCEB}.Production|Any CPU.Build.0 = Production|Any CPU + {44FD1665-B9E0-4B6C-BAE2-043E8EB9DCEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44FD1665-B9E0-4B6C-BAE2-043E8EB9DCEB}.Release|Any CPU.Build.0 = Release|Any CPU + {D98E656A-90DB-449D-9E37-19E315B6C13F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D98E656A-90DB-449D-9E37-19E315B6C13F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D98E656A-90DB-449D-9E37-19E315B6C13F}.Production|Any CPU.ActiveCfg = Production|Any CPU + {D98E656A-90DB-449D-9E37-19E315B6C13F}.Production|Any CPU.Build.0 = Production|Any CPU + {D98E656A-90DB-449D-9E37-19E315B6C13F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D98E656A-90DB-449D-9E37-19E315B6C13F}.Release|Any CPU.Build.0 = Release|Any CPU + {738C7BA8-036F-4B73-B90E-6D26F076D53C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {738C7BA8-036F-4B73-B90E-6D26F076D53C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {738C7BA8-036F-4B73-B90E-6D26F076D53C}.Production|Any CPU.ActiveCfg = Production|Any CPU + {738C7BA8-036F-4B73-B90E-6D26F076D53C}.Production|Any CPU.Build.0 = Production|Any CPU + {738C7BA8-036F-4B73-B90E-6D26F076D53C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {738C7BA8-036F-4B73-B90E-6D26F076D53C}.Release|Any CPU.Build.0 = Release|Any CPU + {CDD3A192-432E-478D-AE93-A16707FCF801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDD3A192-432E-478D-AE93-A16707FCF801}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDD3A192-432E-478D-AE93-A16707FCF801}.Production|Any CPU.ActiveCfg = Production|Any CPU + {CDD3A192-432E-478D-AE93-A16707FCF801}.Production|Any CPU.Build.0 = Production|Any CPU + {CDD3A192-432E-478D-AE93-A16707FCF801}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDD3A192-432E-478D-AE93-A16707FCF801}.Release|Any CPU.Build.0 = Release|Any CPU + {45821070-B75C-458C-A25E-2EDD86E47152}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45821070-B75C-458C-A25E-2EDD86E47152}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45821070-B75C-458C-A25E-2EDD86E47152}.Production|Any CPU.ActiveCfg = Production|Any CPU + {45821070-B75C-458C-A25E-2EDD86E47152}.Production|Any CPU.Build.0 = Production|Any CPU + {45821070-B75C-458C-A25E-2EDD86E47152}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45821070-B75C-458C-A25E-2EDD86E47152}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -393,7 +437,7 @@ Global {49D08B64-AC8E-4607-820F-8A0B989CFD33} = {BC9DDFD1-FB81-4996-812A-68BEBCA33A97} {9CD43CAC-C149-41E1-9654-157D578143B7} = {BCE1EE3C-ADB1-48CC-9FD1-C7324D886964} {AE6D0618-60E1-40B2-A46F-664A19C9503C} = {BCE1EE3C-ADB1-48CC-9FD1-C7324D886964} - {F34C6504-590B-480A-A239-F230CDFF8CED} = {AE6D0618-60E1-40B2-A46F-664A19C9503C} + {F34C6504-590B-480A-A239-F230CDFF8CED} = {1761E09E-402B-4273-BDE5-0A8D9BD2BAC0} {5C2C1630-8A7A-451F-81A8-8547C939F5FD} = {9CD43CAC-C149-41E1-9654-157D578143B7} {F71CFDA8-0770-4761-896A-FB0098113F87} = {9CD43CAC-C149-41E1-9654-157D578143B7} {93F3D023-37BD-4C5C-9442-DE2631CD4954} = {9CD43CAC-C149-41E1-9654-157D578143B7} @@ -405,9 +449,9 @@ Global {CD765A37-EB16-4E35-AA03-6E706D70E8A0} = {5F398170-87FD-4368-9930-FAAAD2D9FDCC} {3ED61776-83A0-426C-9B3A-3AB755DA01EF} = {9CD43CAC-C149-41E1-9654-157D578143B7} {396817BD-94A7-4559-A7F1-2FAB8009BCF8} = {5F398170-87FD-4368-9930-FAAAD2D9FDCC} - {55E1C531-2B9B-49B1-BDFE-9DE322E7FE00} = {AE6D0618-60E1-40B2-A46F-664A19C9503C} - {F364B0C4-1882-46A1-9B08-22587BEF05A2} = {AE6D0618-60E1-40B2-A46F-664A19C9503C} - {0C31EA31-6A10-47D0-82E5-6D224E1CE532} = {AE6D0618-60E1-40B2-A46F-664A19C9503C} + {55E1C531-2B9B-49B1-BDFE-9DE322E7FE00} = {1761E09E-402B-4273-BDE5-0A8D9BD2BAC0} + {F364B0C4-1882-46A1-9B08-22587BEF05A2} = {1761E09E-402B-4273-BDE5-0A8D9BD2BAC0} + {0C31EA31-6A10-47D0-82E5-6D224E1CE532} = {1761E09E-402B-4273-BDE5-0A8D9BD2BAC0} {13E4D721-2E96-497E-9657-503D09468F9F} = {BCE1EE3C-ADB1-48CC-9FD1-C7324D886964} {6E013582-9E44-4D7E-8CFE-5F6FB6757B03} = {13E4D721-2E96-497E-9657-503D09468F9F} {C457929B-91BE-48CC-A714-217D9CABD3CC} = {13E4D721-2E96-497E-9657-503D09468F9F} @@ -415,7 +459,7 @@ Global {8DF88C85-594D-45D7-B12E-6B641D497853} = {13E4D721-2E96-497E-9657-503D09468F9F} {9EAA687B-951E-4D89-8857-99151FF1BCD7} = {E91D4BE3-61FE-441C-A227-29850D414216} {43DBBB02-CA43-42AD-BE21-04AC867BA168} = {C733D087-7051-4E35-BCDB-081252A108E5} - {0FF699EF-8156-43CB-8D18-8EA28F30E9EE} = {AE6D0618-60E1-40B2-A46F-664A19C9503C} + {0FF699EF-8156-43CB-8D18-8EA28F30E9EE} = {1761E09E-402B-4273-BDE5-0A8D9BD2BAC0} {0BC12804-A858-427B-88E2-F9CDE9E97986} = {0FF699EF-8156-43CB-8D18-8EA28F30E9EE} {53E4F002-E708-45F7-8444-19EB8977B5C9} = {13E4D721-2E96-497E-9657-503D09468F9F} {602768DD-F063-469D-9CD3-95CB02F6E441} = {53E4F002-E708-45F7-8444-19EB8977B5C9} @@ -440,6 +484,13 @@ Global {165E76B9-DB0C-49B7-B3DC-52DFBEA55A79} = {C733D087-7051-4E35-BCDB-081252A108E5} {00B904C6-D29A-4F26-B7AD-116C701DB73F} = {BC9DDFD1-FB81-4996-812A-68BEBCA33A97} {1853847F-9988-43A1-B3E1-DDBE4B2F3365} = {8B08A9EE-CE27-4CC3-ACB3-3BD9628E5479} + {1761E09E-402B-4273-BDE5-0A8D9BD2BAC0} = {AE6D0618-60E1-40B2-A46F-664A19C9503C} + {10AFBD87-F071-47A5-B3E6-F7B4C63E1207} = {AE6D0618-60E1-40B2-A46F-664A19C9503C} + {44FD1665-B9E0-4B6C-BAE2-043E8EB9DCEB} = {10AFBD87-F071-47A5-B3E6-F7B4C63E1207} + {D98E656A-90DB-449D-9E37-19E315B6C13F} = {10AFBD87-F071-47A5-B3E6-F7B4C63E1207} + {738C7BA8-036F-4B73-B90E-6D26F076D53C} = {10AFBD87-F071-47A5-B3E6-F7B4C63E1207} + {CDD3A192-432E-478D-AE93-A16707FCF801} = {10AFBD87-F071-47A5-B3E6-F7B4C63E1207} + {45821070-B75C-458C-A25E-2EDD86E47152} = {BC9DDFD1-FB81-4996-812A-68BEBCA33A97} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6B94C21A-AA6D-4D82-963E-C69C0353B938} diff --git a/src/Database/CompanyName.MyMeetings.Database/CompanyName.MyMeetings.Database.sqlproj b/src/Database/CompanyName.MyMeetings.Database/CompanyName.MyMeetings.Database.sqlproj index 350ad2505..69295181a 100644 --- a/src/Database/CompanyName.MyMeetings.Database/CompanyName.MyMeetings.Database.sqlproj +++ b/src/Database/CompanyName.MyMeetings.Database/CompanyName.MyMeetings.Database.sqlproj @@ -89,6 +89,9 @@ + + + @@ -172,8 +175,28 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Scripts/CreateStructure.sql b/src/Database/CompanyName.MyMeetings.Database/Scripts/CreateStructure.sql index a28aaeb1a..296ff7fc7 100644 --- a/src/Database/CompanyName.MyMeetings.Database/Scripts/CreateStructure.sql +++ b/src/Database/CompanyName.MyMeetings.Database/Scripts/CreateStructure.sql @@ -34,6 +34,15 @@ CREATE SCHEMA [users] AUTHORIZATION [dbo]; +GO +PRINT N'Creating [usersmi]...'; + + +GO +CREATE SCHEMA [usersmi] + AUTHORIZATION [dbo]; + + GO PRINT N'Creating [payments].[NewStreamMessages]...'; @@ -717,6 +726,279 @@ CREATE TABLE [users].[OutboxMessages] ( ); +GO +PRINT N'Tabelle "[usersmi].[Permissions]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[Permissions] ( + [Code] VARCHAR (100) NOT NULL, + [Name] VARCHAR (100) NOT NULL, + [Description] VARCHAR (255) NULL, + CONSTRAINT [PK_Permission_Code] PRIMARY KEY CLUSTERED ([Code] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[Roles]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[Roles] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [Name] NVARCHAR (256) NULL, + [NormalizedName] NVARCHAR (256) NULL, + [ConcurrencyStamp] NVARCHAR (MAX) NULL, + CONSTRAINT [PK_Role_Id] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [UQ_Role_Name] UNIQUE NONCLUSTERED ([Name] ASC), + CONSTRAINT [UQ_Role_NormalizedName] UNIQUE NONCLUSTERED ([NormalizedName] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[RoleClaims]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[RoleClaims] ( + [Id] INT IDENTITY (1, 1) NOT NULL, + [RoleId] UNIQUEIDENTIFIER NOT NULL, + [ClaimType] NVARCHAR (MAX) NULL, + [ClaimValue] NVARCHAR (MAX) NULL, + CONSTRAINT [PK_RoleClaim_Id] PRIMARY KEY CLUSTERED ([Id] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[Users]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[Users] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [Name] NVARCHAR (255) NULL, + [FirstName] NVARCHAR (100) NULL, + [LastName] NVARCHAR (100) NULL, + [UserName] NVARCHAR (256) NULL, + [NormalizedUserName] NVARCHAR (256) NULL, + [Email] NVARCHAR (256) NULL, + [NormalizedEmail] NVARCHAR (256) NULL, + [EmailConfirmed] BIT NOT NULL, + [PasswordHash] NVARCHAR (MAX) NULL, + [SecurityStamp] NVARCHAR (MAX) NULL, + [ConcurrencyStamp] NVARCHAR (MAX) NULL, + [PhoneNumber] NVARCHAR (MAX) NULL, + [PhoneNumberConfirmed] BIT NOT NULL, + [TwoFactorEnabled] BIT NOT NULL, + [LockoutEnd] DATETIMEOFFSET (7) NULL, + [LockoutEnabled] BIT NOT NULL, + [AccessFailedCount] INT NOT NULL, + CONSTRAINT [PK_User_Id] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [UQ_User_NormalizedUserName] UNIQUE NONCLUSTERED ([NormalizedUserName] ASC), + CONSTRAINT [UQ_User_UserName] UNIQUE NONCLUSTERED ([UserName] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[UserClaims]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[UserClaims] ( + [Id] INT IDENTITY (1, 1) NOT NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + [ClaimType] NVARCHAR (MAX) NULL, + [ClaimValue] NVARCHAR (MAX) NULL, + CONSTRAINT [PK_UserClaim_Id] PRIMARY KEY CLUSTERED ([Id] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[UserLogins]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[UserLogins] ( + [LoginProvider] NVARCHAR (450) NOT NULL, + [ProviderKey] NVARCHAR (450) NOT NULL, + [ProviderDisplayName] NVARCHAR (MAX) NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + CONSTRAINT [PK_UserLogin_LoginProvider_ProviderKey] PRIMARY KEY CLUSTERED ([LoginProvider] ASC, [ProviderKey] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[UserRefreshTokens]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[UserRefreshTokens] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + [Token] NVARCHAR (MAX) NOT NULL, + [JwtId] NVARCHAR (MAX) NOT NULL, + [IsRevoked] BIT NOT NULL, + [AddedDate] DATETIME2 (7) NOT NULL, + [ExpiryDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_UserRefreshToken_Id] PRIMARY KEY CLUSTERED ([Id] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[UserRoles]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[UserRoles] ( + [UserId] UNIQUEIDENTIFIER NOT NULL, + [RoleId] UNIQUEIDENTIFIER NOT NULL, + CONSTRAINT [PK_UserRole_UserId_RoleId] PRIMARY KEY CLUSTERED ([UserId] ASC, [RoleId] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[UserTokens]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[UserTokens] ( + [UserId] UNIQUEIDENTIFIER NOT NULL, + [LoginProvider] NVARCHAR (450) NOT NULL, + [Name] NVARCHAR (450) NOT NULL, + [Value] NVARCHAR (MAX) NULL, + CONSTRAINT [PK_UserToken_UserId_LoginProvider_Name] PRIMARY KEY CLUSTERED ([UserId] ASC, [LoginProvider] ASC, [Name] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[InboxMessages]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[InboxMessages] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OccurredOn] DATETIME2 (7) NOT NULL, + [Type] VARCHAR (255) NOT NULL, + [Data] VARCHAR (MAX) NOT NULL, + [ProcessedDate] DATETIME2 (7) NULL, + CONSTRAINT [PK_InboxMessages_Id] PRIMARY KEY CLUSTERED ([Id] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[InternalCommands]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[InternalCommands] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [EnqueueDate] DATETIME2 (7) NOT NULL, + [Type] VARCHAR (255) NOT NULL, + [Data] VARCHAR (MAX) NOT NULL, + [ProcessedDate] DATETIME2 (7) NULL, + [Error] NVARCHAR (MAX) NULL, + CONSTRAINT [PK_InternalCommands_Id] PRIMARY KEY CLUSTERED ([Id] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[OutboxMessages]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[OutboxMessages] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OccurredOn] DATETIME2 (7) NOT NULL, + [Type] VARCHAR (255) NOT NULL, + [Data] VARCHAR (MAX) NOT NULL, + [ProcessedDate] DATETIME2 (7) NULL, + CONSTRAINT [PK_OutboxMessages_Id] PRIMARY KEY CLUSTERED ([Id] ASC) +); + + +GO +PRINT N'Tabelle "[usersmi].[UserRegistrations]" wird erstellt...'; + + +GO +CREATE TABLE [usersmi].[UserRegistrations] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserName] NVARCHAR (100) NOT NULL, + [Email] NVARCHAR (255) NOT NULL, + [Password] NVARCHAR (255) NOT NULL, + [FirstName] NVARCHAR (50) NOT NULL, + [LastName] NVARCHAR (50) NOT NULL, + [Name] NVARCHAR (255) NOT NULL, + [StatusCode] VARCHAR (50) NOT NULL, + [RegisterDate] DATETIME NOT NULL, + [ConfirmedDate] DATETIME NULL, + CONSTRAINT [PK_UserRegistrations_Id] PRIMARY KEY CLUSTERED ([Id] ASC) +); + + +GO +PRINT N'Fremdschlüssel "[usersmi].[FK_RoleClaim_RoleId_Role_Id]" wird erstellt...'; + + +GO +ALTER TABLE [usersmi].[RoleClaims] WITH NOCHECK + ADD CONSTRAINT [FK_RoleClaim_RoleId_Role_Id] FOREIGN KEY ([RoleId]) REFERENCES [usersmi].[Roles] ([Id]) ON DELETE CASCADE; + + +GO +PRINT N'Fremdschlüssel "[usersmi].[FK_UserClaim_UserId_User_Id]" wird erstellt...'; + + +GO +ALTER TABLE [usersmi].[UserClaims] WITH NOCHECK + ADD CONSTRAINT [FK_UserClaim_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE; + + +GO +PRINT N'Fremdschlüssel "[usersmi].[FK_UserLogin_UserId_User_Id]" wird erstellt...'; + + +GO +ALTER TABLE [usersmi].[UserLogins] WITH NOCHECK + ADD CONSTRAINT [FK_UserLogin_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE; + + +GO +PRINT N'Fremdschlüssel "[usersmi].[FK_UserRefreshToken_UserId_User_Id]" wird erstellt...'; + + +GO +ALTER TABLE [usersmi].[UserRefreshTokens] WITH NOCHECK + ADD CONSTRAINT [FK_UserRefreshToken_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE; + + +GO +PRINT N'Fremdschlüssel "[usersmi].[FK_UserRole_RoleId_Role_Id]" wird erstellt...'; + + +GO +ALTER TABLE [usersmi].[UserRoles] WITH NOCHECK + ADD CONSTRAINT [FK_UserRole_RoleId_Role_Id] FOREIGN KEY ([RoleId]) REFERENCES [usersmi].[Roles] ([Id]) ON DELETE CASCADE; + + +GO +PRINT N'Fremdschlüssel "[usersmi].[FK_UserRole_UserId_User_Id]" wird erstellt...'; + + +GO +ALTER TABLE [usersmi].[UserRoles] WITH NOCHECK + ADD CONSTRAINT [FK_UserRole_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE; + + +GO +PRINT N'Fremdschlüssel "[usersmi].[FK_UserToken_UserId_User_Id]" wird erstellt...'; + + +GO +ALTER TABLE [usersmi].[UserTokens] WITH NOCHECK + ADD CONSTRAINT [FK_UserToken_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE; + + GO PRINT N'Creating [payments].[DF_payments_Streams_Version]...'; @@ -911,6 +1193,69 @@ SELECT FROM [users].UserRoles AS [UserRole] INNER JOIN [users].RolesToPermissions AS [RolesToPermission] ON [UserRole].RoleCode = [RolesToPermission].RoleCode +GO + +PRINT N'Sicht "[usersmi].[v_UserPermissions]" wird erstellt...'; + + +GO +CREATE VIEW [usersmi].[v_UserPermissions] +AS +SELECT + DISTINCT + [UserRole].[UserId] AS [UserId], + [RoleClaim].[ClaimValue] AS [PermissionCode] +FROM [usersmi].UserRoles AS [UserRole] + INNER JOIN [usersmi].[RoleClaims] AS [RoleClaim] + ON [UserRole].[RoleId] = [RoleClaim].[RoleId] +GO +PRINT N'Sicht "[usersmi].[v_UserRegistrations]" wird erstellt...'; + + +GO +CREATE VIEW [usersmi].[v_UserRegistrations] +AS +SELECT + [UserRegistration].[Id], + [UserRegistration].[UserName], + [UserRegistration].[Email], + [UserRegistration].[FirstName], + [UserRegistration].[LastName], + [UserRegistration].[Name], + [UserRegistration].[StatusCode] +FROM [usersmi].[UserRegistrations] AS [UserRegistration] +GO +PRINT N'Sicht "[usersmi].[v_UserRoles]" wird erstellt...'; + + +GO +CREATE VIEW [usersmi].[v_UserRoles] +AS +SELECT + [UserRole].[UserId], + [Role].[Name] +FROM [usersmi].[UserRoles] AS [UserRole] + INNER JOIN [usersmi].[Roles] AS [Role] + ON [UserRole].[UserId] = [Role].[Id] +GO +PRINT N'Sicht "[usersmi].[v_Users]" wird erstellt...'; + + +GO +CREATE VIEW [usersmi].[v_Users] +AS +SELECT + [User].[Id], + IIF([User].[LockoutEnabled] = 1, 0, 1) AS [IsActive], + [User].[UserName] AS [Login], + [User].[PasswordHash] AS [Password], + [User].[Email], + [User].[Name] +FROM [usersmi].[Users] AS [User] +GO +PRINT N'Update abgeschlossen.'; + + GO PRINT N'Checking existing data against newly created constraints'; diff --git a/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0002_SeedPermissions.sql b/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0002_SeedPermissions.sql new file mode 100644 index 000000000..76fb42993 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0002_SeedPermissions.sql @@ -0,0 +1,91 @@ +INSERT INTO [usersmi].[Permissions] ([Code], [Name], [Description]) + VALUES + + -- ############################################################################################################## + -- # *** APPLICATION *** # + -- ############################################################################################################## + ('Application.Administrator', 'Administrator', NULL), + + + -- ############################################################################################################## + -- # *** USERS *** # + -- ############################################################################################################## + + -- Identity + ('Users.GetUserAccounts', 'GetUserAccounts', NULL), + + -- Users + ('Users.GetUsers', 'GetUsers', NULL), + ('Users.CreateUserAccount', 'CreateUserAccount', NULL), + ('Users.UpdateUserAccount', 'UpdateUserAccount', NULL), + ('Users.UnlockUserAccount', 'UnlockUserAccount', NULL), + ('Users.ConfirmEmailAddress', 'ConfirmEmailAddress', NULL), + ('Users.GetAuthenticatorKey', 'GetAuthenticatorKey', NULL), + ('Users.RegisterAuthenticator', 'RegisterAuthenticator', NULL), + ('Users.GetUserRoles', 'GetUserRoles', NULL), + ('Users.SetUserRoles', 'SetUserRoles', NULL), + ('Users.GetUserPermissions', 'GetUserPermissions', NULL), + ('Users.SetUserPermissions', 'SetUserPermissions', NULL), + + -- User roles + ('Users.GetRoles', 'GetRoles', NULL), + ('Users.AddRole', 'AddRole', NULL), + ('Users.RenameRole', 'RenameRole', NULL), + ('Users.DeleteRole', 'DeleteRole', NULL), + ('Users.GetRolePermissions', 'GetRolePermissions', NULL), + ('Users.SetRolePermissions', 'SetRolePermissions', NULL), + + -- ############################################################################################################## + -- # *** MEETINGS *** # + -- ############################################################################################################## + ('GetMeetingGroupProposals', 'GetMeetingGroupProposals', NULL), + ('ProposeMeetingGroup', 'ProposeMeetingGroup', NULL), + ('CreateNewMeeting', 'CreateNewMeeting', NULL), + ('EditMeeting', 'EditMeeting', NULL), + ('AddMeetingAttendee', 'AddMeetingAttendee', NULL), + ('RemoveMeetingAttendee', 'RemoveMeetingAttendee', NULL), + ('AddNotAttendee', 'AddNotAttendee', NULL), + ('ChangeNotAttendeeDecision', 'ChangeNotAttendeeDecision', NULL), + ('SignUpMemberToWaitlist', 'SignUpMemberToWaitlist', NULL), + ('SignOffMemberFromWaitlist', 'SignOffMemberFromWaitlist', NULL), + ('SetMeetingHostRole', 'SetMeetingHostRole', NULL), + ('SetMeetingAttendeeRole', 'SetMeetingAttendeeRole', NULL), + ('CancelMeeting', 'CancelMeeting', NULL), + ('GetAllMeetingGroups', 'GetAllMeetingGroups', NULL), + ('EditMeetingGroupGeneralAttributes', 'EditMeetingGroupGeneralAttributes', NULL), + ('JoinToGroup', 'JoinToGroup', NULL), + ('LeaveMeetingGroup', 'LeaveMeetingGroup', NULL), + ('AddMeetingComment', 'AddMeetingComment', NULL), + ('EditMeetingComment', 'EditMeetingComment', NULL), + ('RemoveMeetingComment', 'RemoveMeetingComment', NULL), + ('AddMeetingCommentReply', 'AddMeetingCommentReply', NULL), + ('LikeMeetingComment', 'LikeMeetingComment', NULL), + ('UnlikeMeetingComment', 'UnlikeMeetingComment', NULL), + ('EnableMeetingCommenting', 'EnableMeetingCommenting', NULL), + ('DisableMeetingCommenting', 'DisableMeetingCommenting', NULL), + ('MyMeetingGroupsView', 'MyMeetingGroupsView', NULL), + ('AllMeetingGroupsView', 'AllMeetingGroupsView', NULL), + ('SubscriptionView', 'SubscriptionView', NULL), + ('EmailsView', 'EmailsView', NULL), + ('MyMeetingsView', 'MyMeetingsView', NULL), + ('GetAuthenticatedMemberMeetings', 'GetAuthenticatedMemberMeetings', NULL), + + -- ############################################################################################################## + -- # *** ADMINISTRATION *** # + -- ############################################################################################################## + -- + ('AcceptMeetingGroupProposal', 'AcceptMeetingGroupProposal', NULL), + ('AdministrationsView', 'AdministrationsView', NULL), + + -- ############################################################################################################## + -- # *** PAYMENTS *** # + -- ############################################################################################################## + ('RegisterPayment', 'RegisterPayment', NULL), + ('BuySubscription', 'BuySubscription', NULL), + ('RenewSubscription', 'RenewSubscription', NULL), + ('CreatePriceListItem', 'CreatePriceListItem', NULL), + ('ActivatePriceListItem', 'ActivatePriceListItem', NULL), + ('DeactivatePriceListItem', 'DeactivatePriceListItem', NULL), + ('ChangePriceListItemAttributes', 'ChangePriceListItemAttributes', NULL), + ('GetAuthenticatedPayerSubscription', 'GetAuthenticatedPayerSubscription', NULL), + ('GetPriceListItem', 'GetPriceListItem', NULL); \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0003_SeedRoles.sql b/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0003_SeedRoles.sql new file mode 100644 index 000000000..796f4c2d8 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0003_SeedRoles.sql @@ -0,0 +1,60 @@ +INSERT INTO [usersmi].[Roles] + ([Id], [Name], [NormalizedName], [ConcurrencyStamp]) + VALUES + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Administrator', 'ADMINISTRATOR', 'ff5d947e-723d-4bf6-b6b9-0c1487c72f73'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Member', 'MEMBER', 'ad88d3f5-37ef-473e-addc-9ee10184e7d0'); + + +INSERT INTO [usersmi].[RoleClaims] + ([RoleId], [ClaimType], [ClaimValue]) + VALUES + -- Administator + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Application.Permission', 'AcceptMeetingGroupProposal'), + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Application.Permission', 'AdministrationsView'), + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Application.Permission', 'CreatePriceListItem'), + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Application.Permission', 'ActivatePriceListItem'), + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Application.Permission', 'DeactivatePriceListItem'), + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Application.Permission', 'ChangePriceListItemAttributes'), + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Application.Permission', 'GetPriceListItem'), + ('1E5A7F47-A258-4127-42A1-08DC00AA0515', 'Application.Permission', 'Users.GetUserAccounts'), + + -- Member + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetMeetingGroupProposals'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'ProposeMeetingGroup'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'CreateNewMeeting'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'EditMeeting'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'AddMeetingAttendee'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'RemoveMeetingAttendee'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'AddNotAttendee'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'ChangeNotAttendeeDecision'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'SignUpMemberToWaitlist'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'SignOffMemberFromWaitlist'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'SetMeetingHostRole'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'SetMeetingAttendeeRole'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'CancelMeeting'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetAllMeetingGroups'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'EditMeetingGroupGeneralAttributes'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'JoinToGroup'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'LeaveMeetingGroup'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'AddMeetingComment'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'EditMeetingComment'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'RemoveMeetingComment'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'AddMeetingCommentReply'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'LikeMeetingComment'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'UnlikeMeetingComment'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetAuthenticatedMemberMeetingGroups'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetMeetingGroupDetails'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetMeetingDetails'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetMeetingAttendees'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'MyMeetingsGroupsView'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'SubscriptionView'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'EmailsView'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'AllMeetingGroupsView'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'MyMeetingsView'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetAuthenticatedMemberMeetings'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'RegisterPayment'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'BuySubscription'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'RenewSubscription'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetAuthenticatedPayerSubscription'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'GetPriceListItem'), + ('9CE09287-D003-439F-42A0-08DC00AA0515', 'Application.Permission', 'Users.GetUserAccounts'); \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0004_SeedUsers.sql b/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0004_SeedUsers.sql new file mode 100644 index 000000000..6d26e4ac5 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Scripts/Seeds/0004_SeedUsers.sql @@ -0,0 +1,48 @@ +-- Global administrator +INSERT INTO [usersmi].[Users] ([Id], [Name], [FirstName], [LastName], [UserName], [NormalizedUserName], [Email], [NormalizedEmail], [EmailConfirmed], [PasswordHash], [SecurityStamp], [ConcurrencyStamp], [PhoneNumber], [PhoneNumberConfirmed], [TwoFactorEnabled], [LockoutEnd], [LockoutEnabled], [AccessFailedCount]) + VALUES + ('39C63EC6-9AFF-473D-010F-08DBFFF6E9DF', + 'Administrator', + NULL, + NULL, + 'administrator', + 'ADMINISTRATOR', + 'administrator@mymeetings.com', + 'ADMINISTRATOR@MYMEETINGS.COM', + 1, + 'AQAAAAIAAYagAAAAEMWzWgeHA5LEvzLC9ke6LfqO/ju2RvcguKHjDONHn/CZuOaXwk4WMg8CXP2OOKLHsw==', -- AdminP@$$123. + 'CELIHCK47VKN4IL3ZGTYRVL4WX7AKTS6', + '60c49c98-2932-43b0-9363-099906901875', + NULL, + 0, + 0, + NULL, + 0, + 0); + +-- Assign permission to administrator +INSERT INTO [usersmi].[UserClaims] + ([ClaimType], [ClaimValue], [UserId]) + VALUES ('Application.Permission', 'Application.Administrator', '39C63EC6-9AFF-473D-010F-08DBFFF6E9DF'); + + +-- Test users +INSERT INTO [usersmi].[UserRegistrations] + ([Id], [UserName], [Email], [Password], [FirstName], [LastName], [Name], [StatusCode], [RegisterDate], [ConfirmedDate]) + VALUES + ('2EBFECFC-ED13-43B8-B516-6AC89D51C510', 'testMember@mail.com', 'testMember@mail.com', 'testMemberPass', 'John', 'Doe', 'John Doe', 'Confirmed', GETDATE(), GETDATE()), + ('4065630E-4A4C-4F01-9142-0BACF6B8C64D', 'testAdmin@mail.com', 'testAdmin@mail.com', 'testAdminPass', 'Jane', 'Doe', 'Jane Doe', 'Confirmed', GETDATE(), GETDATE()); + +INSERT INTO [usersmi].[Users] + ([Id], [Name], [FirstName], [LastName], [UserName], [NormalizedUserName], [Email], [NormalizedEmail], [EmailConfirmed], [PasswordHash], [SecurityStamp], [ConcurrencyStamp], [PhoneNumber], [PhoneNumberConfirmed], [TwoFactorEnabled], [LockoutEnd], [LockoutEnabled], [AccessFailedCount]) + VALUES + ('2EBFECFC-ED13-43B8-B516-6AC89D51C510', 'John Doe', 'John', 'Doe', 'testMember@mail.com', 'TESTMEMBER@MAIL.COM', 'testMember@mail.com', 'TESTMEMBER@MAIL.COM', 0, 'AQAAAAIAAYagAAAAEBRXigjUOJjPf/7wwJV1YlCOk2MzBOne1zLXXQoHzZNVqTnl5UuQWwM94ORZqOTR3g==', 'NMMJRCDE4JDHMJB32D43US57X5WALBPV', 'b7eb06fb-a904-492e-9717-6771cb6ad4af', NULL, 0, 0, NULL, 1, 0), + ('4065630E-4A4C-4F01-9142-0BACF6B8C64D', 'Jane Doe', 'Jane', 'Doe', 'testAdmin@mail.com', 'TESTADMIN@MAIL.COM', 'testAdmin@mail.com', 'TESTADMIN@MAIL.COM', 0, 'AQAAAAIAAYagAAAAEFjQQOh8ii3LbgV0+0C69GCmygp0I8Z9FV0n6SQCFRK+bN/uFkd5ksBjbWNSiz9/Ag==', 'FTD34O4LXMPZFBMJ4DGS5LN2VVUNCHU5', '29467032-0908-4bd8-9ef5-62676a4e5009', NULL, 0, 0, NULL, 1, 0); + + +-- Assign Roles to Users +INSERT INTO [usersmi].[UserRoles] + ([UserId], [RoleId]) + VALUES + ('2EBFECFC-ED13-43B8-B516-6AC89D51C510', '9CE09287-D003-439F-42A0-08DC00AA0515'), -- John Doe / Member + ('4065630E-4A4C-4F01-9142-0BACF6B8C64D', '1E5A7F47-A258-4127-42A1-08DC00AA0515'); -- Jane Doe / Administrator \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/Security/Schemas.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/Security/Schemas.sql index 18d3cd7f8..0ce03a6f0 100644 --- a/src/Database/CompanyName.MyMeetings.Database/Structure/Security/Schemas.sql +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/Security/Schemas.sql @@ -11,4 +11,7 @@ CREATE SCHEMA users AUTHORIZATION dbo GO CREATE SCHEMA payments AUTHORIZATION dbo +GO + +CREATE SCHEMA usersmi AUTHORIZATION dbo GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/InboxMessages.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/InboxMessages.sql new file mode 100644 index 000000000..b4611c485 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/InboxMessages.sql @@ -0,0 +1,10 @@ +CREATE TABLE [usersmi].InboxMessages +( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OccurredOn] DATETIME2 NOT NULL, + [Type] VARCHAR(255) NOT NULL, + [Data] VARCHAR(MAX) NOT NULL, + [ProcessedDate] DATETIME2 NULL, + CONSTRAINT [PK_InboxMessages_Id] PRIMARY KEY ([Id] ASC) +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/InternalCommands.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/InternalCommands.sql new file mode 100644 index 000000000..84aa2240a --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/InternalCommands.sql @@ -0,0 +1,11 @@ +CREATE TABLE [usersmi].InternalCommands +( + [Id] UNIQUEIDENTIFIER NOT NULL, + [EnqueueDate] DATETIME2 NOT NULL, + [Type] VARCHAR(255) NOT NULL, + [Data] VARCHAR(MAX) NOT NULL, + [ProcessedDate] DATETIME2 NULL, + [Error] NVARCHAR(MAX) NULL, + CONSTRAINT [PK_InternalCommands_Id] PRIMARY KEY ([Id] ASC) +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/OutboxMessages.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/OutboxMessages.sql new file mode 100644 index 000000000..06526d471 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/OutboxMessages.sql @@ -0,0 +1,10 @@ +CREATE TABLE [usersmi].OutboxMessages +( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OccurredOn] DATETIME2 NOT NULL, + [Type] VARCHAR(255) NOT NULL, + [Data] VARCHAR(MAX) NOT NULL, + [ProcessedDate] DATETIME2 NULL, + CONSTRAINT [PK_OutboxMessages_Id] PRIMARY KEY ([Id] ASC) +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/Permission.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/Permission.sql new file mode 100644 index 000000000..ae8820002 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/Permission.sql @@ -0,0 +1,8 @@ +CREATE TABLE [usersmi].[Permissions] +( + [Code] VARCHAR(100) NOT NULL, + [Name] VARCHAR(100) NOT NULL, + [Description] VARCHAR(255) NULL, + CONSTRAINT [PK_Permission_Code] PRIMARY KEY ([Code]) +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/Role.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/Role.sql new file mode 100644 index 000000000..64041e627 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/Role.sql @@ -0,0 +1,11 @@ +CREATE TABLE [usersmi].[Roles] +( + [Id] UNIQUEIDENTIFIER NOT NULL, + [Name] NVARCHAR(256) NULL, + [NormalizedName] NVARCHAR(256) NULL, + [ConcurrencyStamp] NVARCHAR(max) NULL, + CONSTRAINT [PK_Role_Id] PRIMARY KEY ([Id]), + CONSTRAINT [UQ_Role_Name] UNIQUE([Name]), + CONSTRAINT [UQ_Role_NormalizedName] UNIQUE([NormalizedName]) +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/RoleClaim.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/RoleClaim.sql new file mode 100644 index 000000000..9fa263ff1 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/RoleClaim.sql @@ -0,0 +1,10 @@ +CREATE TABLE [usersmi].[RoleClaims] +( + [Id] int NOT NULL IDENTITY, + [RoleId] UNIQUEIDENTIFIER NOT NULL, + [ClaimType] NVARCHAR(max) NULL, + [ClaimValue] NVARCHAR(max) NULL, + CONSTRAINT [PK_RoleClaim_Id] PRIMARY KEY ([Id]), + CONSTRAINT [FK_RoleClaim_RoleId_Role_Id] FOREIGN KEY ([RoleId]) REFERENCES [usersmi].[Roles] ([Id]) ON DELETE CASCADE +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/User.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/User.sql new file mode 100644 index 000000000..d01496f83 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/User.sql @@ -0,0 +1,25 @@ +CREATE TABLE [usersmi].[Users] +( + [Id] UNIQUEIDENTIFIER NOT NULL, + [Name] NVARCHAR(255) NULL, + [FirstName] NVARCHAR(100) NULL, + [LastName] NVARCHAR(100) NULL, + [UserName] NVARCHAR(256) NULL, + [NormalizedUserName] NVARCHAR(256) NULL, + [Email] NVARCHAR(256) NULL, + [NormalizedEmail] NVARCHAR(256) NULL, + [EmailConfirmed] BIT NOT NULL, + [PasswordHash] NVARCHAR(max) NULL, + [SecurityStamp] NVARCHAR(max) NULL, + [ConcurrencyStamp] NVARCHAR(max) NULL, + [PhoneNumber] NVARCHAR(max) NULL, + [PhoneNumberConfirmed] BIT NOT NULL, + [TwoFactorEnabled] BIT NOT NULL, + [LockoutEnd] DATETIMEOFFSET NULL, + [LockoutEnabled] BIT NOT NULL, + [AccessFailedCount] INT NOT NULL, + CONSTRAINT [PK_User_Id] PRIMARY KEY ([Id]), + CONSTRAINT [UQ_User_UserName] UNIQUE([UserName]), + CONSTRAINT [UQ_User_NormalizedUserName] UNIQUE([NormalizedUserName]) +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserClaim.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserClaim.sql new file mode 100644 index 000000000..a050db775 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserClaim.sql @@ -0,0 +1,10 @@ +CREATE TABLE [usersmi].[UserClaims] +( + [Id] INT NOT NULL IDENTITY, + [UserId] UNIQUEIDENTIFIER NOT NULL, + [ClaimType] NVARCHAR(max) NULL, + [ClaimValue] NVARCHAR(max) NULL, + CONSTRAINT [PK_UserClaim_Id] PRIMARY KEY ([Id]), + CONSTRAINT [FK_UserClaim_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserLogin.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserLogin.sql new file mode 100644 index 000000000..3e630bfcb --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserLogin.sql @@ -0,0 +1,10 @@ +CREATE TABLE [usersmi].[UserLogins] +( + [LoginProvider] NVARCHAR(450) NOT NULL, + [ProviderKey] NVARCHAR(450) NOT NULL, + [ProviderDisplayName] NVARCHAR(max) NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + CONSTRAINT [PK_UserLogin_LoginProvider_ProviderKey] PRIMARY KEY ([LoginProvider], [ProviderKey]), + CONSTRAINT [FK_UserLogin_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRefreshToken.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRefreshToken.sql new file mode 100644 index 000000000..7d1832d24 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRefreshToken.sql @@ -0,0 +1,13 @@ +CREATE TABLE [usersmi].[UserRefreshTokens] +( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + [Token] NVARCHAR(max) NOT NULL, + [JwtId] NVARCHAR(max) NOT NULL, + [IsRevoked] BIT NOT NULL, + [AddedDate] DATETIME2 NOT NULL, + [ExpiryDate] DATETIME2 NOT NULL, + CONSTRAINT [PK_UserRefreshToken_Id] PRIMARY KEY ([Id]), + CONSTRAINT [FK_UserRefreshToken_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRegistrations.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRegistrations.sql new file mode 100644 index 000000000..1bf47c253 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRegistrations.sql @@ -0,0 +1,15 @@ +CREATE TABLE [usersmi].[UserRegistrations] +( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserName] NVARCHAR(100) NOT NULL, + [Email] NVARCHAR (255) NOT NULL, + [Password] NVARCHAR(255) NOT NULL, + [FirstName] NVARCHAR(50) NOT NULL, + [LastName] NVARCHAR(50) NOT NULL, + [Name] NVARCHAR (255) NOT NULL, + [StatusCode] VARCHAR(50) NOT NULL, + [RegisterDate] DATETIME NOT NULL, + [ConfirmedDate] DATETIME NULL, + CONSTRAINT [PK_UserRegistrations_Id] PRIMARY KEY ([Id] ASC) +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRole.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRole.sql new file mode 100644 index 000000000..58a502bdb --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserRole.sql @@ -0,0 +1,9 @@ +CREATE TABLE [usersmi].[UserRoles] +( + [UserId] UNIQUEIDENTIFIER NOT NULL, + [RoleId] UNIQUEIDENTIFIER NOT NULL, + CONSTRAINT [PK_UserRole_UserId_RoleId] PRIMARY KEY ([UserId], [RoleId]), + CONSTRAINT [FK_UserRole_RoleId_Role_Id] FOREIGN KEY ([RoleId]) REFERENCES [usersmi].[Roles] ([Id]) ON DELETE CASCADE, + CONSTRAINT [FK_UserRole_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserToken.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserToken.sql new file mode 100644 index 000000000..0fa2c2232 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Tables/UserToken.sql @@ -0,0 +1,10 @@ +CREATE TABLE [usersmi].[UserTokens] +( + [UserId] UNIQUEIDENTIFIER NOT NULL, + [LoginProvider] NVARCHAR(450) NOT NULL, + [Name] NVARCHAR(450) NOT NULL, + [Value] NVARCHAR(max) NULL, + CONSTRAINT [PK_UserToken_UserId_LoginProvider_Name] PRIMARY KEY ([UserId], [LoginProvider], [Name]), + CONSTRAINT [FK_UserToken_UserId_User_Id] FOREIGN KEY ([UserId]) REFERENCES [usersmi].[Users] ([Id]) ON DELETE CASCADE +) +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserPermissions.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserPermissions.sql new file mode 100644 index 000000000..847a4a571 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserPermissions.sql @@ -0,0 +1,10 @@ +CREATE VIEW [usersmi].[v_UserPermissions] +AS +SELECT + DISTINCT + [UserRole].[UserId] AS [UserId], + [RoleClaim].[ClaimValue] AS [PermissionCode] +FROM [usersmi].UserRoles AS [UserRole] + INNER JOIN [usersmi].[RoleClaims] AS [RoleClaim] + ON [UserRole].[RoleId] = [RoleClaim].[RoleId] +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserRegistrations.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserRegistrations.sql new file mode 100644 index 000000000..162bd6db9 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserRegistrations.sql @@ -0,0 +1,12 @@ +CREATE VIEW [usersmi].[v_UserRegistrations] +AS +SELECT + [UserRegistration].[Id], + [UserRegistration].[UserName], + [UserRegistration].[Email], + [UserRegistration].[FirstName], + [UserRegistration].[LastName], + [UserRegistration].[Name], + [UserRegistration].[StatusCode] +FROM [usersmi].[UserRegistrations] AS [UserRegistration] +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserRoles.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserRoles.sql new file mode 100644 index 000000000..558a5dc98 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_UserRoles.sql @@ -0,0 +1,8 @@ +CREATE VIEW [usersmi].[v_UserRoles] +AS +SELECT [UserRole].[UserId] AS [UserId], + [Role].[Name] AS [RoleCode] + FROM [usersmi].[UserRoles] AS [UserRole] + INNER JOIN [usersmi].[Roles] AS [Role] + ON [UserRole].[RoleId] = [Role].[Id] +GO \ No newline at end of file diff --git a/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_Users.sql b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_Users.sql new file mode 100644 index 000000000..f603e1826 --- /dev/null +++ b/src/Database/CompanyName.MyMeetings.Database/Structure/usersmi/Views/v_Users.sql @@ -0,0 +1,11 @@ +CREATE VIEW [usersmi].[v_Users] +AS +SELECT + [User].[Id], + IIF([User].[LockoutEnabled] = 1, 0, 1) AS [IsActive], + [User].[UserName] AS [Login], + [User].[PasswordHash] AS [Password], + [User].[Email], + [User].[Name] +FROM [usersmi].[Users] AS [User] +GO \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 64e1191c3..3d2b3285c 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,6 +6,7 @@ Copyright © $(Company) $([System.DateTime]::Now.Year) $(Company)™ $(Company) Projects + @@ -21,7 +22,7 @@ True True True - True + true diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index a98909f23..27d418490 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -19,6 +19,7 @@ + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 07ff07dc6..335b42753 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -7,6 +7,8 @@ + + @@ -19,7 +21,8 @@ - + + diff --git a/src/Modules/Administration/Application/CompanyName.MyMeetings.Modules.Administration.Application.csproj b/src/Modules/Administration/Application/CompanyName.MyMeetings.Modules.Administration.Application.csproj index 8f337a235..dbac45113 100644 --- a/src/Modules/Administration/Application/CompanyName.MyMeetings.Modules.Administration.Application.csproj +++ b/src/Modules/Administration/Application/CompanyName.MyMeetings.Modules.Administration.Application.csproj @@ -1,6 +1,6 @@  - + diff --git a/src/Modules/Administration/Application/Members/NewUserRegisteredIntegrationEventHandler.cs b/src/Modules/Administration/Application/Members/NewUserRegisteredIntegrationEventHandler.cs index 4455f317c..0c329774a 100644 --- a/src/Modules/Administration/Application/Members/NewUserRegisteredIntegrationEventHandler.cs +++ b/src/Modules/Administration/Application/Members/NewUserRegisteredIntegrationEventHandler.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.Modules.Administration.Application.Configuration.Commands; using CompanyName.MyMeetings.Modules.Administration.Application.Members.CreateMember; -using CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents; +using CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents; using MediatR; namespace CompanyName.MyMeetings.Modules.Administration.Application.Members diff --git a/src/Modules/Administration/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs b/src/Modules/Administration/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs index 7b18b98cc..f9c3192ff 100644 --- a/src/Modules/Administration/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs +++ b/src/Modules/Administration/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs @@ -1,7 +1,7 @@ using Autofac; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; using CompanyName.MyMeetings.Modules.Meetings.IntegrationEvents; -using CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents; +using CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents; using Serilog; namespace CompanyName.MyMeetings.Modules.Administration.Infrastructure.Configuration.EventsBus diff --git a/src/Modules/Administration/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs b/src/Modules/Administration/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs index fe53237c4..0387e14f7 100644 --- a/src/Modules/Administration/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs +++ b/src/Modules/Administration/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs @@ -14,5 +14,7 @@ public ExecutionContextMock(Guid userId) public Guid CorrelationId { get; } public bool IsAvailable { get; } + + public bool IsAuthenticated { get; } } } \ No newline at end of file diff --git a/src/Modules/Meetings/Application/CompanyName.MyMeetings.Modules.Meetings.Application.csproj b/src/Modules/Meetings/Application/CompanyName.MyMeetings.Modules.Meetings.Application.csproj index 717ad07d6..3a9d78b3e 100644 --- a/src/Modules/Meetings/Application/CompanyName.MyMeetings.Modules.Meetings.Application.csproj +++ b/src/Modules/Meetings/Application/CompanyName.MyMeetings.Modules.Meetings.Application.csproj @@ -1,7 +1,7 @@  - - + + diff --git a/src/Modules/Meetings/Application/Members/CreateMember/NewUserRegisteredIntegrationEventHandler.cs b/src/Modules/Meetings/Application/Members/CreateMember/NewUserRegisteredIntegrationEventHandler.cs index 8b55061c2..52546e647 100644 --- a/src/Modules/Meetings/Application/Members/CreateMember/NewUserRegisteredIntegrationEventHandler.cs +++ b/src/Modules/Meetings/Application/Members/CreateMember/NewUserRegisteredIntegrationEventHandler.cs @@ -1,5 +1,5 @@ using CompanyName.MyMeetings.Modules.Meetings.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents; +using CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents; using MediatR; namespace CompanyName.MyMeetings.Modules.Meetings.Application.Members.CreateMember diff --git a/src/Modules/Meetings/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs b/src/Modules/Meetings/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs index ce0691773..0cfce84d3 100644 --- a/src/Modules/Meetings/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs +++ b/src/Modules/Meetings/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs @@ -2,7 +2,7 @@ using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; using CompanyName.MyMeetings.Modules.Administration.IntegrationEvents.MeetingGroupProposals; using CompanyName.MyMeetings.Modules.Payments.IntegrationEvents; -using CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents; +using CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents; using Serilog; namespace CompanyName.MyMeetings.Modules.Meetings.Infrastructure.Configuration.EventsBus diff --git a/src/Modules/Meetings/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs b/src/Modules/Meetings/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs index 2c1387401..a43080ff3 100644 --- a/src/Modules/Meetings/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs +++ b/src/Modules/Meetings/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs @@ -14,5 +14,7 @@ public ExecutionContextMock(Guid userId) public Guid CorrelationId { get; } public bool IsAvailable { get; } + + public bool IsAuthenticated { get; } } } \ No newline at end of file diff --git a/src/Modules/Payments/Application/CompanyName.MyMeetings.Modules.Payments.Application.csproj b/src/Modules/Payments/Application/CompanyName.MyMeetings.Modules.Payments.Application.csproj index aa2b1916f..fd963b8e5 100644 --- a/src/Modules/Payments/Application/CompanyName.MyMeetings.Modules.Payments.Application.csproj +++ b/src/Modules/Payments/Application/CompanyName.MyMeetings.Modules.Payments.Application.csproj @@ -2,6 +2,6 @@ - + diff --git a/src/Modules/Payments/Application/Payers/CreatePayer/NewUserRegisteredIntegrationEventHandler.cs b/src/Modules/Payments/Application/Payers/CreatePayer/NewUserRegisteredIntegrationEventHandler.cs index bd1a6df0a..af8ac57e3 100644 --- a/src/Modules/Payments/Application/Payers/CreatePayer/NewUserRegisteredIntegrationEventHandler.cs +++ b/src/Modules/Payments/Application/Payers/CreatePayer/NewUserRegisteredIntegrationEventHandler.cs @@ -1,5 +1,5 @@ using CompanyName.MyMeetings.Modules.Payments.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents; +using CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents; using MediatR; namespace CompanyName.MyMeetings.Modules.Payments.Application.Payers.CreatePayer diff --git a/src/Modules/Payments/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs b/src/Modules/Payments/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs index 5c448de6d..60a37985c 100644 --- a/src/Modules/Payments/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs +++ b/src/Modules/Payments/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs @@ -2,7 +2,7 @@ using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; using CompanyName.MyMeetings.Modules.Administration.IntegrationEvents.MeetingGroupProposals; using CompanyName.MyMeetings.Modules.Meetings.IntegrationEvents; -using CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents; +using CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents; using Serilog; namespace CompanyName.MyMeetings.Modules.Payments.Infrastructure.Configuration.EventsBus diff --git a/src/Modules/Payments/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs b/src/Modules/Payments/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs index 0e4e19700..38602f606 100644 --- a/src/Modules/Payments/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs +++ b/src/Modules/Payments/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs @@ -14,5 +14,7 @@ public ExecutionContextMock(Guid userId) public Guid CorrelationId { get; } public bool IsAvailable { get; } + + public bool IsAuthenticated { get; } } } \ No newline at end of file diff --git a/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommand.cs b/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommand.cs index dbca797e0..a5f7bb314 100644 --- a/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommand.cs +++ b/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommand.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication.Authenticate +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication.Authenticate { public class AuthenticateCommand : CommandBase { diff --git a/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommandHandler.cs b/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommandHandler.cs index 0b9208394..b1942a0ab 100644 --- a/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommandHandler.cs +++ b/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommandHandler.cs @@ -1,10 +1,10 @@ using System.Security.Claims; using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using Dapper; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication.Authenticate +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication.Authenticate { internal class AuthenticateCommandHandler : ICommandHandler { diff --git a/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommandValidator.cs b/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommandValidator.cs index 53eac0d9e..bb446e8a0 100644 --- a/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommandValidator.cs +++ b/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticateCommandValidator.cs @@ -1,6 +1,6 @@ using FluentValidation; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication.Authenticate +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication.Authenticate { internal class AuthenticateCommandValidator : AbstractValidator { diff --git a/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticationResult.cs b/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticationResult.cs index 1446b4450..578e34798 100644 --- a/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticationResult.cs +++ b/src/Modules/UserAccess/Application/Authentication/Authenticate/AuthenticationResult.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication.Authenticate +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication.Authenticate { public class AuthenticationResult { diff --git a/src/Modules/UserAccess/Application/Authentication/Authenticate/UserDto.cs b/src/Modules/UserAccess/Application/Authentication/Authenticate/UserDto.cs index b7911a0e1..71caa8490 100644 --- a/src/Modules/UserAccess/Application/Authentication/Authenticate/UserDto.cs +++ b/src/Modules/UserAccess/Application/Authentication/Authenticate/UserDto.cs @@ -1,6 +1,6 @@ using System.Security.Claims; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication.Authenticate +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication.Authenticate { public class UserDto { diff --git a/src/Modules/UserAccess/Application/Authentication/PasswordManager.cs b/src/Modules/UserAccess/Application/Authentication/PasswordManager.cs index ea7af0666..b00795fb0 100644 --- a/src/Modules/UserAccess/Application/Authentication/PasswordManager.cs +++ b/src/Modules/UserAccess/Application/Authentication/PasswordManager.cs @@ -1,7 +1,7 @@ using System.Runtime.CompilerServices; using System.Security.Cryptography; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication { public class PasswordManager { diff --git a/src/Modules/UserAccess/Application/Authorization/GetAuthenticatedUserPermissions/GetAuthenticatedUserPermissionsQuery.cs b/src/Modules/UserAccess/Application/Authorization/GetAuthenticatedUserPermissions/GetAuthenticatedUserPermissionsQuery.cs index 99824cfb5..f06d3a9f1 100644 --- a/src/Modules/UserAccess/Application/Authorization/GetAuthenticatedUserPermissions/GetAuthenticatedUserPermissionsQuery.cs +++ b/src/Modules/UserAccess/Application/Authorization/GetAuthenticatedUserPermissions/GetAuthenticatedUserPermissionsQuery.cs @@ -1,7 +1,7 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetUserPermissions; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetUserPermissions; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetAuthenticatedUserPermissions +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetAuthenticatedUserPermissions { public class GetAuthenticatedUserPermissionsQuery : QueryBase> { diff --git a/src/Modules/UserAccess/Application/Authorization/GetAuthenticatedUserPermissions/GetAuthenticatedUserPermissionsQueryHandler.cs b/src/Modules/UserAccess/Application/Authorization/GetAuthenticatedUserPermissions/GetAuthenticatedUserPermissionsQueryHandler.cs index 4daffd4e1..a5a02289e 100644 --- a/src/Modules/UserAccess/Application/Authorization/GetAuthenticatedUserPermissions/GetAuthenticatedUserPermissionsQueryHandler.cs +++ b/src/Modules/UserAccess/Application/Authorization/GetAuthenticatedUserPermissions/GetAuthenticatedUserPermissionsQueryHandler.cs @@ -1,10 +1,10 @@ using CompanyName.MyMeetings.BuildingBlocks.Application; using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetUserPermissions; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetUserPermissions; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Queries; using Dapper; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetAuthenticatedUserPermissions +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetAuthenticatedUserPermissions { internal class GetAuthenticatedUserPermissionsQueryHandler : IQueryHandler> { diff --git a/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQuery.cs b/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQuery.cs index b7fd6db40..cfb03d8d6 100644 --- a/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQuery.cs +++ b/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQuery.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetUserPermissions +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetUserPermissions { public class GetUserPermissionsQuery : QueryBase> { diff --git a/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQueryHandler.cs b/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQueryHandler.cs index 5c9270b7e..bf9fd7c8a 100644 --- a/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQueryHandler.cs +++ b/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQueryHandler.cs @@ -1,8 +1,8 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Queries; using Dapper; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetUserPermissions +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetUserPermissions { internal class GetUserPermissionsQueryHandler : IQueryHandler> { diff --git a/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/UserPermissionDto.cs b/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/UserPermissionDto.cs index 65af856d8..2683c9815 100644 --- a/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/UserPermissionDto.cs +++ b/src/Modules/UserAccess/Application/Authorization/GetUserPermissions/UserPermissionDto.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Authorization.GetUserPermissions +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authorization.GetUserPermissions { public class UserPermissionDto { diff --git a/src/Modules/UserAccess/Application/CompanyName.MyMeetings.Modules.UserAccess.Application.csproj b/src/Modules/UserAccess/Application/CompanyName.MyMeetings.Modules.UserAccessIS.Application.csproj similarity index 68% rename from src/Modules/UserAccess/Application/CompanyName.MyMeetings.Modules.UserAccess.Application.csproj rename to src/Modules/UserAccess/Application/CompanyName.MyMeetings.Modules.UserAccessIS.Application.csproj index 3252d9bd8..7608b9ffb 100644 --- a/src/Modules/UserAccess/Application/CompanyName.MyMeetings.Modules.UserAccess.Application.csproj +++ b/src/Modules/UserAccess/Application/CompanyName.MyMeetings.Modules.UserAccessIS.Application.csproj @@ -2,7 +2,4 @@ - - - \ No newline at end of file diff --git a/src/Modules/UserAccess/Application/Configuration/Commands/ICommandHandler.cs b/src/Modules/UserAccess/Application/Configuration/Commands/ICommandHandler.cs index 652c8358b..bc5ae91ea 100644 --- a/src/Modules/UserAccess/Application/Configuration/Commands/ICommandHandler.cs +++ b/src/Modules/UserAccess/Application/Configuration/Commands/ICommandHandler.cs @@ -1,7 +1,7 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands { public interface ICommandHandler : IRequestHandler where TCommand : ICommand diff --git a/src/Modules/UserAccess/Application/Configuration/Commands/ICommandsScheduler.cs b/src/Modules/UserAccess/Application/Configuration/Commands/ICommandsScheduler.cs index 876fd61be..3eebcb4e3 100644 --- a/src/Modules/UserAccess/Application/Configuration/Commands/ICommandsScheduler.cs +++ b/src/Modules/UserAccess/Application/Configuration/Commands/ICommandsScheduler.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands { public interface ICommandsScheduler { diff --git a/src/Modules/UserAccess/Application/Configuration/Commands/InternalCommandBase.cs b/src/Modules/UserAccess/Application/Configuration/Commands/InternalCommandBase.cs index d975e270a..f18d712ad 100644 --- a/src/Modules/UserAccess/Application/Configuration/Commands/InternalCommandBase.cs +++ b/src/Modules/UserAccess/Application/Configuration/Commands/InternalCommandBase.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands { public abstract class InternalCommandBase : ICommand { diff --git a/src/Modules/UserAccess/Application/Configuration/Queries/IQueryHandler.cs b/src/Modules/UserAccess/Application/Configuration/Queries/IQueryHandler.cs index 3b8e2a81d..cd896b35c 100644 --- a/src/Modules/UserAccess/Application/Configuration/Queries/IQueryHandler.cs +++ b/src/Modules/UserAccess/Application/Configuration/Queries/IQueryHandler.cs @@ -1,7 +1,7 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Queries +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Queries { public interface IQueryHandler : IRequestHandler diff --git a/src/Modules/UserAccess/Application/Contracts/CommandBase.cs b/src/Modules/UserAccess/Application/Contracts/CommandBase.cs index 979d4905b..b307689da 100644 --- a/src/Modules/UserAccess/Application/Contracts/CommandBase.cs +++ b/src/Modules/UserAccess/Application/Contracts/CommandBase.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts { public abstract class CommandBase : ICommand { diff --git a/src/Modules/UserAccess/Application/Contracts/CustomClaimTypes.cs b/src/Modules/UserAccess/Application/Contracts/CustomClaimTypes.cs index 219d54dc6..3286ff5ac 100644 --- a/src/Modules/UserAccess/Application/Contracts/CustomClaimTypes.cs +++ b/src/Modules/UserAccess/Application/Contracts/CustomClaimTypes.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts { internal class CustomClaimTypes { diff --git a/src/Modules/UserAccess/Application/Contracts/ICommand.cs b/src/Modules/UserAccess/Application/Contracts/ICommand.cs index dbc194e04..d9d3f80ed 100644 --- a/src/Modules/UserAccess/Application/Contracts/ICommand.cs +++ b/src/Modules/UserAccess/Application/Contracts/ICommand.cs @@ -1,6 +1,6 @@ using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts { public interface ICommand : IRequest { diff --git a/src/Modules/UserAccess/Application/Contracts/IQuery.cs b/src/Modules/UserAccess/Application/Contracts/IQuery.cs index 874e8e147..937bfbf7b 100644 --- a/src/Modules/UserAccess/Application/Contracts/IQuery.cs +++ b/src/Modules/UserAccess/Application/Contracts/IQuery.cs @@ -1,6 +1,6 @@ using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts { public interface IQuery : IRequest { diff --git a/src/Modules/UserAccess/Application/Contracts/IRecurringCommand.cs b/src/Modules/UserAccess/Application/Contracts/IRecurringCommand.cs index b7bfd7671..c1db782af 100644 --- a/src/Modules/UserAccess/Application/Contracts/IRecurringCommand.cs +++ b/src/Modules/UserAccess/Application/Contracts/IRecurringCommand.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts { public interface IRecurringCommand { diff --git a/src/Modules/UserAccess/Application/Contracts/IUserAccessModule.cs b/src/Modules/UserAccess/Application/Contracts/IUserAccessModule.cs index 41043fc88..2744e9c1b 100644 --- a/src/Modules/UserAccess/Application/Contracts/IUserAccessModule.cs +++ b/src/Modules/UserAccess/Application/Contracts/IUserAccessModule.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts { public interface IUserAccessModule { diff --git a/src/Modules/UserAccess/Application/Contracts/QueryBase.cs b/src/Modules/UserAccess/Application/Contracts/QueryBase.cs index c0105a3b4..5e282c6f5 100644 --- a/src/Modules/UserAccess/Application/Contracts/QueryBase.cs +++ b/src/Modules/UserAccess/Application/Contracts/QueryBase.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts { public abstract class QueryBase : IQuery { diff --git a/src/Modules/UserAccess/Application/Contracts/Roles.cs b/src/Modules/UserAccess/Application/Contracts/Roles.cs index 7eb199d1d..7f6a34bf4 100644 --- a/src/Modules/UserAccess/Application/Contracts/Roles.cs +++ b/src/Modules/UserAccess/Application/Contracts/Roles.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts { public class Roles { diff --git a/src/Modules/UserAccess/Application/Emails/EmailDto.cs b/src/Modules/UserAccess/Application/Emails/EmailDto.cs index 71793f760..cba8af6df 100644 --- a/src/Modules/UserAccess/Application/Emails/EmailDto.cs +++ b/src/Modules/UserAccess/Application/Emails/EmailDto.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Emails +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Emails { public class EmailDto { diff --git a/src/Modules/UserAccess/Application/Emails/GetAllEmailsQuery.cs b/src/Modules/UserAccess/Application/Emails/GetAllEmailsQuery.cs index 228d01912..44af63344 100644 --- a/src/Modules/UserAccess/Application/Emails/GetAllEmailsQuery.cs +++ b/src/Modules/UserAccess/Application/Emails/GetAllEmailsQuery.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Emails +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Emails { public class GetAllEmailsQuery : QueryBase> { diff --git a/src/Modules/UserAccess/Application/Emails/GetAllEmailsQueryHandler.cs b/src/Modules/UserAccess/Application/Emails/GetAllEmailsQueryHandler.cs index fa26e171a..feca69435 100644 --- a/src/Modules/UserAccess/Application/Emails/GetAllEmailsQueryHandler.cs +++ b/src/Modules/UserAccess/Application/Emails/GetAllEmailsQueryHandler.cs @@ -1,8 +1,8 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Queries; using Dapper; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Emails +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Emails { internal class GetAllEmailsQueryHandler : IQueryHandler> { diff --git a/src/Modules/UserAccess/Application/IdentityServer/IdentityServerConfig.cs b/src/Modules/UserAccess/Application/IdentityServer/IdentityServerConfig.cs index 2d2e9cccc..653781aa2 100644 --- a/src/Modules/UserAccess/Application/IdentityServer/IdentityServerConfig.cs +++ b/src/Modules/UserAccess/Application/IdentityServer/IdentityServerConfig.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using IdentityServer4; using IdentityServer4.Models; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.IdentityServer +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.IdentityServer { public class IdentityServerConfig { diff --git a/src/Modules/UserAccess/Application/IdentityServer/ProfileService.cs b/src/Modules/UserAccess/Application/IdentityServer/ProfileService.cs index eae2f97c3..fbf15d0a4 100644 --- a/src/Modules/UserAccess/Application/IdentityServer/ProfileService.cs +++ b/src/Modules/UserAccess/Application/IdentityServer/ProfileService.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using IdentityServer4.Models; using IdentityServer4.Services; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.IdentityServer +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.IdentityServer { public class ProfileService : IProfileService { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommand.cs b/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommand.cs index 2bc0fd088..cf877ab35 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommand.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommand.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.ConfirmUserRegistration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.ConfirmUserRegistration { public class ConfirmUserRegistrationCommand : CommandBase { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommandHandler.cs b/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommandHandler.cs index 48598696c..7b2428a32 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommandHandler.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommandHandler.cs @@ -1,7 +1,7 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.ConfirmUserRegistration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.ConfirmUserRegistration { internal class ConfirmUserRegistrationCommandHandler : ICommandHandler { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/UserRegistrationConfirmedHandler.cs b/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/UserRegistrationConfirmedHandler.cs index 2420aa163..6f2ce3a64 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/UserRegistrationConfirmedHandler.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/ConfirmUserRegistration/UserRegistrationConfirmedHandler.cs @@ -1,9 +1,9 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Events; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Events; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users; using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.ConfirmUserRegistration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.ConfirmUserRegistration { public class UserRegistrationConfirmedHandler : INotificationHandler { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQuery.cs b/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQuery.cs index 21225e233..1bcbf6fc7 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQuery.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQuery.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.GetUserRegistration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.GetUserRegistration { public class GetUserRegistrationQuery : QueryBase { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQueryHandler.cs b/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQueryHandler.cs index 7d45eac6d..b5ca4a056 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQueryHandler.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQueryHandler.cs @@ -1,8 +1,8 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Queries; using Dapper; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.GetUserRegistration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.GetUserRegistration { internal class GetUserRegistrationQueryHandler : IQueryHandler { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/UserRegistrationDto.cs b/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/UserRegistrationDto.cs index 091015d5f..68bc871b6 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/UserRegistrationDto.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/GetUserRegistration/UserRegistrationDto.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.GetUserRegistration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.GetUserRegistration { public class UserRegistrationDto { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredEnqueueEmailConfirmationHandler.cs b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredEnqueueEmailConfirmationHandler.cs index b093b5d57..74f5d2e20 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredEnqueueEmailConfirmationHandler.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredEnqueueEmailConfirmationHandler.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.SendUserRegistrationConfirmationEmail; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.SendUserRegistrationConfirmationEmail; using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser { public class NewUserRegisteredEnqueueEmailConfirmationHandler : INotificationHandler { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredNotification.cs b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredNotification.cs index d5dfd9fee..8f4c5cab7 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredNotification.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredNotification.cs @@ -1,8 +1,8 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Events; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Events; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Events; using Newtonsoft.Json; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser { public class NewUserRegisteredNotification : DomainNotificationBase { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredPublishEventHandler.cs b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredPublishEventHandler.cs index 2c41aff0e..fbb8ce79b 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredPublishEventHandler.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredPublishEventHandler.cs @@ -1,8 +1,8 @@ using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; -using CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents; +using CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationEvents; using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser { public class NewUserRegisteredPublishEventHandler : INotificationHandler { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommand.cs b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommand.cs index b8257b6b4..74725b909 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommand.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommand.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser { public class RegisterNewUserCommand : CommandBase { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommandHandler.cs b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommandHandler.cs index cbcc313c7..464c7c5d1 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommandHandler.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommandHandler.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser { internal class RegisterNewUserCommandHandler : ICommandHandler { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommand.cs b/src/Modules/UserAccess/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommand.cs index f1e55149e..899476d86 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommand.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommand.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; using Newtonsoft.Json; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.SendUserRegistrationConfirmationEmail +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.SendUserRegistrationConfirmationEmail { public class SendUserRegistrationConfirmationEmailCommand : InternalCommandBase { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommandHandler.cs b/src/Modules/UserAccess/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommandHandler.cs index f1bd10b7d..7317ec18a 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommandHandler.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommandHandler.cs @@ -1,7 +1,7 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.SendUserRegistrationConfirmationEmail +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.SendUserRegistrationConfirmationEmail { internal class SendUserRegistrationConfirmationEmailCommandHandler : ICommandHandler { diff --git a/src/Modules/UserAccess/Application/UserRegistrations/UsersCounter.cs b/src/Modules/UserAccess/Application/UserRegistrations/UsersCounter.cs index 16e66b080..579c84a6e 100644 --- a/src/Modules/UserAccess/Application/UserRegistrations/UsersCounter.cs +++ b/src/Modules/UserAccess/Application/UserRegistrations/UsersCounter.cs @@ -1,8 +1,8 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; using Dapper; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations { public class UsersCounter : IUsersCounter { diff --git a/src/Modules/UserAccess/Application/Users/AddAdminUser/AddAdminUserCommand.cs b/src/Modules/UserAccess/Application/Users/AddAdminUser/AddAdminUserCommand.cs index 5786fd97d..a0b30bc70 100644 --- a/src/Modules/UserAccess/Application/Users/AddAdminUser/AddAdminUserCommand.cs +++ b/src/Modules/UserAccess/Application/Users/AddAdminUser/AddAdminUserCommand.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Users.AddAdminUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.AddAdminUser { public class AddAdminUserCommand : CommandBase { diff --git a/src/Modules/UserAccess/Application/Users/AddAdminUser/AddAdminUserCommandHandler.cs b/src/Modules/UserAccess/Application/Users/AddAdminUser/AddAdminUserCommandHandler.cs index 297b1d126..16627d418 100644 --- a/src/Modules/UserAccess/Application/Users/AddAdminUser/AddAdminUserCommandHandler.cs +++ b/src/Modules/UserAccess/Application/Users/AddAdminUser/AddAdminUserCommandHandler.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Authentication; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Authentication; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Users.AddAdminUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.AddAdminUser { internal class AddAdminUserCommandHandler : ICommandHandler { diff --git a/src/Modules/UserAccess/Application/Users/GetAuthenticatedUser/GetAuthenticatedUserQuery.cs b/src/Modules/UserAccess/Application/Users/GetAuthenticatedUser/GetAuthenticatedUserQuery.cs index 05beeac2c..cb8092f7e 100644 --- a/src/Modules/UserAccess/Application/Users/GetAuthenticatedUser/GetAuthenticatedUserQuery.cs +++ b/src/Modules/UserAccess/Application/Users/GetAuthenticatedUser/GetAuthenticatedUserQuery.cs @@ -1,7 +1,7 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetUser; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetAuthenticatedUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetAuthenticatedUser { public class GetAuthenticatedUserQuery : QueryBase { diff --git a/src/Modules/UserAccess/Application/Users/GetAuthenticatedUser/GetAuthenticatedUserQueryHandler.cs b/src/Modules/UserAccess/Application/Users/GetAuthenticatedUser/GetAuthenticatedUserQueryHandler.cs index 9041e55d1..2507ba900 100644 --- a/src/Modules/UserAccess/Application/Users/GetAuthenticatedUser/GetAuthenticatedUserQueryHandler.cs +++ b/src/Modules/UserAccess/Application/Users/GetAuthenticatedUser/GetAuthenticatedUserQueryHandler.cs @@ -1,10 +1,10 @@ using CompanyName.MyMeetings.BuildingBlocks.Application; using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Queries; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetUser; using Dapper; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetAuthenticatedUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetAuthenticatedUser { internal class GetAuthenticatedUserQueryHandler : IQueryHandler { diff --git a/src/Modules/UserAccess/Application/Users/GetUser/GetUserQuery.cs b/src/Modules/UserAccess/Application/Users/GetUser/GetUserQuery.cs index 2ab1f06f5..66f41bf31 100644 --- a/src/Modules/UserAccess/Application/Users/GetUser/GetUserQuery.cs +++ b/src/Modules/UserAccess/Application/Users/GetUser/GetUserQuery.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetUser { public class GetUserQuery : QueryBase { diff --git a/src/Modules/UserAccess/Application/Users/GetUser/GetUserQueryHandler.cs b/src/Modules/UserAccess/Application/Users/GetUser/GetUserQueryHandler.cs index 06b97f32a..cc47ecd8e 100644 --- a/src/Modules/UserAccess/Application/Users/GetUser/GetUserQueryHandler.cs +++ b/src/Modules/UserAccess/Application/Users/GetUser/GetUserQueryHandler.cs @@ -1,8 +1,8 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Queries; using Dapper; -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetUser { internal class GetUserQueryHandler : IQueryHandler { diff --git a/src/Modules/UserAccess/Application/Users/GetUser/UserDto.cs b/src/Modules/UserAccess/Application/Users/GetUser/UserDto.cs index d664e4e07..67d158983 100644 --- a/src/Modules/UserAccess/Application/Users/GetUser/UserDto.cs +++ b/src/Modules/UserAccess/Application/Users/GetUser/UserDto.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetUser +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetUser { public class UserDto { diff --git a/src/Modules/UserAccess/Domain/CompanyName.MyMeetings.Modules.UserAccess.Domain.csproj b/src/Modules/UserAccess/Domain/CompanyName.MyMeetings.Modules.UserAccessIS.Domain.csproj similarity index 100% rename from src/Modules/UserAccess/Domain/CompanyName.MyMeetings.Modules.UserAccess.Domain.csproj rename to src/Modules/UserAccess/Domain/CompanyName.MyMeetings.Modules.UserAccessIS.Domain.csproj diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/Events/NewUserRegisteredDomainEvent.cs b/src/Modules/UserAccess/Domain/UserRegistrations/Events/NewUserRegisteredDomainEvent.cs index 0adce92ed..c5dfc556e 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/Events/NewUserRegisteredDomainEvent.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/Events/NewUserRegisteredDomainEvent.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Events +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Events { public class NewUserRegisteredDomainEvent : DomainEventBase { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/Events/UserRegistrationConfirmedDomainEvent.cs b/src/Modules/UserAccess/Domain/UserRegistrations/Events/UserRegistrationConfirmedDomainEvent.cs index deaa90744..3ec639e49 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/Events/UserRegistrationConfirmedDomainEvent.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/Events/UserRegistrationConfirmedDomainEvent.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Events +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Events { public class UserRegistrationConfirmedDomainEvent : DomainEventBase { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/Events/UserRegistrationExpiredDomainEvent.cs b/src/Modules/UserAccess/Domain/UserRegistrations/Events/UserRegistrationExpiredDomainEvent.cs index 9b47582aa..e8a558deb 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/Events/UserRegistrationExpiredDomainEvent.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/Events/UserRegistrationExpiredDomainEvent.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Events +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Events { public class UserRegistrationExpiredDomainEvent : DomainEventBase { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/IUserRegistrationRepository.cs b/src/Modules/UserAccess/Domain/UserRegistrations/IUserRegistrationRepository.cs index ac797d530..b134a3c22 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/IUserRegistrationRepository.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/IUserRegistrationRepository.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations { public interface IUserRegistrationRepository { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/IUsersCounter.cs b/src/Modules/UserAccess/Domain/UserRegistrations/IUsersCounter.cs index e3451cfea..90039ebb8 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/IUsersCounter.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/IUsersCounter.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations { public interface IUsersCounter { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule.cs b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule.cs index 8cca80925..28dbf6ed6 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Rules +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Rules { public class UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule : IBusinessRule { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserLoginMustBeUniqueRule.cs b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserLoginMustBeUniqueRule.cs index 43513e366..b4a471df6 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserLoginMustBeUniqueRule.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserLoginMustBeUniqueRule.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Rules +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Rules { public class UserLoginMustBeUniqueRule : IBusinessRule { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedAfterExpirationRule.cs b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedAfterExpirationRule.cs index 898214688..f2526be2f 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedAfterExpirationRule.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedAfterExpirationRule.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Rules +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Rules { public class UserRegistrationCannotBeConfirmedAfterExpirationRule : IBusinessRule { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedMoreThanOnceRule.cs b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedMoreThanOnceRule.cs index ba59993b4..69f84e5db 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedMoreThanOnceRule.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedMoreThanOnceRule.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Rules +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Rules { public class UserRegistrationCannotBeConfirmedMoreThanOnceRule : IBusinessRule { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeExpiredMoreThanOnceRule.cs b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeExpiredMoreThanOnceRule.cs index 478d6fc07..02cf68ec5 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeExpiredMoreThanOnceRule.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/Rules/UserRegistrationCannotBeExpiredMoreThanOnceRule.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Rules +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Rules { public class UserRegistrationCannotBeExpiredMoreThanOnceRule : IBusinessRule { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistration.cs b/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistration.cs index c7c261cb3..80ef58b00 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistration.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistration.cs @@ -1,9 +1,9 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Events; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Rules; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Events; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Rules; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations { public class UserRegistration : Entity, IAggregateRoot { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistrationId.cs b/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistrationId.cs index 3cb893ef6..49549f332 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistrationId.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistrationId.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations { public class UserRegistrationId : TypedIdValueBase { diff --git a/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistrationStatus.cs b/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistrationStatus.cs index 7a29fc5b4..8c27a5abc 100644 --- a/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistrationStatus.cs +++ b/src/Modules/UserAccess/Domain/UserRegistrations/UserRegistrationStatus.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations { public class UserRegistrationStatus : ValueObject { diff --git a/src/Modules/UserAccess/Domain/Users/Events/UserCreatedDomainEvent.cs b/src/Modules/UserAccess/Domain/Users/Events/UserCreatedDomainEvent.cs index 5d1920323..a75623c5a 100644 --- a/src/Modules/UserAccess/Domain/Users/Events/UserCreatedDomainEvent.cs +++ b/src/Modules/UserAccess/Domain/Users/Events/UserCreatedDomainEvent.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.Users.Events +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users.Events { public class UserCreatedDomainEvent : DomainEventBase { diff --git a/src/Modules/UserAccess/Domain/Users/IUserRepository.cs b/src/Modules/UserAccess/Domain/Users/IUserRepository.cs index ebcae180e..7a8b95f66 100644 --- a/src/Modules/UserAccess/Domain/Users/IUserRepository.cs +++ b/src/Modules/UserAccess/Domain/Users/IUserRepository.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.Users +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users { public interface IUserRepository { diff --git a/src/Modules/UserAccess/Domain/Users/User.cs b/src/Modules/UserAccess/Domain/Users/User.cs index 769483579..3941f2b66 100644 --- a/src/Modules/UserAccess/Domain/Users/User.cs +++ b/src/Modules/UserAccess/Domain/Users/User.cs @@ -1,8 +1,8 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users.Events; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users.Events; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.Users +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users { public class User : Entity, IAggregateRoot { diff --git a/src/Modules/UserAccess/Domain/Users/UserId.cs b/src/Modules/UserAccess/Domain/Users/UserId.cs index d8d2499b5..22f9c2764 100644 --- a/src/Modules/UserAccess/Domain/Users/UserId.cs +++ b/src/Modules/UserAccess/Domain/Users/UserId.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.Users +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users { public class UserId : TypedIdValueBase { diff --git a/src/Modules/UserAccess/Domain/Users/UserRole.cs b/src/Modules/UserAccess/Domain/Users/UserRole.cs index 0690e44e2..b3e5202bd 100644 --- a/src/Modules/UserAccess/Domain/Users/UserRole.cs +++ b/src/Modules/UserAccess/Domain/Users/UserRole.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.Users +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users { public class UserRole : ValueObject { diff --git a/src/Modules/UserAccess/Infrastructure/CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.csproj b/src/Modules/UserAccess/Infrastructure/CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.csproj similarity index 100% rename from src/Modules/UserAccess/Infrastructure/CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.csproj rename to src/Modules/UserAccess/Infrastructure/CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.csproj diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/AllConstructorFinder.cs b/src/Modules/UserAccess/Infrastructure/Configuration/AllConstructorFinder.cs index 52bda4011..4ec2fe15a 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/AllConstructorFinder.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/AllConstructorFinder.cs @@ -2,7 +2,7 @@ using System.Reflection; using Autofac.Core.Activators.Reflection; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration { internal class AllConstructorFinder : IConstructorFinder { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Assemblies.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Assemblies.cs index 775b01850..97d2a5f9b 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Assemblies.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Assemblies.cs @@ -1,7 +1,7 @@ using System.Reflection; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration { internal static class Assemblies { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/DataAccess/DataAccessModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/DataAccess/DataAccessModule.cs index 140d6959e..fdb8830f4 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/DataAccess/DataAccessModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/DataAccess/DataAccessModule.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.Extensions.Logging; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.DataAccess +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.DataAccess { internal class DataAccessModule : Autofac.Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Domain/DomainModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Domain/DomainModule.cs index a026da24d..4e6e214e0 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Domain/DomainModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Domain/DomainModule.cs @@ -1,8 +1,8 @@ using Autofac; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Domain +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Domain { internal class DomainModule : Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Email/EmailModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Email/EmailModule.cs index 476e12029..02620e8a0 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Email/EmailModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Email/EmailModule.cs @@ -2,7 +2,7 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Emails; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Email +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Email { internal class EmailModule : Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/EventsBusModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/EventsBusModule.cs index ae2e8fd1c..019c7a2ec 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/EventsBusModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/EventsBusModule.cs @@ -1,7 +1,7 @@ using Autofac; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.EventsBus +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.EventsBus { internal class EventsBusModule : Autofac.Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs b/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs index 9c60bb06b..52868df05 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs @@ -3,7 +3,7 @@ using CompanyName.MyMeetings.Modules.Meetings.IntegrationEvents; using Serilog; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.EventsBus +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.EventsBus { public static class EventsBusStartup { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/IntegrationEventGenericHandler.cs b/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/IntegrationEventGenericHandler.cs index e004f4726..e6ec8ad73 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/IntegrationEventGenericHandler.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/EventsBus/IntegrationEventGenericHandler.cs @@ -5,7 +5,7 @@ using Dapper; using Newtonsoft.Json; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.EventsBus +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.EventsBus { internal class IntegrationEventGenericHandler : IIntegrationEventHandler where T : IntegrationEvent diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Logging/LoggingModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Logging/LoggingModule.cs index a42522f70..b44075bd6 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Logging/LoggingModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Logging/LoggingModule.cs @@ -1,7 +1,7 @@ using Autofac; using Serilog; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Logging +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Logging { internal class LoggingModule : Autofac.Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Mediation/MediatorModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Mediation/MediatorModule.cs index 50148a614..5eed38448 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Mediation/MediatorModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Mediation/MediatorModule.cs @@ -3,12 +3,12 @@ using Autofac.Core; using Autofac.Features.Variance; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; using FluentValidation; using MediatR; using MediatR.Pipeline; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Mediation +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Mediation { public class MediatorModule : Autofac.Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/CommandsExecutor.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/CommandsExecutor.cs index b5ca1a030..127d5c5ad 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/CommandsExecutor.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/CommandsExecutor.cs @@ -1,8 +1,8 @@ using Autofac; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { internal static class CommandsExecutor { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/IRecurringCommand.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/IRecurringCommand.cs index d6e0e1ff8..fc8b8f1a1 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/IRecurringCommand.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/IRecurringCommand.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { public interface IRecurringCommand { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/InboxMessageDto.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/InboxMessageDto.cs index bbb10ccd6..9caca68b4 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/InboxMessageDto.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/InboxMessageDto.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Inbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Inbox { public class InboxMessageDto { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommand.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommand.cs index cf3ae1bfc..f77f73d6a 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommand.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommand.cs @@ -1,8 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; - -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Inbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Inbox { - public class ProcessInboxCommand : CommandBase, IRecurringCommand + public class ProcessInboxCommand : UserAccessIS.Application.Contracts.CommandBase, IRecurringCommand { } } \ No newline at end of file diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommandHandler.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommandHandler.cs index 7a8a832c6..b88df3037 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommandHandler.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommandHandler.cs @@ -1,10 +1,10 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; using Dapper; using MediatR; using Newtonsoft.Json; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Inbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Inbox { internal class ProcessInboxCommandHandler : ICommandHandler { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxJob.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxJob.cs index 897046458..7361bcc88 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxJob.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Inbox/ProcessInboxJob.cs @@ -1,6 +1,6 @@ using Quartz; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Inbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Inbox { [DisallowConcurrentExecution] public class ProcessInboxJob : IJob diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/CommandsScheduler.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/CommandsScheduler.cs index ef92e18ff..ee02fd718 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/CommandsScheduler.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/CommandsScheduler.cs @@ -1,11 +1,11 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Serialization; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using Dapper; using Newtonsoft.Json; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.InternalCommands +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.InternalCommands { public class CommandsScheduler : ICommandsScheduler { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommand.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommand.cs index e5a1d4ee5..99af1304a 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommand.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommand.cs @@ -1,8 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; - -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.InternalCommands +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.InternalCommands { - internal class ProcessInternalCommandsCommand : CommandBase, IRecurringCommand + internal class ProcessInternalCommandsCommand : UserAccessIS.Application.Contracts.CommandBase, IRecurringCommand { } } \ No newline at end of file diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommandHandler.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommandHandler.cs index 0a08b8264..4a2ca6707 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommandHandler.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommandHandler.cs @@ -1,10 +1,10 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; using Dapper; using Newtonsoft.Json; using Polly; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.InternalCommands +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.InternalCommands { internal class ProcessInternalCommandsCommandHandler : ICommandHandler { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsJob.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsJob.cs index 40e3f3287..faf07a982 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsJob.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsJob.cs @@ -1,6 +1,6 @@ using Quartz; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.InternalCommands +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.InternalCommands { [DisallowConcurrentExecution] public class ProcessInternalCommandsJob : IJob diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/LoggingCommandHandlerDecorator.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/LoggingCommandHandlerDecorator.cs index 6d0c02a99..71f8a0a1e 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/LoggingCommandHandlerDecorator.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/LoggingCommandHandlerDecorator.cs @@ -1,15 +1,14 @@ using CompanyName.MyMeetings.BuildingBlocks.Application; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; using Serilog; using Serilog.Context; using Serilog.Core; using Serilog.Events; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { internal class LoggingCommandHandlerDecorator : ICommandHandler - where T : ICommand + where T : UserAccessIS.Application.Contracts.ICommand { private readonly ILogger _logger; private readonly IExecutionContextAccessor _executionContextAccessor; @@ -57,9 +56,9 @@ public async Task Handle(T command, CancellationToken cancellationToken) private class CommandLogEnricher : ILogEventEnricher { - private readonly ICommand _command; + private readonly UserAccessIS.Application.Contracts.ICommand _command; - public CommandLogEnricher(ICommand command) + public CommandLogEnricher(UserAccessIS.Application.Contracts.ICommand command) { _command = command; } diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/LoggingCommandHandlerWithResultDecorator.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/LoggingCommandHandlerWithResultDecorator.cs index abf3770a2..9d18dd437 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/LoggingCommandHandlerWithResultDecorator.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/LoggingCommandHandlerWithResultDecorator.cs @@ -1,12 +1,12 @@ using CompanyName.MyMeetings.BuildingBlocks.Application; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using Serilog; using Serilog.Context; using Serilog.Core; using Serilog.Events; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { internal class LoggingCommandHandlerWithResultDecorator : ICommandHandler where T : ICommand diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/OutboxMessageDto.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/OutboxMessageDto.cs index dfcc95b0e..ec6d150c8 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/OutboxMessageDto.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/OutboxMessageDto.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Outbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Outbox { public class OutboxMessageDto { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/OutboxModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/OutboxModule.cs index d2d67df78..f826c1d71 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/OutboxModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/OutboxModule.cs @@ -3,10 +3,10 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Outbox; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.DomainEventsDispatching; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Outbox; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Outbox; using Module = Autofac.Module; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Outbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Outbox { internal class OutboxModule : Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommand.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommand.cs index c6f671614..fbf030835 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommand.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommand.cs @@ -1,8 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; - -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Outbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Outbox { - public class ProcessOutboxCommand : CommandBase, IRecurringCommand + public class ProcessOutboxCommand : UserAccessIS.Application.Contracts.CommandBase, IRecurringCommand { } } \ No newline at end of file diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommandHandler.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommandHandler.cs index f63715d30..9498bb070 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommandHandler.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommandHandler.cs @@ -1,7 +1,7 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Data; using CompanyName.MyMeetings.BuildingBlocks.Application.Events; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.DomainEventsDispatching; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; using Dapper; using MediatR; using Newtonsoft.Json; @@ -9,7 +9,7 @@ using Serilog.Core; using Serilog.Events; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Outbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Outbox { internal class ProcessOutboxCommandHandler : ICommandHandler { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxJob.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxJob.cs index af1824765..9cb0de950 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxJob.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxJob.cs @@ -1,6 +1,6 @@ using Quartz; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Outbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Outbox { [DisallowConcurrentExecution] public class ProcessOutboxJob : IJob diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ProcessingModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ProcessingModule.cs index 4316b4656..c443cc8c1 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ProcessingModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ProcessingModule.cs @@ -2,11 +2,11 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Events; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.DomainEventsDispatching; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.InternalCommands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.InternalCommands; using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { internal class ProcessingModule : Autofac.Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerDecorator.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerDecorator.cs index 7d71a2e47..93de5d0a8 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerDecorator.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerDecorator.cs @@ -1,9 +1,9 @@ using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using Microsoft.EntityFrameworkCore; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { internal class UnitOfWorkCommandHandlerDecorator : ICommandHandler where T : ICommand diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs index 01be1418e..7b6ebd14e 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs @@ -1,9 +1,9 @@ using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using Microsoft.EntityFrameworkCore; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { internal class UnitOfWorkCommandHandlerWithResultDecorator : ICommandHandler where T : ICommand diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs index a59465704..64df85fde 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs @@ -1,9 +1,9 @@ using CompanyName.MyMeetings.BuildingBlocks.Application; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using FluentValidation; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { internal class ValidationCommandHandlerDecorator : ICommandHandler where T : ICommand diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ValidationCommandHandlerWithResultDecorator.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ValidationCommandHandlerWithResultDecorator.cs index 5c7fc61e5..b8db82d09 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ValidationCommandHandlerWithResultDecorator.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Processing/ValidationCommandHandlerWithResultDecorator.cs @@ -1,9 +1,9 @@ using CompanyName.MyMeetings.BuildingBlocks.Application; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; using FluentValidation; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing { internal class ValidationCommandHandlerWithResultDecorator : ICommandHandler where T : ICommand diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/QuartzModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/QuartzModule.cs index 94ca85c09..e77321d70 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/QuartzModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/QuartzModule.cs @@ -1,7 +1,7 @@ using Autofac; using Quartz; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Quartz +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Quartz { public class QuartzModule : Autofac.Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/QuartzStartup.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/QuartzStartup.cs index a34e7cdca..0f1bccf78 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/QuartzStartup.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/QuartzStartup.cs @@ -1,13 +1,13 @@ using System.Collections.Specialized; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Inbox; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.InternalCommands; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Outbox; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Inbox; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.InternalCommands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Outbox; using Quartz; using Quartz.Impl; using Quartz.Logging; using Serilog; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Quartz +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Quartz { internal static class QuartzStartup { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/SerilogLogProvider.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/SerilogLogProvider.cs index 7f4762e84..1003ff150 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/SerilogLogProvider.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Quartz/SerilogLogProvider.cs @@ -1,7 +1,7 @@ using Quartz.Logging; using Serilog; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Quartz +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Quartz { internal class SerilogLogProvider : ILogProvider { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Security/AesDataProtector.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Security/AesDataProtector.cs index f5048343b..26421a41a 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Security/AesDataProtector.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Security/AesDataProtector.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using System.Text; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Security +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Security { public class AesDataProtector : IDataProtector { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Security/IDataProtector.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Security/IDataProtector.cs index e27cc0338..85a431d86 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Security/IDataProtector.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Security/IDataProtector.cs @@ -1,4 +1,4 @@ -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Security +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Security { public interface IDataProtector { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/Security/SecurityModule.cs b/src/Modules/UserAccess/Infrastructure/Configuration/Security/SecurityModule.cs index b6ea58ad0..44b23752d 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/Security/SecurityModule.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/Security/SecurityModule.cs @@ -1,6 +1,6 @@ using Autofac; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Security +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Security { internal class SecurityModule : Module { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/UserAccessCompositionRoot.cs b/src/Modules/UserAccess/Infrastructure/Configuration/UserAccessCompositionRoot.cs index e322f8466..ecdf81d28 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/UserAccessCompositionRoot.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/UserAccessCompositionRoot.cs @@ -1,6 +1,6 @@ using Autofac; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration { internal static class UserAccessCompositionRoot { diff --git a/src/Modules/UserAccess/Infrastructure/Configuration/UserAccessStartup.cs b/src/Modules/UserAccess/Infrastructure/Configuration/UserAccessStartup.cs index ef5d3547b..1c6f00f72 100644 --- a/src/Modules/UserAccess/Infrastructure/Configuration/UserAccessStartup.cs +++ b/src/Modules/UserAccess/Infrastructure/Configuration/UserAccessStartup.cs @@ -4,20 +4,20 @@ using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Emails; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.DataAccess; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Domain; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Email; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.EventsBus; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Logging; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Mediation; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Outbox; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Quartz; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Security; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.DataAccess; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Domain; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Email; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.EventsBus; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Logging; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Mediation; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Outbox; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Quartz; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Security; using Serilog; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration { public class UserAccessStartup { diff --git a/src/Modules/UserAccess/Infrastructure/Domain/UserRegistrations/UserRegistrationEntityTypeConfiguration.cs b/src/Modules/UserAccess/Infrastructure/Domain/UserRegistrations/UserRegistrationEntityTypeConfiguration.cs index bfcfedda7..ac886267f 100644 --- a/src/Modules/UserAccess/Infrastructure/Domain/UserRegistrations/UserRegistrationEntityTypeConfiguration.cs +++ b/src/Modules/UserAccess/Infrastructure/Domain/UserRegistrations/UserRegistrationEntityTypeConfiguration.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Domain.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Domain.UserRegistrations { internal class UserRegistrationEntityTypeConfiguration : IEntityTypeConfiguration { diff --git a/src/Modules/UserAccess/Infrastructure/Domain/UserRegistrations/UserRegistrationRepository.cs b/src/Modules/UserAccess/Infrastructure/Domain/UserRegistrations/UserRegistrationRepository.cs index 8a304f904..3e4a59a03 100644 --- a/src/Modules/UserAccess/Infrastructure/Domain/UserRegistrations/UserRegistrationRepository.cs +++ b/src/Modules/UserAccess/Infrastructure/Domain/UserRegistrations/UserRegistrationRepository.cs @@ -1,7 +1,7 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; using Microsoft.EntityFrameworkCore; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Domain.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Domain.UserRegistrations { public class UserRegistrationRepository : IUserRegistrationRepository { diff --git a/src/Modules/UserAccess/Infrastructure/Domain/Users/UserEntityTypeConfiguration.cs b/src/Modules/UserAccess/Infrastructure/Domain/Users/UserEntityTypeConfiguration.cs index 7dc8d3aac..f38ab80c4 100644 --- a/src/Modules/UserAccess/Infrastructure/Domain/Users/UserEntityTypeConfiguration.cs +++ b/src/Modules/UserAccess/Infrastructure/Domain/Users/UserEntityTypeConfiguration.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Domain.Users +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Domain.Users { internal class UserEntityTypeConfiguration : IEntityTypeConfiguration { diff --git a/src/Modules/UserAccess/Infrastructure/Domain/Users/UserRepository.cs b/src/Modules/UserAccess/Infrastructure/Domain/Users/UserRepository.cs index d6873cb80..ade0fb618 100644 --- a/src/Modules/UserAccess/Infrastructure/Domain/Users/UserRepository.cs +++ b/src/Modules/UserAccess/Infrastructure/Domain/Users/UserRepository.cs @@ -1,6 +1,6 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Domain.Users +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Domain.Users { public class UserRepository : IUserRepository { diff --git a/src/Modules/UserAccess/Infrastructure/InternalCommands/InternalCommandEntityTypeConfiguration.cs b/src/Modules/UserAccess/Infrastructure/InternalCommands/InternalCommandEntityTypeConfiguration.cs index 0116511df..939fdbf9f 100644 --- a/src/Modules/UserAccess/Infrastructure/InternalCommands/InternalCommandEntityTypeConfiguration.cs +++ b/src/Modules/UserAccess/Infrastructure/InternalCommands/InternalCommandEntityTypeConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.InternalCommands +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.InternalCommands { internal class InternalCommandEntityTypeConfiguration : IEntityTypeConfiguration { diff --git a/src/Modules/UserAccess/Infrastructure/Outbox/OutboxAccessor.cs b/src/Modules/UserAccess/Infrastructure/Outbox/OutboxAccessor.cs index 5502b0c01..ba9422a05 100644 --- a/src/Modules/UserAccess/Infrastructure/Outbox/OutboxAccessor.cs +++ b/src/Modules/UserAccess/Infrastructure/Outbox/OutboxAccessor.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Outbox; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Outbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Outbox { public class OutboxAccessor : IOutbox { diff --git a/src/Modules/UserAccess/Infrastructure/Outbox/OutboxMessageEntityTypeConfiguration.cs b/src/Modules/UserAccess/Infrastructure/Outbox/OutboxMessageEntityTypeConfiguration.cs index 84b299d6b..c2f5abd43 100644 --- a/src/Modules/UserAccess/Infrastructure/Outbox/OutboxMessageEntityTypeConfiguration.cs +++ b/src/Modules/UserAccess/Infrastructure/Outbox/OutboxMessageEntityTypeConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Outbox +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Outbox { internal class OutboxMessageEntityTypeConfiguration : IEntityTypeConfiguration { diff --git a/src/Modules/UserAccess/Infrastructure/UserAccessContext.cs b/src/Modules/UserAccess/Infrastructure/UserAccessContext.cs index 8b94db909..874e61678 100644 --- a/src/Modules/UserAccess/Infrastructure/UserAccessContext.cs +++ b/src/Modules/UserAccess/Infrastructure/UserAccessContext.cs @@ -1,15 +1,15 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Outbox; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.InternalCommands; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Domain.UserRegistrations; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Domain.Users; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.InternalCommands; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Outbox; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.InternalCommands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Outbox; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure { public class UserAccessContext : DbContext { diff --git a/src/Modules/UserAccess/Infrastructure/UserAccessModule.cs b/src/Modules/UserAccess/Infrastructure/UserAccessModule.cs index 3b10cc5f9..e1f3da00f 100644 --- a/src/Modules/UserAccess/Infrastructure/UserAccessModule.cs +++ b/src/Modules/UserAccess/Infrastructure/UserAccessModule.cs @@ -1,10 +1,10 @@ using Autofac; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing; using MediatR; -namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure { public class UserAccessModule : IUserAccessModule { diff --git a/src/Modules/UserAccess/IntegrationEvents/CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents.csproj b/src/Modules/UserAccess/IntegrationEvents/CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationEvents.csproj similarity index 100% rename from src/Modules/UserAccess/IntegrationEvents/CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents.csproj rename to src/Modules/UserAccess/IntegrationEvents/CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationEvents.csproj diff --git a/src/Modules/UserAccess/IntegrationEvents/NewUserRegisteredIntegrationEvent.cs b/src/Modules/UserAccess/IntegrationEvents/NewUserRegisteredIntegrationEvent.cs index fcb88dac3..cc850bfec 100644 --- a/src/Modules/UserAccess/IntegrationEvents/NewUserRegisteredIntegrationEvent.cs +++ b/src/Modules/UserAccess/IntegrationEvents/NewUserRegisteredIntegrationEvent.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; -namespace CompanyName.MyMeetings.Modules.UserAccess.IntegrationEvents +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationEvents { public class NewUserRegisteredIntegrationEvent : IntegrationEvent { diff --git a/src/Modules/UserAccess/Tests/ArchTests/Application/ApplicationTests.cs b/src/Modules/UserAccess/Tests/ArchTests/Application/ApplicationTests.cs index d0463569b..47d0790c4 100644 --- a/src/Modules/UserAccess/Tests/ArchTests/Application/ApplicationTests.cs +++ b/src/Modules/UserAccess/Tests/ArchTests/Application/ApplicationTests.cs @@ -1,15 +1,15 @@ using System.Reflection; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Commands; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Configuration.Queries; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.ArchTests.SeedWork; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.SeedWork; using FluentValidation; using MediatR; using NetArchTest.Rules; using Newtonsoft.Json; using NUnit.Framework; -namespace CompanyName.MyMeetings.Modules.UserAccess.ArchTests.Application +namespace CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.Application { [TestFixture] public class ApplicationTests : TestBase diff --git a/src/Modules/UserAccess/Tests/ArchTests/CompanyName.MyMeetings.Modules.UserAccess.ArchTests.csproj b/src/Modules/UserAccess/Tests/ArchTests/CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.csproj similarity index 100% rename from src/Modules/UserAccess/Tests/ArchTests/CompanyName.MyMeetings.Modules.UserAccess.ArchTests.csproj rename to src/Modules/UserAccess/Tests/ArchTests/CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.csproj diff --git a/src/Modules/UserAccess/Tests/ArchTests/Domain/DomainTests.cs b/src/Modules/UserAccess/Tests/ArchTests/Domain/DomainTests.cs index 161941196..6a3c38b8e 100644 --- a/src/Modules/UserAccess/Tests/ArchTests/Domain/DomainTests.cs +++ b/src/Modules/UserAccess/Tests/ArchTests/Domain/DomainTests.cs @@ -1,10 +1,10 @@ using System.Reflection; using CompanyName.MyMeetings.BuildingBlocks.Domain; -using CompanyName.MyMeetings.Modules.UserAccess.ArchTests.SeedWork; +using CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.SeedWork; using NetArchTest.Rules; using NUnit.Framework; -namespace CompanyName.MyMeetings.Modules.UserAccess.ArchTests.Domain +namespace CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.Domain { public class DomainTests : TestBase { diff --git a/src/Modules/UserAccess/Tests/ArchTests/Module/LayersTests.cs b/src/Modules/UserAccess/Tests/ArchTests/Module/LayersTests.cs index ec7b9e1a2..0e1798cbc 100644 --- a/src/Modules/UserAccess/Tests/ArchTests/Module/LayersTests.cs +++ b/src/Modules/UserAccess/Tests/ArchTests/Module/LayersTests.cs @@ -1,8 +1,8 @@ -using CompanyName.MyMeetings.Modules.UserAccess.ArchTests.SeedWork; +using CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.SeedWork; using NetArchTest.Rules; using NUnit.Framework; -namespace CompanyName.MyMeetings.Modules.UserAccess.ArchTests.Module +namespace CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.Module { [TestFixture] public class LayersTests : TestBase diff --git a/src/Modules/UserAccess/Tests/ArchTests/SeedWork/TestBase.cs b/src/Modules/UserAccess/Tests/ArchTests/SeedWork/TestBase.cs index 58feb086f..1149e3842 100644 --- a/src/Modules/UserAccess/Tests/ArchTests/SeedWork/TestBase.cs +++ b/src/Modules/UserAccess/Tests/ArchTests/SeedWork/TestBase.cs @@ -1,11 +1,11 @@ using System.Reflection; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure; using NetArchTest.Rules; using NUnit.Framework; -namespace CompanyName.MyMeetings.Modules.UserAccess.ArchTests.SeedWork +namespace CompanyName.MyMeetings.Modules.UserAccessIS.ArchTests.SeedWork { public abstract class TestBase { diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/AssemblyInfo.cs b/src/Modules/UserAccess/Tests/IntegrationTests/AssemblyInfo.cs index c3707f714..ffe779400 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/AssemblyInfo.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/AssemblyInfo.cs @@ -3,7 +3,7 @@ [assembly: NonParallelizable] [assembly: LevelOfParallelism(1)] -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests { public class AssemblyInfo { diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.csproj b/src/Modules/UserAccess/Tests/IntegrationTests/CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.csproj similarity index 100% rename from src/Modules/UserAccess/Tests/IntegrationTests/CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.csproj rename to src/Modules/UserAccess/Tests/IntegrationTests/CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.csproj diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs b/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs index 59628ef13..ec99f4bc8 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs @@ -1,6 +1,6 @@ using CompanyName.MyMeetings.BuildingBlocks.Application; -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.SeedWork +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.SeedWork { public class ExecutionContextMock : IExecutionContextAccessor { @@ -15,6 +15,8 @@ public ExecutionContextMock(Guid userId) public bool IsAvailable { get; } + public bool IsAuthenticated { get; } + public void SetUserId(Guid userId) { this.UserId = userId; diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/OutboxMessagesHelper.cs b/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/OutboxMessagesHelper.cs index 9cfdec37d..8e2633996 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/OutboxMessagesHelper.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/OutboxMessagesHelper.cs @@ -1,12 +1,12 @@ using System.Data; using System.Reflection; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Processing.Outbox; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration.Processing.Outbox; using Dapper; using MediatR; using Newtonsoft.Json; -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.SeedWork +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.SeedWork { public class OutboxMessagesHelper { diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/TestBase.cs b/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/TestBase.cs index 5135fa6cc..da5717c91 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/TestBase.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/SeedWork/TestBase.cs @@ -3,16 +3,16 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Emails; using CompanyName.MyMeetings.BuildingBlocks.IntegrationTests; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration; using Dapper; using MediatR; using NSubstitute; using NUnit.Framework; using Serilog; -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.SeedWork +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.SeedWork { public class TestBase { diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/ConfirmUserRegistrationTests.cs b/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/ConfirmUserRegistrationTests.cs index 337493a46..22539e5c0 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/ConfirmUserRegistrationTests.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/ConfirmUserRegistrationTests.cs @@ -1,11 +1,11 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.ConfirmUserRegistration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.GetUserRegistration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; -using CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.SeedWork; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.ConfirmUserRegistration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.GetUserRegistration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.SeedWork; using NUnit.Framework; -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.UserRegistrations { [TestFixture] public class ConfirmUserRegistrationTests : TestBase diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/SendUserRegistrationConfirmationEmailTests.cs b/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/SendUserRegistrationConfirmationEmailTests.cs index ed2163247..641848e11 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/SendUserRegistrationConfirmationEmailTests.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/SendUserRegistrationConfirmationEmailTests.cs @@ -1,11 +1,11 @@ using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.SendUserRegistrationConfirmationEmail; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; -using CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.SeedWork; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.SendUserRegistrationConfirmationEmail; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.SeedWork; using NSubstitute.ReceivedExtensions; using NUnit.Framework; -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.UserRegistrations { [TestFixture] public class SendUserRegistrationConfirmationEmailTests : TestBase diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/UserRegistrationSampleData.cs b/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/UserRegistrationSampleData.cs index 0cbe855eb..89283d314 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/UserRegistrationSampleData.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/UserRegistrationSampleData.cs @@ -1,4 +1,4 @@ -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.UserRegistrations { public struct UserRegistrationSampleData { diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/UserRegistrationTests.cs b/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/UserRegistrationTests.cs index e01449241..947495393 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/UserRegistrationTests.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/UserRegistrations/UserRegistrationTests.cs @@ -1,10 +1,10 @@ using System.Data.SqlClient; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.GetUserRegistration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser; -using CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.SeedWork; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.GetUserRegistration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.SeedWork; using NUnit.Framework; -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.UserRegistrations { [TestFixture] public class UserRegistrationTests : TestBase diff --git a/src/Modules/UserAccess/Tests/IntegrationTests/Users/CreateUserTests.cs b/src/Modules/UserAccess/Tests/IntegrationTests/Users/CreateUserTests.cs index 347eaa72a..322227599 100644 --- a/src/Modules/UserAccess/Tests/IntegrationTests/Users/CreateUserTests.cs +++ b/src/Modules/UserAccess/Tests/IntegrationTests/Users/CreateUserTests.cs @@ -1,11 +1,11 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.ConfirmUserRegistration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Users.GetUser; -using CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.SeedWork; -using CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.ConfirmUserRegistration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.GetUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.SeedWork; +using CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.UserRegistrations; using NUnit.Framework; -namespace CompanyNames.MyMeetings.Modules.UserAccess.IntegrationTests.Users +namespace CompanyName.MyMeetings.Modules.UserAccessIS.IntegrationTests.Users { [TestFixture] public class CreateUserTests : TestBase diff --git a/src/Modules/UserAccess/Tests/UnitTests/CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests.csproj b/src/Modules/UserAccess/Tests/UnitTests/CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests.csproj similarity index 100% rename from src/Modules/UserAccess/Tests/UnitTests/CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests.csproj rename to src/Modules/UserAccess/Tests/UnitTests/CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests.csproj diff --git a/src/Modules/UserAccess/Tests/UnitTests/SeedWork/DomainEventsTestHelper.cs b/src/Modules/UserAccess/Tests/UnitTests/SeedWork/DomainEventsTestHelper.cs index ee6b1c113..84c3b0e99 100644 --- a/src/Modules/UserAccess/Tests/UnitTests/SeedWork/DomainEventsTestHelper.cs +++ b/src/Modules/UserAccess/Tests/UnitTests/SeedWork/DomainEventsTestHelper.cs @@ -2,7 +2,7 @@ using System.Reflection; using CompanyName.MyMeetings.BuildingBlocks.Domain; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests.SeedWork +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests.SeedWork { public class DomainEventsTestHelper { diff --git a/src/Modules/UserAccess/Tests/UnitTests/SeedWork/TestBase.cs b/src/Modules/UserAccess/Tests/UnitTests/SeedWork/TestBase.cs index 616ac79a6..7ae720c88 100644 --- a/src/Modules/UserAccess/Tests/UnitTests/SeedWork/TestBase.cs +++ b/src/Modules/UserAccess/Tests/UnitTests/SeedWork/TestBase.cs @@ -1,7 +1,7 @@ using CompanyName.MyMeetings.BuildingBlocks.Domain; using NUnit.Framework; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests.SeedWork +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests.SeedWork { public abstract class TestBase { diff --git a/src/Modules/UserAccess/Tests/UnitTests/UserRegistrations/UserRegistrationTests.cs b/src/Modules/UserAccess/Tests/UnitTests/UserRegistrations/UserRegistrationTests.cs index c05229164..ed6af0e03 100644 --- a/src/Modules/UserAccess/Tests/UnitTests/UserRegistrations/UserRegistrationTests.cs +++ b/src/Modules/UserAccess/Tests/UnitTests/UserRegistrations/UserRegistrationTests.cs @@ -1,12 +1,12 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests.SeedWork; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Events; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.UserRegistrations.Rules; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users.Events; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests.SeedWork; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Events; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UserRegistrations.Rules; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users.Events; using NSubstitute; using NUnit.Framework; -namespace CompanyName.MyMeetings.Modules.UserAccess.Domain.UnitTests.UserRegistrations +namespace CompanyName.MyMeetings.Modules.UserAccessIS.Domain.UnitTests.UserRegistrations { [TestFixture] public class UserRegistrationTests : TestBase diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommand.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommand.cs new file mode 100644 index 000000000..20225af53 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommand.cs @@ -0,0 +1,16 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login; + +public class AccountLoginCommand : CommandBase +{ + public AccountLoginCommand(string login, string password) + { + Login = login; + Password = password; + } + + public string Login { get; } + + public string Password { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommandHandler.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommandHandler.cs new file mode 100644 index 000000000..0c14f35c9 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommandHandler.cs @@ -0,0 +1,162 @@ +using System.Security.Claims; +using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login; + +internal class AccountLoginCommandHandler : ICommandHandler +{ + private readonly IEmailSender _emailSender; + private readonly UserManager _userManager; + private readonly IdentityOptions _identityOptions; + private readonly ITokenClaimsService _tokenClaimsService; + private readonly IUserClaimsPrincipalFactory _userClaimsPrincipalFactory; + + public AccountLoginCommandHandler( + IEmailSender emailSender, + UserManager userManager, + ITokenClaimsService tokenClaimsService, + IUserClaimsPrincipalFactory userClaimsPrincipalFactory) + { + _emailSender = emailSender; + _userManager = userManager; + _identityOptions = _userManager.Options; + _tokenClaimsService = tokenClaimsService; + _userClaimsPrincipalFactory = userClaimsPrincipalFactory; + } + + public async Task Handle(AccountLoginCommand request, CancellationToken cancellationToken) + { + var response = new AuthenticationResult(); + + var user = await _userManager.FindByNameAsync(request.Login); + if (user != null && !await _userManager.IsLockedOutAsync(user)) + { + if (await _userManager.HasPasswordAsync(user)) + { + var passwordCheckSucceeded = await _userManager.CheckPasswordAsync(user, request.Password); + if (passwordCheckSucceeded) + { + // Reset failed account login attempts + await _userManager.ResetAccessFailedCountAsync(user); + + // Check if user is allowed to login + if (_identityOptions.SignIn.RequireConfirmedEmail && !await _userManager.IsEmailConfirmedAsync(user)) + { + if (!string.IsNullOrEmpty(user.Email)) + { + var emailMessage = new EmailMessage( + user.Email!, + "MyMeetings user account not validated!", + $@"You cannot log in with this user account because the e-mail address you have entered has not yet been validated.\n\nUser name: {user.UserName}\nEmail: {user.Email}"); + + await _emailSender.SendEmail(emailMessage); + } + + response.AddError(Errors.UserAccess.EmailNotConfirmed); + } + else + { + if (await _userManager.GetTwoFactorEnabledAsync(user)) + { + var validProviders = await _userManager.GetValidTwoFactorProvidersAsync(user); + + if (validProviders.Contains(_userManager.Options.Tokens.AuthenticatorTokenProvider)) + { + response.StoreTwoFactorAuthentication(Generate2FA(user.Id, _userManager.Options.Tokens.AuthenticatorTokenProvider)); + } + else if (validProviders.Contains("Email")) + { + var token = await _userManager.GenerateTwoFactorTokenAsync(user, "Email"); + + var emailMessage = new EmailMessage( + user.Email, + "MyMeetings Token", + "Here is your token which you need for the registration.\n\nToken: {token}"); + + await _emailSender.SendEmail(emailMessage); + + response.StoreTwoFactorAuthentication(Generate2FA(user.Id, "Email")); + } + } + else + { + var userDto = new UserDto() + { + Id = user.Id, + Name = $"{user.Name}".Trim(), + UserName = user.UserName, + Email = user.Email, + Claims = _tokenClaimsService.GetUserClaims(user) + }; + + var tokens = _tokenClaimsService.GenerateTokens(user); + var principal = await _userClaimsPrincipalFactory.CreateAsync(user); + + response.SetAuthenticatedUser(userDto, tokens.AccessToken, tokens.RefreshToken, principal); + } + } + } + else + { + // Increase the failed account login count + await _userManager.AccessFailedAsync(user); + if (await _userManager.IsLockedOutAsync(user)) + { + await SendNotificationEmailAsync(user); + } + + response.AddError(Errors.UserAccess.InvalidUserNameOrPassword); + } + } + else + { + var emailMessage = new EmailMessage( + user.Email, + "MyMeetings user account not enabled!", + $"You cannot log in with this user account because no password has been defined for this account.\n\nUser name: {user.UserName}\nEmail: {user.Email}"); + + await _emailSender.SendEmail(emailMessage); + + response.AddError(Errors.UserAccess.LoginNotAllowed); + } + } + else + { + // Instead of returning an "User not found." error message return an more generic one. + response.AddError(Errors.UserAccess.InvalidUserNameOrPassword); + } + + return response; + } + + private static ClaimsPrincipal Generate2FA(Guid userId, string provider) + { + var identity = new ClaimsIdentity( + new List + { + new Claim("sub", userId.ToString()), + new Claim("amr", provider) // Authentication method reference + }, + IdentityConstants.TwoFactorUserIdScheme); + + return new ClaimsPrincipal(identity); + } + + private async Task SendNotificationEmailAsync(ApplicationUser user) + { + if (!string.IsNullOrEmpty(user.Email)) + { + var emailMessage = new EmailMessage( + user.Email, + "MyMeetings user account locked!", + $"Your user account has been blocked for security reasons.\n\nUser name: {user.UserName}\n\nThe account has been locked for {_identityOptions.Lockout.DefaultLockoutTimeSpan.Minutes}minutes because the password was entered incorrectly {_identityOptions.Lockout.MaxFailedAccessAttempts} times in a row. After this time, the lock is automatically removed.\n\nIf you are not responsible for locking the user account, please contact the administrator."); + + await _emailSender.SendEmail(emailMessage); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommandValidator.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommandValidator.cs new file mode 100644 index 000000000..048ea2653 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountLoginCommandValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login; + +public class AccountLoginCommandValidator : AbstractValidator +{ + public AccountLoginCommandValidator() + { + RuleFor(x => x.Login).CustomNotEmpty(); + RuleFor(x => x.Password).CustomNotEmpty(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/AccountTwoFactorLoginCommand.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountTwoFactorLoginCommand.cs new file mode 100644 index 000000000..c09ec6142 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountTwoFactorLoginCommand.cs @@ -0,0 +1,19 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login; + +public class AccountTwoFactorLoginCommand : CommandBase +{ + public AccountTwoFactorLoginCommand(Guid userId, string provider, string token) + { + UserId = userId; + Provider = provider; + Token = token; + } + + public Guid UserId { get; } + + public string Provider { get; } + + public string Token { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/AccountTwoFactorLoginCommandHandler.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountTwoFactorLoginCommandHandler.cs new file mode 100644 index 000000000..b9fb761b9 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/AccountTwoFactorLoginCommandHandler.cs @@ -0,0 +1,57 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login; + +internal class AccountTwoFactorLoginCommandHandler : ICommandHandler +{ + private readonly ITokenClaimsService _tokenClaimsService; + private readonly UserManager _userManager; + private readonly IUserClaimsPrincipalFactory _userClaimsPrincipalFactory; + + public AccountTwoFactorLoginCommandHandler( + UserManager userManager, + ITokenClaimsService tokenClaimsService, + IUserClaimsPrincipalFactory userClaimsPrincipalFactory) + { + _userManager = userManager; + _tokenClaimsService = tokenClaimsService; + _userClaimsPrincipalFactory = userClaimsPrincipalFactory; + } + + public async Task Handle(AccountTwoFactorLoginCommand request, CancellationToken cancellationToken) + { + var response = new AuthenticationResult(); + + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + response.AddError(Errors.General.NotFound(request.UserId, "User")); + return response; + } + + var isValid = await _userManager.VerifyTwoFactorTokenAsync(user, request.Provider, request.Token); + + if (isValid) + { + var userDto = new UserDto() + { + Id = user.Id, + Name = $"{user.FirstName} {user.LastName}", + UserName = user.UserName, + Email = user.Email, + Claims = _tokenClaimsService.GetUserClaims(user) + }; + + var tokens = _tokenClaimsService.GenerateTokens(user); + var principal = await _userClaimsPrincipalFactory.CreateAsync(user); + + response.SetAuthenticatedUser(userDto, tokens.AccessToken, tokens.RefreshToken, principal); + } + + return response; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/AuthenticationResult.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/AuthenticationResult.cs new file mode 100644 index 000000000..0a677d93d --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/AuthenticationResult.cs @@ -0,0 +1,62 @@ +using System.Security.Claims; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login; + +public class AuthenticationResult : Result +{ + public AuthenticationResult() + { + RequiresTwoFactor = false; + } + + public bool IsAuthenticated => ClaimsPrincipal is not null; + + public string? AccessToken { get; private set; } + + public string? RefreshToken { get; private set; } + + public bool RequiresTwoFactor { get; private set; } + + public UserDto? User { get; private set; } + + public ClaimsPrincipal? ClaimsPrincipal { get; private set; } + + public void SetAuthenticatedUser(UserDto user, string accessToken, string refreshToken) + { + ArgumentNullException.ThrowIfNull(user, nameof(user)); + + User = user; + AccessToken = accessToken; + RefreshToken = refreshToken; + } + + public void SetAuthenticatedUser(UserDto user, ClaimsPrincipal claimsPrincipal) + { + ArgumentNullException.ThrowIfNull(user, nameof(user)); + ArgumentNullException.ThrowIfNull(claimsPrincipal, nameof(claimsPrincipal)); + + User = user; + ClaimsPrincipal = claimsPrincipal; + } + + public void SetAuthenticatedUser(UserDto user, string accessToken, string refreshToken, ClaimsPrincipal claimsPrincipal) + { + SetAuthenticatedUser(user, accessToken, refreshToken); + + ArgumentNullException.ThrowIfNull(claimsPrincipal, nameof(claimsPrincipal)); + + ClaimsPrincipal = claimsPrincipal; + } + + public void StoreTwoFactorAuthentication(ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal), "Missing the claims principal."); + } + + RequiresTwoFactor = true; + ClaimsPrincipal = claimsPrincipal; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/External/ExternalAccountLoginCommand.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/External/ExternalAccountLoginCommand.cs new file mode 100644 index 000000000..333929ace --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/External/ExternalAccountLoginCommand.cs @@ -0,0 +1,22 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login.External; + +public class ExternalAccountLoginCommand : CommandBase +{ + public ExternalAccountLoginCommand(string provider, string externalUserId, string emailAddress, bool autoCreateUser) + { + Provider = provider; + ExternalUserId = externalUserId; + EmailAddress = emailAddress; + AutoCreateUser = autoCreateUser; + } + + public string Provider { get; } + + public string ExternalUserId { get; } + + public string EmailAddress { get; } + + public bool AutoCreateUser { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/External/ExternalAccountLoginCommandHandler.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/External/ExternalAccountLoginCommandHandler.cs new file mode 100644 index 000000000..44385e935 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/External/ExternalAccountLoginCommandHandler.cs @@ -0,0 +1,76 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login.External; + +internal class ExternalAccountLoginCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + private readonly ITokenClaimsService _tokenClaimsService; + private readonly IUserClaimsPrincipalFactory _userClaimsPrincipalFactory; + + public ExternalAccountLoginCommandHandler( + UserManager userManager, + ITokenClaimsService tokenClaimsService, + IUserClaimsPrincipalFactory userClaimsPrincipalFactory) + { + _userManager = userManager; + _tokenClaimsService = tokenClaimsService; + _userClaimsPrincipalFactory = userClaimsPrincipalFactory; + } + + public async Task Handle(ExternalAccountLoginCommand request, CancellationToken cancellationToken) + { + var response = new AuthenticationResult(); + + // Try to find the user by the provider and external user unique identifier + // This will return the user we have that is link to this external account + var user = await _userManager.FindByLoginAsync(request.Provider, request.ExternalUserId); + + // If the user is null, so we have never seen this external user before + if (user is null) + { + // ... We have a few options, but in the case we have the user's email address + var email = request.EmailAddress; + if (!string.IsNullOrEmpty(email)) + { + // we check if we can find an user by that email address + user = await _userManager.FindByEmailAsync(email); + + // If we still have no user we are going to auto create the user if enabled + if (user == null) + { + if (!request.AutoCreateUser) + { + response.AddError(Errors.UserAccess.InvalidUserNameOrPassword); + return response; + } + + user = new ApplicationUser(email) { Email = email }; + + // Create the user without a password + await _userManager.CreateAsync(user); + } + + // Finally we have to link the user with the external account + await _userManager.AddLoginAsync(user, new UserLoginInfo(request.Provider, request.ExternalUserId, request.Provider)); + } + } + + var userDto = new UserDto() + { + Id = user!.Id, + Name = $"{user.Name}".Trim(), + UserName = user.UserName, + Email = user.Email, + Claims = _tokenClaimsService.GetUserClaims(user) + }; + + var principal = await _userClaimsPrincipalFactory.CreateAsync(user); + response.SetAuthenticatedUser(userDto, principal); + return response; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/Login/UserDto.cs b/src/Modules/UserAccessMI/Application/Authentication/Login/UserDto.cs new file mode 100644 index 000000000..60c5715a4 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/Login/UserDto.cs @@ -0,0 +1,16 @@ +using System.Security.Claims; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.Login; + +public class UserDto +{ + public Guid Id { get; init; } + + public string? UserName { get; init; } = null!; + + public string? Name { get; init; } + + public string? Email { get; init; } + + public List? Claims { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/RefreshTokenCommand.cs b/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/RefreshTokenCommand.cs new file mode 100644 index 000000000..93c1ba9e6 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/RefreshTokenCommand.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.RefreshToken; + +public class RefreshTokenCommand : CommandBase> +{ + public RefreshTokenCommand(string accessToken, string refreshToken) + { + AccessToken = accessToken; + RefreshToken = refreshToken; + } + + public string AccessToken { get; } + + public string RefreshToken { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/RefreshTokenCommandHandler.cs b/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/RefreshTokenCommandHandler.cs new file mode 100644 index 000000000..912677df5 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/RefreshTokenCommandHandler.cs @@ -0,0 +1,22 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CSharpFunctionalExtensions; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.RefreshToken; + +internal class RefreshTokenCommandHandler : ICommandHandler> +{ + private readonly ITokenClaimsService _tokenService; + + public RefreshTokenCommandHandler(ITokenClaimsService tokenService) + { + _tokenService = tokenService; + } + + public async Task> Handle(RefreshTokenCommand request, CancellationToken cancellationToken) + { + return await _tokenService.GenerateNewTokensAsync(request.AccessToken, request.RefreshToken, cancellationToken) + .Map(tokens => new TokenDto(tokens.AccessToken, tokens.RefreshToken)); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/TokenDto.cs b/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/TokenDto.cs new file mode 100644 index 000000000..7b3b10cd0 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/RefreshToken/TokenDto.cs @@ -0,0 +1,3 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.RefreshToken; + +public record TokenDto(string AccessToken, string RefreshToken); \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/ForgotPasswordLinkResult.cs b/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/ForgotPasswordLinkResult.cs new file mode 100644 index 000000000..9f210d27e --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/ForgotPasswordLinkResult.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.RequestForgotPasswordLink; + +public class ForgotPasswordLinkResult : Result +{ + public ForgotPasswordLinkResult() + { + } + + public ForgotPasswordLinkResult(string token) + { + Token = token; + } + + public string? Token { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/RequestForgotPasswordLinkCommand.cs b/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/RequestForgotPasswordLinkCommand.cs new file mode 100644 index 000000000..db9c8d97e --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/RequestForgotPasswordLinkCommand.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.RequestForgotPasswordLink; + +public class RequestForgotPasswordLinkCommand : CommandBase +{ + public RequestForgotPasswordLinkCommand(string emailAddress) + { + EmailAddress = emailAddress; + } + + public string EmailAddress { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/RequestForgotPasswordLinkCommandHandler.cs b/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/RequestForgotPasswordLinkCommandHandler.cs new file mode 100644 index 000000000..fe122a522 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/RequestForgotPasswordLink/RequestForgotPasswordLinkCommandHandler.cs @@ -0,0 +1,41 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.RequestForgotPasswordLink; + +internal class RequestForgotPasswordLinkCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public RequestForgotPasswordLinkCommandHandler(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + public async Task Handle(RequestForgotPasswordLinkCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByEmailAsync(request.EmailAddress); + if (user != null) + { + var token = await _userManager.GeneratePasswordResetTokenAsync(user); + return new ForgotPasswordLinkResult(token); + } + + // email user and inform them that they do not have an account with that email address + var message = new EmailMessage( + request.EmailAddress, + "MyMeetings - Forgot password", + $"We could not find your account with the given email addess '{request.EmailAddress}'.\n\nPlease check if you registered with an different email address."); + + await _emailSender.SendEmail(message); + + var response = new ForgotPasswordLinkResult(); + response.AddError(Errors.General.NotFound(request.EmailAddress, "User")); + return response; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/ResetPassword/ResetPasswordCommand.cs b/src/Modules/UserAccessMI/Application/Authentication/ResetPassword/ResetPasswordCommand.cs new file mode 100644 index 000000000..ff6839e9f --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/ResetPassword/ResetPasswordCommand.cs @@ -0,0 +1,20 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.ResetPassword; + +public class ResetPasswordCommand : CommandBase +{ + public ResetPasswordCommand(string token, string emailAddress, string password) + { + Token = token; + EmailAddress = emailAddress; + Password = password; + } + + public string Token { get; } + + public string EmailAddress { get; } + + public string Password { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authentication/ResetPassword/ResetPasswordCommandHandler.cs b/src/Modules/UserAccessMI/Application/Authentication/ResetPassword/ResetPasswordCommandHandler.cs new file mode 100644 index 000000000..12d5e5d4b --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authentication/ResetPassword/ResetPasswordCommandHandler.cs @@ -0,0 +1,40 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authentication.ResetPassword; + +internal class ResetPasswordCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + + public ResetPasswordCommandHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task Handle(ResetPasswordCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByEmailAsync(request.EmailAddress); + if (user is null) + { + return Errors.General.NotFound(request.EmailAddress, "User"); + } + + var result = await _userManager.ResetPasswordAsync(user, request.Token, request.Password); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + + // Check if we have to unlock the user account + if (await _userManager.IsLockedOutAsync(user)) + { + await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow); + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/ByUserId/GetPermissionsQuery.cs b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/ByUserId/GetPermissionsQuery.cs new file mode 100644 index 000000000..a3bf1553c --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/ByUserId/GetPermissionsQuery.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions.ByUserId; + +public class GetPermissionsQuery : QueryBase>> +{ + public GetPermissionsQuery(Guid userId) + { + UserId = userId; + } + + public Guid UserId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/ByUserId/GetPermissionsQueryHandler.cs b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/ByUserId/GetPermissionsQueryHandler.cs new file mode 100644 index 000000000..47335c530 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/ByUserId/GetPermissionsQueryHandler.cs @@ -0,0 +1,84 @@ +using System.Security.Claims; +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Dapper; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions.ByUserId; + +internal class GetPermissionsQueryHandler : IQueryHandler>> +{ + private readonly UserManager _userManager; + private readonly ISqlConnectionFactory _connectionFactory; + private readonly RoleManager _roleManager; + + public GetPermissionsQueryHandler( + UserManager userManager, + RoleManager roleManager, + ISqlConnectionFactory connectionFactory) + { + _userManager = userManager; + _roleManager = roleManager; + _connectionFactory = connectionFactory; + } + + public async Task>> Handle(GetPermissionsQuery request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + var roleNames = await _userManager.GetRolesAsync(user); + var roleClaims = await GetClaimsAsync(roleNames); + var userClaims = await _userManager.GetClaimsAsync(user); + var claims = roleClaims.Union(userClaims) + .Where(x => x.Type == CustomClaimTypes.Permission) + .Select(x => x.Value) + .ToList(); + + // Short circuit + if (!claims.Any()) + { + return Result.Ok(Enumerable.Empty()); + } + + using (var connection = _connectionFactory.CreateNewConnection()) + { + string query = $@" + SELECT [P].[Code] AS {nameof(PermissionDto.Code)}, + [P].[Name] AS {nameof(PermissionDto.Name)}, + [P].[Description] AS {nameof(PermissionDto.Description)} + FROM [usersmi].[Permissions] AS [P] + WHERE [P].[Code] IN @Claims"; + + var permissions = await connection.QueryAsync( + new CommandDefinition(query, new { Claims = claims }, cancellationToken: cancellationToken)); + + return Result.Ok(permissions); + } + } + + private async Task> GetClaimsAsync(IEnumerable roleNames) + { + var claims = new List(); + foreach (var roleName in roleNames) + { + var role = await _roleManager.FindByNameAsync(roleName); + if (role is null) + { + continue; + } + + var roleClaims = await _roleManager.GetClaimsAsync(role); + claims.AddRange(roleClaims ?? Enumerable.Empty()); + } + + return claims; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/Directory/GetPermissionsQuery.cs b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/Directory/GetPermissionsQuery.cs new file mode 100644 index 000000000..7bcaca018 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/Directory/GetPermissionsQuery.cs @@ -0,0 +1,8 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions.Directory; + +public class GetPermissionsQuery : QueryBase>> +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/Directory/GetPermissionsQueryHandler.cs b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/Directory/GetPermissionsQueryHandler.cs new file mode 100644 index 000000000..e7a245010 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/Directory/GetPermissionsQueryHandler.cs @@ -0,0 +1,31 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using Dapper; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions.Directory; + +internal class GetPermissionsQueryHandler : IQueryHandler>> +{ + private readonly ISqlConnectionFactory _connectionFactory; + + public GetPermissionsQueryHandler(ISqlConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory; + } + + public async Task>> Handle(GetPermissionsQuery request, CancellationToken cancellationToken) + { + using (var connection = _connectionFactory.CreateNewConnection()) + { + string query = $@" + SELECT [P].[Code] AS {nameof(PermissionDto.Code)}, + [P].[Name] AS {nameof(PermissionDto.Name)}, + [P].[Description] AS {nameof(PermissionDto.Description)} + FROM [usersmi].[Permissions] AS [P]"; + + var permissions = await connection.QueryAsync(query, cancellationToken); + return Result.Ok(permissions); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/PermissionDto.cs b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/PermissionDto.cs new file mode 100644 index 000000000..6b08782f2 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authorization/GetPermissions/PermissionDto.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetPermissions; + +public class PermissionDto +{ + public string Code { get; set; } = null!; + + public string Name { get; set; } = null!; + + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/GetUserRolesQuery.cs b/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/GetUserRolesQuery.cs new file mode 100644 index 000000000..c1de798c6 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/GetUserRolesQuery.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetUserRoles; + +public class GetUserRolesQuery : QueryBase>> +{ + public GetUserRolesQuery(Guid userId) + { + UserId = userId; + } + + public Guid UserId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/GetUserRolesQueryHandler.cs b/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/GetUserRolesQueryHandler.cs new file mode 100644 index 000000000..d6b1e7106 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/GetUserRolesQueryHandler.cs @@ -0,0 +1,44 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetUserRoles; + +internal class GetUserRolesQueryHandler : IQueryHandler>> +{ + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + + public GetUserRolesQueryHandler(UserManager userManager, RoleManager roleManager) + { + _userManager = userManager; + _roleManager = roleManager; + } + + public async Task>> Handle(GetUserRolesQuery request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + var roleNames = await _userManager.GetRolesAsync(user); + if (!roleNames.Any()) + { + return Result.Ok(Enumerable.Empty()); + } + + var roles = (from role in _roleManager.Roles + where roleNames.Contains(role.Name ?? string.Empty) + select new RoleDto() + { + Id = role.Id, + Name = role.Name + }).ToList(); + + return Result.Ok(roles.AsEnumerable()); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/RoleDto.cs b/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/RoleDto.cs new file mode 100644 index 000000000..8d38d3fb0 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Authorization/GetUserRoles/RoleDto.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Authorization.GetUserRoles; + +public class RoleDto +{ + public string Id { get; set; } = null!; + + public string? Name { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/CompanyName.MyMeetings.Modules.UserAccessMI.Application.csproj b/src/Modules/UserAccessMI/Application/CompanyName.MyMeetings.Modules.UserAccessMI.Application.csproj new file mode 100644 index 000000000..4bb97b9ec --- /dev/null +++ b/src/Modules/UserAccessMI/Application/CompanyName.MyMeetings.Modules.UserAccessMI.Application.csproj @@ -0,0 +1,11 @@ + + + enable + + + + + + + + \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Configuration/Commands/ICommandHandler.cs b/src/Modules/UserAccessMI/Application/Configuration/Commands/ICommandHandler.cs new file mode 100644 index 000000000..ca412fec0 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Commands/ICommandHandler.cs @@ -0,0 +1,15 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; + +public interface ICommandHandler : IRequestHandler + where TCommand : ICommand +{ +} + +public interface ICommandHandler : + IRequestHandler + where TCommand : ICommand +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Configuration/Commands/ICommandsScheduler.cs b/src/Modules/UserAccessMI/Application/Configuration/Commands/ICommandsScheduler.cs new file mode 100644 index 000000000..21b2aed75 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Commands/ICommandsScheduler.cs @@ -0,0 +1,10 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; + +public interface ICommandsScheduler +{ + Task EnqueueAsync(ICommand command); + + Task EnqueueAsync(ICommand command); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Configuration/Commands/InternalCommandBase.cs b/src/Modules/UserAccessMI/Application/Configuration/Commands/InternalCommandBase.cs new file mode 100644 index 000000000..7680c5da8 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Commands/InternalCommandBase.cs @@ -0,0 +1,28 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; + +public abstract class InternalCommandBase : ICommand +{ + protected InternalCommandBase(Guid id) + { + Id = id; + } + + public Guid Id { get; } +} + +public abstract class InternalCommandBase : ICommand +{ + protected InternalCommandBase() + { + Id = Guid.NewGuid(); + } + + protected InternalCommandBase(Guid id) + { + Id = id; + } + + public Guid Id { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Configuration/Queries/IQueryHandler.cs b/src/Modules/UserAccessMI/Application/Configuration/Queries/IQueryHandler.cs new file mode 100644 index 000000000..7bc5cea98 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Queries/IQueryHandler.cs @@ -0,0 +1,10 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; + +public interface IQueryHandler : + IRequestHandler + where TQuery : IQuery +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Configuration/Results/IResult.cs b/src/Modules/UserAccessMI/Application/Configuration/Results/IResult.cs new file mode 100644 index 000000000..bf34baa8d --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Results/IResult.cs @@ -0,0 +1,26 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +public interface IResult +{ + ResultStatus Status { get; } + + IReadOnlyList Errors { get; } + + bool HasError { get; } + + IResult AddError(Error error); + + IResult Forbidden(); + + IResult Forbidden(string? message); + + IResult Forbidden(Error error); + + IResult NotFound(); + + IResult NotFound(string message); + + IResult NotFound(Error error); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Configuration/Results/IResultOfT.cs b/src/Modules/UserAccessMI/Application/Configuration/Results/IResultOfT.cs new file mode 100644 index 000000000..1776d2a46 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Results/IResultOfT.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +#pragma warning disable SA1649 // File name should match first type name +public interface IResult : IResult +#pragma warning restore SA1649 // File name should match first type name +{ + T? Value { get; } +} diff --git a/src/Modules/UserAccessMI/Application/Configuration/Results/RespultOfT.cs b/src/Modules/UserAccessMI/Application/Configuration/Results/RespultOfT.cs new file mode 100644 index 000000000..93224c502 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Results/RespultOfT.cs @@ -0,0 +1,42 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CSharpFunctionalExtensions; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +#pragma warning disable SA1649 // File name should match first type name +public class Result : Result, IResult +#pragma warning restore SA1649 // File name should match first type name +{ + public Result() + : base() + { + } + + public Result(T value) + : this() + { + Value = value; + } + + public T? Value { get; set; } + + public static implicit operator Result(T result) + => Result.Ok(result); + + public static implicit operator Result(Error error) + => Result.Error(error); + + public static implicit operator Result(Result result) + => result.IsSuccess ? Result.Ok(result.Value) : Result.Error(result.Error); + + public static Result Ok(T value) + => new Result(value); + + public static new Result Error(Error error) + { + var response = new Result(); + response.AddError(error); + + return response; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Configuration/Results/Result.cs b/src/Modules/UserAccessMI/Application/Configuration/Results/Result.cs new file mode 100644 index 000000000..8b4e66ae0 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Results/Result.cs @@ -0,0 +1,117 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CSharpFunctionalExtensions; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +public class Result : IResult +{ + private readonly IList _errors = new List(); + + public Result() + { + Status = ResultStatus.Ok; + } + + public ResultStatus Status { get; protected set; } + + public IReadOnlyList Errors => _errors.AsReadOnly(); + + public bool HasError => _errors.Count > 0; + + public IResult AddError(Error error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error), "Error should not be empty."); + } + + if (error.Errors != null) + { + foreach (var errorObj in error.Errors) + { + _errors.Add(errorObj); + } + } + + if (error.Code != null) + { + _errors.Add(error); + } + + Status = ResultStatus.Error; + + return this; + } + + public IResult Forbidden() => + Forbidden(string.Empty); + + public IResult Forbidden(string? message) => + Forbidden(Domain.ErrorHandling.Errors.Authentication.NotAuthorized(message?.Trim() ?? string.Empty)); + + public IResult Forbidden(Error error) + { + AddError(error); + + Status = ResultStatus.Forbidden; + return this; + } + + public IResult NotFound() => + NotFound(Domain.ErrorHandling.Errors.General.NotFound()); + + public IResult NotFound(string message) => + NotFound(Domain.ErrorHandling.Errors.General.NotFound(message)); + + public IResult NotFound(long id) => + NotFound(Domain.ErrorHandling.Errors.General.NotFound(id)); + + public static IResult NotFound(long id, string item) + { + var response = new Result(); + response.NotFound(Domain.ErrorHandling.Errors.General.NotFound(id, item)); + return response; + } + + public IResult NotFound(Error error) + { + AddError(error); + + Status = ResultStatus.NotFound; + return this; + } + + public static Result Error(Error error) + { + var response = new Result(); + response.AddError(error); + + return response; + } + + public static Result Error(Error error) + { + var response = new Result(); + response.AddError(error); + + return response; + } + + public static Result Ok() => + new Result(); + + public static Result Ok() => + new Result(); + + public static Result Ok(T value) => + new Result(value); + + public static implicit operator Result(Error error) => + Result.Error(error); + + public static implicit operator Result(UnitResult result) => + result.IsSuccess ? Result.Ok() : Result.Error(result.Error); + + public static implicit operator Result(Result result) => + result.IsSuccess ? Result.Ok(result.Value) : Result.Error(result.Error); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Configuration/Results/ResultStatus.cs b/src/Modules/UserAccessMI/Application/Configuration/Results/ResultStatus.cs new file mode 100644 index 000000000..c5bc5178e --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Configuration/Results/ResultStatus.cs @@ -0,0 +1,24 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +public enum ResultStatus +{ + /// + /// Successful result. + /// + Ok, + + /// + /// General error result. + /// + Error, + + /// + /// Not found error result. + /// + NotFound, + + /// + /// Forbidden error result. + /// + Forbidden +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/ApplicationPermissions.cs b/src/Modules/UserAccessMI/Application/Contracts/ApplicationPermissions.cs new file mode 100644 index 000000000..c7467a91c --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/ApplicationPermissions.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +public class ApplicationPermissions +{ + public const string Administrator = "Application.Administrator"; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/CommandBase.cs b/src/Modules/UserAccessMI/Application/Contracts/CommandBase.cs new file mode 100644 index 000000000..430310ff4 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/CommandBase.cs @@ -0,0 +1,31 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +public abstract class CommandBase : ICommand +{ + public Guid Id { get; } + + protected CommandBase() + { + Id = Guid.NewGuid(); + } + + protected CommandBase(Guid id) + { + Id = id; + } +} + +public abstract class CommandBase : ICommand +{ + protected CommandBase() + { + Id = Guid.NewGuid(); + } + + protected CommandBase(Guid id) + { + Id = id; + } + + public Guid Id { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/CustomClaimTypes.cs b/src/Modules/UserAccessMI/Application/Contracts/CustomClaimTypes.cs new file mode 100644 index 000000000..dc8364cc1 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/CustomClaimTypes.cs @@ -0,0 +1,15 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +internal class CustomClaimTypes +{ + internal const string Roles = "roles"; + internal const string Sub = "sub"; + internal const string Email = "email"; + internal const string UserName = "userName"; + + internal const string Name = "name"; + internal const string FirstName = "firstName"; + internal const string LastName = "lastName"; + + internal const string Permission = "Application.Permission"; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/ICommand.cs b/src/Modules/UserAccessMI/Application/Contracts/ICommand.cs new file mode 100644 index 000000000..6d7b13f04 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/ICommand.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +public interface ICommand : IRequest +{ + Guid Id { get; } +} + +public interface ICommand : IRequest +{ + Guid Id { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/IQuery.cs b/src/Modules/UserAccessMI/Application/Contracts/IQuery.cs new file mode 100644 index 000000000..3b7295e2c --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/IQuery.cs @@ -0,0 +1,7 @@ +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +public interface IQuery : IRequest +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/IRecurringCommand.cs b/src/Modules/UserAccessMI/Application/Contracts/IRecurringCommand.cs new file mode 100644 index 000000000..96b8c44e8 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/IRecurringCommand.cs @@ -0,0 +1,5 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +public interface IRecurringCommand +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/ITokenClaimsService.cs b/src/Modules/UserAccessMI/Application/Contracts/ITokenClaimsService.cs new file mode 100644 index 000000000..2f33cb755 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/ITokenClaimsService.cs @@ -0,0 +1,20 @@ +using System.Security.Claims; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using CSharpFunctionalExtensions; +using Microsoft.IdentityModel.Tokens; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +public interface ITokenClaimsService +{ + TokenValidationParameters GetTokenValidationParameters(); + + Tokens GenerateTokens(ApplicationUser user); + + Task> GenerateNewTokensAsync(string accessToken, string refreshToken, CancellationToken cancellationToken); + + List GetUserClaims(ApplicationUser user); +} + +public record Tokens(string AccessToken, string RefreshToken); \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/IUserAccessModule.cs b/src/Modules/UserAccessMI/Application/Contracts/IUserAccessModule.cs new file mode 100644 index 000000000..aaf59ab3f --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/IUserAccessModule.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +public interface IUserAccessModule +{ + Task ExecuteCommandAsync(ICommand command); + + Task ExecuteCommandAsync(ICommand command); + + Task ExecuteQueryAsync(IQuery query); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/QueryBase.cs b/src/Modules/UserAccessMI/Application/Contracts/QueryBase.cs new file mode 100644 index 000000000..df61f33e9 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/QueryBase.cs @@ -0,0 +1,16 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +public abstract class QueryBase : IQuery +{ + public Guid Id { get; } + + protected QueryBase() + { + Id = Guid.NewGuid(); + } + + protected QueryBase(Guid id) + { + Id = id; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Contracts/Roles.cs b/src/Modules/UserAccessMI/Application/Contracts/Roles.cs new file mode 100644 index 000000000..b790e7bbd --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Contracts/Roles.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts +{ + public class Roles + { + public const string Admin = "Admin"; + public const string User = "User"; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/CustomValidators.cs b/src/Modules/UserAccessMI/Application/CustomValidators.cs new file mode 100644 index 000000000..d5153417b --- /dev/null +++ b/src/Modules/UserAccessMI/Application/CustomValidators.cs @@ -0,0 +1,110 @@ +using System.Linq.Expressions; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CSharpFunctionalExtensions; +using FluentValidation; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application +{ + public static class CustomValidators + { + public static IRuleBuilderOptions CustomNotNull(this IRuleBuilder ruleBuilder) + { + return DefaultValidatorExtensions.NotNull(ruleBuilder) + .WithMessage(Errors.General.ValueIsRequired().Serialize()); + } + + public static IRuleBuilderOptions CustomNotEmpty(this IRuleBuilder ruleBuilder) + { + return DefaultValidatorExtensions.NotEmpty(ruleBuilder) + .WithMessage(Errors.General.ValueIsRequired().Serialize()); + } + + public static IRuleBuilderOptions CustomLength(this IRuleBuilder ruleBuilder, int min, int max) + { + return DefaultValidatorExtensions.Length(ruleBuilder, min, max) + .WithMessage(Errors.General.InvalidLength().Serialize()); + } + + public static IRuleBuilderOptions CustomMaximumLength(this IRuleBuilder ruleBuilder, int maximumLength) + { + return DefaultValidatorExtensions.MaximumLength(ruleBuilder, maximumLength) + .WithMessage(Errors.General.InvalidLength(maxLength: maximumLength).Serialize()); + } + + public static IRuleBuilderOptions CustomGreaterThanOrEqualTo(this IRuleBuilder ruleBuilder, TProperty valueToCompare) + where TProperty : IComparable, IComparable + { + return DefaultValidatorExtensions.GreaterThanOrEqualTo(ruleBuilder, valueToCompare) + .WithMessage(Errors.General.ValueIsInvalid().Serialize()); + } + + public static IRuleBuilderOptions CustomGreaterThanOrEqualTo(this IRuleBuilder ruleBuilder, int valueToCompare) + { + return DefaultValidatorExtensions.GreaterThanOrEqualTo(ruleBuilder, valueToCompare) + .WithMessage(Errors.General.ValueIsInvalid().Serialize()); + } + + public static IRuleBuilderOptions CustomGreaterThan(this IRuleBuilder ruleBuilder, TProperty valueToCompare) + where TProperty : IComparable, IComparable + { + return DefaultValidatorExtensions.GreaterThan(ruleBuilder, valueToCompare) + .WithMessage(Errors.General.ValueIsInvalid().Serialize()); + } + + public static IRuleBuilderOptions CustomEmailAddress(this IRuleBuilder ruleBuilder) + { + return DefaultValidatorExtensions.EmailAddress(ruleBuilder) + .WithMessage(Errors.General.ValueIsInvalid().Serialize()); + } + + public static IRuleBuilderOptions CustomEqual(this IRuleBuilder ruleBuilder, Expression> expression, IEqualityComparer? comparer = null) + { + return DefaultValidatorExtensions.Equal(ruleBuilder, expression, comparer) + .WithMessage(Errors.General.ValueIsInvalid().Serialize()); + } + + public static IRuleBuilderOptions MustBeValueObject( + this IRuleBuilder ruleBuilder, + Func> factoryMethod) + where TValueObject : ValueObject + { + return (IRuleBuilderOptions)ruleBuilder.Custom((value, context) => + { + Result result = factoryMethod(value); + + if (result.IsFailure) + { + if (result.Error.Errors != null) + { + foreach (var error in result.Error.Errors) + { + context.AddFailure(error.Serialize()); + } + } + + if (result.Error.Code != null) + { + context.AddFailure(result.Error.Serialize()); + } + } + }); + } + + public static IRuleBuilderOptionsConditions> ListMustContainNumberOfItems( + this IRuleBuilder> ruleBuilder, int? min = null, int? max = null) + { + return ruleBuilder.Custom((list, context) => + { + if (min.HasValue && list.Count < min.Value) + { + context.AddFailure(Errors.General.CollectionIsTooSmall(min.Value, list.Count).Serialize()); + } + + if (max.HasValue && list.Count > max.Value) + { + context.AddFailure(Errors.General.CollectionIsTooLarge(max.Value, list.Count).Serialize()); + } + }); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/GetUserAccountQuery.cs b/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/GetUserAccountQuery.cs new file mode 100644 index 000000000..f088df1be --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/GetUserAccountQuery.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Identity.GetUserAccount; + +public class GetUserAccountQuery : QueryBase> +{ + public GetUserAccountQuery(Guid userId) + { + UserId = userId; + } + + public Guid UserId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/GetUserAccountQueryHandler.cs b/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/GetUserAccountQueryHandler.cs new file mode 100644 index 000000000..e0d58b1af --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/GetUserAccountQueryHandler.cs @@ -0,0 +1,37 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Identity.GetUserAccount; + +internal class GetUserAccountQueryHandler : IQueryHandler> +{ + private readonly UserManager _userManager; + + public GetUserAccountQueryHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task> Handle(GetUserAccountQuery request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "user"); + } + + return new UserAccountDto + { + Id = user.Id, + IsActive = user.LockoutEnd is null, + EmailAddress = user.Email, + UserName = user.UserName, + Name = user.Name, + FirstName = user.FirstName, + LastName = user.LastName + }; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/UserAccountDto.cs b/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/UserAccountDto.cs new file mode 100644 index 000000000..ed46efe86 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Identity/GetUserAccount/UserAccountDto.cs @@ -0,0 +1,18 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Identity.GetUserAccount; + +public class UserAccountDto +{ + public Guid Id { get; set; } + + public bool IsActive { get; set; } + + public string? UserName { get; set; } + + public string? Name { get; set; } + + public string? FirstName { get; set; } + + public string? LastName { get; set; } + + public string? EmailAddress { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/GetUserPermissionsQuery.cs b/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/GetUserPermissionsQuery.cs new file mode 100644 index 000000000..fc3955c83 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/GetUserPermissionsQuery.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Identity.GetUserPermissions; + +public class GetUserPermissionsQuery : QueryBase>> +{ + public GetUserPermissionsQuery(Guid userId) + { + UserId = userId; + } + + public Guid UserId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/GetUserPermissionsQueryHandler.cs b/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/GetUserPermissionsQueryHandler.cs new file mode 100644 index 000000000..3d21ada55 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/GetUserPermissionsQueryHandler.cs @@ -0,0 +1,29 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using Dapper; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Identity.GetUserPermissions; + +internal class GetUserPermissionsQueryHandler : IQueryHandler>> +{ + private readonly ISqlConnectionFactory _sqlConnectionFactory; + + public GetUserPermissionsQueryHandler(ISqlConnectionFactory sqlConnectionFactory) + { + _sqlConnectionFactory = sqlConnectionFactory; + } + + public async Task>> Handle(GetUserPermissionsQuery request, CancellationToken cancellationToken) + { + var connection = _sqlConnectionFactory.GetOpenConnection(); + + const string sql = "SELECT " + + "[UserPermission].[PermissionCode] AS [Code] " + + "FROM [usersmi].[v_UserPermissions] AS [UserPermission] " + + "WHERE [UserPermission].UserId = @UserId"; + + var permissions = await connection.QueryAsync(sql, new { request.UserId }); + return Result.Ok(permissions); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/UserPermissionDto.cs b/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/UserPermissionDto.cs new file mode 100644 index 000000000..bba6ad5c7 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Identity/GetUserPermissions/UserPermissionDto.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Identity.GetUserPermissions; + +public class UserPermissionDto +{ + public string Code { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/IdentityHelpers.cs b/src/Modules/UserAccessMI/Application/IdentityHelpers.cs new file mode 100644 index 000000000..41d3e54e9 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/IdentityHelpers.cs @@ -0,0 +1,12 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application; + +internal static class IdentityHelpers +{ + public static List Map(this IEnumerable errors) + { + return errors.Select(x => new Error(x.Code, x.Description)).ToList(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/CreateRole/CreateRoleCommand.cs b/src/Modules/UserAccessMI/Application/Roles/CreateRole/CreateRoleCommand.cs new file mode 100644 index 000000000..05e9e6b1b --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/CreateRole/CreateRoleCommand.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.CreateRole; + +public class CreateRoleCommand : CommandBase> +{ + public CreateRoleCommand(string name, IEnumerable? permissions) + { + Name = name; + Permissions = permissions; + } + + public string Name { get; } + + public IEnumerable? Permissions { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/CreateRole/CreateRoleCommandHandler.cs b/src/Modules/UserAccessMI/Application/Roles/CreateRole/CreateRoleCommandHandler.cs new file mode 100644 index 000000000..f9d5058b1 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/CreateRole/CreateRoleCommandHandler.cs @@ -0,0 +1,39 @@ +using System.Security.Claims; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.CreateRole; + +internal class CreateRoleCommandHandler : ICommandHandler> +{ + private readonly RoleManager _roleManager; + + public CreateRoleCommandHandler(RoleManager roleManager) + { + _roleManager = roleManager; + } + + public async Task> Handle(CreateRoleCommand request, CancellationToken cancellationToken) + { + var result = await _roleManager.CreateAsync(new Role(request.Name)); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + + var role = await _roleManager.FindByNameAsync(request.Name); + + var permissions = request.Permissions ?? Enumerable.Empty(); + foreach (var permission in permissions) + { + await _roleManager.AddClaimAsync(role!, new Claim(CustomClaimTypes.Permission, permission)); + role = await _roleManager.FindByNameAsync(role!.Name!); + } + + return Result.Ok(role!.Id); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/DeleteRole/DeleteRoleCommand.cs b/src/Modules/UserAccessMI/Application/Roles/DeleteRole/DeleteRoleCommand.cs new file mode 100644 index 000000000..acc068fc6 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/DeleteRole/DeleteRoleCommand.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.DeleteRole; + +public class DeleteRoleCommand : CommandBase +{ + public DeleteRoleCommand(Guid roleId) + { + RoleId = roleId; + } + + public Guid RoleId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/DeleteRole/DeleteRoleCommandHandler.cs b/src/Modules/UserAccessMI/Application/Roles/DeleteRole/DeleteRoleCommandHandler.cs new file mode 100644 index 000000000..02d060235 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/DeleteRole/DeleteRoleCommandHandler.cs @@ -0,0 +1,29 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.DeleteRole; + +internal class DeleteRoleCommandHandler : ICommandHandler +{ + private readonly RoleManager _roleManager; + + public DeleteRoleCommandHandler(RoleManager roleManager) + { + _roleManager = roleManager; + } + + public async Task Handle(DeleteRoleCommand request, CancellationToken cancellationToken) + { + var role = await _roleManager.FindByIdAsync(request.RoleId.ToString()); + if (role == null) + { + return Errors.General.NotFound(request.RoleId, "User role"); + } + + await _roleManager.DeleteAsync(role); + return Result.Ok(role); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/GetRolePermissionsQuery.cs b/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/GetRolePermissionsQuery.cs new file mode 100644 index 000000000..ac37728ac --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/GetRolePermissionsQuery.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRolePermissions; + +public class GetRolePermissionsQuery : QueryBase>> +{ + public GetRolePermissionsQuery(Guid roleId) + { + RoleId = roleId; + } + + public Guid RoleId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/GetRolePermissionsQueryHandler.cs b/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/GetRolePermissionsQueryHandler.cs new file mode 100644 index 000000000..4aa91f69a --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/GetRolePermissionsQueryHandler.cs @@ -0,0 +1,64 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Dapper; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRolePermissions; + +internal class GetRolePermissionsQueryHandler : IQueryHandler>> +{ + private readonly ISqlConnectionFactory _connectionFactory; + private readonly RoleManager _roleManager; + + public GetRolePermissionsQueryHandler( + RoleManager roleManager, + ISqlConnectionFactory connectionFactory) + { + _roleManager = roleManager; + _connectionFactory = connectionFactory; + } + + public async Task>> Handle(GetRolePermissionsQuery request, CancellationToken cancellationToken) + { + var role = _roleManager.Roles + .Where(x => x.Id == request.RoleId) + .Select(x => x) + .FirstOrDefault(); + + if (role is null) + { + return Errors.General.NotFound(request.RoleId, "Role"); + } + + var roleClaims = await _roleManager.GetClaimsAsync(role); + var claims = roleClaims + .Where(x => x.Type == CustomClaimTypes.Permission) + .Select(x => x.Value) + .ToList(); + + // Short circuit + if (!claims.Any()) + { + return Result.Ok(Enumerable.Empty()); + } + + using (var connection = _connectionFactory.CreateNewConnection()) + { + string query = $@" + SELECT [P].[Code] AS {nameof(PermissionDto.Code)}, + [P].[Name] AS {nameof(PermissionDto.Name)}, + [P].[Description] AS {nameof(PermissionDto.Description)} + FROM [usersmi].[Permissions] AS [P] + WHERE [P].[Code] IN @Claims"; + + var permissions = await connection.QueryAsync( + new CommandDefinition(query, new { Claims = claims }, cancellationToken: cancellationToken)); + + return Result.Ok(permissions); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/PermissionDto.cs b/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/PermissionDto.cs new file mode 100644 index 000000000..813031c3f --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/GetRolePermissions/PermissionDto.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRolePermissions; + +public class PermissionDto +{ + public string Code { get; set; } = null!; + + public string Name { get; set; } = null!; + + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/GetRoles/ById/GetRolesQuery.cs b/src/Modules/UserAccessMI/Application/Roles/GetRoles/ById/GetRolesQuery.cs new file mode 100644 index 000000000..95d31143c --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/GetRoles/ById/GetRolesQuery.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRoles.ById; + +public class GetRolesQuery : QueryBase> +{ + public GetRolesQuery(Guid roleId) + { + RoleId = roleId; + } + + public Guid RoleId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/GetRoles/ById/GetRolesQueryHandler.cs b/src/Modules/UserAccessMI/Application/Roles/GetRoles/ById/GetRolesQueryHandler.cs new file mode 100644 index 000000000..13eebeaaa --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/GetRoles/ById/GetRolesQueryHandler.cs @@ -0,0 +1,35 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRoles.ById; + +internal class GetRolesQueryHandler : IQueryHandler> +{ + private readonly RoleManager _roleManager; + + public GetRolesQueryHandler(RoleManager roleManager) + { + _roleManager = roleManager; + } + + public Task> Handle(GetRolesQuery request, CancellationToken cancellationToken) + { + var userRole = (from role in _roleManager.Roles + where role.Id == request.RoleId + select new RoleDto() + { + Id = role.Id, + Name = role.Name + }).SingleOrDefault(); + + if (userRole is null) + { + return Task.FromResult(Result.Error(Errors.General.NotFound(request.RoleId, "User role"))); + } + + return Task.FromResult(Result.Ok(userRole)); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/GetRoles/Directory/GetRolesQuery.cs b/src/Modules/UserAccessMI/Application/Roles/GetRoles/Directory/GetRolesQuery.cs new file mode 100644 index 000000000..58ff14f55 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/GetRoles/Directory/GetRolesQuery.cs @@ -0,0 +1,8 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRoles.Directory; + +public class GetRolesQuery : QueryBase>> +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/GetRoles/Directory/GetRolesQueryHandler.cs b/src/Modules/UserAccessMI/Application/Roles/GetRoles/Directory/GetRolesQueryHandler.cs new file mode 100644 index 000000000..e71a8c485 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/GetRoles/Directory/GetRolesQueryHandler.cs @@ -0,0 +1,28 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRoles.Directory; + +internal class GetRolesQueryHandler : IQueryHandler>> +{ + private readonly RoleManager _roleManager; + + public GetRolesQueryHandler(RoleManager roleManager) + { + _roleManager = roleManager; + } + + public Task>> Handle(GetRolesQuery request, CancellationToken cancellationToken) + { + var roles = (from role in _roleManager.Roles + select new RoleDto() + { + Id = role.Id, + Name = role.Name + }).ToList(); + + return Task.FromResult(Result.Ok(roles.AsEnumerable())); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/GetRoles/RoleDto.cs b/src/Modules/UserAccessMI/Application/Roles/GetRoles/RoleDto.cs new file mode 100644 index 000000000..833f8fe67 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/GetRoles/RoleDto.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.GetRoles; + +public class RoleDto +{ + public Guid Id { get; set; } + + public string? Name { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/RenameRole/RenameRoleCommand.cs b/src/Modules/UserAccessMI/Application/Roles/RenameRole/RenameRoleCommand.cs new file mode 100644 index 000000000..97f12ffd1 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/RenameRole/RenameRoleCommand.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.RenameRole; + +public class RenameRoleCommand : CommandBase +{ + public RenameRoleCommand(Guid roleId, string name) + { + RoleId = roleId; + Name = name; + } + + public Guid RoleId { get; } + + public string Name { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/RenameRole/RenameRoleCommandHander.cs b/src/Modules/UserAccessMI/Application/Roles/RenameRole/RenameRoleCommandHander.cs new file mode 100644 index 000000000..cb2c2837e --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/RenameRole/RenameRoleCommandHander.cs @@ -0,0 +1,36 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.RenameRole; + +internal class RenameRoleCommandHander : ICommandHandler +{ + private readonly RoleManager _roleManager; + + public RenameRoleCommandHander(RoleManager roleManager) + { + _roleManager = roleManager; + } + + public async Task Handle(RenameRoleCommand request, CancellationToken cancellationToken) + { + var role = (from r in _roleManager.Roles + where r.Id == request.RoleId + select r).SingleOrDefault(); + if (role == null) + { + return Errors.General.NotFound(request.RoleId, "User role"); + } + + var result = await _roleManager.SetRoleNameAsync(role, request.Name); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/SetRolePermissions/SetRolePermissionsCommand.cs b/src/Modules/UserAccessMI/Application/Roles/SetRolePermissions/SetRolePermissionsCommand.cs new file mode 100644 index 000000000..5d14d1de4 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/SetRolePermissions/SetRolePermissionsCommand.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.SetRolePermissions; + +public class SetRolePermissionsCommand : CommandBase +{ + public SetRolePermissionsCommand(Guid roleId, IEnumerable permissions) + { + RoleId = roleId; + Permissions = permissions; + } + + public Guid RoleId { get; } + + public IEnumerable Permissions { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/Roles/SetRolePermissions/SetRolePermissionsCommandHandler.cs b/src/Modules/UserAccessMI/Application/Roles/SetRolePermissions/SetRolePermissionsCommandHandler.cs new file mode 100644 index 000000000..d0fd46cc1 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/Roles/SetRolePermissions/SetRolePermissionsCommandHandler.cs @@ -0,0 +1,61 @@ +using System.Security.Claims; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.Roles.SetRolePermissions; + +internal class SetRolePermissionsCommandHandler : ICommandHandler +{ + private readonly RoleManager _roleManager; + + public SetRolePermissionsCommandHandler(RoleManager roleManager) + { + _roleManager = roleManager; + } + + public async Task Handle(SetRolePermissionsCommand request, CancellationToken cancellationToken) + { + Role? role = GetRoleById(request.RoleId); + if (role is null) + { + return Errors.General.NotFound(request.RoleId, "User role"); + } + + var permissions = request.Permissions ?? Enumerable.Empty(); + + var roleClaims = (await _roleManager.GetClaimsAsync(role)) ?? Enumerable.Empty(); + var permissionsToAdd = permissions.ExceptBy(roleClaims.Select(x => x.Value), y => y).ToList(); + var claimsToRemove = roleClaims.ExceptBy(permissions, y => y.Value).ToList(); + + if (permissionsToAdd.Any()) + { + foreach (var permission in permissionsToAdd) + { + await _roleManager.AddClaimAsync(role, new Claim(CustomClaimTypes.Permission, permission)); + role = GetRoleById(role.Id)!; + } + } + + if (claimsToRemove.Any()) + { + foreach (var claim in claimsToRemove) + { + await _roleManager.RemoveClaimAsync(role, claim); + role = GetRoleById(role.Id)!; + } + } + + return Result.Ok(); + } + + private Role? GetRoleById(Guid roleId) + { + return (from r in _roleManager.Roles + where r.Id == roleId + select r).SingleOrDefault(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/GetAuthenticatorKey/GetAuthenticatorKeyQuery.cs b/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/GetAuthenticatorKey/GetAuthenticatorKeyQuery.cs new file mode 100644 index 000000000..7e376d184 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/GetAuthenticatorKey/GetAuthenticatorKeyQuery.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.AuthenticatorRegistration.GetAuthenticatorKey; + +public class GetAuthenticatorKeyQuery : QueryBase> +{ + public GetAuthenticatorKeyQuery(Guid userId) + { + UserId = userId; + } + + public Guid UserId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/GetAuthenticatorKey/GetAuthenticatorKeyQueryHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/GetAuthenticatorKey/GetAuthenticatorKeyQueryHandler.cs new file mode 100644 index 000000000..8d240edd7 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/GetAuthenticatorKey/GetAuthenticatorKeyQueryHandler.cs @@ -0,0 +1,46 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.AuthenticatorRegistration.GetAuthenticatorKey; + +internal class GetAuthenticatorKeyQueryHandler : IQueryHandler> +{ + private readonly UserManager _userManager; + + public GetAuthenticatorKeyQueryHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task> Handle(GetAuthenticatorKeyQuery request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + // Try to get the authenticator key from the user + var authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user); + + // if none is provided, which means none was generate before + if (authenticatorKey == null) + { + // reset the authenticator key + await _userManager.ResetAuthenticatorKeyAsync(user); + + // and get it again + authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user); + } + + if (!string.IsNullOrEmpty(authenticatorKey)) + { + return Result.Ok(authenticatorKey); + } + + return Errors.Authentication.AuthenticatorKeyNotFound(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/RegisterAuthenticator/RegisterAuthenticatorCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/RegisterAuthenticator/RegisterAuthenticatorCommand.cs new file mode 100644 index 000000000..979afe991 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/RegisterAuthenticator/RegisterAuthenticatorCommand.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.AuthenticatorRegistration.RegisterAuthenticator; + +public class RegisterAuthenticatorCommand : CommandBase +{ + public RegisterAuthenticatorCommand(Guid userId, string otpCode) + { + UserId = userId; + OtpCode = otpCode; + } + + public Guid UserId { get; } + + public string OtpCode { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/RegisterAuthenticator/RegisterAuthenticatorCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/RegisterAuthenticator/RegisterAuthenticatorCommandHandler.cs new file mode 100644 index 000000000..317cae0e6 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/AuthenticatorRegistration/RegisterAuthenticator/RegisterAuthenticatorCommandHandler.cs @@ -0,0 +1,35 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.AuthenticatorRegistration.RegisterAuthenticator; + +internal class RegisterAuthenticatorCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + + public RegisterAuthenticatorCommandHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task Handle(RegisterAuthenticatorCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + var isValid = await _userManager.VerifyTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider, request.OtpCode); + if (!isValid) + { + return Errors.Authentication.InvalidTwoFactorAuthenticationToken(); + } + + await _userManager.SetTwoFactorEnabledAsync(user, true); + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/ChangeEmailAddress/ChangeEmailAddressCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/ChangeEmailAddress/ChangeEmailAddressCommand.cs new file mode 100644 index 000000000..51b0e502f --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/ChangeEmailAddress/ChangeEmailAddressCommand.cs @@ -0,0 +1,20 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.ChangeEmailAddress; + +public class ChangeEmailAddressCommand : CommandBase> +{ + public ChangeEmailAddressCommand(long userId, string newEmailAddress, string token) + { + UserId = userId; + NewEmailAddress = newEmailAddress; + Token = token; + } + + public long UserId { get; } + + public string NewEmailAddress { get; } + + public string Token { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/ChangeEmailAddress/ChangeEmailAddressCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/ChangeEmailAddress/ChangeEmailAddressCommandHandler.cs new file mode 100644 index 000000000..53eee7c09 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/ChangeEmailAddress/ChangeEmailAddressCommandHandler.cs @@ -0,0 +1,47 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.ChangeEmailAddress; + +internal class ChangeEmailAddressCommandHandler : ICommandHandler> +{ + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public ChangeEmailAddressCommandHandler(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + public async Task> Handle(ChangeEmailAddressCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + var result = await _userManager.ChangeEmailAsync(user, request.NewEmailAddress, request.Token); + + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + + var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); + + // Send confirmation email + var emailMessage = new EmailMessage( + request.NewEmailAddress, + "MyMeetings - Confirm email address", + $"Please confirm your email by calling the API with the provided token\n\nTOKEN: {token}"); + + await _emailSender.SendEmail(emailMessage); + return Result.Ok(token); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/ChangePassword/ChangePasswordCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/ChangePassword/ChangePasswordCommand.cs new file mode 100644 index 000000000..2e6559b11 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/ChangePassword/ChangePasswordCommand.cs @@ -0,0 +1,20 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.ChangePassword; + +public class ChangePasswordCommand : CommandBase +{ + public ChangePasswordCommand(long userId, string currentPassword, string newPassword) + { + UserId = userId; + CurrentPassword = currentPassword; + NewPassword = newPassword; + } + + public long UserId { get; } + + public string CurrentPassword { get; } + + public string NewPassword { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/ChangePassword/ChangePasswordCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/ChangePassword/ChangePasswordCommandHandler.cs new file mode 100644 index 000000000..ee63aaaa1 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/ChangePassword/ChangePasswordCommandHandler.cs @@ -0,0 +1,42 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.ChangePassword; + +internal class ChangePasswordCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + private readonly IExecutionContextAccessor _executionContextAccessor; + + public ChangePasswordCommandHandler(UserManager userManager, IExecutionContextAccessor executionContextAccessor) + { + _userManager = userManager; + _executionContextAccessor = executionContextAccessor; + } + + public async Task Handle(ChangePasswordCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + if (_executionContextAccessor.UserId != user.Id) + { + return Errors.Authentication.NotAllowed("Not allowed to change password."); + } + + var result = await _userManager.ChangePasswordAsync(user, request.CurrentPassword, request.NewPassword); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/ConfirmEmailAddress/ConfirmEmailAddressCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/ConfirmEmailAddress/ConfirmEmailAddressCommand.cs new file mode 100644 index 000000000..789d23019 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/ConfirmEmailAddress/ConfirmEmailAddressCommand.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.ConfirmEmailAddress; + +public class ConfirmEmailAddressCommand : CommandBase +{ + public ConfirmEmailAddressCommand(string emailAddress, string token) + { + EmailAddress = emailAddress; + Token = token; + } + + public string EmailAddress { get; } + + public string Token { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/ConfirmEmailAddress/ConfirmEmailAddressCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/ConfirmEmailAddress/ConfirmEmailAddressCommandHandler.cs new file mode 100644 index 000000000..5abee0a28 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/ConfirmEmailAddress/ConfirmEmailAddressCommandHandler.cs @@ -0,0 +1,34 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.ConfirmEmailAddress; + +internal class ConfirmEmailAddressCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + + public ConfirmEmailAddressCommandHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task Handle(ConfirmEmailAddressCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByEmailAsync(request.EmailAddress); + if (user is null) + { + return Errors.General.NotFound(request.EmailAddress, "User"); + } + + var result = await _userManager.ConfirmEmailAsync(user, request.Token); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/CreateUserAccount/CreateUserAccountCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/CreateUserAccount/CreateUserAccountCommand.cs new file mode 100644 index 000000000..f3178c340 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/CreateUserAccount/CreateUserAccountCommand.cs @@ -0,0 +1,29 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.CreateUserAccount; + +public class CreateUserAccountCommand : CommandBase> +{ + public CreateUserAccountCommand(string userName, string? password, string? name, string? firstName, string? lastName, string? emailAddress) + { + UserName = userName; + Password = password; + Name = name; + FirstName = firstName; + LastName = lastName; + EmailAddress = emailAddress; + } + + public string UserName { get; } + + public string? Password { get; } + + public string? Name { get; } + + public string? FirstName { get; } + + public string? LastName { get; } + + public string? EmailAddress { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/CreateUserAccount/CreateUserAccountCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/CreateUserAccount/CreateUserAccountCommandHandler.cs new file mode 100644 index 000000000..7fa3736f5 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/CreateUserAccount/CreateUserAccountCommandHandler.cs @@ -0,0 +1,67 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.CreateUserAccount; + +internal class CreateUserAccountCommandHandler : ICommandHandler> +{ + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public CreateUserAccountCommandHandler(UserManager userManager, IEmailSender emailSender) + { + _emailSender = emailSender; + _userManager = userManager; + } + + public async Task> Handle(CreateUserAccountCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByNameAsync(request.UserName); + + // If user wasn't found, go ahead and create the new user. + // But if user exists don't tell anyone. -> Protection against account enumeration + if (user == null) + { + user = new ApplicationUser(request.UserName) + { + Email = request.EmailAddress, + Name = request.Name, + FirstName = request.FirstName, + LastName = request.LastName, + }; + + var identityResult = await _userManager.CreateAsync(user); + if (!identityResult.Succeeded) + { + return identityResult.Errors.Map().Combine(); + } + + if (!string.IsNullOrEmpty(request.Password)) + { + var passwordResult = await _userManager.AddPasswordAsync(user, request.Password); + if (!passwordResult.Succeeded) + { + return passwordResult.Errors.Map().Combine(); + } + } + + return Result.Ok(user.Id); + } + else + { + // Send email to user informing them that he already have an account. + // This way they can pro actively go out and use the forgot password functionality and reclaim there account. + var emailMessage = new EmailMessage( + user.Email, + "MyMeetings - Create user account", + $"An account with the provided email address {user.Email} already exists. Please use the forgot password functionality to reclaim your account."); + await _emailSender.SendEmail(emailMessage); + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/ById/GetUserAccountsQuery.cs b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/ById/GetUserAccountsQuery.cs new file mode 100644 index 000000000..4e505a3f4 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/ById/GetUserAccountsQuery.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.GetUserAccounts.ById; + +public class GetUserAccountsQuery : QueryBase> +{ + public GetUserAccountsQuery(Guid userId) + { + UserId = userId; + } + + public Guid UserId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/ById/GetUserAccountsQueryHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/ById/GetUserAccountsQueryHandler.cs new file mode 100644 index 000000000..a4ccb6b09 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/ById/GetUserAccountsQueryHandler.cs @@ -0,0 +1,47 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.GetUserAccounts.ById; + +internal class GetUserAccountsQueryHandler : IQueryHandler> +{ + private readonly UserManager _userManager; + + public GetUserAccountsQueryHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task> Handle(GetUserAccountsQuery request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + var userDto = new UserAccountDto() + { + Id = user.Id, + AccessFailedCount = user.AccessFailedCount, + Email = user.Email, + EmailConfirmed = user.EmailConfirmed, + Name = user.Name, + FirstName = user.FirstName, + LastName = user.LastName, + LockoutEnabled = user.LockoutEnabled, + LockoutEnd = user.LockoutEnd, + UserName = user.UserName, + NormalizedEmail = user.NormalizedEmail, + NormalizedUserName = user.NormalizedUserName, + PhoneNumber = user.PhoneNumber, + PhoneNumberConfirmed = user.PhoneNumberConfirmed, + TwoFactorEnabled = user.TwoFactorEnabled + }; + + return Result.Ok(userDto); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/Directory/GetUserAccountsQuery.cs b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/Directory/GetUserAccountsQuery.cs new file mode 100644 index 000000000..67004e8e3 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/Directory/GetUserAccountsQuery.cs @@ -0,0 +1,8 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.GetUserAccounts.Directory; + +public class GetUserAccountsQuery : QueryBase>> +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/Directory/GetUserAccountsQueryHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/Directory/GetUserAccountsQueryHandler.cs new file mode 100644 index 000000000..5d00f9ad4 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/Directory/GetUserAccountsQueryHandler.cs @@ -0,0 +1,41 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.GetUserAccounts.Directory; + +internal class GetUserAccountsQueryHandler : IQueryHandler>> +{ + private readonly UserManager _userManager; + + public GetUserAccountsQueryHandler(UserManager userManager) + { + _userManager = userManager; + } + + public Task>> Handle(GetUserAccountsQuery request, CancellationToken cancellationToken) + { + var users = (from user in _userManager.Users + select new UserAccountDto() + { + Id = user.Id, + AccessFailedCount = user.AccessFailedCount, + Email = user.Email, + EmailConfirmed = user.EmailConfirmed, + Name = user.Name, + FirstName = user.FirstName, + LastName = user.LastName, + LockoutEnabled = user.LockoutEnabled, + LockoutEnd = user.LockoutEnd, + UserName = user.UserName, + NormalizedEmail = user.NormalizedEmail, + NormalizedUserName = user.NormalizedUserName, + PhoneNumber = user.PhoneNumber, + PhoneNumberConfirmed = user.PhoneNumberConfirmed, + TwoFactorEnabled = user.TwoFactorEnabled + }).ToList(); + + return Task.FromResult(Result.Ok(users.AsEnumerable())); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/UserAccountDto.cs b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/UserAccountDto.cs new file mode 100644 index 000000000..cb9e3db76 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/GetUserAccounts/UserAccountDto.cs @@ -0,0 +1,83 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.GetUserAccounts; + +public class UserAccountDto +{ + /// + /// Gets or sets the primary key for this user. + /// + public Guid Id { get; init; } + + /// + /// Gets the name. + /// + public string? Name { get; init; } + + /// + /// Gets or sets the first name. + /// + public string? FirstName { get; init; } + + /// + /// Gets or sets the last name. + /// + public string? LastName { get; init; } + + /// + /// Gets or sets the date and time, in UTC, when any user lockout ends. + /// A value in the past means the user is not locked out. + /// + public virtual DateTimeOffset? LockoutEnd { get; init; } + + /// + /// Gets or sets a flag indicating if two factor authentication is enabled for this user. + /// True if 2fa is enabled, otherwise false. + /// + public virtual bool TwoFactorEnabled { get; init; } + + /// + /// Gets or sets a flag indicating if a user has confirmed their telephone address. + /// True if the telephone number has been confirmed, otherwise false. + /// + public virtual bool PhoneNumberConfirmed { get; init; } + + /// + /// Gets or sets a telephone number for the user. + /// + public virtual string? PhoneNumber { get; init; } + + /// + /// Gets or sets a flag indicating if a user has confirmed their email address. + /// True if the email address has been confirmed, otherwise false. + /// + public virtual bool EmailConfirmed { get; init; } + + /// + /// Gets or sets the normalized email address for this user. + /// + public virtual string? NormalizedEmail { get; init; } + + /// + /// Gets or sets the email address for this user. + /// + public virtual string? Email { get; init; } + + /// + /// Gets or sets the normalized user name for this user. + /// + public virtual string? NormalizedUserName { get; init; } = null!; + + /// + /// Gets or sets the login for this user. + /// + public virtual string? UserName { get; init; } + + /// + /// True if the user could be locked out, otherwise false. + /// + public virtual bool LockoutEnabled { get; init; } + + /// + /// Gets or sets the number of failed login attempts for the current user. + /// + public virtual int AccessFailedCount { get; init; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressCommand.cs new file mode 100644 index 000000000..4ef45a3bd --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressCommand.cs @@ -0,0 +1,18 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.RequestChangeEmailAddress +{ + public class RequestChangeEmailAddressCommand : CommandBase + { + public RequestChangeEmailAddressCommand(Guid userId, string newEmailAddress) + { + UserId = userId; + NewEmailAddress = newEmailAddress; + } + + public Guid UserId { get; } + + public string NewEmailAddress { get; } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressCommandHandler.cs new file mode 100644 index 000000000..828dc52b5 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressCommandHandler.cs @@ -0,0 +1,31 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.RequestChangeEmailAddress; + +internal class RequestChangeEmailAddressCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + + public RequestChangeEmailAddressCommandHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task Handle(RequestChangeEmailAddressCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user != null) + { + var token = await _userManager.GenerateChangeEmailTokenAsync(user, request.NewEmailAddress); + return new RequestChangeEmailAddressResult(token); + } + + var response = new RequestChangeEmailAddressResult(); + response.AddError(Errors.General.NotFound(request.UserId, "User")); + + return response; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressResult.cs b/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressResult.cs new file mode 100644 index 000000000..63f413052 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/RequestChangeEmailAddress/RequestChangeEmailAddressResult.cs @@ -0,0 +1,18 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.RequestChangeEmailAddress; + +public class RequestChangeEmailAddressResult : Result +{ + public RequestChangeEmailAddressResult() + { + } + + public RequestChangeEmailAddressResult(string token) + : base() + { + Token = token; + } + + public string? Token { get; private set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/SetUserPermissions/SetUserPermissionsCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/SetUserPermissions/SetUserPermissionsCommand.cs new file mode 100644 index 000000000..2252a1acd --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/SetUserPermissions/SetUserPermissionsCommand.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.SetUserPermissions; + +public class SetUserPermissionsCommand : CommandBase +{ + public SetUserPermissionsCommand(Guid userId, IEnumerable permissions) + { + UserId = userId; + Permissions = permissions; + } + + public Guid UserId { get; } + + public IEnumerable Permissions { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/SetUserPermissions/SetUserPermissionsCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/SetUserPermissions/SetUserPermissionsCommandHandler.cs new file mode 100644 index 000000000..0654cc80c --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/SetUserPermissions/SetUserPermissionsCommandHandler.cs @@ -0,0 +1,48 @@ +using System.Security.Claims; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.SetUserPermissions; + +internal class SetUserPermissionsCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + + public SetUserPermissionsCommandHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task Handle(SetUserPermissionsCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + var permissions = request.Permissions ?? Enumerable.Empty(); + + var userClaims = (await _userManager.GetClaimsAsync(user)) ?? Enumerable.Empty(); + var permissionsToAdd = permissions.ExceptBy(userClaims.Select(x => x.Value), y => y).ToList(); + var claimsToRemove = userClaims.ExceptBy(permissions, y => y.Value).ToList(); + + if (permissionsToAdd.Any()) + { + await _userManager.AddClaimsAsync(user, permissionsToAdd.Select(x => new Claim(CustomClaimTypes.Permission, x)).ToArray()); + user = await _userManager.FindByIdAsync(user.Id.ToString()); + } + + if (claimsToRemove.Any()) + { + await _userManager.RemoveClaimsAsync(user!, claimsToRemove); + user = await _userManager.FindByIdAsync(user!.Id.ToString()); + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/SetUserRoles/SetUserRolesCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/SetUserRoles/SetUserRolesCommand.cs new file mode 100644 index 000000000..f452055e8 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/SetUserRoles/SetUserRolesCommand.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.SetUserRoles; + +public class SetUserRolesCommand : CommandBase +{ + public SetUserRolesCommand(Guid userId, Guid[] roleIds) + { + UserId = userId; + RoleIds = roleIds; + } + + public Guid UserId { get; } + + public Guid[] RoleIds { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/SetUserRoles/SetUserRolesCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/SetUserRoles/SetUserRolesCommandHandler.cs new file mode 100644 index 000000000..1cb233d83 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/SetUserRoles/SetUserRolesCommandHandler.cs @@ -0,0 +1,66 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.SetUserRoles; + +internal class SetUserRolesCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + + public SetUserRolesCommandHandler(UserManager userManager, RoleManager roleManager) + { + _userManager = userManager; + _roleManager = roleManager; + } + + public async Task Handle(SetUserRolesCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user == null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + List roleNames = new(); + foreach (var roleId in request.RoleIds) + { + var role = _roleManager.Roles + .Where(x => x.Id == roleId.ToString()) + .Select(x => new { x.Id, x.Name }) + .FirstOrDefault(); + + if (role?.Name is not null) + { + roleNames.Add(role.Name); + } + } + + var userRoles = await _userManager.GetRolesAsync(user); + var rolesToAdd = roleNames.ExceptBy(userRoles, y => y).ToList(); + var rolesToRemove = userRoles.ExceptBy(roleNames, y => y).ToList(); + + if (rolesToAdd.Any()) + { + var result = await _userManager.AddToRolesAsync(user, rolesToAdd); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + } + + if (rolesToRemove.Any()) + { + var result = await _userManager.RemoveFromRolesAsync(user, rolesToRemove); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/UnlockUserAccount/UnlockUserAccountCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/UnlockUserAccount/UnlockUserAccountCommand.cs new file mode 100644 index 000000000..ff24b311a --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/UnlockUserAccount/UnlockUserAccountCommand.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.UnlockUserAccount; + +public class UnlockUserAccountCommand : CommandBase +{ + public UnlockUserAccountCommand(Guid userId) + { + UserId = userId; + } + + public Guid UserId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/UnlockUserAccount/UnlockUserAccountCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/UnlockUserAccount/UnlockUserAccountCommandHandler.cs new file mode 100644 index 000000000..77c2e0d32 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/UnlockUserAccount/UnlockUserAccountCommandHandler.cs @@ -0,0 +1,34 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.UnlockUserAccount; + +internal class UnlockUserAccountCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + + public UnlockUserAccountCommandHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task Handle(UnlockUserAccountCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + var result = await _userManager.SetLockoutEndDateAsync(user, null); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/UpdateUserAccount/UpdateUserAccountCommand.cs b/src/Modules/UserAccessMI/Application/UserAccounts/UpdateUserAccount/UpdateUserAccountCommand.cs new file mode 100644 index 000000000..82a00e0ee --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/UpdateUserAccount/UpdateUserAccountCommand.cs @@ -0,0 +1,23 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.UpdateUserAccount; + +public class UpdateUserAccountCommand : CommandBase +{ + public UpdateUserAccountCommand(Guid userId, string? name, string? firstName, string? lastName) + { + UserId = userId; + Name = name; + FirstName = firstName; + LastName = lastName; + } + + public Guid UserId { get; } + + public string? Name { get; } + + public string? FirstName { get; } + + public string? LastName { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserAccounts/UpdateUserAccount/UpdateUserAccountCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserAccounts/UpdateUserAccount/UpdateUserAccountCommandHandler.cs new file mode 100644 index 000000000..31149329d --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserAccounts/UpdateUserAccount/UpdateUserAccountCommandHandler.cs @@ -0,0 +1,38 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserAccounts.UpdateUserAccount; + +internal class UpdateUserAccountCommandHandler : ICommandHandler +{ + private readonly UserManager _userManager; + + public UpdateUserAccountCommandHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task Handle(UpdateUserAccountCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user is null) + { + return Errors.General.NotFound(request.UserId, "User"); + } + + user.Name = request.Name; + user.FirstName = request.FirstName; + user.LastName = request.LastName; + + var result = await _userManager.UpdateAsync(user); + if (!result.Succeeded) + { + return result.Errors.Map().Combine(); + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommand.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommand.cs new file mode 100644 index 000000000..256da13d5 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommand.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.ConfirmUserRegistration; + +public class ConfirmUserRegistrationCommand : CommandBase +{ + public ConfirmUserRegistrationCommand(Guid userRegistrationId) + { + UserRegistrationId = userRegistrationId; + } + + public Guid UserRegistrationId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommandHandler.cs new file mode 100644 index 000000000..4f095c0ab --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/ConfirmUserRegistrationCommandHandler.cs @@ -0,0 +1,30 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.ConfirmUserRegistration; + +internal class ConfirmUserRegistrationCommandHandler : ICommandHandler +{ + private readonly IUserRegistrationRepository _userRegistrationRepository; + + public ConfirmUserRegistrationCommandHandler(IUserRegistrationRepository userRegistrationRepository) + { + _userRegistrationRepository = userRegistrationRepository; + } + + public async Task Handle(ConfirmUserRegistrationCommand request, CancellationToken cancellationToken) + { + var userRegistration = + await _userRegistrationRepository.GetByIdAsync(new UserRegistrationId(request.UserRegistrationId)); + + if (userRegistration is null) + { + return Errors.General.NotFound(request.UserRegistrationId, "User registration"); + } + + userRegistration.Confirm(); + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/UserRegistrationConfirmedHandler.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/UserRegistrationConfirmedHandler.cs new file mode 100644 index 000000000..8eb67060b --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/ConfirmUserRegistration/UserRegistrationConfirmedHandler.cs @@ -0,0 +1,48 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Events; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using MediatR; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.ConfirmUserRegistration; + +public class UserRegistrationConfirmedHandler : INotificationHandler +{ + private readonly IUserRegistrationRepository _userRegistrationRepository; + private readonly UserManager _userManager; + + public UserRegistrationConfirmedHandler( + IUserRegistrationRepository userRegistrationRepository, + UserManager userManager) + { + _userManager = userManager; + _userRegistrationRepository = userRegistrationRepository; + } + + public async Task Handle(UserRegistrationConfirmedDomainEvent @event, CancellationToken cancellationToken) + { + var userRegistration = await _userRegistrationRepository.GetByIdAsync(@event.UserRegistrationId); + if (userRegistration is null) + { + return; + } + + var user = userRegistration.CreateUser(); + var identityResult = await _userManager.CreateAsync(user); + if (!identityResult.Succeeded) + { + return; + } + + if (!string.IsNullOrEmpty(userRegistration.Password)) + { + var passwordResult = await _userManager.AddPasswordAsync(user, userRegistration.Password); + if (!passwordResult.Succeeded) + { + return; + } + } + + await _userManager.AddToRoleAsync(user, UserRole.Member.Value); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQuery.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQuery.cs new file mode 100644 index 000000000..866334d1f --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQuery.cs @@ -0,0 +1,15 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.GetUserRegistration +{ + public class GetUserRegistrationQuery : QueryBase> + { + public GetUserRegistrationQuery(Guid userRegistrationId) + { + UserRegistrationId = userRegistrationId; + } + + public Guid UserRegistrationId { get; } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQueryHandler.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQueryHandler.cs new file mode 100644 index 000000000..bd5829bd4 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/GetUserRegistrationQueryHandler.cs @@ -0,0 +1,48 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Queries; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using Dapper; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.GetUserRegistration +{ + internal class GetUserRegistrationQueryHandler : IQueryHandler> + { + private readonly ISqlConnectionFactory _sqlConnectionFactory; + + public GetUserRegistrationQueryHandler(ISqlConnectionFactory sqlConnectionFactory) + { + _sqlConnectionFactory = sqlConnectionFactory; + } + + public async Task> Handle(GetUserRegistrationQuery query, CancellationToken cancellationToken) + { + var connection = _sqlConnectionFactory.GetOpenConnection(); + + const string sql = "SELECT " + + "[UserRegistration].[Id], " + + "[UserRegistration][UserName] AS [Login], " + + "[UserRegistration].[Email], " + + "[UserRegistration].[FirstName], " + + "[UserRegistration].[LastName], " + + "[UserRegistration].[Name], " + + "[UserRegistration].[StatusCode] " + + "FROM [usersmi].[v_UserRegistrations] AS [UserRegistration] " + + "WHERE [UserRegistration].[Id] = @UserRegistrationId"; + + var userRegistration = await connection.QuerySingleAsync( + sql, + new + { + query.UserRegistrationId + }); + + if (userRegistration is null) + { + return Errors.General.NotFound(query.UserRegistrationId, "User registration"); + } + + return userRegistration; + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/UserRegistrationDto.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/UserRegistrationDto.cs new file mode 100644 index 000000000..31d448383 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/GetUserRegistration/UserRegistrationDto.cs @@ -0,0 +1,18 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.GetUserRegistration; + +public class UserRegistrationDto +{ + public Guid Id { get; set; } + + public string Login { get; set; } = null!; + + public string Email { get; set; } = null!; + + public string FirstName { get; set; } = null!; + + public string LastName { get; set; } = null!; + + public string Name { get; set; } = null!; + + public string StatusCode { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredEnqueueEmailConfirmationHandler.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredEnqueueEmailConfirmationHandler.cs new file mode 100644 index 000000000..101a54819 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredEnqueueEmailConfirmationHandler.cs @@ -0,0 +1,24 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.SendUserRegistrationConfirmationEmail; +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.RegisterNewUser; + +public class NewUserRegisteredEnqueueEmailConfirmationHandler : INotificationHandler +{ + private readonly ICommandsScheduler _commandsScheduler; + + public NewUserRegisteredEnqueueEmailConfirmationHandler(ICommandsScheduler commandsScheduler) + { + _commandsScheduler = commandsScheduler; + } + + public async Task Handle(NewUserRegisteredNotification notification, CancellationToken cancellationToken) + { + await _commandsScheduler.EnqueueAsync(new SendUserRegistrationConfirmationEmailCommand( + Guid.NewGuid(), + notification.DomainEvent.UserRegistrationId, + notification.DomainEvent.Email, + notification.DomainEvent.ConfirmLink)); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredNotification.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredNotification.cs new file mode 100644 index 000000000..3c62d7180 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredNotification.cs @@ -0,0 +1,14 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Events; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Events; +using Newtonsoft.Json; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.RegisterNewUser; + +public class NewUserRegisteredNotification : DomainNotificationBase +{ + [JsonConstructor] + public NewUserRegisteredNotification(NewUserRegisteredDomainEvent domainEvent, Guid id) + : base(domainEvent, id) + { + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredPublishEventHandler.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredPublishEventHandler.cs new file mode 100644 index 000000000..9bb1afc19 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/NewUserRegisteredPublishEventHandler.cs @@ -0,0 +1,28 @@ +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; +using CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents; +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.RegisterNewUser; + +public class NewUserRegisteredPublishEventHandler : INotificationHandler +{ + private readonly IEventsBus _eventsBus; + + public NewUserRegisteredPublishEventHandler(IEventsBus eventsBus) + { + _eventsBus = eventsBus; + } + + public async Task Handle(NewUserRegisteredNotification notification, CancellationToken cancellationToken) + { + await _eventsBus.Publish(new NewUserRegisteredIntegrationEvent( + notification.Id, + notification.DomainEvent.OccurredOn, + notification.DomainEvent.UserRegistrationId.Value, + notification.DomainEvent.Login, + notification.DomainEvent.Email, + notification.DomainEvent.FirstName, + notification.DomainEvent.LastName, + notification.DomainEvent.Name)); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommand.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommand.cs new file mode 100644 index 000000000..0ede82df8 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommand.cs @@ -0,0 +1,35 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.RegisterNewUser; + +public class RegisterNewUserCommand : CommandBase> +{ + public RegisterNewUserCommand( + string login, + string password, + string email, + string firstName, + string lastName, + string confirmLink) + { + Login = login; + Password = password; + Email = email; + FirstName = firstName; + LastName = lastName; + ConfirmLink = confirmLink; + } + + public string Login { get; } + + public string Password { get; } + + public string Email { get; } + + public string FirstName { get; } + + public string LastName { get; } + + public string ConfirmLink { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommandHandler.cs new file mode 100644 index 000000000..5bf6d9a34 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/RegisterNewUser/RegisterNewUserCommandHandler.cs @@ -0,0 +1,42 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Results; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.RegisterNewUser; + +internal class RegisterNewUserCommandHandler : ICommandHandler> +{ + private readonly IUserRegistrationRepository _userRegistrationRepository; + private readonly IUsersCounter _usersCounter; + + public RegisterNewUserCommandHandler( + IUserRegistrationRepository userRegistrationRepository, + IUsersCounter usersCounter) + { + _userRegistrationRepository = userRegistrationRepository; + _usersCounter = usersCounter; + } + + public async Task> Handle(RegisterNewUserCommand command, CancellationToken cancellationToken) + { + /* + TODO: How to handle password encryption + var password = PasswordManager.HashPassword(command.Password); + */ + + var password = command.Password; + + var userRegistration = UserRegistration.RegisterNewUser( + command.Login, + password, + command.Email, + command.FirstName, + command.LastName, + _usersCounter, + command.ConfirmLink); + + await _userRegistrationRepository.AddAsync(userRegistration); + + return userRegistration.Id.Value; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommand.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommand.cs new file mode 100644 index 000000000..35441a7f2 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommand.cs @@ -0,0 +1,27 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; +using Newtonsoft.Json; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.SendUserRegistrationConfirmationEmail; + +public class SendUserRegistrationConfirmationEmailCommand : InternalCommandBase +{ + [JsonConstructor] + public SendUserRegistrationConfirmationEmailCommand( + Guid id, + UserRegistrationId userRegistrationId, + string email, + string confirmLink) + : base(id) + { + UserRegistrationId = userRegistrationId; + Email = email; + ConfirmLink = confirmLink; + } + + internal UserRegistrationId UserRegistrationId { get; } + + internal string Email { get; } + + internal string ConfirmLink { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommandHandler.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommandHandler.cs new file mode 100644 index 000000000..5e2d0d01b --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/SendUserRegistrationConfirmationEmail/SendUserRegistrationConfirmationEmailCommandHandler.cs @@ -0,0 +1,28 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.SendUserRegistrationConfirmationEmail; + +internal class SendUserRegistrationConfirmationEmailCommandHandler : ICommandHandler +{ + private readonly IEmailSender _emailSender; + + public SendUserRegistrationConfirmationEmailCommandHandler(IEmailSender emailSender) + { + _emailSender = emailSender; + } + + public async Task Handle(SendUserRegistrationConfirmationEmailCommand command, CancellationToken cancellationToken) + { + string link = $"link"; + + string content = $"Welcome to MyMeetings application! Please confirm your registration using this {link}."; + + var emailMessage = new EmailMessage( + command.Email, + "MyMeetings - Please confirm your registration", + content); + + await _emailSender.SendEmail(emailMessage); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Application/UserRegistrations/UsersCounter.cs b/src/Modules/UserAccessMI/Application/UserRegistrations/UsersCounter.cs new file mode 100644 index 000000000..9dc7905a6 --- /dev/null +++ b/src/Modules/UserAccessMI/Application/UserRegistrations/UsersCounter.cs @@ -0,0 +1,31 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; +using Dapper; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations; + +public class UsersCounter : IUsersCounter +{ + private readonly ISqlConnectionFactory _sqlConnectionFactory; + + public UsersCounter(ISqlConnectionFactory sqlConnectionFactory) + { + _sqlConnectionFactory = sqlConnectionFactory; + } + + public int CountUsersWithLogin(string login) + { + var connection = _sqlConnectionFactory.GetOpenConnection(); + + const string sql = "SELECT " + + "COUNT(*) " + + "FROM [usersmi].[v_Users] AS [User]" + + "WHERE [User].[Login] = @Login"; + return connection.QuerySingle( + sql, + new + { + login + }); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/CompanyName.MyMeetings.Modules.UserAccessMI.Domain.csproj b/src/Modules/UserAccessMI/Domain/CompanyName.MyMeetings.Modules.UserAccessMI.Domain.csproj new file mode 100644 index 000000000..59e929594 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/CompanyName.MyMeetings.Modules.UserAccessMI.Domain.csproj @@ -0,0 +1,12 @@ + + + enable + + + + + + + + + \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/ErrorHandling/Error.cs b/src/Modules/UserAccessMI/Domain/ErrorHandling/Error.cs new file mode 100644 index 000000000..8453666eb --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/ErrorHandling/Error.cs @@ -0,0 +1,125 @@ +using System.Text.Json.Serialization; +using CSharpFunctionalExtensions; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; + +/// +/// Class that represents an error. +/// +/// Errors should not be instantiated arbitrarily, but should be taken from a predefined set of errors. +/// Error messages should not be handled by the domain layer. This is an application concern as you might +/// want to translate them based on user settings. +/// Further more as the Error class is part of the domain layer this is technically a violation of domain model +/// purity and as said it shouldn't deal with application concerns. But this is a minor concession. +/// +public class Error : ValueObject, ICombine +{ + private const string SEPARATOR = "||"; + + private readonly List? _errors; + + /// + /// Initializes a new instance of the class. + /// + /// Error code. + /// Error message. + public Error(string code, string? message) + : this() + { + Code = code; + Message = message; + } + + /// + /// Initializes a new instance of the class. + /// + internal Error() + : base() + { + } + + internal Error(IEnumerable errors) + : this() + { + if (errors == null) + { + throw new ArgumentNullException(nameof(errors)); + } + + _errors = new List(errors); + } + + /// + /// Error code. + /// The code is going to be part of the contract between the API and it's clients. + /// Once published error codes should not be changed. + /// + public string Code { get; } = null!; + + /// + /// The error message. + /// Error messages are just for informational purposes. So we can specify some additional information for debugging. + /// Ideally the client should not show that message to the end user and instead should map there own error messages + /// onto the code. + /// + public string? Message { get; } + + [JsonIgnore] + public IReadOnlyCollection? Errors => _errors; + + public ICombine Combine(ICombine value) + { + if (value is Error error) + { + var errorList = new List(); + + if (!string.IsNullOrEmpty(error.Code)) + { + errorList.Add(new Error(error.Code, error.Message)); + } + + if (error._errors is not null) + { + errorList.AddRange(error._errors); + } + + if (!string.IsNullOrEmpty(Code)) + { + errorList.Add(new Error(Code, Message)); + } + + if (_errors is not null) + { + errorList.AddRange(_errors); + } + + return new Error(errorList); + } + + return this; + } + + public string Serialize() => $"{Code}{SEPARATOR}{Message}"; + + public static Error Deserialize(string serialized) + { + if (serialized == "A non-empty request body is required.") + { + return ErrorHandling.Errors.General.ValueIsRequired(); + } + + string[] data = serialized.Split(new[] { SEPARATOR }, StringSplitOptions.RemoveEmptyEntries); + if (data.Length < 2) + { + throw new ArgumentException($"Invalid error serialization: '{serialized}'"); + } + + return new Error(data[0], data[1]); + } + + protected override IEnumerable GetEqualityComponents() + { + // Only the code field is required as the code will be part of the contract between the API and it's clients. + yield return Code; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/ErrorHandling/ErrorExtensions.cs b/src/Modules/UserAccessMI/Domain/ErrorHandling/ErrorExtensions.cs new file mode 100644 index 000000000..de52ddf58 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/ErrorHandling/ErrorExtensions.cs @@ -0,0 +1,71 @@ +using CSharpFunctionalExtensions; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; + +public static class ErrorExtensions +{ + public static UnitResult ToErrorOrUnit(this IEnumerable errors) + { + if (errors.Any()) + { + return errors.Combine(); + } + + return UnitResult.Success(); + } + + public static Error Combine(this IEnumerable errors) + => MergeErrors(errors); + + private static Error MergeErrors(IEnumerable errors) + { + if (errors is null) + { + throw new ArgumentNullException(nameof(errors)); + } + + if (errors.Count() <= 0) + { + return new Error(); + } + + // Take the first error of the collection + var errorDestination = errors.First(); + + if (errors.Count() == 1) + { + return errorDestination; + } + + // and merge the remaining one's into the first one + for (int i = 1; i < errors.Count(); i++) + { + var errorSource = errors.ElementAt(i); + errorSource = MergeChildren(errorSource); + + errorDestination.Combine(errorSource); + } + + return errorDestination; + } + + private static Error MergeChildren(Error parent) + { + if (parent is null) + { + throw new ArgumentNullException(nameof(parent)); + } + + var children = parent.Errors; + if (children is not null) + { + foreach (var child in children) + { + MergeChildren(child); + parent.Combine(child); + } + } + + return parent; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/ErrorHandling/Errors.cs b/src/Modules/UserAccessMI/Domain/ErrorHandling/Errors.cs new file mode 100644 index 000000000..5d77f578c --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/ErrorHandling/Errors.cs @@ -0,0 +1,125 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; + +/// +/// This class will enumerate all possible errors of the application. +/// +/// As the Error class, this class is an other violation in domain model purity, +/// because we are combining all errors in one list including errors that don't +/// belong to the domain layer. Technically those should be separated into domain +/// and none domain errors. But this is a small concession and it's more useful to keep the +/// full list of errors in one place rather then maintain perfect separation. +/// +public static partial class Errors +{ + public static class General + { + public static Error NotFound() => + new Error("record.not.found", "Record not found."); + + public static Error NotFound(string message) => + new Error("record.not.found", message); + + public static Error NotFound(long id) => + new Error("record.not.found", $"Record not found for Id '{id}'."); + + public static Error NotFound(long id, string itemName) => + new Error("record.not.found", $"{itemName} not found for Id '{id}'."); + + public static Error NotFound(Guid id) => + new Error("record.not.found", $"Record not found for Id '{id}'."); + + public static Error NotFound(Guid id, string itemName) => + new Error("record.not.found", $"{itemName} not found for Id '{id}'."); + + public static Error NotFound(string value, string itemName) => + new Error("record.not.found", $"{itemName} not found for value '{value}'."); + + public static Error ValueIsInvalid() => + new Error("value.is.invalid", "Value is invalid."); + + public static Error ValueIsInvalid(string message) => + new Error("value.is.invalid", message); + + public static Error ValueIsRequired() => + new Error("value.is.required", "Value is required."); + + public static Error ValueIsRequired(string? name = null) + { + string label = name == null ? "Value" : name.Trim(); + return new Error("value.is.required", $"{label} is required."); + } + + public static Error ValueMustBeUnique(string name) => + new Error("value.must.be.unique", $"Record already present for Value '{name}'"); + + public static Error ValueMustBePositive(string valueName) => + new("value.must.be.positive", $"{valueName} must be positive."); + + public static Error ValueMustBeGreaterThan(string valueName, int numberToCompare) => + new("value.must.be.greater.than", $"{valueName} must be greater than {numberToCompare}."); + + public static Error ValueMustBeLessThan(string valueName, int numberToCompare) => + new("value.must.be.less.than", $"{valueName} must be less than {numberToCompare}."); + + public static Error InvalidLength(string? name = null, int? maxLength = null) + { + string label = name == null ? " " : $" {name} "; + string lengthHint = maxLength == null ? string.Empty : $" The length may not be longer than {maxLength} characters."; + return new Error("invalid.string.length", $"Invalid{label}length.{lengthHint}"); + } + + public static Error CollectionIsTooSmall(int min, int current) => + new Error("collection.is.too.small", $"The collection must contain {min} items or more. It contains {current} items."); + + public static Error CollectionIsTooLarge(int max, int current) => + new Error("collection.is.too.large", $"The collection must contain {max} items or less. It contains {current} items."); + + public static Error InternalServerError(string message) => + new Error("internal.server.error", message); + + public static Error InvalidRequest() => + new Error("invalid.request", "Invalid request"); + + public static Error InvalidModel() => + new Error("invalid.model", "Invalid model"); + } + + public static class Authentication + { + public static Error InvalidToken() => + new Error("invalid.token", "Invalid token"); + + public static Error InvalidToken(string message) => + new Error("invalid.token", message); + + public static Error LoginRequestExpired() => + new Error("login.request.expired", "Your login request has expired, please start over."); + + public static Error NotAuthorized() => + new Error("not.authorized", "You are not authorized to perform this action."); + + public static Error NotAuthorized(string message) => + new Error("not.authorized", message); + + public static Error AuthenticatorKeyNotFound() => + new Error("authenticator.key.not.found", "Authenticator key could not be retrieved."); + + public static Error NotAllowed(string? message = null) => + new Error("not.allowed", message ?? "You are not allowed to perform this operation."); + + public static Error InvalidTwoFactorAuthenticationToken() => + new Error("invalid.two.factor.authentication.token", "Two factor authentication token is invalid."); + } + + public static class UserAccess + { + public static Error InvalidUserNameOrPassword => + new Error("invalid.username.or.password", "Invalid UserName or Password."); + + public static Error EmailNotConfirmed => + new Error("email.not.confirmed", "Email is not confirmed."); + + public static Error LoginNotAllowed => + new Error("login.not.allowed", "Not allowed tp login."); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/IUserRefreshTokenRepository.cs b/src/Modules/UserAccessMI/Domain/IUserRefreshTokenRepository.cs new file mode 100644 index 000000000..d9726be28 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/IUserRefreshTokenRepository.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain; + +public interface IUserRefreshTokenRepository +{ + Task GetByJwtIdAsync(string id, CancellationToken cancellationToken); + + void Add(UserRefreshToken userRefreshToken); + + Task DeleteAsync(UserRefreshToken userRefreshToken, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRefreshToken.cs b/src/Modules/UserAccessMI/Domain/UserRefreshToken.cs new file mode 100644 index 000000000..1883e91db --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRefreshToken.cs @@ -0,0 +1,43 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain; + +public class UserRefreshToken : Entity, IAggregateRoot +{ + protected UserRefreshToken() + : base() + { + Id = new UserRefreshTokenId(Guid.NewGuid()); + IsRevoked = false; + AddedDate = DateTime.UtcNow; + ExpiryDate = DateTime.UtcNow.AddMonths(6); + } + + protected UserRefreshToken(ApplicationUser user, string jwtId, string token) + : this() + { + User = user; + JwtId = jwtId; + Token = token; + } + + public static UserRefreshToken Create(ApplicationUser user, string jwtId, string token) + { + return new UserRefreshToken(user, jwtId, token); + } + + public UserRefreshTokenId Id { get; protected set; } + + public ApplicationUser User { get; protected set; } = null!; + + public string Token { get; protected set; } = null!; + + public string JwtId { get; protected set; } = null!; + + public bool IsRevoked { get; set; } + + public DateTime AddedDate { get; set; } + + public DateTime ExpiryDate { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRefreshTokenId.cs b/src/Modules/UserAccessMI/Domain/UserRefreshTokenId.cs new file mode 100644 index 000000000..0f9dfd314 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRefreshTokenId.cs @@ -0,0 +1,11 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain; + +public class UserRefreshTokenId : TypedIdValueBase +{ + public UserRefreshTokenId(Guid value) + : base(value) + { + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/NewUserRegisteredDomainEvent.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/NewUserRegisteredDomainEvent.cs new file mode 100644 index 000000000..e4bf74160 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/NewUserRegisteredDomainEvent.cs @@ -0,0 +1,42 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Events; + +public class NewUserRegisteredDomainEvent : DomainEventBase +{ + public UserRegistrationId UserRegistrationId { get; } + + public string Login { get; } + + public string Email { get; } + + public string FirstName { get; } + + public string LastName { get; } + + public string Name { get; } + + public DateTime RegisterDate { get; } + + public string ConfirmLink { get; } + + public NewUserRegisteredDomainEvent( + UserRegistrationId userRegistrationId, + string login, + string email, + string firstName, + string lastName, + string name, + DateTime registerDate, + string confirmLink) + { + UserRegistrationId = userRegistrationId; + Login = login; + Email = email; + FirstName = firstName; + LastName = lastName; + Name = name; + RegisterDate = registerDate; + ConfirmLink = confirmLink; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/UserRegistrationConfirmedDomainEvent.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/UserRegistrationConfirmedDomainEvent.cs new file mode 100644 index 000000000..ea95c5f77 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/UserRegistrationConfirmedDomainEvent.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Events; + +public class UserRegistrationConfirmedDomainEvent : DomainEventBase +{ + public UserRegistrationConfirmedDomainEvent(UserRegistrationId userRegistrationId) + { + UserRegistrationId = userRegistrationId; + } + + public UserRegistrationId UserRegistrationId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/UserRegistrationExpiredDomainEvent.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/UserRegistrationExpiredDomainEvent.cs new file mode 100644 index 000000000..a0bf75bbf --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/Events/UserRegistrationExpiredDomainEvent.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Events; + +public class UserRegistrationExpiredDomainEvent : DomainEventBase +{ + public UserRegistrationExpiredDomainEvent(UserRegistrationId userRegistrationId) + { + UserRegistrationId = userRegistrationId; + } + + public UserRegistrationId UserRegistrationId { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/IUserRegistrationRepository.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/IUserRegistrationRepository.cs new file mode 100644 index 000000000..552c78275 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/IUserRegistrationRepository.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; + +public interface IUserRegistrationRepository +{ + Task AddAsync(UserRegistration userRegistration); + + Task GetByIdAsync(UserRegistrationId userRegistrationId); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/IUsersCounter.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/IUsersCounter.cs new file mode 100644 index 000000000..80c7cdad1 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/IUsersCounter.cs @@ -0,0 +1,6 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; + +public interface IUsersCounter +{ + int CountUsersWithLogin(string login); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule.cs new file mode 100644 index 000000000..10de86849 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Rules; + +public class UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule : IBusinessRule +{ + private readonly UserRegistrationStatus _actualRegistrationStatus; + + internal UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule(UserRegistrationStatus actualRegistrationStatus) + { + this._actualRegistrationStatus = actualRegistrationStatus; + } + + public bool IsBroken() => _actualRegistrationStatus != UserRegistrationStatus.Confirmed; + + public string Message => "User cannot be created when registration is not confirmed"; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserLoginMustBeUniqueRule.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserLoginMustBeUniqueRule.cs new file mode 100644 index 000000000..8a4292938 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserLoginMustBeUniqueRule.cs @@ -0,0 +1,19 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Rules; + +public class UserLoginMustBeUniqueRule : IBusinessRule +{ + private readonly IUsersCounter _usersCounter; + private readonly string _login; + + internal UserLoginMustBeUniqueRule(IUsersCounter usersCounter, string login) + { + _usersCounter = usersCounter; + _login = login; + } + + public bool IsBroken() => _usersCounter.CountUsersWithLogin(_login) > 0; + + public string Message => "User Login must be unique"; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedAfterExpirationRule.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedAfterExpirationRule.cs new file mode 100644 index 000000000..a7c06d3f6 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedAfterExpirationRule.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Rules; + +public class UserRegistrationCannotBeConfirmedAfterExpirationRule : IBusinessRule +{ + private readonly UserRegistrationStatus _actualRegistrationStatus; + + internal UserRegistrationCannotBeConfirmedAfterExpirationRule(UserRegistrationStatus actualRegistrationStatus) + { + this._actualRegistrationStatus = actualRegistrationStatus; + } + + public bool IsBroken() => _actualRegistrationStatus == UserRegistrationStatus.Expired; + + public string Message => "User Registration cannot be confirmed because it is expired"; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedMoreThanOnceRule.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedMoreThanOnceRule.cs new file mode 100644 index 000000000..14cfee908 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeConfirmedMoreThanOnceRule.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Rules; + +public class UserRegistrationCannotBeConfirmedMoreThanOnceRule : IBusinessRule +{ + private readonly UserRegistrationStatus _actualRegistrationStatus; + + internal UserRegistrationCannotBeConfirmedMoreThanOnceRule(UserRegistrationStatus actualRegistrationStatus) + { + this._actualRegistrationStatus = actualRegistrationStatus; + } + + public bool IsBroken() => _actualRegistrationStatus == UserRegistrationStatus.Confirmed; + + public string Message => "User Registration cannot be confirmed more than once"; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeExpiredMoreThanOnceRule.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeExpiredMoreThanOnceRule.cs new file mode 100644 index 000000000..44f6a2b4b --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/Rules/UserRegistrationCannotBeExpiredMoreThanOnceRule.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Rules; + +public class UserRegistrationCannotBeExpiredMoreThanOnceRule : IBusinessRule +{ + private readonly UserRegistrationStatus _actualRegistrationStatus; + + internal UserRegistrationCannotBeExpiredMoreThanOnceRule(UserRegistrationStatus actualRegistrationStatus) + { + this._actualRegistrationStatus = actualRegistrationStatus; + } + + public bool IsBroken() => _actualRegistrationStatus == UserRegistrationStatus.Expired; + + public string Message => "User Registration cannot be expired more than once"; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistration.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistration.cs new file mode 100644 index 000000000..a53020008 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistration.cs @@ -0,0 +1,113 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Events; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations.Rules; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; + +public class UserRegistration : Entity, IAggregateRoot +{ + public UserRegistrationId Id { get; private set; } = null!; + + private string _login = null!; + + private string _password = null!; + + private string _email = null!; + + private string _firstName = null!; + + private string _lastName = null!; + + private string _name = null!; + + private DateTime _registerDate; + + private UserRegistrationStatus _status = null!; + + private DateTime? _confirmedDate; + + private UserRegistration() + { + // Only EF. + } + + public static UserRegistration RegisterNewUser( + string login, + string password, + string email, + string firstName, + string lastName, + IUsersCounter usersCounter, + string confirmLink) + { + return new UserRegistration(login, password, email, firstName, lastName, usersCounter, confirmLink); + } + + private UserRegistration( + string login, + string password, + string email, + string firstName, + string lastName, + IUsersCounter usersCounter, + string confirmLink) + { + CheckRule(new UserLoginMustBeUniqueRule(usersCounter, login)); + + Id = new UserRegistrationId(Guid.NewGuid()); + _login = login; + _password = password; + _email = email; + _firstName = firstName; + _lastName = lastName; + _name = $"{firstName} {lastName}"; + _registerDate = DateTime.UtcNow; + _status = UserRegistrationStatus.WaitingForConfirmation; + + AddDomainEvent(new NewUserRegisteredDomainEvent( + Id, + _login, + _email, + _firstName, + _lastName, + _name, + _registerDate, + confirmLink)); + } + + public string Password => _password; + + public ApplicationUser CreateUser() + { + CheckRule(new UserCannotBeCreatedWhenRegistrationIsNotConfirmedRule(_status)); + + return ApplicationUser.CreateFromUserRegistration( + Id, + _login, + _email, + _firstName, + _lastName, + _name); + } + + public void Confirm() + { + CheckRule(new UserRegistrationCannotBeConfirmedMoreThanOnceRule(_status)); + CheckRule(new UserRegistrationCannotBeConfirmedAfterExpirationRule(_status)); + + _status = UserRegistrationStatus.Confirmed; + _confirmedDate = DateTime.UtcNow; + + AddDomainEvent(new UserRegistrationConfirmedDomainEvent(Id)); + } + + public void Expire() + { + CheckRule(new UserRegistrationCannotBeExpiredMoreThanOnceRule(_status)); + + _status = UserRegistrationStatus.Expired; + + AddDomainEvent(new UserRegistrationExpiredDomainEvent(Id)); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistrationId.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistrationId.cs new file mode 100644 index 000000000..5ceff2e47 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistrationId.cs @@ -0,0 +1,11 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; + +public class UserRegistrationId : TypedIdValueBase +{ + public UserRegistrationId(Guid value) + : base(value) + { + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistrationStatus.cs b/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistrationStatus.cs new file mode 100644 index 000000000..f2a2c1b25 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/UserRegistrations/UserRegistrationStatus.cs @@ -0,0 +1,20 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; + +public class UserRegistrationStatus : ValueObject +{ + public static UserRegistrationStatus WaitingForConfirmation => + new UserRegistrationStatus(nameof(WaitingForConfirmation)); + + public static UserRegistrationStatus Confirmed => new UserRegistrationStatus(nameof(Confirmed)); + + public static UserRegistrationStatus Expired => new UserRegistrationStatus(nameof(Expired)); + + public string Value { get; } + + private UserRegistrationStatus(string value) + { + Value = value; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/Users/ApplicationUser.cs b/src/Modules/UserAccessMI/Domain/Users/ApplicationUser.cs new file mode 100644 index 000000000..986f26e9e --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/Users/ApplicationUser.cs @@ -0,0 +1,76 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users.Events; +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; + +public class ApplicationUser : IdentityUser, IEntity +{ + private List? _domainEvents; + + public ApplicationUser(string userName) + : base(userName) + { + AddDomainEvent(new UserCreatedDomainEvent(Id)); + } + + private ApplicationUser() + : base() + { + // Only EF. + } + + internal static ApplicationUser CreateFromUserRegistration( + UserRegistrationId userRegistrationId, + string login, + string email, + string firstName, + string lastName, + string name) + { + return new ApplicationUser(login) + { + Id = userRegistrationId.Value, + FirstName = firstName, + LastName = lastName, + Name = name, + Email = email + }; + } + + /// + /// Domain events occurred. + /// + public IReadOnlyCollection? DomainEvents => _domainEvents?.AsReadOnly(); + + public void ClearDomainEvents() + { + _domainEvents?.Clear(); + } + + /// + /// Add domain event. + /// + /// Domain event. + protected void AddDomainEvent(IDomainEvent domainEvent) + { + _domainEvents ??= new List(); + + _domainEvents.Add(domainEvent); + } + + protected void CheckRule(IBusinessRule rule) + { + if (rule.IsBroken()) + { + throw new BusinessRuleValidationException(rule); + } + } + + public virtual string? Name { get; set; } + + public virtual string? FirstName { get; set; } + + public virtual string? LastName { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/Users/Events/UserCreatedDomainEvent.cs b/src/Modules/UserAccessMI/Domain/Users/Events/UserCreatedDomainEvent.cs new file mode 100644 index 000000000..226c3eb21 --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/Users/Events/UserCreatedDomainEvent.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.BuildingBlocks.Domain; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users.Events; + +public class UserCreatedDomainEvent : DomainEventBase +{ + public UserCreatedDomainEvent(Guid id) + { + Id = id; + } + + public new Guid Id { get; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/Users/Role.cs b/src/Modules/UserAccessMI/Domain/Users/Role.cs new file mode 100644 index 000000000..3b329367c --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/Users/Role.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; + +public class Role : IdentityRole +{ + public Role() + : base() + { + } + + public Role(string roleName) + : base(roleName) + { + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Domain/Users/UserRole.cs b/src/Modules/UserAccessMI/Domain/Users/UserRole.cs new file mode 100644 index 000000000..78ae1221a --- /dev/null +++ b/src/Modules/UserAccessMI/Domain/Users/UserRole.cs @@ -0,0 +1,22 @@ +using CSharpFunctionalExtensions; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; + +public class UserRole : ValueObject +{ + public static UserRole Member => new UserRole(nameof(Member)); + + public static UserRole Administrator => new UserRole(nameof(Administrator)); + + public string Value { get; } + + private UserRole(string value) + { + Value = value; + } + + protected override IEnumerable GetEqualityComponents() + { + yield return Value; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.csproj b/src/Modules/UserAccessMI/Infrastructure/CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.csproj new file mode 100644 index 000000000..c3ef5f2be --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.csproj @@ -0,0 +1,16 @@ + + + enable + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/AllConstructorFinder.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/AllConstructorFinder.cs new file mode 100644 index 000000000..a5c14e29e --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/AllConstructorFinder.cs @@ -0,0 +1,20 @@ +using System.Collections.Concurrent; +using System.Reflection; +using Autofac.Core.Activators.Reflection; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; + +internal class AllConstructorFinder : IConstructorFinder +{ + private static readonly ConcurrentDictionary Cache = + new ConcurrentDictionary(); + + public ConstructorInfo[] FindConstructors(Type targetType) + { + var result = Cache.GetOrAdd( + targetType, + t => t.GetTypeInfo().DeclaredConstructors.ToArray()); + + return result.Length > 0 ? result : throw new NoConstructorsFoundException(targetType, this); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Assemblies.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Assemblies.cs new file mode 100644 index 000000000..d4ed28b97 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Assemblies.cs @@ -0,0 +1,9 @@ +using System.Reflection; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; + +internal static class Assemblies +{ + public static readonly Assembly Application = typeof(IUserAccessModule).Assembly; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/DataAccess/DataAccessModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/DataAccess/DataAccessModule.cs new file mode 100644 index 000000000..15cc5e191 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/DataAccess/DataAccessModule.cs @@ -0,0 +1,41 @@ +using Autofac; +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; +using Microsoft.EntityFrameworkCore; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.DataAccess; + +internal class DataAccessModule : Autofac.Module +{ + private readonly string _databaseConnectionString; + + internal DataAccessModule(string databaseConnectionString) + { + _databaseConnectionString = databaseConnectionString; + } + + protected override void Load(ContainerBuilder builder) + { + builder.Register(x => new DatabaseConfiguration(_databaseConnectionString)) + .As(); + + builder.RegisterType() + .As() + .WithParameter("connectionString", _databaseConnectionString) + .InstancePerLifetimeScope(); + + builder + .RegisterType() + .AsSelf() + .As() + .InstancePerLifetimeScope(); + + var infrastructureAssembly = typeof(UserAccessContext).Assembly; + + builder.RegisterAssemblyTypes(infrastructureAssembly) + .Where(type => type.Name.EndsWith("Repository")) + .AsImplementedInterfaces() + .InstancePerLifetimeScope() + .FindConstructorsWith(new AllConstructorFinder()); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Domain/DomainModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Domain/DomainModule.cs new file mode 100644 index 000000000..337ea156e --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Domain/DomainModule.cs @@ -0,0 +1,16 @@ +using Autofac; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; + +namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Domain +{ + internal class DomainModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Email/EmailModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Email/EmailModule.cs new file mode 100644 index 000000000..7d01c7b34 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Email/EmailModule.cs @@ -0,0 +1,34 @@ +using Autofac; +using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Emails; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Email; + +internal class EmailModule : Module +{ + private readonly IEmailSender _emailSender; + private readonly EmailsConfiguration _configuration; + + public EmailModule( + EmailsConfiguration configuration, + IEmailSender emailSender) + { + _configuration = configuration; + _emailSender = emailSender; + } + + protected override void Load(ContainerBuilder builder) + { + if (_emailSender != null) + { + builder.RegisterInstance(_emailSender); + } + else + { + builder.RegisterType() + .As() + .WithParameter("configuration", _configuration) + .InstancePerLifetimeScope(); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/EventsBusModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/EventsBusModule.cs new file mode 100644 index 000000000..e8500a373 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/EventsBusModule.cs @@ -0,0 +1,28 @@ +using Autofac; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.EventsBus; + +internal class EventsBusModule : Autofac.Module +{ + private readonly IEventsBus _eventsBus; + + public EventsBusModule(IEventsBus eventsBus) + { + _eventsBus = eventsBus; + } + + protected override void Load(ContainerBuilder builder) + { + if (_eventsBus != null) + { + builder.RegisterInstance(_eventsBus).SingleInstance(); + } + else + { + builder.RegisterType() + .As() + .SingleInstance(); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs new file mode 100644 index 000000000..f56f77d3e --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/EventsBusStartup.cs @@ -0,0 +1,30 @@ +using Autofac; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; +using CompanyName.MyMeetings.Modules.Meetings.IntegrationEvents; +using Serilog; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.EventsBus; + +public static class EventsBusStartup +{ + public static void Initialize( + ILogger logger) + { + SubscribeToIntegrationEvents(logger); + } + + private static void SubscribeToIntegrationEvents(ILogger logger) + { + var eventBus = UserAccessCompositionRoot.BeginLifetimeScope().Resolve(); + + SubscribeToIntegrationEvent(eventBus, logger); + } + + private static void SubscribeToIntegrationEvent(IEventsBus eventBus, ILogger logger) + where T : IntegrationEvent + { + logger.Information("Subscribe to {@IntegrationEvent}", typeof(T).FullName); + eventBus.Subscribe( + new IntegrationEventGenericHandler()); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/IntegrationEventGenericHandler.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/IntegrationEventGenericHandler.cs new file mode 100644 index 000000000..b69020798 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/EventsBus/IntegrationEventGenericHandler.cs @@ -0,0 +1,38 @@ +using Autofac; +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Serialization; +using Dapper; +using Newtonsoft.Json; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.EventsBus; + +internal class IntegrationEventGenericHandler : IIntegrationEventHandler + where T : IntegrationEvent +{ + public async Task Handle(T @event) + { + using (var scope = UserAccessCompositionRoot.BeginLifetimeScope()) + { + using (var connection = scope.Resolve().GetOpenConnection()) + { + string type = @event.GetType().FullName!; + var data = JsonConvert.SerializeObject(@event, new JsonSerializerSettings + { + ContractResolver = new AllPropertiesContractResolver() + }); + + var sql = "INSERT INTO [usersmi].[InboxMessages] (Id, OccurredOn, Type, Data) " + + "VALUES (@Id, @OccurredOn, @Type, @Data)"; + + await connection.ExecuteScalarAsync(sql, new + { + @event.Id, + @event.OccurredOn, + type, + data + }); + } + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/IUserAccessConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/IUserAccessConfiguration.cs new file mode 100644 index 000000000..8dbc85ede --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/IUserAccessConfiguration.cs @@ -0,0 +1,30 @@ +using System.Text; +using Microsoft.IdentityModel.Tokens; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; + +public interface IUserAccessConfiguration +{ + public Security? Security { get; set; } +} + +public static class UserAccessConfigurationExtensions +{ + public static string? GetValidAudience(this IUserAccessConfiguration configuration) + => configuration.Security?.JwtAudience; + + public static bool ShouldValidateAudience(this IUserAccessConfiguration configuration) + => !string.IsNullOrEmpty(configuration.GetValidAudience()); + + public static string? GetValidIssuer(this IUserAccessConfiguration configuration) + => configuration.Security?.JwtIssuer; + + public static bool ShouldValidateIssuer(this IUserAccessConfiguration configuration) + => !string.IsNullOrEmpty(configuration.GetValidIssuer()); + + public static byte[] GetJwtSecretKeyEncrypted(this IUserAccessConfiguration configuration) + => Encoding.ASCII.GetBytes(configuration.Security?.JwtSecretKey ?? string.Empty); + + public static SymmetricSecurityKey GetIssuerSigningKey(this IUserAccessConfiguration configuration) + => new SymmetricSecurityKey(configuration.GetJwtSecretKeyEncrypted()); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/DoesNotContainPasswordValidator.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/DoesNotContainPasswordValidator.cs new file mode 100644 index 000000000..758b7ab76 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/DoesNotContainPasswordValidator.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Identity; + +public class DoesNotContainPasswordValidator : IPasswordValidator + where TUser : class +{ + public async Task ValidateAsync(UserManager manager, TUser user, string? password) + { + if (password is null) + { + return IdentityResult.Success; + } + + var username = await manager.GetUserNameAsync(user); + + if (username == password) + { + return IdentityResult.Failed(new IdentityError() { Description = "Password cannot contain username." }); + } + + if (password.Contains("password")) + { + return IdentityResult.Failed(new IdentityError() { Description = "Password cannot contain password." }); + } + + return IdentityResult.Success; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/EmailConfirmationTokenProvider.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/EmailConfirmationTokenProvider.cs new file mode 100644 index 000000000..627238d7f --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/EmailConfirmationTokenProvider.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Identity; + +public class EmailConfirmationTokenProvider : DataProtectorTokenProvider + where TUser : class +{ + /// + /// Initializes a new instance of the class. + /// + /// The system data protection provider. + /// The configured . + /// The logger. + public EmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider, IOptions options, ILogger> logger) + : base(dataProtectionProvider, options, logger) + { + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/EmailConfirmationTokenProviderOptions.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/EmailConfirmationTokenProviderOptions.cs new file mode 100644 index 000000000..eba083003 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/EmailConfirmationTokenProviderOptions.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Identity; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Identity; + +public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/IdentityModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/IdentityModule.cs new file mode 100644 index 000000000..a9f9dbf47 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Identity/IdentityModule.cs @@ -0,0 +1,47 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Identity; + +internal class IdentityModule : Autofac.Module +{ + protected override void Load(ContainerBuilder builder) + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddIdentity(options => + { + options.SignIn.RequireConfirmedEmail = false; + + // Configure password policy + options.Password.RequiredUniqueChars = 1; + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireUppercase = false; + options.Password.RequiredLength = 1; + options.Password.RequireNonAlphanumeric = false; + + // Configure user policy + options.User.RequireUniqueEmail = false; + + // Protecting against brute-force attacks with user lockout + options.Lockout.AllowedForNewUsers = true; + options.Lockout.MaxFailedAccessAttempts = 5; + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders() + .AddPasswordValidator>(); + + serviceCollection.AddOptions(); + serviceCollection.Configure(options => + options.TokenLifespan = TimeSpan.FromHours(3)); + serviceCollection.Configure(options => + options.TokenLifespan = TimeSpan.FromDays(2)); + + builder.Populate(serviceCollection); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Logging/LoggingModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Logging/LoggingModule.cs new file mode 100644 index 000000000..d2a428d42 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Logging/LoggingModule.cs @@ -0,0 +1,21 @@ +using Autofac; +using Serilog; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Logging; + +internal class LoggingModule : Autofac.Module +{ + private readonly ILogger _logger; + + internal LoggingModule(ILogger logger) + { + _logger = logger; + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterInstance(_logger) + .As() + .SingleInstance(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Mediation/MediatorModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Mediation/MediatorModule.cs new file mode 100644 index 000000000..2210bf0fd --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Mediation/MediatorModule.cs @@ -0,0 +1,92 @@ +using System.Reflection; +using Autofac; +using Autofac.Core; +using Autofac.Features.Variance; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using FluentValidation; +using MediatR; +using MediatR.Pipeline; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Mediation; + +public class MediatorModule : Autofac.Module +{ + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .InstancePerDependency() + .IfNotRegistered(typeof(IServiceProvider)); + + builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly) + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + + var mediatorOpenTypes = new[] + { + typeof(IRequestHandler<,>), + typeof(INotificationHandler<>), + typeof(IValidator<>), + typeof(IRequestPreProcessor<>), + typeof(IRequestHandler<>), + typeof(IStreamRequestHandler<,>), + typeof(IRequestPostProcessor<,>), + typeof(IRequestExceptionHandler<,,>), + typeof(IRequestExceptionAction<,>), + typeof(ICommandHandler<>), + typeof(ICommandHandler<,>), + }; + builder.RegisterSource(new ScopedContravariantRegistrationSource( + mediatorOpenTypes)); + foreach (var mediatorOpenType in mediatorOpenTypes) + { + builder + .RegisterAssemblyTypes(Assemblies.Application, ThisAssembly) + .AsClosedTypesOf(mediatorOpenType) + .AsImplementedInterfaces() + .FindConstructorsWith(new AllConstructorFinder()); + } + + builder.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); + builder.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); + } + + private class ScopedContravariantRegistrationSource : IRegistrationSource + { + private readonly ContravariantRegistrationSource _source = new(); + private readonly List _types = new(); + + public ScopedContravariantRegistrationSource(params Type[] types) + { + ArgumentNullException.ThrowIfNull(types); + + if (!types.All(x => x.IsGenericTypeDefinition)) + { + throw new ArgumentException("Supplied types should be generic type definitions"); + } + + _types.AddRange(types); + } + + public IEnumerable RegistrationsFor( + Service service, + Func> registrationAccessor) + { + var components = _source.RegistrationsFor(service, registrationAccessor); + foreach (var c in components) + { + var defs = c.Target.Services + .OfType() + .Select(x => x.ServiceType.GetGenericTypeDefinition()); + + if (defs.Any(_types.Contains)) + { + yield return c; + } + } + } + + public bool IsAdapterForIndividualComponents => _source.IsAdapterForIndividualComponents; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/CommandsExecutor.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/CommandsExecutor.cs new file mode 100644 index 000000000..032700dbc --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/CommandsExecutor.cs @@ -0,0 +1,26 @@ +using Autofac; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +internal static class CommandsExecutor +{ + internal static async Task Execute(ICommand command) + { + using (var scope = UserAccessCompositionRoot.BeginLifetimeScope()) + { + var mediator = scope.Resolve(); + await mediator.Send(command); + } + } + + internal static async Task Execute(ICommand command) + { + using (var scope = UserAccessCompositionRoot.BeginLifetimeScope()) + { + var mediator = scope.Resolve(); + return await mediator.Send(command); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/IRecurringCommand.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/IRecurringCommand.cs new file mode 100644 index 000000000..a5032a5c1 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/IRecurringCommand.cs @@ -0,0 +1,5 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +public interface IRecurringCommand +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/InboxMessageDto.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/InboxMessageDto.cs new file mode 100644 index 000000000..66ee600a0 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/InboxMessageDto.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Inbox; + +public class InboxMessageDto +{ + public Guid Id { get; set; } + + public string? Type { get; set; } + + public string? Data { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommand.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommand.cs new file mode 100644 index 000000000..3a82618bc --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommand.cs @@ -0,0 +1,5 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Inbox; + +public class ProcessInboxCommand : UserAccessMI.Application.Contracts.CommandBase, IRecurringCommand +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommandHandler.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommandHandler.cs new file mode 100644 index 000000000..67c956019 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxCommandHandler.cs @@ -0,0 +1,62 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using Dapper; +using MediatR; +using Newtonsoft.Json; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Inbox; + +internal class ProcessInboxCommandHandler : ICommandHandler +{ + private readonly IMediator _mediator; + private readonly ISqlConnectionFactory _sqlConnectionFactory; + + public ProcessInboxCommandHandler(IMediator mediator, ISqlConnectionFactory sqlConnectionFactory) + { + _mediator = mediator; + _sqlConnectionFactory = sqlConnectionFactory; + } + + public async Task Handle(ProcessInboxCommand command, CancellationToken cancellationToken) + { + var connection = this._sqlConnectionFactory.GetOpenConnection(); + string sql = "SELECT " + + $"[InboxMessage].[Id] AS [{nameof(InboxMessageDto.Id)}], " + + $"[InboxMessage].[Type] AS [{nameof(InboxMessageDto.Type)}], " + + $"[InboxMessage].[Data] AS [{nameof(InboxMessageDto.Data)}] " + + "FROM [usersmi].[InboxMessages] AS [InboxMessage] " + + "WHERE [InboxMessage].[ProcessedDate] IS NULL " + + "ORDER BY [InboxMessage].[OccurredOn]"; + + var messages = await connection.QueryAsync(sql); + + const string sqlUpdateProcessedDate = "UPDATE [usersmi].[InboxMessages] " + + "SET [ProcessedDate] = @Date " + + "WHERE [Id] = @Id"; + + foreach (var message in messages) + { + var messageAssembly = AppDomain.CurrentDomain.GetAssemblies() + .SingleOrDefault(assembly => message.Type!.Contains(assembly.GetName().Name!)); + + Type type = messageAssembly!.GetType(message.Type!)!; + var request = JsonConvert.DeserializeObject(message.Data!, type); + + try + { + await _mediator.Publish((INotification)request!, cancellationToken); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + await connection.ExecuteAsync(sqlUpdateProcessedDate, new + { + Date = DateTime.UtcNow, + message.Id + }); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxJob.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxJob.cs new file mode 100644 index 000000000..64a8c3489 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Inbox/ProcessInboxJob.cs @@ -0,0 +1,12 @@ +using Quartz; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Inbox; + +[DisallowConcurrentExecution] +public class ProcessInboxJob : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + await CommandsExecutor.Execute(new ProcessInboxCommand()); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/CommandsScheduler.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/CommandsScheduler.cs new file mode 100644 index 000000000..45a95bf11 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/CommandsScheduler.cs @@ -0,0 +1,56 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Serialization; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using Dapper; +using Newtonsoft.Json; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.InternalCommands; + +public class CommandsScheduler : ICommandsScheduler +{ + private readonly ISqlConnectionFactory _sqlConnectionFactory; + + public CommandsScheduler(ISqlConnectionFactory sqlConnectionFactory) + { + _sqlConnectionFactory = sqlConnectionFactory; + } + + public async Task EnqueueAsync(ICommand command) + { + var connection = this._sqlConnectionFactory.GetOpenConnection(); + + const string sqlInsert = "INSERT INTO [usersmi].[InternalCommands] ([Id], [EnqueueDate] , [Type], [Data]) VALUES " + + "(@Id, @EnqueueDate, @Type, @Data)"; + + await connection.ExecuteAsync(sqlInsert, new + { + command.Id, + EnqueueDate = DateTime.UtcNow, + Type = command.GetType().FullName, + Data = JsonConvert.SerializeObject(command, new JsonSerializerSettings + { + ContractResolver = new AllPropertiesContractResolver() + }) + }); + } + + public async Task EnqueueAsync(ICommand command) + { + var connection = this._sqlConnectionFactory.GetOpenConnection(); + + const string sqlInsert = "INSERT INTO [usersmi].[InternalCommands] ([Id], [EnqueueDate] , [Type], [Data]) VALUES " + + "(@Id, @EnqueueDate, @Type, @Data)"; + + await connection.ExecuteAsync(sqlInsert, new + { + command.Id, + EnqueueDate = DateTime.UtcNow, + Type = command.GetType().FullName, + Data = JsonConvert.SerializeObject(command, new JsonSerializerSettings + { + ContractResolver = new AllPropertiesContractResolver() + }) + }); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommand.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommand.cs new file mode 100644 index 000000000..f517e086e --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommand.cs @@ -0,0 +1,5 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.InternalCommands; + +internal class ProcessInternalCommandsCommand : UserAccessMI.Application.Contracts.CommandBase, IRecurringCommand +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommandHandler.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommandHandler.cs new file mode 100644 index 000000000..09717b350 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsCommandHandler.cs @@ -0,0 +1,82 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using Dapper; +using Newtonsoft.Json; +using Polly; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.InternalCommands; + +internal class ProcessInternalCommandsCommandHandler : ICommandHandler +{ + private readonly ISqlConnectionFactory _sqlConnectionFactory; + + public ProcessInternalCommandsCommandHandler( + ISqlConnectionFactory sqlConnectionFactory) + { + _sqlConnectionFactory = sqlConnectionFactory; + } + + public async Task Handle(ProcessInternalCommandsCommand command, CancellationToken cancellationToken) + { + var connection = this._sqlConnectionFactory.GetOpenConnection(); + + string sql = "SELECT " + + $"[Command].[Id] AS [{nameof(InternalCommandDto.Id)}], " + + $"[Command].[Type] AS [{nameof(InternalCommandDto.Type)}], " + + $"[Command].[Data] AS [{nameof(InternalCommandDto.Data)}] " + + "FROM [usersmi].[InternalCommands] AS [Command] " + + "WHERE [Command].[ProcessedDate] IS NULL " + + "ORDER BY [Command].[EnqueueDate]"; + var commands = await connection.QueryAsync(sql); + + var internalCommandsList = commands.AsList(); + + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(3) + }); + + foreach (var internalCommand in internalCommandsList) + { + var result = await policy.ExecuteAndCaptureAsync(() => ProcessCommand( + internalCommand)); + + if (result.Outcome == OutcomeType.Failure) + { + await connection.ExecuteScalarAsync( + "UPDATE [usersmi].[InternalCommands] " + + "SET ProcessedDate = @NowDate, " + + "Error = @Error " + + "WHERE [Id] = @Id", + new + { + NowDate = DateTime.UtcNow, + Error = result.FinalException.ToString(), + internalCommand.Id + }); + } + } + } + + private async Task ProcessCommand( + InternalCommandDto internalCommand) + { + Type type = Assemblies.Application.GetType(internalCommand.Type!)!; + dynamic commandToProcess = JsonConvert.DeserializeObject(internalCommand.Data!, type)!; + + await CommandsExecutor.Execute(commandToProcess); + } + + private class InternalCommandDto + { + public Guid Id { get; set; } + + public string? Type { get; set; } + + public string? Data { get; set; } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsJob.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsJob.cs new file mode 100644 index 000000000..ed210d250 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/InternalCommands/ProcessInternalCommandsJob.cs @@ -0,0 +1,12 @@ +using Quartz; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.InternalCommands; + +[DisallowConcurrentExecution] +public class ProcessInternalCommandsJob : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + await CommandsExecutor.Execute(new ProcessInternalCommandsCommand()); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/LoggingCommandHandlerDecorator.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/LoggingCommandHandlerDecorator.cs new file mode 100644 index 000000000..8a92a71ca --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/LoggingCommandHandlerDecorator.cs @@ -0,0 +1,89 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using Serilog; +using Serilog.Context; +using Serilog.Core; +using Serilog.Events; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +internal class LoggingCommandHandlerDecorator : ICommandHandler + where T : Application.Contracts.ICommand +{ + private readonly ILogger _logger; + private readonly IExecutionContextAccessor _executionContextAccessor; + private readonly ICommandHandler _decorated; + + public LoggingCommandHandlerDecorator( + ILogger logger, + IExecutionContextAccessor executionContextAccessor, + ICommandHandler decorated) + { + _logger = logger; + _executionContextAccessor = executionContextAccessor; + _decorated = decorated; + } + + public async Task Handle(T command, CancellationToken cancellationToken) + { + if (command is IRecurringCommand) + { + await _decorated.Handle(command, cancellationToken); + } + + using ( + LogContext.Push( + new LoggingCommandHandlerDecorator.RequestLogEnricher(_executionContextAccessor), + new LoggingCommandHandlerDecorator.CommandLogEnricher(command))) + { + try + { + this._logger.Information( + "Executing command {Command}", + command.GetType().Name); + + await _decorated.Handle(command, cancellationToken); + + this._logger.Information("Command {Command} processed successful", command.GetType().Name); + } + catch (Exception exception) + { + this._logger.Error(exception, "Command {Command} processing failed", command.GetType().Name); + throw; + } + } + } + + private class CommandLogEnricher : ILogEventEnricher + { + private readonly Application.Contracts.ICommand _command; + + public CommandLogEnricher(Application.Contracts.ICommand command) + { + _command = command; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddOrUpdateProperty(new LogEventProperty("Context", new ScalarValue($"Command:{_command.Id.ToString()}"))); + } + } + + private class RequestLogEnricher : ILogEventEnricher + { + private readonly IExecutionContextAccessor _executionContextAccessor; + + public RequestLogEnricher(IExecutionContextAccessor executionContextAccessor) + { + _executionContextAccessor = executionContextAccessor; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + if (_executionContextAccessor.IsAvailable) + { + logEvent.AddOrUpdateProperty(new LogEventProperty("CorrelationId", new ScalarValue(_executionContextAccessor.CorrelationId))); + } + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/LoggingCommandHandlerWithResultDecorator.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/LoggingCommandHandlerWithResultDecorator.cs new file mode 100644 index 000000000..a6743be2a --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/LoggingCommandHandlerWithResultDecorator.cs @@ -0,0 +1,87 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using Serilog; +using Serilog.Context; +using Serilog.Core; +using Serilog.Events; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +internal class LoggingCommandHandlerWithResultDecorator : ICommandHandler + where T : ICommand +{ + private readonly ILogger _logger; + private readonly IExecutionContextAccessor _executionContextAccessor; + private readonly ICommandHandler _decorated; + + public LoggingCommandHandlerWithResultDecorator( + ILogger logger, + IExecutionContextAccessor executionContextAccessor, + ICommandHandler decorated) + { + _logger = logger; + _executionContextAccessor = executionContextAccessor; + _decorated = decorated; + } + + public async Task Handle(T command, CancellationToken cancellationToken) + { + using ( + LogContext.Push( + new LoggingCommandHandlerWithResultDecorator.RequestLogEnricher(_executionContextAccessor), + new LoggingCommandHandlerWithResultDecorator.CommandLogEnricher(command))) + { + try + { + this._logger.Information( + "Executing command {@Command}", + command); + + var result = await _decorated.Handle(command, cancellationToken); + + this._logger.Information("Command processed successful, result {Result}", result); + + return result; + } + catch (Exception exception) + { + this._logger.Error(exception, "Command processing failed"); + throw; + } + } + } + + private class CommandLogEnricher : ILogEventEnricher + { + private readonly ICommand _command; + + public CommandLogEnricher(ICommand command) + { + _command = command; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddOrUpdateProperty(new LogEventProperty("Context", new ScalarValue($"Command:{_command.Id.ToString()}"))); + } + } + + private class RequestLogEnricher : ILogEventEnricher + { + private readonly IExecutionContextAccessor _executionContextAccessor; + + public RequestLogEnricher(IExecutionContextAccessor executionContextAccessor) + { + _executionContextAccessor = executionContextAccessor; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + if (_executionContextAccessor.IsAvailable) + { + logEvent.AddOrUpdateProperty(new LogEventProperty("CorrelationId", new ScalarValue(_executionContextAccessor.CorrelationId))); + } + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/OutboxMessageDto.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/OutboxMessageDto.cs new file mode 100644 index 000000000..0a749ed1d --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/OutboxMessageDto.cs @@ -0,0 +1,10 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Outbox; + +public class OutboxMessageDto +{ + public Guid Id { get; set; } + + public string? Type { get; set; } + + public string? Data { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/OutboxModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/OutboxModule.cs new file mode 100644 index 000000000..a5092acb2 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/OutboxModule.cs @@ -0,0 +1,59 @@ +using Autofac; +using CompanyName.MyMeetings.BuildingBlocks.Application.Events; +using CompanyName.MyMeetings.BuildingBlocks.Application.Outbox; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.DomainEventsDispatching; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Outbox; +using Module = Autofac.Module; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Outbox; + +internal class OutboxModule : Module +{ + private readonly BiDictionary _domainNotificationsMap; + + public OutboxModule(BiDictionary domainNotificationsMap) + { + _domainNotificationsMap = domainNotificationsMap; + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .FindConstructorsWith(new AllConstructorFinder()) + .InstancePerLifetimeScope(); + + CheckMappings(); + + builder.RegisterType() + .As() + .FindConstructorsWith(new AllConstructorFinder()) + .WithParameter("domainNotificationsMap", _domainNotificationsMap) + .SingleInstance(); + } + + private void CheckMappings() + { + var domainEventNotifications = Assemblies.Application + .GetTypes() + .Where(x => x.GetInterfaces().Contains(typeof(IDomainEventNotification))) + .ToList(); + + List notMappedNotifications = new List(); + foreach (var domainEventNotification in domainEventNotifications) + { + _domainNotificationsMap.TryGetBySecond(domainEventNotification, out var name); + + if (name == null) + { + notMappedNotifications.Add(domainEventNotification); + } + } + + if (notMappedNotifications.Any()) + { + throw new ApplicationException($"Domain Event Notifications {notMappedNotifications.Select(x => x.FullName).Aggregate((x, y) => x + "," + y)} not mapped"); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommand.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommand.cs new file mode 100644 index 000000000..c2080719d --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommand.cs @@ -0,0 +1,5 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Outbox; + +public class ProcessOutboxCommand : UserAccessMI.Application.Contracts.CommandBase, IRecurringCommand +{ +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommandHandler.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommandHandler.cs new file mode 100644 index 000000000..70d18c40b --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxCommandHandler.cs @@ -0,0 +1,84 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.BuildingBlocks.Application.Events; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.DomainEventsDispatching; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using Dapper; +using MediatR; +using Newtonsoft.Json; +using Serilog.Context; +using Serilog.Core; +using Serilog.Events; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Outbox; + +internal class ProcessOutboxCommandHandler : ICommandHandler +{ + private readonly IMediator _mediator; + + private readonly ISqlConnectionFactory _sqlConnectionFactory; + + private readonly IDomainNotificationsMapper _domainNotificationsMapper; + + public ProcessOutboxCommandHandler( + IMediator mediator, + ISqlConnectionFactory sqlConnectionFactory, + IDomainNotificationsMapper domainNotificationsMapper) + { + _mediator = mediator; + _sqlConnectionFactory = sqlConnectionFactory; + _domainNotificationsMapper = domainNotificationsMapper; + } + + public async Task Handle(ProcessOutboxCommand command, CancellationToken cancellationToken) + { + var connection = this._sqlConnectionFactory.GetOpenConnection(); + string sql = "SELECT " + + $"[OutboxMessage].[Id] AS [{nameof(OutboxMessageDto.Id)}], " + + $"[OutboxMessage].[Type] AS [{nameof(OutboxMessageDto.Type)}], " + + $"[OutboxMessage].[Data] AS [{nameof(OutboxMessageDto.Data)}] " + + "FROM [usersmi].[OutboxMessages] AS [OutboxMessage] " + + "WHERE [OutboxMessage].[ProcessedDate] IS NULL " + + "ORDER BY [OutboxMessage].[OccurredOn]"; + + var messages = await connection.QueryAsync(sql); + var messagesList = messages.AsList(); + + const string sqlUpdateProcessedDate = "UPDATE [usersmi].[OutboxMessages] " + + "SET [ProcessedDate] = @Date " + + "WHERE [Id] = @Id"; + if (messagesList.Count > 0) + { + foreach (var message in messagesList) + { + var type = _domainNotificationsMapper.GetType(message.Type); + var @event = JsonConvert.DeserializeObject(message.Data!, type) as IDomainEventNotification; + + using (LogContext.Push(new OutboxMessageContextEnricher(@event!))) + { + await this._mediator.Publish(@event!, cancellationToken); + + await connection.ExecuteAsync(sqlUpdateProcessedDate, new + { + Date = DateTime.UtcNow, + message.Id + }); + } + } + } + } + + private class OutboxMessageContextEnricher : ILogEventEnricher + { + private readonly IDomainEventNotification _notification; + + public OutboxMessageContextEnricher(IDomainEventNotification notification) + { + _notification = notification; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddOrUpdateProperty(new LogEventProperty("Context", new ScalarValue($"OutboxMessage:{_notification.Id.ToString()}"))); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxJob.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxJob.cs new file mode 100644 index 000000000..f2569921e --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/Outbox/ProcessOutboxJob.cs @@ -0,0 +1,12 @@ +using Quartz; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Outbox; + +[DisallowConcurrentExecution] +public class ProcessOutboxJob : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + await CommandsExecutor.Execute(new ProcessOutboxCommand()); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ProcessingModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ProcessingModule.cs new file mode 100644 index 000000000..c908a1afc --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ProcessingModule.cs @@ -0,0 +1,68 @@ +using Autofac; +using CompanyName.MyMeetings.BuildingBlocks.Application.Events; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.DomainEventsDispatching; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.InternalCommands; +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +internal class ProcessingModule : Autofac.Module +{ + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + + builder.RegisterGenericDecorator( + typeof(UnitOfWorkCommandHandlerDecorator<>), + typeof(ICommandHandler<>)); + + builder.RegisterGenericDecorator( + typeof(UnitOfWorkCommandHandlerWithResultDecorator<,>), + typeof(ICommandHandler<,>)); + + builder.RegisterGenericDecorator( + typeof(ValidationCommandHandlerDecorator<>), + typeof(ICommandHandler<>)); + + builder.RegisterGenericDecorator( + typeof(ValidationCommandHandlerWithResultDecorator<,>), + typeof(ICommandHandler<,>)); + + builder.RegisterGenericDecorator( + typeof(LoggingCommandHandlerDecorator<>), + typeof(IRequestHandler<>)); + + builder.RegisterGenericDecorator( + typeof(LoggingCommandHandlerWithResultDecorator<,>), + typeof(IRequestHandler<,>)); + + builder.RegisterGenericDecorator( + typeof(DomainEventsDispatcherNotificationHandlerDecorator<>), + typeof(INotificationHandler<>)); + + builder.RegisterAssemblyTypes(Assemblies.Application) + .AsClosedTypesOf(typeof(IDomainEventNotification<>)) + .InstancePerDependency() + .FindConstructorsWith(new AllConstructorFinder()); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerDecorator.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerDecorator.cs new file mode 100644 index 000000000..2f2ca2513 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerDecorator.cs @@ -0,0 +1,41 @@ +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using Microsoft.EntityFrameworkCore; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +internal class UnitOfWorkCommandHandlerDecorator : ICommandHandler + where T : ICommand +{ + private readonly ICommandHandler _decorated; + private readonly IUnitOfWork _unitOfWork; + private readonly UserAccessContext _userAccessContext; + + public UnitOfWorkCommandHandlerDecorator( + ICommandHandler decorated, + IUnitOfWork unitOfWork, + UserAccessContext userAccessContext) + { + _decorated = decorated; + _unitOfWork = unitOfWork; + _userAccessContext = userAccessContext; + } + + public async Task Handle(T command, CancellationToken cancellationToken) + { + await this._decorated.Handle(command, cancellationToken); + + if (command is InternalCommandBase) + { + var internalCommand = await _userAccessContext.InternalCommands.FirstOrDefaultAsync(x => x.Id == command.Id, cancellationToken: cancellationToken); + + if (internalCommand != null) + { + internalCommand.ProcessedDate = DateTime.UtcNow; + } + } + + await this._unitOfWork.CommitAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs new file mode 100644 index 000000000..38cae2a00 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs @@ -0,0 +1,43 @@ +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using Microsoft.EntityFrameworkCore; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +internal class UnitOfWorkCommandHandlerWithResultDecorator : ICommandHandler + where T : ICommand +{ + private readonly ICommandHandler _decorated; + private readonly IUnitOfWork _unitOfWork; + private readonly UserAccessContext _userAccessContext; + + public UnitOfWorkCommandHandlerWithResultDecorator( + ICommandHandler decorated, + IUnitOfWork unitOfWork, + UserAccessContext userAccessContext) + { + _decorated = decorated; + _unitOfWork = unitOfWork; + _userAccessContext = userAccessContext; + } + + public async Task Handle(T command, CancellationToken cancellationToken) + { + var result = await this._decorated.Handle(command, cancellationToken); + + if (command is InternalCommandBase) + { + var internalCommand = await _userAccessContext.InternalCommands.FirstOrDefaultAsync(x => x.Id == command.Id, cancellationToken: cancellationToken); + + if (internalCommand != null) + { + internalCommand.ProcessedDate = DateTime.UtcNow; + } + } + + await this._unitOfWork.CommitAsync(cancellationToken); + + return result; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs new file mode 100644 index 000000000..3886cf7fa --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ValidationCommandHandlerDecorator.cs @@ -0,0 +1,37 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using FluentValidation; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +internal class ValidationCommandHandlerDecorator : ICommandHandler + where T : ICommand +{ + private readonly IList> _validators; + private readonly ICommandHandler _decorated; + + public ValidationCommandHandlerDecorator( + IList> validators, + ICommandHandler decorated) + { + this._validators = validators; + _decorated = decorated; + } + + public async Task Handle(T command, CancellationToken cancellationToken) + { + var errors = _validators + .Select(v => v.Validate(command)) + .SelectMany(result => result.Errors) + .Where(error => error != null) + .ToList(); + + if (errors.Any()) + { + throw new InvalidCommandException(errors.Select(x => x.ErrorMessage).ToList()); + } + + await _decorated.Handle(command, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ValidationCommandHandlerWithResultDecorator.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ValidationCommandHandlerWithResultDecorator.cs new file mode 100644 index 000000000..d9f2158f8 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Processing/ValidationCommandHandlerWithResultDecorator.cs @@ -0,0 +1,38 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Configuration.Commands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using FluentValidation; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; + +internal class ValidationCommandHandlerWithResultDecorator : ICommandHandler + where T : ICommand +{ + private readonly IList> _validators; + + private readonly ICommandHandler _decorated; + + public ValidationCommandHandlerWithResultDecorator( + IList> validators, + ICommandHandler decorated) + { + this._validators = validators; + _decorated = decorated; + } + + public Task Handle(T command, CancellationToken cancellationToken) + { + var errors = _validators + .Select(v => v.Validate(command)) + .SelectMany(result => result.Errors) + .Where(error => error != null) + .ToList(); + + if (errors.Any()) + { + throw new InvalidCommandException(errors.Select(x => x.ErrorMessage).ToList()); + } + + return _decorated.Handle(command, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/QuartzModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/QuartzModule.cs new file mode 100644 index 000000000..d44fd1795 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/QuartzModule.cs @@ -0,0 +1,13 @@ +using Autofac; +using Quartz; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Quartz; + +public class QuartzModule : Autofac.Module +{ + protected override void Load(ContainerBuilder builder) + { + builder.RegisterAssemblyTypes(ThisAssembly) + .Where(x => typeof(IJob).IsAssignableFrom(x)).InstancePerDependency(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/QuartzStartup.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/QuartzStartup.cs new file mode 100644 index 000000000..a9d482be8 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/QuartzStartup.cs @@ -0,0 +1,111 @@ +using System.Collections.Specialized; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Inbox; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.InternalCommands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Outbox; +using Quartz; +using Quartz.Impl; +using Quartz.Logging; +using Serilog; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Quartz; + +internal static class QuartzStartup +{ + internal static void Initialize(ILogger logger, long? internalProcessingPoolingInterval = null) + { + logger.Information("Quartz starting..."); + + var schedulerConfiguration = new NameValueCollection(); + schedulerConfiguration.Add("quartz.scheduler.instanceName", "Meetings"); + + ISchedulerFactory schedulerFactory = new StdSchedulerFactory(schedulerConfiguration); + IScheduler scheduler = schedulerFactory.GetScheduler().GetAwaiter().GetResult(); + + LogProvider.SetCurrentLogProvider(new SerilogLogProvider(logger)); + + scheduler.Start().GetAwaiter().GetResult(); + + var processOutboxJob = JobBuilder.Create().Build(); + ITrigger trigger; + if (internalProcessingPoolingInterval.HasValue) + { + trigger = + TriggerBuilder + .Create() + .StartNow() + .WithSimpleSchedule(x => + x.WithInterval(TimeSpan.FromMilliseconds(internalProcessingPoolingInterval.Value)) + .RepeatForever()) + .Build(); + } + else + { + trigger = + TriggerBuilder + .Create() + .StartNow() + .WithCronSchedule("0/2 * * ? * *") + .Build(); + } + + scheduler + .ScheduleJob(processOutboxJob, trigger) + .GetAwaiter().GetResult(); + + var processInboxJob = JobBuilder.Create().Build(); + + ITrigger processInboxTrigger; + if (internalProcessingPoolingInterval.HasValue) + { + processInboxTrigger = + TriggerBuilder + .Create() + .StartNow() + .WithSimpleSchedule(x => + x.WithInterval(TimeSpan.FromMilliseconds(internalProcessingPoolingInterval.Value)) + .RepeatForever()) + .Build(); + } + else + { + processInboxTrigger = + TriggerBuilder + .Create() + .StartNow() + .WithCronSchedule("0/2 * * ? * *") + .Build(); + } + + scheduler + .ScheduleJob(processInboxJob, processInboxTrigger) + .GetAwaiter().GetResult(); + + var processInternalCommandsJob = JobBuilder.Create().Build(); + + ITrigger processInternalCommandsTrigger; + if (internalProcessingPoolingInterval.HasValue) + { + processInternalCommandsTrigger = + TriggerBuilder + .Create() + .StartNow() + .WithSimpleSchedule(x => + x.WithInterval(TimeSpan.FromMilliseconds(internalProcessingPoolingInterval.Value)) + .RepeatForever()) + .Build(); + } + else + { + processInternalCommandsTrigger = + TriggerBuilder + .Create() + .StartNow() + .WithCronSchedule("0/2 * * ? * *") + .Build(); + } + + scheduler.ScheduleJob(processInternalCommandsJob, processInternalCommandsTrigger).GetAwaiter().GetResult(); + + logger.Information("Quartz started."); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/SerilogLogProvider.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/SerilogLogProvider.cs new file mode 100644 index 000000000..c0649c17f --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Quartz/SerilogLogProvider.cs @@ -0,0 +1,67 @@ +using Quartz.Logging; +using Serilog; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Quartz; + +internal class SerilogLogProvider : ILogProvider +{ + private readonly ILogger _logger; + + internal SerilogLogProvider(ILogger logger) + { + _logger = logger; + } + + public Logger GetLogger(string name) + { + return (level, func, exception, parameters) => + { + if (func == null) + { + return true; + } + + if (level == LogLevel.Debug || level == LogLevel.Trace) + { + _logger.Debug(exception, func(), parameters); + } + + if (level == LogLevel.Info) + { + _logger.Information(exception, func(), parameters); + } + + if (level == LogLevel.Warn) + { + _logger.Warning(exception, func(), parameters); + } + + if (level == LogLevel.Error) + { + _logger.Error(exception, func(), parameters); + } + + if (level == LogLevel.Fatal) + { + _logger.Fatal(exception, func(), parameters); + } + + return true; + }; + } + + public IDisposable OpenNestedContext(string message) + { + throw new NotImplementedException(); + } + + public IDisposable OpenMappedContext(string key, string value) + { + throw new NotImplementedException(); + } + + public IDisposable OpenMappedContext(string key, object value, bool destructure = false) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/AesDataProtector.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/AesDataProtector.cs new file mode 100644 index 000000000..409f558e3 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/AesDataProtector.cs @@ -0,0 +1,63 @@ +using System.Security.Cryptography; +using System.Text; + +namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Security; + +public class AesDataProtector : IDataProtector +{ + private readonly string _encryptionKey; + + public AesDataProtector(string encryptionKey) + { + _encryptionKey = encryptionKey; + } + + public string Encrypt(string plainText) + { + var key = Encoding.UTF8.GetBytes(_encryptionKey); + + using var aesAlg = Aes.Create(); + using var encryptor = aesAlg.CreateEncryptor(key, aesAlg.IV); + using var msEncrypt = new MemoryStream(); + using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) + { + using var swEncrypt = new StreamWriter(csEncrypt); + swEncrypt.Write(plainText); + } + + var iv = aesAlg.IV; + + var decryptedContent = msEncrypt.ToArray(); + + var result = new byte[iv.Length + decryptedContent.Length]; + + Buffer.BlockCopy(iv, 0, result, 0, iv.Length); + Buffer.BlockCopy(decryptedContent, 0, result, iv.Length, decryptedContent.Length); + + return Convert.ToBase64String(result); + } + + public string Decrypt(string encryptedText) + { + var fullCipher = Convert.FromBase64String(encryptedText); + + var iv = new byte[16]; + var cipher = new byte[fullCipher.Length - iv.Length]; + + Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length); + Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, fullCipher.Length - iv.Length); + var key = Encoding.UTF8.GetBytes(_encryptionKey); + + using var aesAlg = Aes.Create(); + using var decryptor = aesAlg.CreateDecryptor(key, iv); + string result; + using (var msDecrypt = new MemoryStream(cipher)) + { + using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read); + using var srDecrypt = new StreamReader(csDecrypt); + result = srDecrypt.ReadToEnd(); + } + + return result; + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/IDataProtector.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/IDataProtector.cs new file mode 100644 index 000000000..53804ed28 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/IDataProtector.cs @@ -0,0 +1,8 @@ +namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Security; + +public interface IDataProtector +{ + string Encrypt(string plainText); + + string Decrypt(string encryptedText); +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/SecurityModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/SecurityModule.cs new file mode 100644 index 000000000..f67497746 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Security/SecurityModule.cs @@ -0,0 +1,20 @@ +using Autofac; + +namespace CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Security; + +internal class SecurityModule : Module +{ + private readonly string _encryptionKey; + + public SecurityModule(string encryptionKey) + { + _encryptionKey = encryptionKey; + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .WithParameter("encryptionKey", _encryptionKey); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/Services/ServicesModule.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/Services/ServicesModule.cs new file mode 100644 index 000000000..6af8fb826 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/Services/ServicesModule.cs @@ -0,0 +1,25 @@ +using Autofac; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Services.IdentityTokenService; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Services; + +internal class ServicesModule : Module +{ + private readonly IUserAccessConfiguration _userAccessConfiguration; + + public ServicesModule(IUserAccessConfiguration userAccessConfiguration) + { + _userAccessConfiguration = userAccessConfiguration; + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + + builder.Register(x => _userAccessConfiguration) + .As(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessCompositionRoot.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessCompositionRoot.cs new file mode 100644 index 000000000..602ad50d9 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessCompositionRoot.cs @@ -0,0 +1,23 @@ +using Autofac; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; + +internal static class UserAccessCompositionRoot +{ + private static IContainer? _container; + + internal static void SetContainer(IContainer container) + { + _container = container; + } + + internal static ILifetimeScope BeginLifetimeScope() + { + if (_container is null) + { + throw new Exception("Container has not been initialised. Call SetContainer(instance) before using it."); + } + + return _container.BeginLifetimeScope(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessConfiguration.cs new file mode 100644 index 000000000..e481328b6 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessConfiguration.cs @@ -0,0 +1,15 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; + +public class UserAccessConfiguration : IUserAccessConfiguration +{ + public Security? Security { get; set; } +} + +public class Security +{ + public string? JwtSecretKey { get; set; } + + public string? JwtIssuer { get; set; } + + public string? JwtAudience { get; set; } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessStartup.cs b/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessStartup.cs new file mode 100644 index 000000000..9d734ed3a --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Configuration/UserAccessStartup.cs @@ -0,0 +1,93 @@ +using Autofac; +using CompanyName.MyMeetings.BuildingBlocks.Application; +using CompanyName.MyMeetings.BuildingBlocks.Application.Emails; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.Emails; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; +using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration.Domain; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.UserRegistrations.RegisterNewUser; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.DataAccess; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Email; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.EventsBus; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Identity; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Logging; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Mediation; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing.Outbox; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Quartz; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Services; +using Serilog; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration +{ + public class UserAccessStartup + { + private static IContainer? _container; + + public static void Initialize( + string connectionString, + IExecutionContextAccessor executionContextAccessor, + ILogger logger, + EmailsConfiguration emailsConfiguration, + string textEncryptionKey, + IEmailSender emailSender, + IEventsBus eventsBus, + IUserAccessConfiguration userAccessConfiguration, + long? internalProcessingPoolingInterval = null) + { + var moduleLogger = logger.ForContext("Module", "UserAccess"); + + ConfigureCompositionRoot( + connectionString, + executionContextAccessor, + logger, + emailsConfiguration, + textEncryptionKey, + emailSender, + eventsBus, + userAccessConfiguration); + + QuartzStartup.Initialize(moduleLogger, internalProcessingPoolingInterval); + + EventsBusStartup.Initialize(moduleLogger); + } + + private static void ConfigureCompositionRoot( + string connectionString, + IExecutionContextAccessor executionContextAccessor, + ILogger logger, + EmailsConfiguration emailsConfiguration, + string textEncryptionKey, + IEmailSender emailSender, + IEventsBus eventsBus, + IUserAccessConfiguration userAccessConfiguration) + { + var containerBuilder = new ContainerBuilder(); + + containerBuilder.RegisterModule(new LoggingModule(logger.ForContext("Module", "UserAccess"))); + + var loggerFactory = new Serilog.Extensions.Logging.SerilogLoggerFactory(logger); + containerBuilder.RegisterModule(new DataAccessModule(connectionString)); + containerBuilder.RegisterModule(new DomainModule()); + containerBuilder.RegisterModule(new ProcessingModule()); + containerBuilder.RegisterModule(new EventsBusModule(eventsBus)); + containerBuilder.RegisterModule(new MediatorModule()); + + var domainNotificationsMap = new BiDictionary(); + domainNotificationsMap.Add("NewUserRegisteredNotification", typeof(NewUserRegisteredNotification)); + containerBuilder.RegisterModule(new OutboxModule(domainNotificationsMap)); + + containerBuilder.RegisterModule(new QuartzModule()); + containerBuilder.RegisterModule(new EmailModule(emailsConfiguration, emailSender)); + + // containerBuilder.RegisterModule(new SecurityModule(textEncryptionKey)); + containerBuilder.RegisterInstance(executionContextAccessor); + containerBuilder.RegisterModule(new IdentityModule()); + containerBuilder.RegisterModule(new ServicesModule(userAccessConfiguration)); + + _container = containerBuilder.Build(); + + UserAccessCompositionRoot.SetContainer(_container); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/ApplicationUserEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/ApplicationUserEntityTypeConfiguration.cs new file mode 100644 index 000000000..dbdea93de --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/ApplicationUserEntityTypeConfiguration.cs @@ -0,0 +1,17 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class ApplicationUserEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Users", "usersmi"); + + builder.Property("Name").HasMaxLength(255); + builder.Property("FirstName").HasMaxLength(100); + builder.Property("LastName").HasMaxLength(100); + } +} diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityRoleClaimEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityRoleClaimEntityTypeConfiguration.cs new file mode 100644 index 000000000..e0ccaa87c --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityRoleClaimEntityTypeConfiguration.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class IdentityRoleClaimEntityTypeConfiguration : IEntityTypeConfiguration> +{ + public void Configure(EntityTypeBuilder> builder) + { + builder.ToTable("RoleClaims", "usersmi"); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserClaimEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserClaimEntityTypeConfiguration.cs new file mode 100644 index 000000000..9f77e1857 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserClaimEntityTypeConfiguration.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class IdentityUserClaimEntityTypeConfiguration : IEntityTypeConfiguration> +{ + public void Configure(EntityTypeBuilder> builder) + { + builder.ToTable("UserClaims", "usersmi"); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserLoginEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserLoginEntityTypeConfiguration.cs new file mode 100644 index 000000000..84f6ef572 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserLoginEntityTypeConfiguration.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class IdentityUserLoginEntityTypeConfiguration : IEntityTypeConfiguration> +{ + public void Configure(EntityTypeBuilder> builder) + { + builder.ToTable("UserLogins", "usersmi"); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserRoleEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserRoleEntityTypeConfiguration.cs new file mode 100644 index 000000000..c3ae2c469 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserRoleEntityTypeConfiguration.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class IdentityUserRoleEntityTypeConfiguration : IEntityTypeConfiguration> +{ + public void Configure(EntityTypeBuilder> builder) + { + builder.ToTable("UserRoles", "usersmi"); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserTokenEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserTokenEntityTypeConfiguration.cs new file mode 100644 index 000000000..cd82c36d0 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/IdentityUserTokenEntityTypeConfiguration.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class IdentityUserTokenEntityTypeConfiguration : IEntityTypeConfiguration> +{ + public void Configure(EntityTypeBuilder> builder) + { + builder.ToTable("UserTokens", "usersmi"); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/RoleEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/RoleEntityTypeConfiguration.cs new file mode 100644 index 000000000..a567a2052 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/RoleEntityTypeConfiguration.cs @@ -0,0 +1,13 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class RoleEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Roles", "usersmi"); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/UserRefreshTokenEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/UserRefreshTokenEntityTypeConfiguration.cs new file mode 100644 index 000000000..d0b0c957f --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/UserRefreshTokenEntityTypeConfiguration.cs @@ -0,0 +1,22 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class UserRefreshTokenEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("UserRefreshTokens", "usersmi").HasKey(k => k.Id); + + builder.Property(p => p.Token).IsRequired(); + builder.Property(p => p.JwtId).IsRequired(); + builder.Property(p => p.IsRevoked).IsRequired(); + builder.Property(p => p.AddedDate).IsRequired(); + builder.Property(p => p.ExpiryDate).IsRequired(); + + builder.HasOne(p => p.User).WithMany().IsRequired(); + builder.Navigation(p => p.User).AutoInclude(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/UserRegistrationEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/UserRegistrationEntityTypeConfiguration.cs new file mode 100644 index 000000000..2f8a1cbd3 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Configuration/UserRegistrationEntityTypeConfiguration.cs @@ -0,0 +1,29 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; + +internal class UserRegistrationEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("UserRegistrations", "usersmi"); + + builder.HasKey(x => x.Id); + + builder.Property("_login").HasColumnName("UserName"); + builder.Property("_email").HasColumnName("Email"); + builder.Property("_password").HasColumnName("Password").HasField("_password"); + builder.Property("_firstName").HasColumnName("FirstName"); + builder.Property("_lastName").HasColumnName("LastName"); + builder.Property("_name").HasColumnName("Name"); + builder.Property("_registerDate").HasColumnName("RegisterDate"); + builder.Property("_confirmedDate").HasColumnName("ConfirmedDate"); + + builder.OwnsOne("_status", b => + { + b.Property(x => x.Value).HasColumnName("StatusCode"); + }); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Repositories/UserRefreshTokenRepository.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Repositories/UserRefreshTokenRepository.cs new file mode 100644 index 000000000..5378b9a0d --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Repositories/UserRefreshTokenRepository.cs @@ -0,0 +1,31 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain; +using Microsoft.EntityFrameworkCore; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Repositories; + +internal class UserRefreshTokenRepository : IUserRefreshTokenRepository +{ + private readonly UserAccessContext _context; + + public UserRefreshTokenRepository(UserAccessContext context) + { + _context = context; + } + + public Task GetByJwtIdAsync(string id, CancellationToken cancellationToken) + { + return _context.UserRefreshTokens + .SingleOrDefaultAsync(x => x.JwtId == id, cancellationToken); + } + + public void Add(UserRefreshToken userRefreshToken) + { + _context.UserRefreshTokens.Add(userRefreshToken); + } + + public Task DeleteAsync(UserRefreshToken userRefreshToken, CancellationToken cancellationToken = default) + { + _context.UserRefreshTokens.Remove(userRefreshToken); + return _context.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Domain/Repositories/UserRegistrationRepository.cs b/src/Modules/UserAccessMI/Infrastructure/Domain/Repositories/UserRegistrationRepository.cs new file mode 100644 index 000000000..32810f609 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Domain/Repositories/UserRegistrationRepository.cs @@ -0,0 +1,24 @@ +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; +using Microsoft.EntityFrameworkCore; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Repositories; + +public class UserRegistrationRepository : IUserRegistrationRepository +{ + private readonly UserAccessContext _userAccessContext; + + public UserRegistrationRepository(UserAccessContext userAccessContext) + { + _userAccessContext = userAccessContext; + } + + public async Task AddAsync(UserRegistration userRegistration) + { + await _userAccessContext.AddAsync(userRegistration); + } + + public Task GetByIdAsync(UserRegistrationId userRegistrationId) + { + return _userAccessContext.UserRegistrations.FirstOrDefaultAsync(x => x.Id == userRegistrationId); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/InternalCommands/InternalCommandEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/InternalCommands/InternalCommandEntityTypeConfiguration.cs new file mode 100644 index 000000000..0ec16e8f2 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/InternalCommands/InternalCommandEntityTypeConfiguration.cs @@ -0,0 +1,16 @@ +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.InternalCommands; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.InternalCommands; + +internal class InternalCommandEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("InternalCommands", "users"); + + builder.HasKey(b => b.Id); + builder.Property(b => b.Id).ValueGeneratedNever(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Outbox/OutboxAccessor.cs b/src/Modules/UserAccessMI/Infrastructure/Outbox/OutboxAccessor.cs new file mode 100644 index 000000000..ee965654a --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Outbox/OutboxAccessor.cs @@ -0,0 +1,23 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Outbox; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Outbox; + +public class OutboxAccessor : IOutbox +{ + private readonly UserAccessContext _userAccessContext; + + public OutboxAccessor(UserAccessContext userAccessContext) + { + _userAccessContext = userAccessContext; + } + + public void Add(OutboxMessage message) + { + _userAccessContext.OutboxMessages.Add(message); + } + + public Task Save() + { + return Task.CompletedTask; // Save is done automatically using EF Core Change Tracking mechanism during SaveChanges. + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Outbox/OutboxMessageEntityTypeConfiguration.cs b/src/Modules/UserAccessMI/Infrastructure/Outbox/OutboxMessageEntityTypeConfiguration.cs new file mode 100644 index 000000000..9e433c0ca --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Outbox/OutboxMessageEntityTypeConfiguration.cs @@ -0,0 +1,16 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Outbox; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Outbox; + +internal class OutboxMessageEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("OutboxMessages", "usersmi"); + + builder.HasKey(b => b.Id); + builder.Property(b => b.Id).ValueGeneratedNever(); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Services/IdentityTokenService/CustomClaimTypes.cs b/src/Modules/UserAccessMI/Infrastructure/Services/IdentityTokenService/CustomClaimTypes.cs new file mode 100644 index 000000000..11a016788 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Services/IdentityTokenService/CustomClaimTypes.cs @@ -0,0 +1,12 @@ +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Services.IdentityTokenService; + +internal class CustomClaimTypes +{ + internal const string Roles = "roles"; + internal const string Sub = "sub"; + internal const string Email = "email"; + internal const string Name = "name"; + internal const string LoginName = "loginName"; + internal const string FirstName = "firstName"; + internal const string LastName = "lastName"; +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/Services/IdentityTokenService/IdentityTokenClaimService.cs b/src/Modules/UserAccessMI/Infrastructure/Services/IdentityTokenService/IdentityTokenClaimService.cs new file mode 100644 index 000000000..a8731aa47 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/Services/IdentityTokenService/IdentityTokenClaimService.cs @@ -0,0 +1,186 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.ErrorHandling; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; +using CSharpFunctionalExtensions; +using Microsoft.IdentityModel.Tokens; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Services.IdentityTokenService; + +internal class IdentityTokenClaimService : ITokenClaimsService +{ + private readonly IUserAccessConfiguration _userAccessConfiguration; + private readonly IUserRefreshTokenRepository _refreshTokenRepository; + + public IdentityTokenClaimService( + IUserAccessConfiguration userAccessConfiguration, + IUserRefreshTokenRepository refreshTokenRepository) + { + _refreshTokenRepository = refreshTokenRepository; + _userAccessConfiguration = userAccessConfiguration; + } + + public Tokens GenerateTokens(ApplicationUser user) + { + var tokenHandler = new JwtSecurityTokenHandler(); + + var claims = GetUserClaims(user); + var tokenId = Guid.NewGuid().ToString(); + + // Add an unique identifier which is used by the refresh token + claims.Add(new Claim(JwtRegisteredClaimNames.Jti, tokenId)); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims), + Issuer = _userAccessConfiguration.GetValidIssuer(), + Audience = _userAccessConfiguration.GetValidAudience(), + + // JWT tokens are not supposed to be long-lived, quite the opposite. + // They're designed to be short-lived (5 to 10 minutes) with refresh capabilities. + Expires = DateTime.UtcNow.AddMinutes(7), + SigningCredentials = new SigningCredentials(_userAccessConfiguration.GetIssuerSigningKey(), SecurityAlgorithms.HmacSha256) + }; + + // Generate the security object token + var token = tokenHandler.CreateToken(tokenDescriptor); + + // Convert the security object token into a string + var accessToken = tokenHandler.WriteToken(token); + var refreshToken = RandomString(35) + Guid.NewGuid(); + var userRefreshToken = UserRefreshToken.Create(user, tokenId, refreshToken); + + _refreshTokenRepository.Add(userRefreshToken); + return new Tokens(accessToken, refreshToken); + } + + public async Task> GenerateNewTokensAsync(string accessToken, string refreshToken, CancellationToken cancellationToken) + { + var accessTokenValidationResult = await ValidateAccessTokenAsync(accessToken, refreshToken, cancellationToken); + if (accessTokenValidationResult.IsFailure) + { + return accessTokenValidationResult.Error; + } + + // As refresh tokens should only be used once + var userRefreshToken = accessTokenValidationResult.Value; + + // .. go ahead an delete the current one + await _refreshTokenRepository.DeleteAsync(userRefreshToken, cancellationToken); + + // .. and finally generate an new pair of tokens + return GenerateTokens(userRefreshToken.User); + } + + public List GetUserClaims(ApplicationUser user) + { + var claims = new List + { + new Claim(CustomClaimTypes.Sub, user.Id.ToString()), + new Claim(CustomClaimTypes.LoginName, user.UserName ?? string.Empty), + new Claim(CustomClaimTypes.Email, user.Email ?? string.Empty) + }; + + if (!string.IsNullOrEmpty(user.Name)) + { + claims.Add(new Claim(CustomClaimTypes.Name, user.Name)); + } + + if (!string.IsNullOrEmpty(user.FirstName)) + { + claims.Add(new Claim(CustomClaimTypes.FirstName, user.FirstName)); + } + + if (!string.IsNullOrEmpty(user.LastName)) + { + claims.Add(new Claim(CustomClaimTypes.LastName, user.LastName)); + } + + return claims; + } + + public TokenValidationParameters GetTokenValidationParameters() + { + return new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = _userAccessConfiguration.GetIssuerSigningKey(), + ValidIssuer = _userAccessConfiguration.GetValidIssuer(), + ValidateIssuer = _userAccessConfiguration.ShouldValidateIssuer(), + ValidAudience = _userAccessConfiguration.GetValidAudience(), + ValidateAudience = _userAccessConfiguration.ShouldValidateAudience(), + ValidateLifetime = true, + RequireExpirationTime = true, + + // Clock skew compensates for server time drift. + ClockSkew = TimeSpan.FromMinutes(5) + }; + } + + private async Task> ValidateAccessTokenAsync(string accessToken, string refreshToken, CancellationToken cancellationToken) + { + // Validate the Jwt format based on the configuration to check if it belongs to the our application. + var tokenValidationParameters = GetTokenValidationParameters(); + + // Here we are saying that we don't care about the accessToken's expiration date + tokenValidationParameters.ValidateLifetime = false; + + var tokenHandler = new JwtSecurityTokenHandler(); + var principal = tokenHandler.ValidateToken(accessToken, tokenValidationParameters, out SecurityToken securityToken); + + // Check if the token has been encrypted using the algorithm we have specified + var jwtSecurityToken = securityToken as JwtSecurityToken; + if (jwtSecurityToken is null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) + { + return Errors.Authentication.InvalidToken(); + } + + // Validate access token expiry date + if (!long.TryParse(principal.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp)?.Value, out long expiryTimeStamp)) + { + return Errors.Authentication.InvalidToken(); + } + + // Unix time stamp convertion + var expiryDate = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + .AddSeconds(expiryTimeStamp).ToLocalTime(); + + if (expiryDate > DateTime.Now) + { + return Errors.Authentication.InvalidToken("Token has not yet expired"); + } + + // Validate if the token exists + UserRefreshToken? userRefreshToken = await _refreshTokenRepository.GetByJwtIdAsync(jwtSecurityToken.Id, cancellationToken); + if (userRefreshToken is null) + { + return Errors.Authentication.InvalidToken("Token not found"); + } + + if (!userRefreshToken.Token.Equals(refreshToken)) + { + return Errors.Authentication.InvalidToken(); + } + + if (userRefreshToken.IsRevoked) + { + // Clean up the revoked token + await _refreshTokenRepository.DeleteAsync(userRefreshToken, cancellationToken); + return Errors.Authentication.InvalidToken("Token has been revoked."); + } + + return userRefreshToken; + } + + private string RandomString(int length) + { + var random = new Random(); + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + return new string(Enumerable.Repeat(chars, length) + .Select(x => x[random.Next(x.Length)]).ToArray()); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/UserAccessContext.cs b/src/Modules/UserAccessMI/Infrastructure/UserAccessContext.cs new file mode 100644 index 000000000..aeed52a59 --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/UserAccessContext.cs @@ -0,0 +1,90 @@ +using CompanyName.MyMeetings.BuildingBlocks.Application.Data; +using CompanyName.MyMeetings.BuildingBlocks.Application.Outbox; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure; +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.InternalCommands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.UserRegistrations; +using CompanyName.MyMeetings.Modules.UserAccessMI.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Domain.Configuration; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.InternalCommands; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Outbox; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.Extensions.Logging; +using Serilog; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure; + +public class UserAccessContext : IdentityDbContext, IdentityUserRole, IdentityUserLogin, IdentityRoleClaim, IdentityUserToken> +{ + private readonly Serilog.ILogger? _logger; + private readonly IDatabaseConfiguration _databaseConfiguration; + + public UserAccessContext(IDatabaseConfiguration databaseConfiguration, Serilog.ILogger logger) + { + _logger = logger; + _databaseConfiguration = databaseConfiguration; + } + + public DbSet UserRefreshTokens { get; set; } = default!; + + public DbSet UserRegistrations { get; set; } = default!; + + public DbSet OutboxMessages { get; set; } = default!; + + public DbSet InternalCommands { get; set; } = default!; + + /// + /// Abstract away the configuration complexity by encapsulating the DbContext. + /// + /// Options builder instance. + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer(_databaseConfiguration.ConnectionString); + optionsBuilder.ReplaceService(); + + if (_logger is not null) + { + optionsBuilder.UseLoggerFactory(CreateLoggerFactory()); +#if DEBUG + optionsBuilder.EnableSensitiveDataLogging(); +#endif + } + else + { + optionsBuilder.UseLoggerFactory(CreateEmptyLoggerFactory()); + } + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ApplyConfiguration(new ApplicationUserEntityTypeConfiguration()); + builder.ApplyConfiguration(new IdentityRoleClaimEntityTypeConfiguration()); + builder.ApplyConfiguration(new IdentityUserClaimEntityTypeConfiguration()); + builder.ApplyConfiguration(new IdentityUserLoginEntityTypeConfiguration()); + builder.ApplyConfiguration(new IdentityUserRoleEntityTypeConfiguration()); + builder.ApplyConfiguration(new IdentityUserTokenEntityTypeConfiguration()); + builder.ApplyConfiguration(new RoleEntityTypeConfiguration()); + builder.ApplyConfiguration(new UserRefreshTokenEntityTypeConfiguration()); + + builder.ApplyConfiguration(new UserRegistrationEntityTypeConfiguration()); + builder.ApplyConfiguration(new OutboxMessageEntityTypeConfiguration()); + builder.ApplyConfiguration(new InternalCommandEntityTypeConfiguration()); + } + + private ILoggerFactory CreateLoggerFactory() + { + return LoggerFactory.Create(builder => builder + .AddSerilog(_logger)); + } + + private ILoggerFactory CreateEmptyLoggerFactory() + { + return LoggerFactory.Create(builder => builder + .AddFilter((_, _) => false)); + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/Infrastructure/UserAccessModule.cs b/src/Modules/UserAccessMI/Infrastructure/UserAccessModule.cs new file mode 100644 index 000000000..6460d3b6d --- /dev/null +++ b/src/Modules/UserAccessMI/Infrastructure/UserAccessModule.cs @@ -0,0 +1,30 @@ +using Autofac; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration; +using CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure.Configuration.Processing; +using MediatR; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.Infrastructure; + +public class UserAccessModule : IUserAccessModule +{ + public async Task ExecuteCommandAsync(ICommand command) + { + return await CommandsExecutor.Execute(command); + } + + public async Task ExecuteCommandAsync(ICommand command) + { + await CommandsExecutor.Execute(command); + } + + public async Task ExecuteQueryAsync(IQuery query) + { + using (var scope = UserAccessCompositionRoot.BeginLifetimeScope()) + { + var mediator = scope.Resolve(); + + return await mediator.Send(query); + } + } +} \ No newline at end of file diff --git a/src/Modules/UserAccessMI/IntegrationEvents/CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents.csproj b/src/Modules/UserAccessMI/IntegrationEvents/CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/src/Modules/UserAccessMI/IntegrationEvents/CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/src/Modules/UserAccessMI/IntegrationEvents/NewUserRegisteredIntegrationEvent.cs b/src/Modules/UserAccessMI/IntegrationEvents/NewUserRegisteredIntegrationEvent.cs new file mode 100644 index 000000000..a3d1678da --- /dev/null +++ b/src/Modules/UserAccessMI/IntegrationEvents/NewUserRegisteredIntegrationEvent.cs @@ -0,0 +1,29 @@ +using CompanyName.MyMeetings.BuildingBlocks.Infrastructure.EventBus; + +namespace CompanyName.MyMeetings.Modules.UserAccessMI.IntegrationEvents; + +public class NewUserRegisteredIntegrationEvent : IntegrationEvent +{ + public Guid UserId { get; } + + public string Login { get; } + + public string Email { get; } + + public string FirstName { get; } + + public string LastName { get; } + + public string Name { get; } + + public NewUserRegisteredIntegrationEvent(Guid id, DateTime occurredOn, Guid userId, string login, string email, string firstName, string lastName, string name) + : base(id, occurredOn) + { + UserId = userId; + Login = login; + Email = email; + FirstName = firstName; + LastName = lastName; + Name = name; + } +} \ No newline at end of file diff --git a/src/Tests/ArchTests/Modules/ModuleTests.cs b/src/Tests/ArchTests/Modules/ModuleTests.cs index 23cef5bb8..df7d5a3ec 100644 --- a/src/Tests/ArchTests/Modules/ModuleTests.cs +++ b/src/Tests/ArchTests/Modules/ModuleTests.cs @@ -9,9 +9,9 @@ using CompanyName.MyMeetings.Modules.Payments.Application.Contracts; using CompanyName.MyMeetings.Modules.Payments.Domain.MeetingFees; using CompanyName.MyMeetings.Modules.Payments.Infrastructure.Configuration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Domain.Users; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessIS.Domain.Users; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessMI.Application.Contracts; using MediatR; using NetArchTest.Rules; using NUnit.Framework; diff --git a/src/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs b/src/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs index 35a6edb38..a983b3d8c 100644 --- a/src/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs +++ b/src/Tests/IntegrationTests/SeedWork/ExecutionContextMock.cs @@ -14,5 +14,7 @@ public ExecutionContextMock(Guid userId) public Guid CorrelationId { get; } public bool IsAvailable { get; } + + public bool IsAuthenticated { get; } } } \ No newline at end of file diff --git a/src/Tests/SUT/Helpers/UsersFactory.cs b/src/Tests/SUT/Helpers/UsersFactory.cs index 94fbd305c..38502d906 100644 --- a/src/Tests/SUT/Helpers/UsersFactory.cs +++ b/src/Tests/SUT/Helpers/UsersFactory.cs @@ -1,7 +1,7 @@ -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.ConfirmUserRegistration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.UserRegistrations.RegisterNewUser; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Users.AddAdminUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.ConfirmUserRegistration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.UserRegistrations.RegisterNewUser; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Users.AddAdminUser; using CompanyName.MyMeetings.SUT.SeedWork; namespace CompanyName.MyMeetings.SUT.Helpers diff --git a/src/Tests/SUT/SeedWork/ExecutionContextMock.cs b/src/Tests/SUT/SeedWork/ExecutionContextMock.cs index 562f2c130..baab08de6 100644 --- a/src/Tests/SUT/SeedWork/ExecutionContextMock.cs +++ b/src/Tests/SUT/SeedWork/ExecutionContextMock.cs @@ -15,6 +15,8 @@ public ExecutionContextMock(Guid userId) public bool IsAvailable { get; } + public bool IsAuthenticated { get; } + public void SetUserId(Guid userId) { this.UserId = userId; diff --git a/src/Tests/SUT/SeedWork/TestBase.cs b/src/Tests/SUT/SeedWork/TestBase.cs index 1f7e49f87..8123f88b4 100644 --- a/src/Tests/SUT/SeedWork/TestBase.cs +++ b/src/Tests/SUT/SeedWork/TestBase.cs @@ -12,9 +12,9 @@ using CompanyName.MyMeetings.Modules.Payments.Application.Contracts; using CompanyName.MyMeetings.Modules.Payments.Infrastructure; using CompanyName.MyMeetings.Modules.Payments.Infrastructure.Configuration; -using CompanyName.MyMeetings.Modules.UserAccess.Application.Contracts; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure; -using CompanyName.MyMeetings.Modules.UserAccess.Infrastructure.Configuration; +using CompanyName.MyMeetings.Modules.UserAccessIS.Application.Contracts; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure; +using CompanyName.MyMeetings.Modules.UserAccessIS.Infrastructure.Configuration; using CompanyName.MyMeetings.SUT.SeedWork.Probing; using Dapper; using NSubstitute;