Repository: tlandeka/authentication-microservice-with-domain-driven-design Branch: main Commit: a7cfd82824d9 Files: 209 Total size: 254.6 KB Directory structure: gitextract_3sq3ne0n/ ├── .github/ │ └── workflows/ │ └── maven.yml ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── 1-init.sql ├── Dockerfile ├── LICENSE ├── README.md ├── ddd_common-v1.0.0.jar ├── docker-compose-ci.yml ├── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── tomo/ │ │ └── mcauthentication/ │ │ ├── McAuthenticationApplication.java │ │ ├── application/ │ │ │ ├── BaseMapper.java │ │ │ ├── McAuthenticationEventHandler.java │ │ │ ├── authentication/ │ │ │ │ ├── BaseLoginCommandHandler.java │ │ │ │ ├── EmailLoginCommandHandler.java │ │ │ │ ├── FacebookLoginCommandHandler.java │ │ │ │ ├── GoogleLoginCommandHandler.java │ │ │ │ ├── LogoutCommandHandler.java │ │ │ │ ├── SessionAuthenticationCommandHandler.java │ │ │ │ ├── command/ │ │ │ │ │ ├── BaseLoginCommand.java │ │ │ │ │ ├── EmailLoginCommand.java │ │ │ │ │ ├── FacebookLoginCommand.java │ │ │ │ │ ├── GoogleLoginCommand.java │ │ │ │ │ ├── LogoutCommand.java │ │ │ │ │ └── SessionAuthenticationCommand.java │ │ │ │ └── dto/ │ │ │ │ ├── RecoveryPasswordDto.java │ │ │ │ └── SessionDto.java │ │ │ ├── configuration/ │ │ │ │ ├── AbstractVoidyCommandHandler.java │ │ │ │ ├── CommandHandler.java │ │ │ │ ├── QueryHandler.java │ │ │ │ └── RequestHandler.java │ │ │ ├── contracts/ │ │ │ │ ├── BaseCommand.java │ │ │ │ ├── BaseQuery.java │ │ │ │ ├── BaseRequest.java │ │ │ │ ├── Command.java │ │ │ │ ├── Identifiable.java │ │ │ │ ├── McAuthenticationModule.java │ │ │ │ ├── Query.java │ │ │ │ ├── Request.java │ │ │ │ ├── Response.java │ │ │ │ ├── Voidy.java │ │ │ │ └── security/ │ │ │ │ ├── AbstractAuthenticateCommand.java │ │ │ │ ├── AbstractAuthenticateQuery.java │ │ │ │ ├── AbstractAuthenticateRequest.java │ │ │ │ ├── AbstractAuthorizeCommand.java │ │ │ │ ├── AbstractAuthorizeQuery.java │ │ │ │ ├── AbstractAuthorizeRequest.java │ │ │ │ ├── Authenticate.java │ │ │ │ └── Authorize.java │ │ │ ├── recovery/ │ │ │ │ ├── CreatePasswordRecoveryCodeCommandHandler.java │ │ │ │ ├── GetUserRegistrationWithRecoveryCodeQueryHandler.java │ │ │ │ ├── PasswordRecoveryCodeCreatedEventHandler.java │ │ │ │ ├── SendPasswordRecoveryEmailCommandHandler.java │ │ │ │ ├── UpdatePasswordWithRecoveryCodeCommandHandler.java │ │ │ │ ├── command/ │ │ │ │ │ ├── CreatePasswordRecoveryCodeCommand.java │ │ │ │ │ ├── SendPasswordRecoveryEmailCommand.java │ │ │ │ │ └── UpdatePasswordWithRecoveryCodeCommand.java │ │ │ │ └── dto/ │ │ │ │ └── GetUserRegistrationWithRecoveryCodeQuery.java │ │ │ ├── registration/ │ │ │ │ ├── ChangePasswordCommandHandler.java │ │ │ │ ├── ConfirmUserRegistrationCommandHandler.java │ │ │ │ ├── GetUserRegistrationQueryHandler.java │ │ │ │ ├── NewUserRegisteredEventHandler.java │ │ │ │ ├── RegisterNewUserCommandHandler.java │ │ │ │ ├── SendRegistrationConfirmationEmailCommandHandler.java │ │ │ │ ├── command/ │ │ │ │ │ ├── ChangePasswordCommand.java │ │ │ │ │ ├── ConfirmUserRegistrationCommand.java │ │ │ │ │ ├── RegisterNewUserCommand.java │ │ │ │ │ └── SendRegistrationConfirmationEmailCommand.java │ │ │ │ ├── dto/ │ │ │ │ │ └── UserRegistrationDto.java │ │ │ │ └── query/ │ │ │ │ └── GetUserRegistrationQuery.java │ │ │ └── users/ │ │ │ ├── ChangeUserDetailsCommandHandler.java │ │ │ ├── GetUserQueryHandler.java │ │ │ ├── command/ │ │ │ │ └── ChangeUserDetailsCommand.java │ │ │ ├── dto/ │ │ │ │ └── BaseUserDto.java │ │ │ └── query/ │ │ │ └── GetUserQuery.java │ │ ├── domain/ │ │ │ ├── DomainRegistry.java │ │ │ ├── EncryptionService.java │ │ │ ├── oauth2/ │ │ │ │ ├── OAuth2Authentication.java │ │ │ │ ├── OAuth2Principal.java │ │ │ │ └── OAuth2Service.java │ │ │ ├── registration/ │ │ │ │ ├── EmailAuthenticationService.java │ │ │ │ ├── PasswordService.java │ │ │ │ ├── UserRegistration.java │ │ │ │ ├── UserRegistrationId.java │ │ │ │ ├── UserRegistrationRepository.java │ │ │ │ ├── UserRegistrationStatus.java │ │ │ │ ├── UsersCounter.java │ │ │ │ ├── events/ │ │ │ │ │ ├── PasswordChanged.java │ │ │ │ │ ├── PasswordRecovered.java │ │ │ │ │ ├── PasswordRecoveryCodeCreated.java │ │ │ │ │ ├── UserRegistrationConfirmed.java │ │ │ │ │ └── UserRegistrationRequested.java │ │ │ │ └── rules/ │ │ │ │ ├── PasswordRecoveryCodeShouldBeExpiredOrNull.java │ │ │ │ ├── PasswordRecoveryCodeShouldNotExpired.java │ │ │ │ ├── PasswordsMustMatch.java │ │ │ │ ├── RecoveryCodeMustMatch.java │ │ │ │ ├── UserRegistrationCannotBeConfirmedAfterExpiration.java │ │ │ │ ├── UserRegistrationCannotBeConfirmedMoreThanOnce.java │ │ │ │ ├── UserRegistrationMustBeConfirmed.java │ │ │ │ └── UserRegistrationMustBeUnique.java │ │ │ ├── session/ │ │ │ │ ├── JwtTokenProvider.java │ │ │ │ ├── Session.java │ │ │ │ ├── SessionAuthenticationService.java │ │ │ │ ├── SessionId.java │ │ │ │ ├── SessionRepository.java │ │ │ │ ├── TokenProvider.java │ │ │ │ ├── events/ │ │ │ │ │ └── SessionCreated.java │ │ │ │ └── rule/ │ │ │ │ └── SessionCannotBeExpiredWhenRefreshTokenIsMissing.java │ │ │ └── users/ │ │ │ ├── EmailLogin.java │ │ │ ├── User.java │ │ │ ├── UserId.java │ │ │ ├── UserRepository.java │ │ │ ├── events/ │ │ │ │ ├── UserCreated.java │ │ │ │ └── UserNameChanged.java │ │ │ └── rules/ │ │ │ └── UserEmailMustBeUnique.java │ │ └── infrastructure/ │ │ ├── McAuthenticationModuleExecutor.java │ │ ├── http/ │ │ │ └── oauth2/ │ │ │ ├── AbstractOAuth2Authentication.java │ │ │ ├── CustomOAuth2UserService.java │ │ │ ├── FacebookOAuth2Authentication.java │ │ │ ├── GoogleOAuth2Authentication.java │ │ │ └── user/ │ │ │ ├── FacebookOAuth2UserInfo.java │ │ │ ├── GoogleOAuth2UserInfo.java │ │ │ ├── OAuth2UserInfo.java │ │ │ └── OAuth2UserInfoFactory.java │ │ ├── persistence/ │ │ │ ├── BaseJpaAdapter.java │ │ │ ├── SessionJpaRepository.java │ │ │ ├── SessionRepositoryJpaAdapter.java │ │ │ ├── UserJpaRepository.java │ │ │ ├── UserRegistrationJpaRepository.java │ │ │ ├── UserRegistrationJpaRepositoryAdapter.java │ │ │ └── UserRepositoryJpaAdapter.java │ │ ├── processing/ │ │ │ ├── ErrorCommandHandlerDecorator.java │ │ │ ├── LoggingCommandHandlerDecorator.java │ │ │ ├── LoggingQueryHandlerDecorator.java │ │ │ ├── PipelineBuilder.java │ │ │ └── builder/ │ │ │ ├── AbstractPipelineBuilder.java │ │ │ ├── CommandHandlerPipelineBuilder.java │ │ │ └── QueryHandlerPipelineBuilder.java │ │ ├── service/ │ │ │ └── MD5EncryptionService.java │ │ ├── springboot/ │ │ │ ├── configuration/ │ │ │ │ ├── AppConfig.java │ │ │ │ ├── AppProperties.java │ │ │ │ ├── GUIProperties.java │ │ │ │ ├── MessageProperties.java │ │ │ │ ├── SecurityConfig.java │ │ │ │ ├── SwaggerConfig.java │ │ │ │ └── SwaggerUiWebMvcConfigurer.java │ │ │ ├── controller/ │ │ │ │ ├── AbstractController.java │ │ │ │ ├── AuthenticationController.java │ │ │ │ ├── RegistrationController.java │ │ │ │ ├── RestApiRoutes.java │ │ │ │ └── UserController.java │ │ │ ├── filter/ │ │ │ │ └── TokenAuthenticationFilter.java │ │ │ └── security/ │ │ │ ├── CurrentUser.java │ │ │ ├── OAuth2AuthenticationFailureHandler.java │ │ │ ├── OAuth2AuthenticationSuccessHandler.java │ │ │ ├── UserAuthPrincipal.java │ │ │ └── UserAuthToken.java │ │ └── util/ │ │ └── CookieUtils.java │ └── resources/ │ ├── application.yml │ ├── db/ │ │ └── migration/ │ │ └── V2022_02_02_1124__initial_structure.sql │ └── repo/ │ └── org/ │ └── tomo/ │ └── ddd_common/ │ ├── 1.0.0/ │ │ ├── ddd_common-1.0.0.jar │ │ ├── ddd_common-1.0.0.jar.md5 │ │ ├── ddd_common-1.0.0.jar.sha1 │ │ ├── ddd_common-1.0.0.pom │ │ ├── ddd_common-1.0.0.pom.md5 │ │ └── ddd_common-1.0.0.pom.sha1 │ ├── maven-metadata.xml │ ├── maven-metadata.xml.md5 │ └── maven-metadata.xml.sha1 └── test/ ├── java/ │ └── com/ │ └── tomo/ │ └── mcauthentication/ │ ├── integration/ │ │ ├── BaseIntegrationTest.java │ │ └── application/ │ │ ├── AbstractApplicationServiceTest.java │ │ ├── authentication/ │ │ │ ├── EmailLoginCommandHandlerTest.java │ │ │ ├── FacebookLoginCommandHandlerTest.java │ │ │ ├── GoogleLoginCommandHandlerTest.java │ │ │ ├── LogoutCommandHandlerTest.java │ │ │ └── SessionAuthenticationCommandHandlerTest.java │ │ ├── recovery/ │ │ │ ├── CreatePasswordRecoveryCodeCommandHandlerTest.java │ │ │ ├── GetUserRegistrationWithRecoveryCodeQueryHandlerTest.java │ │ │ └── UpdatePasswordWithRecoveryCodeCommandHandlerTest.java │ │ ├── registration/ │ │ │ ├── ChangePasswordCommandHandlerTest.java │ │ │ ├── ConfirmUserRegistrationCommandHandlerTest.java │ │ │ └── RegisterNewUserCommandHandlerTest.java │ │ └── users/ │ │ ├── ChangeUserDetailsCommandHandlerTest.java │ │ └── GetUserQueryHandlerTest.java │ ├── smoke/ │ │ └── McAuthenticationApplicationSmokeTest.java │ ├── testdata/ │ │ ├── CommandObjectMother.java │ │ └── StaticFields.java │ ├── unit/ │ │ ├── application/ │ │ │ └── registration/ │ │ │ └── UserRegistrationCommandHandlerTest.java │ │ └── domain/ │ │ ├── AbstractUnitTest.java │ │ ├── registration/ │ │ │ ├── UserRegistrationTest.java │ │ │ └── rules/ │ │ │ ├── PasswordRecoveryCodeShouldBeExpiredOrNullTest.java │ │ │ ├── PasswordRecoveryCodeShouldNotExpiredTest.java │ │ │ ├── PasswordsMustMatchTest.java │ │ │ ├── RecoveryCodeMustMatchTest.java │ │ │ ├── UserRegistrationCannotBeConfirmedAfterExpirationTest.java │ │ │ ├── UserRegistrationCannotBeConfirmedMoreThanOnceTest.java │ │ │ ├── UserRegistrationMustBeConfirmedTest.java │ │ │ └── UserRegistrationMustBeUniqueTest.java │ │ ├── session/ │ │ │ └── rules/ │ │ │ └── SessionCannotBeExpiredWhenRefreshTokenIsMissingTest.java │ │ └── user/ │ │ └── rules/ │ │ └── UserEmailMustBeUniqueTest.java │ └── weblayer/ │ ├── BaseWebLayerTest.java │ └── springboot/ │ └── controller/ │ ├── AbstractControllerTest.java │ └── RegistrationControllerTest.java └── resources/ └── application-ci.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/maven.yml ================================================ name: Run tests on: pull_request: branches: - main workflow_dispatch: jobs: test: runs-on: ubuntu-latest env: RAILS_ENV: test steps: - name: Checkout code uses: actions/checkout@v3 - uses: satackey/action-docker-layer-caching@v0.0.11 continue-on-error: true - name: Start docker containers run: | docker-compose -f docker-compose-ci.yml up --build --detach sleep 10 # wait for database to be ready - name: Run unit tests run: docker-compose -f docker-compose-ci.yml run mc-authentication-service-test ./mvnw test -Punittest -Dspring.profiles.active=ci - name: Run integration tests run: docker-compose -f docker-compose-ci.yml run mc-authentication-service-test ./mvnw test -Pintegrationtest -Dspring.profiles.active=ci ================================================ FILE: .gitignore ================================================ HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ !**/src/main/**/build/ !**/src/test/**/build/ ### VS Code ### .vscode/ .DS_Store ================================================ FILE: .mvn/wrapper/MavenWrapperDownloader.java ================================================ /* * Copyright 2007-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.net.*; import java.io.*; import java.nio.channels.*; import java.util.Properties; public class MavenWrapperDownloader { private static final String WRAPPER_VERSION = "0.5.6"; /** * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; /** * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to * use instead of the default one. */ private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties"; /** * Path where the maven-wrapper.jar will be saved to. */ private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; /** * Name of the property which should be used to override the default download url for the wrapper. */ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; public static void main(String args[]) { System.out.println("- Downloader started"); File baseDirectory = new File(args[0]); System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); // If the maven-wrapper.properties exists, read it and check if it contains a custom // wrapperUrl parameter. File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); String url = DEFAULT_DOWNLOAD_URL; if(mavenWrapperPropertyFile.exists()) { FileInputStream mavenWrapperPropertyFileInputStream = null; try { mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); Properties mavenWrapperProperties = new Properties(); mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); } catch (IOException e) { System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); } finally { try { if(mavenWrapperPropertyFileInputStream != null) { mavenWrapperPropertyFileInputStream.close(); } } catch (IOException e) { // Ignore ... } } } System.out.println("- Downloading from: " + url); File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); if(!outputFile.getParentFile().exists()) { if(!outputFile.getParentFile().mkdirs()) { System.out.println( "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); } } System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); try { downloadFileFromURL(url, outputFile); System.out.println("Done"); System.exit(0); } catch (Throwable e) { System.out.println("- Error downloading"); e.printStackTrace(); System.exit(1); } } private static void downloadFileFromURL(String urlString, File destination) throws Exception { if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { String username = System.getenv("MVNW_USERNAME"); char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); } URL website = new URL(urlString); ReadableByteChannel rbc; rbc = Channels.newChannel(website.openStream()); FileOutputStream fos = new FileOutputStream(destination); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); rbc.close(); } } ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar ================================================ FILE: 1-init.sql ================================================ create user mcuser with superuser; alter user mcuser with password 'mcuser'; create database mc_authentication with owner mcuser; create extension "uuid-ossp"; ================================================ FILE: Dockerfile ================================================ FROM amazoncorretto:11 as base WORKDIR /app COPY .mvn/ .mvn COPY mvnw pom.xml ddd_common-v1.0.0.jar ./ RUN ./mvnw deploy:deploy-file -Durl=file:./src/main/resources/repo -Dfile=ddd_common-v1.0.0.jar -DgroupId=org.tomo -DartifactId=ddd_common -Dpackaging=jar -Dversion=1.0.0 RUN ./mvnw dependency:resolve FROM base as test RUN ./mvnw dependency:resolve-plugins -Punittest,integrationtest COPY src ./src ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache CMD tail -f /dev/null FROM base as build COPY src ./src RUN ./mvnw package ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Tomislav Landeka Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Microservice for authentication with Domain-Driven Design Full Spring Boot authentication microservice with Domain-Driven Design approach. ### 1. Introduction #### 1.1. Purpose of this Repository This is a list of the main goals of this repository: * Showing how you can implement a Domain-Drive design * Showing how you can implement a CQRS * Presentation of the full implementation of an application * This is not another proof of concept (PoC) * The goal is to present the implementation of an application that would be ready to run in production * Showing the application of best practices and object-oriented programming principles * Presentation of the use of design patterns. When, how and why they can be used * Presentation of the implementation using Domain-Driven Design approach (tactical patterns) * Presentation of the implementation of Unit Tests for Domain Model (Testable Design in mind) * Presentation of the implementation of Integration Tests * Presentation of the implementation of testing Web-Layer only #### 1.2 Your contribution * Give it a star * Share it * Become a contributor ### 2. Domain #### 2.1. Business logic of the repository The main idea of this repository is to create small microservice for authentication that provides next functionalities: * Form Registration * Form Login, Google Login, Facebook Login * Password recovery * Email notifications * Session identification and authentication #### 2.2. How to run * Run database docker container first: `docker-compose up -d` * Run application locally with `local` profile * Plan is to dockerize the application in order to run it easy #### 2.3. How to run tests * Run database docker container first: `docker-compose up -d`. Plan is to add testdb for testing purposes. * Run Integration test separately * Run Unit test separately * Run Web-Layer (tests that are testing Spring Boot implementation and Rest Controller) tests separately ### 3. Desired TODO List to complete the application: * Clean the code from inconsistencies * Add more session details * Implement a tool like ELK * Caching if/when needed * More tests * CI * Pull out 'ddd' folder and create a library * Pull out 'CQRS' folders (contracts and configuration) and create a library * Finish SpringBoot security and CSFR token * Dockerize ### 4. The main idea is to create a reusable microservice network that consists of the next microservices: * Service discovery (probably with K8s). * Create a microservice for authentication (current repository). * Create a microservice for authorization - simple implementation of RBAC. * Create a microservice for sending emails. * Create a microservice for localization - the idea is to provide UI for translating the application to various languages as a common part of most applications. * Create a microservice for asynchronous communication (AC) - the idea is to create a microservice that distributes the messages between microservices. The microservice should work over DB (e.g. Redis) and RMQ to provide asynchrony. The microservice should provide the REST API for accessing it. In this way, we should have RMQ in only one place and communication with this microservice should go over REST API. The microservice should provide these routes: * route for registering messages by other microservices. E.g. Email-Microservice could register message **send-email** with _required properties_, _endpoint_, and _version_. That configuration should be saved into a DB. * sending messages - E.g. Authentication microservice should send a message after registering a user with the name **send-email** and _required properties_. The AC microservice will receive that message, validate the required properties, enrich the message body with an endpoint (which is saved in DB) and publish a message to RMQ. The RMQ consumer will consume the message and distribute it to the endpoint. ### 5. License The project is under [MIT license](https://opensource.org/licenses/MIT). ### 6. Inspiration #### 6.1. Domain-Driven Design - ["Domain-Driven Design: Tackling Complexity in the Heart of Software"](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) book, Eric Evans - ["Implementing Domain-Driven Design"](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577) book, Vaughn Vernon - ["Domain-Driven Design in PHP"](https://www.amazon.com/dp/1787284948) book, by Carlos Buenosvinos, Christian Soronellas, Keyvan Akbary - ["IDDD_Samples"](https://github.com/VaughnVernon/IDDD_Samples) GH repository, Vaughn Vernon - ["Blog CQRS"](https://github.com/dddshelf/blog-cqrs) GH repository, by Carlos Buenosvinos, Christian Soronellas, Keyvan Akbary - ["DDD example - Last Wishes"](https://github.com/dddshelf/last-wishes) GH repository, by Carlos Buenosvinos, Christian Soronellas, Keyvan Akbary - ["Modular Monolith with DDD"](https://github.com/kgrzybek/modular-monolith-with-ddd) GH repository, by Kamil Grzybek - ["Mediator implementation"](https://github.com/jbogard/MediatR) GH repository, by Jimmy Bogard #### 6.2 Application Architecture - ["Patterns of Enterprise Application Architecture"](https://martinfowler.com/books/eaa.html) book, Martin Fowler - ["Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin Series"](https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164) book, Robert C. Martin - ["The Clean Architecture"](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) article, Robert C. Martin - ["DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together"](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/) article, Herberto Graca #### 6.2. System Architecture - ["Building Microservices: Designing Fine-Grained Systems"](https://www.amazon.com/Building-Microservices-Designing-Fine-Grained-Systems/dp/1491950358) book, Sam Newman #### 6.3. Design - ["Clean Code: A Handbook of Agile Software Craftsmanship"](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) book, Robert C. Martin - ["Design Patterns: Elements of Reusable Object-Oriented Software"](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) book, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides ================================================ FILE: docker-compose-ci.yml ================================================ version: '3.7' services: mc-authentication-db-test: container_name: mc-authentication-db-test image: postgres:12 restart: always ports: - 127.0.0.1:5432:5432 networks: - mc-network-test environment: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres volumes: - mc-authentication-db-test:/var/lib/postgresql - ./1-init.sql:/docker-entrypoint-initdb.d/1-init.sql mc-authentication-service-test: container_name: mc-authentication-service-test depends_on: - mc-authentication-db-test build: context: ./ dockerfile: Dockerfile target: test ports: - 127.0.0.1:8080:8080 networks: - mc-network-test volumes: mc-authentication-db-test: networks: mc-network-test: driver: bridge ================================================ FILE: docker-compose.yml ================================================ version: '3.7' services: mc-authentication_db: container_name: mc-authentication_db image: postgres:12 restart: always ports: - 127.0.0.1:5432:5432 environment: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres volumes: - mc-authentication-db:/var/lib/postgresql - ./1-init.sql:/docker-entrypoint-initdb.d/1-init.sql volumes: mc-authentication-db: networks: mc-network: driver: bridge ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Maven Start Up Batch script # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then export JAVA_HOME="`/usr/libexec/java_home`" else export JAVA_HOME="/Library/Java/Home" fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi if [ -z "$M2_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` M2_HOME=`dirname "$PRG"`/.. # make it fully qualified M2_HOME=`cd "$M2_HOME" && pwd` cd "$saveddir" # echo Using m2 at $M2_HOME fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="`which java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done echo "${basedir}" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=`find_maven_basedir "$(pwd)"` if [ -z "$BASE_DIR" ]; then exit 1; fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" else jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` fi if command -v wget > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget "$jarUrl" -O "$wrapperJarPath" else wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" fi elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl -o "$wrapperJarPath" "$jarUrl" -f else curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaClass=`cygpath --path --windows "$javaClass"` fi if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaClass") fi if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %DOWNLOAD_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%" == "on" pause if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 com.tomo mc-authentication 0.0.1-SNAPSHOT mc-authentication API for Authentication org.springframework.boot spring-boot-starter-parent 2.6.2 UTF-8 UTF-8 11 4.13.2 4.12.1 5.8.2 1.0.1 project.local project file:${project.basedir}/src/main/resources/repo org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test org.springframework.boot spring-boot-starter-security org.springframework.security spring-security-oauth2-client io.jsonwebtoken jjwt-api 0.11.2 io.jsonwebtoken jjwt-impl 0.11.2 runtime io.jsonwebtoken jjwt-jackson 0.11.2 runtime org.projectlombok lombok 1.18.22 provided net.sizovs pipelinr 0.7 org.postgresql postgresql runtime org.flywaydb flyway-core 8.4.3 org.codehaus.mojo exec-maven-plugin 1.2.1 org.apache.commons commons-lang3 3.12.0 javax.validation validation-api 2.0.1.Final org.modelmapper modelmapper 2.4.5 com.github.javafaker javafaker 1.0.2 net.sargue mailgun 1.10.0 org.glassfish.jersey.inject jersey-hk2 2.28 com.google.code.gson gson 2.8.5 io.springfox springfox-boot-starter 3.0.0 org.mockito mockito-inline 3.8.0 test org.tomo ddd_common v1.0.0 org.tomo ddd_common 1.0.0 flyway org.flywaydb flyway-maven-plugin 8.4.3 generate-sources migrate org.postgresql.Driver mcuser mcuser jdbc:postgresql://localhost:5432/mc_authentication false true public unittest maven-surefire-plugin 2.19.1 com.tomo.mcauthentication.unit.** org.junit.platform junit-platform-surefire-provider ${junit-platform.version} org.junit.vintage junit-vintage-engine ${junit-vintage-engine} org.junit.jupiter junit-jupiter-engine ${junit-jupiter.version} integrationtest org.springframework.boot spring-boot-maven-plugin org.flywaydb flyway-maven-plugin 8.4.3 generate-sources migrate org.postgresql.Driver mcuser mcuser jdbc:postgresql://mc-authentication-db-test:5432/mc_authentication false true public org.apache.maven.plugins maven-failsafe-plugin com.tomo.mcauthentication.integration.* integration-test integration-test verify maven-surefire-plugin 2.19.1 com.tomo.mcauthentication.unit.** org.junit.platform junit-platform-surefire-provider ${junit-platform.version} org.junit.vintage junit-vintage-engine ${junit-vintage-engine} org.junit.jupiter junit-jupiter-engine ${junit-jupiter.version} ================================================ FILE: src/main/java/com/tomo/mcauthentication/McAuthenticationApplication.java ================================================ package com.tomo.mcauthentication; import com.tomo.mcauthentication.infrastructure.springboot.configuration.AppProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import springfox.documentation.swagger2.annotations.EnableSwagger2; @EnableWebMvc @EnableSwagger2 @SpringBootApplication @EnableConfigurationProperties(AppProperties.class) @EnableAspectJAutoProxy(proxyTargetClass=true) @ComponentScan(basePackages = { "com.tomo.*" }) @EntityScan("com.tomo.*") public class McAuthenticationApplication { public static void main(String[] args) { SpringApplication.run(McAuthenticationApplication.class, args); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/BaseMapper.java ================================================ package com.tomo.mcauthentication.application; import org.modelmapper.ModelMapper; public class BaseMapper { protected ModelMapper modelMapper; public BaseMapper() {} public BaseMapper(ModelMapper modelMapper) { this.modelMapper = modelMapper; } protected T toDto(E source, Class destinationType) { return modelMapper.map(source, destinationType); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/McAuthenticationEventHandler.java ================================================ package com.tomo.mcauthentication.application; import com.tomo.ddd.domain.DomainEvent; import com.tomo.ddd.domain.DomainEventPublisher; import com.tomo.ddd.domain.DomainEventSubscriber; import com.tomo.ddd.event.EventStore; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class McAuthenticationEventHandler { private EventStore eventStore; public McAuthenticationEventHandler(EventStore eventStore) { this.eventStore = eventStore; } @Before(value = "execution(* *(..)) && within(com.tomo.mcauthentication.application..*)") public void listen() { DomainEventPublisher .instance() .subscribe(new DomainEventSubscriber() { public void handleEvent(DomainEvent aDomainEvent) { store(aDomainEvent); } public Class subscribedToEventType() { return DomainEvent.class; // all domain events } }); } /** * Stores aDomainEvent to the event store. * @param aDomainEvent the DomainEvent to store */ private void store(DomainEvent aDomainEvent) { this.eventStore().append(aDomainEvent); } /** * Answers my EventStore. * @return EventStore */ private EventStore eventStore() { return this.eventStore; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/BaseLoginCommandHandler.java ================================================ package com.tomo.mcauthentication.application.authentication; import com.tomo.mcauthentication.application.BaseMapper; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionRepository; import com.tomo.mcauthentication.domain.session.TokenProvider; import org.modelmapper.ModelMapper; public class BaseLoginCommandHandler extends BaseMapper { protected SessionRepository sessionRepository; protected TokenProvider tokenProvider; public BaseLoginCommandHandler() { } public BaseLoginCommandHandler(ModelMapper modelMapper, TokenProvider tokenProvider) { super(modelMapper); this.tokenProvider = tokenProvider; } public BaseLoginCommandHandler( ModelMapper modelMapper, SessionRepository sessionRepository, TokenProvider tokenProvider) { super(modelMapper); this.sessionRepository = sessionRepository; this.tokenProvider = tokenProvider; } protected SessionDto toDto(Session session) { return SessionDto.create(session, this.modelMapper); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/EmailLoginCommandHandler.java ================================================ package com.tomo.mcauthentication.application.authentication; import com.tomo.mcauthentication.application.authentication.command.EmailLoginCommand; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionRepository; import com.tomo.mcauthentication.domain.session.TokenProvider; import com.tomo.mcauthentication.domain.registration.EmailAuthenticationService; import com.tomo.mcauthentication.domain.users.User; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component public class EmailLoginCommandHandler extends BaseLoginCommandHandler implements CommandHandler { EmailAuthenticationService authenticationService; public EmailLoginCommandHandler( EmailAuthenticationService emailAuthenticationService, @Qualifier("sessionRepositoryJpaAdapter") SessionRepository sessionRepository, @Qualifier("jwtTokenProvider") TokenProvider tokenProvider, ModelMapper modelMapper) { super(modelMapper, sessionRepository, tokenProvider); this.authenticationService = emailAuthenticationService; this.tokenProvider = tokenProvider; } @Override public SessionDto handle(EmailLoginCommand command) { User user = authenticationService.authenticate(command.getEmail(), command.getPassword()); Session session = new Session( sessionRepository.nextIdentity(), user,tokenProvider, command.getRememberMe(), command.getUserAgent(), command.getIpAddress()); sessionRepository.save(session); return toDto(session); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/FacebookLoginCommandHandler.java ================================================ package com.tomo.mcauthentication.application.authentication; import com.tomo.mcauthentication.application.authentication.command.FacebookLoginCommand; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.application.users.dto.BaseUserDto; import com.tomo.mcauthentication.domain.oauth2.OAuth2Service; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionRepository; import com.tomo.mcauthentication.domain.session.TokenProvider; import com.tomo.mcauthentication.domain.users.User; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component public class FacebookLoginCommandHandler extends BaseLoginCommandHandler implements CommandHandler { OAuth2Service oAuth2Service; public FacebookLoginCommandHandler( @Qualifier("facebookOAuth2Service") OAuth2Service oAuth2Service, @Qualifier("jwtTokenProvider") TokenProvider tokenProvider, @Qualifier("sessionRepositoryJpaAdapter") SessionRepository sessionRepository, ModelMapper modelMapper) { super(modelMapper, sessionRepository, tokenProvider); this.oAuth2Service = oAuth2Service; this.tokenProvider = tokenProvider; } @Override public BaseUserDto handle(FacebookLoginCommand command) { User user = oAuth2Service.registerAuthenticate(command.getAccessCode()); Session session = new Session( sessionRepository.nextIdentity(), user,tokenProvider, command.getRememberMe(), command.getUserAgent(), command.getIpAddress()); sessionRepository.save(session); return null; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/GoogleLoginCommandHandler.java ================================================ package com.tomo.mcauthentication.application.authentication; import com.tomo.mcauthentication.application.authentication.command.GoogleLoginCommand; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.application.users.dto.BaseUserDto; import com.tomo.mcauthentication.domain.oauth2.OAuth2Service; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionRepository; import com.tomo.mcauthentication.domain.session.TokenProvider; import com.tomo.mcauthentication.domain.users.User; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component public class GoogleLoginCommandHandler extends BaseLoginCommandHandler implements CommandHandler { OAuth2Service oAuth2Service; public GoogleLoginCommandHandler( @Qualifier("googleOAuth2Service") OAuth2Service oAuth2Service, @Qualifier("jwtTokenProvider") TokenProvider tokenProvider, @Qualifier("sessionRepositoryJpaAdapter") SessionRepository sessionRepository, ModelMapper modelMapper) { super(modelMapper, sessionRepository, tokenProvider); this.oAuth2Service = oAuth2Service; this.tokenProvider = tokenProvider; } @Override public BaseUserDto handle(GoogleLoginCommand command) { User user = oAuth2Service.registerAuthenticate(command.getAccessCode()); Session session = new Session( sessionRepository.nextIdentity(), user,tokenProvider, command.getRememberMe(), command.getUserAgent(), command.getIpAddress()); sessionRepository.save(session); return null; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/LogoutCommandHandler.java ================================================ package com.tomo.mcauthentication.application.authentication; import com.tomo.mcauthentication.application.authentication.command.LogoutCommand; import com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler; import com.tomo.mcauthentication.domain.session.SessionAuthenticationService; import org.springframework.stereotype.Service; @Service public class LogoutCommandHandler extends AbstractVoidyCommandHandler { SessionAuthenticationService sessionAuthenticationService; public LogoutCommandHandler(SessionAuthenticationService sessionAuthenticationService) { this.sessionAuthenticationService = sessionAuthenticationService; } @Override protected void abstractHandle(LogoutCommand aCommand) { sessionAuthenticationService.logout(aCommand.getAuthToken()); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/SessionAuthenticationCommandHandler.java ================================================ package com.tomo.mcauthentication.application.authentication; import com.tomo.mcauthentication.application.BaseMapper; import com.tomo.mcauthentication.application.authentication.command.SessionAuthenticationCommand; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionAuthenticationService; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; @Service public class SessionAuthenticationCommandHandler extends BaseMapper implements CommandHandler { SessionAuthenticationService sessionAuthenticationService; public SessionAuthenticationCommandHandler( SessionAuthenticationService sessionAuthenticationService, ModelMapper modelMapper) { super(modelMapper); this.sessionAuthenticationService = sessionAuthenticationService; } @Override public SessionDto handle(SessionAuthenticationCommand command) { Session session = sessionAuthenticationService.authenticate(command.getAuthToken()); return SessionDto.create(session, this.modelMapper); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/command/BaseLoginCommand.java ================================================ package com.tomo.mcauthentication.application.authentication.command; import com.tomo.mcauthentication.application.contracts.BaseCommand; import lombok.Getter; import lombok.Setter; @Getter @Setter public class BaseLoginCommand extends BaseCommand { private Boolean rememberMe; private String userAgent; private String ipAddress; public BaseLoginCommand() { super(); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/command/EmailLoginCommand.java ================================================ package com.tomo.mcauthentication.application.authentication.command; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; @Setter @Getter public class EmailLoginCommand extends BaseLoginCommand { @NotBlank @Email private String email; @NotBlank private String password; private Boolean rememberMe; private String userAgent; private String ipAddress; public EmailLoginCommand() { super(); } public EmailLoginCommand(String email, String password) { super(); this.email = email; this.password = password; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/command/FacebookLoginCommand.java ================================================ package com.tomo.mcauthentication.application.authentication.command; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Setter @Getter @NoArgsConstructor public class FacebookLoginCommand extends BaseLoginCommand { private String accessCode; public FacebookLoginCommand(String accessCode) { this.accessCode = accessCode; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/command/GoogleLoginCommand.java ================================================ package com.tomo.mcauthentication.application.authentication.command; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Setter @Getter @NoArgsConstructor public class GoogleLoginCommand extends BaseLoginCommand { private String accessCode; public GoogleLoginCommand(String accessCode) { this.accessCode = accessCode; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/command/LogoutCommand.java ================================================ package com.tomo.mcauthentication.application.authentication.command; import com.tomo.mcauthentication.application.contracts.security.AbstractAuthenticateCommand; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Setter @Getter @NoArgsConstructor public class LogoutCommand extends AbstractAuthenticateCommand { public LogoutCommand(String authToken) { super(authToken); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/command/SessionAuthenticationCommand.java ================================================ package com.tomo.mcauthentication.application.authentication.command; import com.tomo.mcauthentication.application.contracts.security.AbstractAuthenticateCommand; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class SessionAuthenticationCommand extends AbstractAuthenticateCommand { public SessionAuthenticationCommand(String authToken) { super(authToken); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/dto/RecoveryPasswordDto.java ================================================ package com.tomo.mcauthentication.application.authentication.dto; import com.tomo.mcauthentication.application.contracts.Response; import lombok.Getter; @Getter public class RecoveryPasswordDto implements Response { String recoveryCode; public RecoveryPasswordDto(String recoveryCode) { this.recoveryCode = recoveryCode; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/authentication/dto/SessionDto.java ================================================ package com.tomo.mcauthentication.application.authentication.dto; import com.tomo.mcauthentication.application.users.dto.BaseUserDto; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionId; import com.tomo.mcauthentication.domain.users.UserId; import org.modelmapper.ModelMapper; import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class SessionDto extends BaseUserDto { private String sessionId; private String accessToken; private LocalDateTime expirationDate; int expirationDateMilis; private String tokenType; private String refreshToken; private String userAgent; private String ipAddress; private LocalDateTime lastActivity; private String userId; public static SessionDto create(Session session, ModelMapper mapper) { SessionDto dto = mapper.map(session, SessionDto.class); dto.setTokenType(session.getTokenType()); dto.setUserId(session.getUserId()); return dto; } public void setSessionId(SessionId sessionId) { this.sessionId = sessionId.toString(); } public void setTokenType(Session.TokenType tokenType) { this.tokenType = tokenType.toString(); } public void setUserId(UserId userId) { this.userId = userId.id().toString(); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/configuration/AbstractVoidyCommandHandler.java ================================================ package com.tomo.mcauthentication.application.configuration; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.Voidy; import javax.transaction.Transactional; public abstract class AbstractVoidyCommandHandler implements CommandHandler { @Override @Transactional public Voidy handle(T aCommand) { abstractHandle(aCommand); return new Voidy(); } abstract protected void abstractHandle(T aCommand); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/configuration/CommandHandler.java ================================================ package com.tomo.mcauthentication.application.configuration; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.Response; public interface CommandHandler extends RequestHandler { R handle(T aCommand); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/configuration/QueryHandler.java ================================================ package com.tomo.mcauthentication.application.configuration; import com.tomo.mcauthentication.application.contracts.Query; import com.tomo.mcauthentication.application.contracts.Response; public interface QueryHandler extends RequestHandler { R handle(T query); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/configuration/RequestHandler.java ================================================ package com.tomo.mcauthentication.application.configuration; import com.tomo.mcauthentication.application.contracts.Request; import com.tomo.mcauthentication.application.contracts.Response; public interface RequestHandler { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/BaseCommand.java ================================================ package com.tomo.mcauthentication.application.contracts; public class BaseCommand extends BaseRequest implements Command { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/BaseQuery.java ================================================ package com.tomo.mcauthentication.application.contracts; public class BaseQuery extends BaseRequest implements Query {} ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/BaseRequest.java ================================================ package com.tomo.mcauthentication.application.contracts; import java.util.UUID; public class BaseRequest implements Request { private UUID id; public BaseRequest() { setId(UUID.randomUUID()); } public BaseRequest(UUID anId) { this.id = anId; } @Override public UUID id() { return this.id; } protected void setId(UUID anId) { this.id = anId; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/Command.java ================================================ package com.tomo.mcauthentication.application.contracts; public interface Command extends Request { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/Identifiable.java ================================================ package com.tomo.mcauthentication.application.contracts; import java.util.UUID; public interface Identifiable { UUID id(); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/McAuthenticationModule.java ================================================ package com.tomo.mcauthentication.application.contracts; import org.springframework.stereotype.Component; @Component public interface McAuthenticationModule { Response executeCommand(Command command); Response executeQuery(Query query); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/Query.java ================================================ package com.tomo.mcauthentication.application.contracts; public interface Query extends Request { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/Request.java ================================================ package com.tomo.mcauthentication.application.contracts; public interface Request extends Identifiable { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/Response.java ================================================ package com.tomo.mcauthentication.application.contracts; public interface Response { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/Voidy.java ================================================ package com.tomo.mcauthentication.application.contracts; public class Voidy implements Response { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/security/AbstractAuthenticateCommand.java ================================================ package com.tomo.mcauthentication.application.contracts.security; import com.tomo.mcauthentication.application.contracts.Command; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public abstract class AbstractAuthenticateCommand extends AbstractAuthenticateRequest implements Command { public AbstractAuthenticateCommand(String authToken) { super(authToken); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/security/AbstractAuthenticateQuery.java ================================================ package com.tomo.mcauthentication.application.contracts.security; import com.tomo.mcauthentication.application.contracts.BaseRequest; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.Query; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public abstract class AbstractAuthenticateQuery extends AbstractAuthenticateRequest implements Query { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/security/AbstractAuthenticateRequest.java ================================================ package com.tomo.mcauthentication.application.contracts.security; import com.tomo.mcauthentication.application.contracts.BaseRequest; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.Request; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public abstract class AbstractAuthenticateRequest extends BaseRequest implements Authenticate, Request { String authToken; public AbstractAuthenticateRequest(String authToken) { this.authToken = authToken; } @Override public String authToken() { return this.authToken; } @Override public void setAuthToken(String authToken) { this.authToken = authToken; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/security/AbstractAuthorizeCommand.java ================================================ package com.tomo.mcauthentication.application.contracts.security; import com.tomo.mcauthentication.application.contracts.Command; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public abstract class AbstractAuthorizeCommand extends AbstractAuthorizeRequest implements Command { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/security/AbstractAuthorizeQuery.java ================================================ package com.tomo.mcauthentication.application.contracts.security; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.Query; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public abstract class AbstractAuthorizeQuery extends AbstractAuthorizeRequest implements Query { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/security/AbstractAuthorizeRequest.java ================================================ package com.tomo.mcauthentication.application.contracts.security; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.Request; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public abstract class AbstractAuthorizeRequest extends AbstractAuthenticateRequest implements Request, Authorize { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/security/Authenticate.java ================================================ package com.tomo.mcauthentication.application.contracts.security; public interface Authenticate { String authToken(); void setAuthToken(String authToken); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/contracts/security/Authorize.java ================================================ package com.tomo.mcauthentication.application.contracts.security; import java.util.List; public interface Authorize extends Authenticate { default List getAuthorities() { return List.of("USER"); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/CreatePasswordRecoveryCodeCommandHandler.java ================================================ package com.tomo.mcauthentication.application.recovery; import com.tomo.mcauthentication.application.recovery.command.CreatePasswordRecoveryCodeCommand; import com.tomo.mcauthentication.application.authentication.dto.RecoveryPasswordDto; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.domain.registration.EmailAuthenticationService; import org.springframework.stereotype.Service; @Service public class CreatePasswordRecoveryCodeCommandHandler implements CommandHandler { EmailAuthenticationService emailAuthenticationService; public CreatePasswordRecoveryCodeCommandHandler(EmailAuthenticationService emailAuthenticationService) { this.emailAuthenticationService = emailAuthenticationService; } @Override public RecoveryPasswordDto handle(CreatePasswordRecoveryCodeCommand aCommand) { return new RecoveryPasswordDto( emailAuthenticationService .createPasswordRecoveryCode(aCommand.getEmail()) ); //todo send recovery code } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/GetUserRegistrationWithRecoveryCodeQueryHandler.java ================================================ package com.tomo.mcauthentication.application.recovery; import com.tomo.mcauthentication.application.BaseMapper; import com.tomo.mcauthentication.application.configuration.QueryHandler; import com.tomo.mcauthentication.application.registration.dto.UserRegistrationDto; import com.tomo.mcauthentication.application.recovery.dto.GetUserRegistrationWithRecoveryCodeQuery; import com.tomo.mcauthentication.domain.EncryptionService; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Component; @Component public class GetUserRegistrationWithRecoveryCodeQueryHandler extends BaseMapper implements QueryHandler { private UserRegistrationRepository userRegistrationRepository; private EncryptionService encryptionService; public GetUserRegistrationWithRecoveryCodeQueryHandler( UserRegistrationRepository aUserRegistrationRepository, EncryptionService anEncryptionService, ModelMapper modelMapper) { super(modelMapper); this.userRegistrationRepository = aUserRegistrationRepository; this.encryptionService = anEncryptionService; } @Override public UserRegistrationDto handle(GetUserRegistrationWithRecoveryCodeQuery query) { UserRegistration userRegistration = userRegistrationRepository .findByRecoveryCode(encryptionService.encryptedValue(query.getRecoveryCode())); if (userRegistration == null) { throw new IllegalStateException(String.format("User with recovery code %s doesn't exists.", query.getRecoveryCode())); } UserRegistrationDto dto = toDto( userRegistration, UserRegistrationDto.class ); return dto; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/PasswordRecoveryCodeCreatedEventHandler.java ================================================ package com.tomo.mcauthentication.application.recovery; import com.tomo.mcauthentication.application.contracts.McAuthenticationModule; import com.tomo.mcauthentication.application.recovery.command.SendPasswordRecoveryEmailCommand; import com.tomo.ddd.domain.DomainEventPublisher; import com.tomo.ddd.domain.DomainEventSubscriber; import com.tomo.mcauthentication.domain.registration.events.PasswordRecoveryCodeCreated; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class PasswordRecoveryCodeCreatedEventHandler { private McAuthenticationModule module; /** * In order to not mix business logic with plugins like a GUI.. * baseUrl + URI + queryString eg. localhost/reset-passwrod/?recoveryCode= */ private String recoveryLink; public PasswordRecoveryCodeCreatedEventHandler(McAuthenticationModule authenticationModule, String recoveryLink) { this.module = authenticationModule; this.recoveryLink = recoveryLink; } @Before("execution(" + "public * com.tomo.mcauthentication.application.configuration.CommandHandler.*(..)) && " + "target(com.tomo.mcauthentication.application.recovery.CreatePasswordRecoveryCodeCommandHandler))") public void listen() { DomainEventPublisher .instance() .subscribe(new DomainEventSubscriber() { public void handleEvent(PasswordRecoveryCodeCreated aDomainEvent) { module.executeCommand(new SendPasswordRecoveryEmailCommand( aDomainEvent.getEmail(), aDomainEvent.getRecoveryCode(), recoveryLink, aDomainEvent.getRecoveryCodeExpirationDate())); } public Class subscribedToEventType() { return PasswordRecoveryCodeCreated.class; } }); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/SendPasswordRecoveryEmailCommandHandler.java ================================================ package com.tomo.mcauthentication.application.recovery; import com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler; import com.tomo.mcauthentication.application.recovery.command.SendPasswordRecoveryEmailCommand; import com.tomo.ddd.email.EmailSender; import com.tomo.ddd.port.adapter.message.email.EmailMessage; import org.springframework.stereotype.Component; @Component public class SendPasswordRecoveryEmailCommandHandler extends AbstractVoidyCommandHandler { EmailSender emailMessageSender; public SendPasswordRecoveryEmailCommandHandler(EmailSender emailMessageSender) { this.emailMessageSender = emailMessageSender; } @Override protected void abstractHandle(SendPasswordRecoveryEmailCommand aCommand) { //todo maybe validate if exist this.emailMessageSender.send(new EmailMessage( aCommand.getEmail(), "" , " Recovery code", "To reset your password, visit the following link: " + aCommand.getRecoveryLink() + aCommand.getRecoveryCode())); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/UpdatePasswordWithRecoveryCodeCommandHandler.java ================================================ package com.tomo.mcauthentication.application.recovery; import com.tomo.mcauthentication.application.recovery.command.UpdatePasswordWithRecoveryCodeCommand; import com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler; import com.tomo.mcauthentication.domain.registration.EmailAuthenticationService; import org.springframework.stereotype.Service; @Service public class UpdatePasswordWithRecoveryCodeCommandHandler extends AbstractVoidyCommandHandler { EmailAuthenticationService emailAuthenticationService; public UpdatePasswordWithRecoveryCodeCommandHandler(EmailAuthenticationService emailAuthenticationService) { this.emailAuthenticationService = emailAuthenticationService; } @Override protected void abstractHandle(UpdatePasswordWithRecoveryCodeCommand aCommand) { emailAuthenticationService.recoverPasswordWithRecoveryCode( aCommand.getRecoveryCode(), aCommand.getNewPassword(), aCommand.getNewPasswordRepeated()); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/command/CreatePasswordRecoveryCodeCommand.java ================================================ package com.tomo.mcauthentication.application.recovery.command; import com.tomo.mcauthentication.application.contracts.BaseCommand; import javax.validation.constraints.NotNull; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class CreatePasswordRecoveryCodeCommand extends BaseCommand { @NotNull private String email; public CreatePasswordRecoveryCodeCommand(String email) { this.email = email; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/command/SendPasswordRecoveryEmailCommand.java ================================================ package com.tomo.mcauthentication.application.recovery.command; import com.tomo.mcauthentication.application.contracts.BaseCommand; import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class SendPasswordRecoveryEmailCommand extends BaseCommand { String email; String recoveryCode; String recoveryLink; LocalDateTime recoveryCodeExpirationDate; public SendPasswordRecoveryEmailCommand(String email, String recoveryCode, String recoveryLink, LocalDateTime recoveryCodeExpirationDate) { this.email = email; this.recoveryCode = recoveryCode; this.recoveryLink = recoveryLink; this.recoveryCodeExpirationDate = recoveryCodeExpirationDate; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/command/UpdatePasswordWithRecoveryCodeCommand.java ================================================ package com.tomo.mcauthentication.application.recovery.command; import com.tomo.mcauthentication.application.contracts.BaseCommand; import javax.validation.constraints.NotNull; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class UpdatePasswordWithRecoveryCodeCommand extends BaseCommand { @NotNull private String newPassword; @NotNull private String newPasswordRepeated; @NotNull private String recoveryCode; public UpdatePasswordWithRecoveryCodeCommand(String newPassword, String newPasswordRepeated, String recoveryCode) { this.newPassword = newPassword; this.newPasswordRepeated = newPasswordRepeated; this.recoveryCode = recoveryCode; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/recovery/dto/GetUserRegistrationWithRecoveryCodeQuery.java ================================================ package com.tomo.mcauthentication.application.recovery.dto; import com.tomo.mcauthentication.application.contracts.BaseQuery; import javax.validation.constraints.NotNull; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class GetUserRegistrationWithRecoveryCodeQuery extends BaseQuery { @NotNull String recoveryCode; public GetUserRegistrationWithRecoveryCodeQuery(String recoveryCode) { this.recoveryCode = recoveryCode; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/ChangePasswordCommandHandler.java ================================================ package com.tomo.mcauthentication.application.registration; import com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler; import com.tomo.mcauthentication.application.registration.command.ChangePasswordCommand; import com.tomo.mcauthentication.domain.registration.EmailAuthenticationService; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionAuthenticationService; import org.springframework.stereotype.Service; @Service public class ChangePasswordCommandHandler extends AbstractVoidyCommandHandler { EmailAuthenticationService emailAuthenticationService; SessionAuthenticationService sessionAuthenticationService; UserRegistrationRepository userRegistrationRepository; public ChangePasswordCommandHandler( EmailAuthenticationService emailAuthenticationService, SessionAuthenticationService aSessionAuthenticationService, UserRegistrationRepository anUserRegistrationRepository) { this.emailAuthenticationService = emailAuthenticationService; this.sessionAuthenticationService = aSessionAuthenticationService; this.userRegistrationRepository = anUserRegistrationRepository; } @Override protected void abstractHandle(ChangePasswordCommand aCommand) { Session session = sessionAuthenticationService.authenticate(aCommand.getAccessToken()); UserRegistration userRegistration = userRegistrationRepository.findByUserId(session.getUserId()); if (userRegistration == null) { throw new IllegalStateException(String.format("User with id %s doesn't exist.", session.getUserId().id())); } userRegistration.changePassword(aCommand.getOldPassword(), aCommand.getNewPassword(), aCommand.getNewPasswordRepeated()); userRegistrationRepository.save(userRegistration); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/ConfirmUserRegistrationCommandHandler.java ================================================ package com.tomo.mcauthentication.application.registration; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.application.registration.command.ConfirmUserRegistrationCommand; import com.tomo.mcauthentication.application.users.dto.BaseUserDto; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserRepository; import org.springframework.stereotype.Component; @Component public class ConfirmUserRegistrationCommandHandler implements CommandHandler { UserRegistrationRepository userRegistrationRepository; UserRepository userRepository; public ConfirmUserRegistrationCommandHandler( UserRegistrationRepository userRegistrationRepository, UserRepository userRepository) { this.userRegistrationRepository = userRegistrationRepository; this.userRepository = userRepository; } @Override public BaseUserDto handle(ConfirmUserRegistrationCommand command) { UserRegistration userRegistration = userRegistrationRepository.findByConfirmationCode(command.getConfirmationLink()); if (userRegistration == null) { throw new IllegalStateException( String.format("UserRegistration with confirmation link %s cannot be found.", command.getConfirmationLink()) ); } User user = userRegistration.createUser(userRepository); userRegistration.setUserId(user.getUserId()); userRepository.save(user); userRegistrationRepository.save(userRegistration); return null; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/GetUserRegistrationQueryHandler.java ================================================ package com.tomo.mcauthentication.application.registration; import com.tomo.mcauthentication.application.configuration.QueryHandler; import com.tomo.mcauthentication.application.registration.dto.UserRegistrationDto; import com.tomo.mcauthentication.application.registration.query.GetUserRegistrationQuery; public class GetUserRegistrationQueryHandler implements QueryHandler { @Override public UserRegistrationDto handle(GetUserRegistrationQuery request) { return null; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/NewUserRegisteredEventHandler.java ================================================ package com.tomo.mcauthentication.application.registration; import com.tomo.mcauthentication.application.contracts.McAuthenticationModule; import com.tomo.mcauthentication.application.registration.command.SendRegistrationConfirmationEmailCommand; import com.tomo.ddd.domain.DomainEventPublisher; import com.tomo.ddd.domain.DomainEventSubscriber; import com.tomo.mcauthentication.domain.registration.events.UserRegistrationRequested; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class NewUserRegisteredEventHandler { private McAuthenticationModule authenticationModule; /** * In order to not mix business logic with plugins like a GUI.. * baseUrl + URI + queryString eg. localhost/register/confirm/?confirmationCode= */ private String confirmationLink; public NewUserRegisteredEventHandler(McAuthenticationModule authenticationModule, String confirmationLink) { this.authenticationModule = authenticationModule; this.confirmationLink = confirmationLink; } @Before(value = "execution(public * com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler.*(..)) && target(com.tomo.mcauthentication.application.registration.RegisterNewUserCommandHandler))") public void listen() { DomainEventPublisher .instance() .subscribe(new DomainEventSubscriber() { public void handleEvent(UserRegistrationRequested aDomainEvent) { authenticationModule.executeCommand(new SendRegistrationConfirmationEmailCommand( aDomainEvent.getEmail(), confirmationLink, aDomainEvent.getConfirmationCode())); } public Class subscribedToEventType() { return UserRegistrationRequested.class; // all domain events } }); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/RegisterNewUserCommandHandler.java ================================================ package com.tomo.mcauthentication.application.registration; import com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler; import com.tomo.mcauthentication.application.registration.command.RegisterNewUserCommand; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.users.UserRepository; import org.springframework.stereotype.Component; @Component public class RegisterNewUserCommandHandler extends AbstractVoidyCommandHandler { UserRegistrationRepository userRegistrationRepository; UserRepository userRepository; public RegisterNewUserCommandHandler( UserRegistrationRepository userRegistrationRepository, UserRepository userRepository) { this.userRegistrationRepository = userRegistrationRepository; this.userRepository = userRepository; } @Override protected void abstractHandle(RegisterNewUserCommand aCommand) { UserRegistration userRegistration = UserRegistration.registerNewUser( aCommand.getPassword(), aCommand.getEmail(), aCommand.getFirstName(), aCommand.getLastName() ); userRegistrationRepository.save(userRegistration); //todo send email } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/SendRegistrationConfirmationEmailCommandHandler.java ================================================ package com.tomo.mcauthentication.application.registration; import com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler; import com.tomo.mcauthentication.application.registration.command.SendRegistrationConfirmationEmailCommand; import com.tomo.ddd.email.EmailSender; import com.tomo.ddd.port.adapter.message.email.EmailMessage; import org.springframework.stereotype.Component; @Component public class SendRegistrationConfirmationEmailCommandHandler extends AbstractVoidyCommandHandler { EmailSender emailMessageSender; public SendRegistrationConfirmationEmailCommandHandler(EmailSender emailMessageSender) { this.emailMessageSender = emailMessageSender; } @Override protected void abstractHandle(SendRegistrationConfirmationEmailCommand aCommand) { //todo maybe validate if exist this.emailMessageSender.send(new EmailMessage( aCommand.getEmail(), "" , "Confirm registration", "Click on this confirmation link " + aCommand.getConfirmLink() + aCommand.getConfirmationCode())); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/command/ChangePasswordCommand.java ================================================ package com.tomo.mcauthentication.application.registration.command; import com.tomo.mcauthentication.application.contracts.BaseCommand; import javax.validation.constraints.NotNull; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class ChangePasswordCommand extends BaseCommand { String accessToken; @NotNull private String oldPassword; @NotNull private String newPassword; @NotNull private String newPasswordRepeated; public ChangePasswordCommand(String accessToken, String oldPassword, String newPassword, String newPasswordRepeated) { this.accessToken = accessToken; this.oldPassword = oldPassword; this.newPassword = newPassword; this.newPasswordRepeated = newPasswordRepeated; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/command/ConfirmUserRegistrationCommand.java ================================================ package com.tomo.mcauthentication.application.registration.command; import com.tomo.mcauthentication.application.contracts.BaseCommand; import java.util.UUID; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Setter @Getter @NoArgsConstructor public class ConfirmUserRegistrationCommand extends BaseCommand { private String confirmationLink; public ConfirmUserRegistrationCommand(String confirmLink) { this.confirmationLink = confirmLink; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/command/RegisterNewUserCommand.java ================================================ package com.tomo.mcauthentication.application.registration.command; import com.tomo.mcauthentication.application.contracts.BaseCommand; import javax.validation.constraints.NotNull; import java.util.UUID; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class RegisterNewUserCommand extends BaseCommand { @NotNull String firstName; @NotNull String lastName; @NotNull String email; @NotNull String password; public RegisterNewUserCommand(String firstName, String lastName, String email, String password) { this.firstName = firstName; this.lastName = lastName; this.email = email; this.password = password; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/command/SendRegistrationConfirmationEmailCommand.java ================================================ package com.tomo.mcauthentication.application.registration.command; import com.tomo.mcauthentication.application.contracts.BaseCommand; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class SendRegistrationConfirmationEmailCommand extends BaseCommand { String email; String confirmLink; String confirmationCode; public SendRegistrationConfirmationEmailCommand(String email, String confirmLink, String confirmationCode) { this.email = email; this.confirmLink = confirmLink; this.confirmationCode = confirmationCode; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/dto/UserRegistrationDto.java ================================================ package com.tomo.mcauthentication.application.registration.dto; import com.tomo.mcauthentication.application.contracts.Response; import com.tomo.mcauthentication.domain.registration.UserRegistrationId; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class UserRegistrationDto implements Response { private String email; private String firstName; private String lastName; private LocalDateTime registerDate; private UserRegistrationStatus status; } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/registration/query/GetUserRegistrationQuery.java ================================================ package com.tomo.mcauthentication.application.registration.query; import com.tomo.mcauthentication.application.contracts.BaseQuery; import java.util.UUID; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class GetUserRegistrationQuery extends BaseQuery { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/users/ChangeUserDetailsCommandHandler.java ================================================ package com.tomo.mcauthentication.application.users; import com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler; import com.tomo.mcauthentication.application.users.command.ChangeUserDetailsCommand; import com.tomo.mcauthentication.domain.registration.EmailAuthenticationService; import com.tomo.mcauthentication.domain.session.TokenProvider; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; import com.tomo.mcauthentication.domain.users.UserRepository; import org.springframework.stereotype.Service; import lombok.AllArgsConstructor; @Service @AllArgsConstructor public class ChangeUserDetailsCommandHandler extends AbstractVoidyCommandHandler { EmailAuthenticationService emailAuthenticationService; TokenProvider tokenProvider; UserRepository userRepository; @Override public void abstractHandle(ChangeUserDetailsCommand aCommand) { UserId userId = tokenProvider.getUserIdFromToken(aCommand.getAuthToken()); if (!aCommand.getUserId().equals(userId.id())) { throw new IllegalArgumentException(String.format("Forbidden access. User with ID %s cannot change user details for user with ID: %s", userId.id(), aCommand.getUserId())); } User user = userRepository.findById(new UserId(aCommand.getUserId())); user.updateDetails(aCommand.getFirstName(), aCommand.getLastName()); userRepository.save(user); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/users/GetUserQueryHandler.java ================================================ package com.tomo.mcauthentication.application.users; import com.tomo.mcauthentication.application.BaseMapper; import com.tomo.mcauthentication.application.configuration.QueryHandler; import com.tomo.mcauthentication.application.users.dto.BaseUserDto; import com.tomo.mcauthentication.application.users.query.GetUserQuery; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserRepository; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Component; @Component public class GetUserQueryHandler extends BaseMapper implements QueryHandler { private UserRepository userRepository; public GetUserQueryHandler( UserRepository userRepository, ModelMapper modelMapper) { super(modelMapper); this.userRepository = userRepository; } @Override public BaseUserDto handle(GetUserQuery query) { User user = userRepository.findById(query.getUserId()); if (user == null) { throw new IllegalStateException(String.format("User with id %s doesn't exists.", query.getUserId().toString())); } return toDto(user); } private BaseUserDto toDto(User user) { return modelMapper.map(user, BaseUserDto.class); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/users/command/ChangeUserDetailsCommand.java ================================================ package com.tomo.mcauthentication.application.users.command; import com.tomo.mcauthentication.application.contracts.security.AbstractAuthorizeCommand; import java.util.List; import java.util.UUID; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class ChangeUserDetailsCommand extends AbstractAuthorizeCommand { private UUID userId; private String firstName; private String lastName; public ChangeUserDetailsCommand(UUID userId, String firstName, String lastName) { this.userId = userId; this.firstName = firstName; this.lastName = lastName; } @Override public List getAuthorities() { List authorities = super.getAuthorities(); authorities.add("ADMIN"); return authorities; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/users/dto/BaseUserDto.java ================================================ package com.tomo.mcauthentication.application.users.dto; import com.tomo.mcauthentication.application.contracts.Response; import com.tomo.mcauthentication.domain.users.UserId; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class BaseUserDto implements Response { private String userId; String firstName; String lastName; public void setUserId(UserId userId) { this.userId = userId.id().toString(); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/application/users/query/GetUserQuery.java ================================================ package com.tomo.mcauthentication.application.users.query; import com.tomo.mcauthentication.application.contracts.security.AbstractAuthenticateQuery; import com.tomo.mcauthentication.domain.users.UserId; import javax.validation.constraints.NotNull; import java.util.UUID; import lombok.Getter; import lombok.Setter; @Getter @Setter public class GetUserQuery extends AbstractAuthenticateQuery { @NotNull String userId; public GetUserQuery(String userId) { this.userId = userId; } public UserId getUserId() { return new UserId(UUID.fromString(this.userId)); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/DomainRegistry.java ================================================ package com.tomo.mcauthentication.domain; import com.tomo.mcauthentication.domain.registration.PasswordService; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.registration.rules.UserRegistrationMustBeUnique; import com.tomo.mcauthentication.domain.session.TokenProvider; import com.tomo.mcauthentication.domain.users.UserRepository; import com.tomo.mcauthentication.domain.users.rules.UserEmailMustBeUnique; import org.modelmapper.ModelMapper; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class DomainRegistry implements ApplicationContextAware { private static ApplicationContext applicationContext; public static EncryptionService encryptionService() { return (EncryptionService) applicationContext.getBean("MD5EncryptionService"); } public static PasswordService passwordService() { return (PasswordService) applicationContext.getBean("passwordService"); } public static TokenProvider tokenProvider() { return (TokenProvider) applicationContext.getBean("jwtTokenProvider"); } public static ModelMapper modelMapper() { return (ModelMapper) applicationContext.getBean("modelMapper"); } public static UserRepository userRepository() { return (UserRepository) applicationContext.getBean("userRepository"); } public static UserRegistrationRepository userRegistrationRepository() { return (UserRegistrationRepository) applicationContext.getBean("userRegistrationRepository"); } public static UserEmailMustBeUnique userEmailMustBeUnique(String anEmail) { return new UserEmailMustBeUnique(userRepository(), anEmail); } public static UserRegistrationMustBeUnique userRegistrationMustBeUnique(String anEmail) { return new UserRegistrationMustBeUnique(userRegistrationRepository(), anEmail); } @Override public void setApplicationContext(ApplicationContext anApplicationContext) throws BeansException { // if (DomainRegistry.applicationContext == null) { // DomainRegistry.applicationContext = anApplicationContext; // } DomainRegistry.applicationContext = anApplicationContext; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/EncryptionService.java ================================================ package com.tomo.mcauthentication.domain; public interface EncryptionService { String encryptedValue(String aPlainTextValue); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/oauth2/OAuth2Authentication.java ================================================ package com.tomo.mcauthentication.domain.oauth2; public interface OAuth2Authentication { OAuth2Principal authenticate(String anAccessCode); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/oauth2/OAuth2Principal.java ================================================ package com.tomo.mcauthentication.domain.oauth2; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor public class OAuth2Principal { private String id; private String email; private String firstName; private String lastName; private String imageUrl; private String provider; public OAuth2Principal(String id, String email, String firstName, String lastName, String imageUrl, String provider) { this.id = id; this.email = email; this.firstName = firstName; this.lastName = lastName; this.imageUrl = imageUrl; this.provider = provider; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/oauth2/OAuth2Service.java ================================================ package com.tomo.mcauthentication.domain.oauth2; import com.tomo.ddd.domain.BusinessRuleValidationException; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserRepository; import com.tomo.mcauthentication.domain.users.rules.UserEmailMustBeUnique; public class OAuth2Service { private OAuth2Authentication oAuth2Authentication; private UserRepository userRespository; public OAuth2Service(OAuth2Authentication oAuth2Authentication, UserRepository userRespository) { this.oAuth2Authentication = oAuth2Authentication; this.userRespository = userRespository; } public User registerAuthenticate(String anAccessCode) { OAuth2Principal principal = oAuth2Authentication.authenticate(anAccessCode); User user; try { user = authenticateAndRegister(principal); } catch (BusinessRuleValidationException exception) { if (UserEmailMustBeUnique.class == exception.getBrokenRule().getClass()) { user = userRespository.findByEmail(principal.getEmail()); if (!User.AuthProvider.valueOf(principal.getProvider()).equals(user.getProvider())) { throw exception; } } else { throw exception; } } return user; } protected User authenticateAndRegister(OAuth2Principal principal) { User user = new User( userRespository.nextIdentity(), principal.getFirstName(), principal.getLastName(), principal.getEmail(), User.AuthProvider.valueOf(principal.getProvider())); return userRespository.save(user); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/EmailAuthenticationService.java ================================================ package com.tomo.mcauthentication.domain.registration; import com.tomo.ddd.domain.BusinessRuleValidator; import com.tomo.mcauthentication.domain.EncryptionService; import com.tomo.mcauthentication.domain.registration.rules.PasswordsMustMatch; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserRepository; import org.springframework.stereotype.Service; @Service public class EmailAuthenticationService extends BusinessRuleValidator { UserRegistrationRepository userRegistrationRepository; UserRepository userRepository; EncryptionService encryptionService; public EmailAuthenticationService( UserRegistrationRepository userRegistrationRepository, UserRepository userRepository, EncryptionService encryptionService) { this.userRegistrationRepository = userRegistrationRepository; this.userRepository = userRepository; this.encryptionService = encryptionService; } public User authenticate( String anEmail, String aPassword) { this.assertArgumentNotEmpty(anEmail, "Email must be provided."); this.assertArgumentNotEmpty(aPassword, "Password must be provided."); UserRegistration userRegistration = userRegistrationRepository.findByEmail(anEmail); if (userRegistration == null) { throw new IllegalStateException(String.format("User with email %s doesn't exists.", anEmail)); } this.checkRule(new PasswordsMustMatch(userRegistration.getPassword(), encryptionService.encryptedValue(aPassword))); return userRepository.findByEmail(anEmail); } public String createPasswordRecoveryCode(String anEmail) { this.assertArgumentNotEmpty(anEmail, "Email must be provided."); UserRegistration userRegistration = userRegistrationRepository.findByEmail(anEmail); if (userRegistration == null) { throw new IllegalStateException(String.format("User with email %s doesn't exists.", anEmail)); } return userRegistration.createRecoveryCode(); } public void recoverPasswordWithRecoveryCode(String aRecoveryCode, String aNewPassword, String aNewPasswordRepeated) { this.assertArgumentNotNull(aRecoveryCode, "Recovery code is missing."); UserRegistration userRegistration = userRegistrationRepository .findByRecoveryCode(encryptionService.encryptedValue(aRecoveryCode)); if (userRegistration == null) { throw new IllegalStateException(String.format("User with recovery code %s doesn't exists.", aRecoveryCode)); } userRegistration.changePasswordWithRecoveryCode(aRecoveryCode, aNewPassword, aNewPasswordRepeated); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/PasswordService.java ================================================ package com.tomo.mcauthentication.domain.registration; import com.tomo.ddd.AssertionConcern; import org.springframework.stereotype.Component; import java.util.Random; @Component public final class PasswordService extends AssertionConcern { private static final String DIGITS = "0123456789"; private static final String LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final int STRONG_THRESHOLD = 20; private static final String SYMBOLS = "\"`!?$?%^&*()_-+={[}]:;@'~#|\\<,>.?/"; private static final int VERY_STRONG_THRESHOLD = 40; public PasswordService() { super(); } public String generateStrongPassword() { String generatedPassword = null; StringBuffer password = new StringBuffer(); Random random = new Random(); boolean isStrong = false; int index = 0; while (!isStrong) { int opt = random.nextInt(4); switch (opt) { case 0: index = random.nextInt(LETTERS.length()); password.append(LETTERS.substring(index, index+1)); break; case 1: index = random.nextInt(LETTERS.length()); password.append(LETTERS.substring(index, index+1).toLowerCase()); break; case 2: index = random.nextInt(DIGITS.length()); password.append(DIGITS.substring(index, index+1)); break; case 3: index = random.nextInt(SYMBOLS.length()); password.append(SYMBOLS.substring(index, index+1)); break; } generatedPassword = password.toString(); if (generatedPassword.length() >= 7) { isStrong = this.isStrong(generatedPassword); } } return generatedPassword; } public boolean isStrong(String aPlainTextPassword) { return this.calculatePasswordStrength(aPlainTextPassword) >= STRONG_THRESHOLD; } public boolean isVeryStrong(String aPlainTextPassword) { return this.calculatePasswordStrength(aPlainTextPassword) >= VERY_STRONG_THRESHOLD; } public boolean isWeak(String aPlainTextPassword) { return this.calculatePasswordStrength(aPlainTextPassword) < STRONG_THRESHOLD; } private int calculatePasswordStrength(String aPlainTextPassword) { this.assertArgumentNotNull(aPlainTextPassword, "Password strength cannot be tested on null."); int strength = 0; int length = aPlainTextPassword.length(); if (length > 7) { strength += 10; // bonus: one point each additional strength += (length - 7); } int digitCount = 0; int letterCount = 0; int lowerCount = 0; int upperCount = 0; int symbolCount = 0; for (int idx = 0; idx < length; ++idx) { char ch = aPlainTextPassword.charAt(idx); if (Character.isLetter(ch)) { ++letterCount; if (Character.isUpperCase(ch)) { ++upperCount; } else { ++lowerCount; } } else if (Character.isDigit(ch)) { ++digitCount; } else { ++symbolCount; } } strength += (upperCount + lowerCount + symbolCount); // bonus: letters and digits if (letterCount >= 2 && digitCount >= 2) { strength += (letterCount + digitCount); } return strength; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/UserRegistration.java ================================================ package com.tomo.mcauthentication.domain.registration; import com.tomo.ddd.domain.ConcurrencySafeEntity; import com.tomo.ddd.domain.DomainEvent; import com.tomo.ddd.domain.DomainEventPublisher; import com.tomo.mcauthentication.domain.DomainRegistry; import com.tomo.mcauthentication.domain.registration.events.PasswordChanged; import com.tomo.mcauthentication.domain.registration.events.PasswordRecovered; import com.tomo.mcauthentication.domain.registration.events.PasswordRecoveryCodeCreated; import com.tomo.mcauthentication.domain.registration.events.UserRegistrationConfirmed; import com.tomo.mcauthentication.domain.registration.events.UserRegistrationRequested; import com.tomo.mcauthentication.domain.registration.rules.PasswordRecoveryCodeShouldBeExpiredOrNull; import com.tomo.mcauthentication.domain.registration.rules.PasswordRecoveryCodeShouldNotExpired; import com.tomo.mcauthentication.domain.registration.rules.PasswordsMustMatch; import com.tomo.mcauthentication.domain.registration.rules.RecoveryCodeMustMatch; import com.tomo.mcauthentication.domain.registration.rules.UserRegistrationCannotBeConfirmedAfterExpiration; import com.tomo.mcauthentication.domain.registration.rules.UserRegistrationCannotBeConfirmedMoreThanOnce; import com.tomo.mcauthentication.domain.registration.rules.UserRegistrationMustBeConfirmed; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; import com.tomo.mcauthentication.domain.users.UserRepository; import javax.persistence.AttributeOverride; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import java.time.LocalDateTime; import java.time.temporal.ChronoField; import java.util.UUID; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Entity @NoArgsConstructor @Getter @Setter public class UserRegistration extends ConcurrencySafeEntity { public static Long RECOVERY_CODE_EXPIREATION_MSEC = 172800000L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String password; private String email; private String firstName; private String lastName; private String confirmationCode; private LocalDateTime registerDate; @Enumerated(EnumType.STRING) private UserRegistrationStatus status; private String recoveryCode; private LocalDateTime recoveryCodeExpirationDate; @Embedded @AttributeOverride(name="id", column = @Column(name="user_id")) private UserId userId; public static UserRegistration registerNewUser( String password, String email, String firstName, String lastName) { return new UserRegistration(password, email, firstName, lastName); } private UserRegistration( String aPassword, String anEmail, String aFirstName, String aLastName) { this.checkRule(DomainRegistry.userRegistrationMustBeUnique(anEmail)); this.checkRule(DomainRegistry.userEmailMustBeUnique(anEmail)); this.email = anEmail; this.firstName = aFirstName; this.lastName = aLastName; this.confirmationCode = UUID.randomUUID().toString(); this.registerDate = LocalDateTime.now(); this.status = UserRegistrationStatus.WaitingForConfirmation; this.protectPassword("", aPassword); this.publishEvent(new UserRegistrationRequested( this.email, this.confirmationCode, this.firstName, this.lastName, this.registerDate, this.status )); } public User createUser(UserRepository userRespository) { this.checkRule(new UserRegistrationCannotBeConfirmedMoreThanOnce(this.status)); this.checkRule(new UserRegistrationCannotBeConfirmedAfterExpiration(this.registerDate)); this.setStatus(UserRegistrationStatus.Confirmed); UserId userId = userRespository.nextIdentity(); this.setUserId(userId); this.publishEvent(new UserRegistrationConfirmed(this.id, this.getStatus(), this.getUserId())); return new User(userId, getFirstName(), getLastName(), getEmail(), User.AuthProvider.EMAIL); } public String createRecoveryCode() { this.checkRule(new UserRegistrationMustBeConfirmed(this.getStatus())); this.checkRule(new PasswordRecoveryCodeShouldBeExpiredOrNull(this)); String recoveryCode = UUID.randomUUID().toString(); this.recoveryCodeExpirationDate = LocalDateTime.now().plus(RECOVERY_CODE_EXPIREATION_MSEC, ChronoField.MILLI_OF_DAY.getBaseUnit()); this.setRecoveryCodeExpirationDate(recoveryCodeExpirationDate); this.setRecoveryCode(this.asEncryptedValue(recoveryCode)); this.publishEvent(new PasswordRecoveryCodeCreated( this.id, this.email, recoveryCode, this.recoveryCodeExpirationDate)); return recoveryCode; } public boolean isRecoveryCodeUnexpired() { return recoveryCodeExpirationDate != null && recoveryCodeExpirationDate.isAfter(LocalDateTime.now()); } public boolean isRecoveryCodeExpired() { return recoveryCodeExpirationDate != null && recoveryCodeExpirationDate.isBefore(LocalDateTime.now()); } public void changePassword(String anOldPassword, String aNewPassword, String aNewPasswordRepeated) { this.assertArgumentNotNull(anOldPassword, "Old password is missing."); this.assertNewPassword(aNewPassword, aNewPasswordRepeated); this.checkRule(new PasswordsMustMatch(this.getPassword(), this.asEncryptedValue(anOldPassword))); this.protectPassword(this.getPassword(), aNewPassword); this.publishEvent(new PasswordChanged(this.id, this.password)); } public void changePasswordWithRecoveryCode(String aRecoveryCode, String aNewPassword, String aNewPasswordRepeated) { this.assertArgumentNotNull(aNewPassword, "New password is missing."); this.assertNewPassword(aNewPassword, aNewPasswordRepeated); this.checkRule(new RecoveryCodeMustMatch(this.asEncryptedValue(aRecoveryCode), this.getRecoveryCode())); this.checkRule(new PasswordRecoveryCodeShouldNotExpired(this)); this.protectPassword(this.getPassword(), aNewPassword); this.setRecoveryCodeExpirationDate(LocalDateTime.now()); this.publishEvent(new PasswordRecovered( this.id, this.getPassword(), this.getRecoveryCode() )); } protected void assertNewPassword(String aNewPassword, String aNewPasswordRepeated) { this.assertArgumentNotNull(aNewPassword, "New password is missing."); this.assertArgumentNotNull(aNewPasswordRepeated, "Repeated password is missing."); this.assertArgumentEquals(aNewPassword, aNewPasswordRepeated, "Provided passwords must be equal."); } protected void protectPassword(String aCurrentPassword, String aChangedPassword) { this.assertPasswordsNotSame(aCurrentPassword, this.asEncryptedValue(aChangedPassword)); this.assertPasswordNotWeak(aChangedPassword); this.assertUsernamePasswordNotSame(aChangedPassword); this.setPassword(this.asEncryptedValue(aChangedPassword)); } protected void assertPasswordsNotSame(String aCurrentPassword, String aChangedPassword) { this.assertArgumentNotEquals( aCurrentPassword, aChangedPassword, "The password is unchanged."); } protected void assertPasswordNotWeak(String aPlainTextPassword) { this.assertArgumentFalse( DomainRegistry.passwordService().isWeak(aPlainTextPassword), "The password must be stronger."); } protected void assertUsernamePasswordNotSame(String aPlainTextPassword) { this.assertArgumentNotEquals( this.getEmail(), aPlainTextPassword, "The username and password must not be the same."); } protected String asEncryptedValue(String aPlainTextPassword) { String encryptedValue = DomainRegistry .encryptionService() .encryptedValue(aPlainTextPassword); return encryptedValue; } protected void setPassword(String aPassword) { this.password = aPassword; } private void publishEvent(DomainEvent domainEvent) { DomainEventPublisher.instance().publish(domainEvent); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/UserRegistrationId.java ================================================ package com.tomo.mcauthentication.domain.registration; import com.tomo.ddd.domain.AbstractId; import javax.persistence.Embeddable; import java.util.UUID; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Embeddable @Getter @Setter @NoArgsConstructor public class UserRegistrationId extends AbstractId { public UserRegistrationId(UUID anId) { super(anId); } @Override protected int hashPrimeValue() { return 0; } @Override protected int hashOddValue() { return 0; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/UserRegistrationRepository.java ================================================ package com.tomo.mcauthentication.domain.registration; import com.tomo.ddd.domain.BaseRepository; import com.tomo.mcauthentication.domain.users.UserId; import java.util.List; public interface UserRegistrationRepository extends BaseRepository { List findAllByEmail(List emails); UserRegistration findByEmail(String anEmail); long countByEmailAndStatus(String email, UserRegistrationStatus status); UserRegistration findByConfirmationCode(String confirmationCode); UserRegistration findByRecoveryCode(String aRecoveryCode); UserRegistration findByUserId(UserId anUserId); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/UserRegistrationStatus.java ================================================ package com.tomo.mcauthentication.domain.registration; public enum UserRegistrationStatus { WaitingForConfirmation("WaitingForConfirmation"), Confirmed("Confirmed"), Expired("Expired"); String value; UserRegistrationStatus(String value) { this.value = value; } @Override public String toString() { return this.value; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/UsersCounter.java ================================================ package com.tomo.mcauthentication.domain.registration; public interface UsersCounter { int countUsersWithLogin(); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/events/PasswordChanged.java ================================================ package com.tomo.mcauthentication.domain.registration.events; import com.tomo.ddd.domain.BaseDomainEvent; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class PasswordChanged extends BaseDomainEvent { private long userRegistrationId; private String password; //encrypted public PasswordChanged(long userRegistrationId, String password) { this.userRegistrationId = userRegistrationId; this.password = password; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/events/PasswordRecovered.java ================================================ package com.tomo.mcauthentication.domain.registration.events; import com.tomo.ddd.domain.BaseDomainEvent; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class PasswordRecovered extends BaseDomainEvent { private long userRegistrationId; private String password; //encrypted private String recoveryCode; //encrypted public PasswordRecovered(long userRegistrationId, String password, String recoveryCode) { this.userRegistrationId = userRegistrationId; this.password = password; this.recoveryCode = recoveryCode; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/events/PasswordRecoveryCodeCreated.java ================================================ package com.tomo.mcauthentication.domain.registration.events; import com.tomo.ddd.domain.BaseDomainEvent; import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class PasswordRecoveryCodeCreated extends BaseDomainEvent { long userRegistrationId; private String email; private String recoveryCode; private LocalDateTime recoveryCodeExpirationDate; public PasswordRecoveryCodeCreated(long userRegistrationId, String email, String recoveryCode, LocalDateTime recoveryCodeExpirationDate) { this.userRegistrationId = userRegistrationId; this.email = email; this.recoveryCode = recoveryCode; this.recoveryCodeExpirationDate = recoveryCodeExpirationDate; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/events/UserRegistrationConfirmed.java ================================================ package com.tomo.mcauthentication.domain.registration.events; import com.tomo.ddd.domain.BaseDomainEvent; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; import com.tomo.mcauthentication.domain.users.UserId; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class UserRegistrationConfirmed extends BaseDomainEvent { private long userRegistrationId; private UserRegistrationStatus status; private UserId userId; public UserRegistrationConfirmed(long userRegistrationId, UserRegistrationStatus status, UserId userId) { this.userRegistrationId = userRegistrationId; this.status = status; this.userId = userId; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/events/UserRegistrationRequested.java ================================================ package com.tomo.mcauthentication.domain.registration.events; import com.tomo.ddd.domain.BaseDomainEvent; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class UserRegistrationRequested extends BaseDomainEvent { private long userRegistrationId; private String email; private String confirmationCode; private String firstName; private String lastName; private LocalDateTime registerDate; private UserRegistrationStatus status; public UserRegistrationRequested( String email, String confirmationCode, String firstName, String lastName, LocalDateTime registerDate, UserRegistrationStatus status) { this.email = email; this.confirmationCode = confirmationCode; this.firstName = firstName; this.lastName = lastName; this.registerDate = registerDate; this.status = status; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/rules/PasswordRecoveryCodeShouldBeExpiredOrNull.java ================================================ package com.tomo.mcauthentication.domain.registration.rules; import com.tomo.ddd.domain.BusinessRule; import com.tomo.mcauthentication.domain.registration.UserRegistration; public class PasswordRecoveryCodeShouldBeExpiredOrNull implements BusinessRule { private UserRegistration userRegistration; public PasswordRecoveryCodeShouldBeExpiredOrNull(UserRegistration aUserRegistration) { this.userRegistration = aUserRegistration; } @Override public Boolean isRuleComplied() { return userRegistration.isRecoveryCodeExpired() || userRegistration.getRecoveryCode() == null; } @Override public String message() { return "User recovery code is not expired yet. You can't get new one after the current code expire."; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/rules/PasswordRecoveryCodeShouldNotExpired.java ================================================ package com.tomo.mcauthentication.domain.registration.rules; import com.tomo.ddd.domain.BusinessRule; import com.tomo.mcauthentication.domain.registration.UserRegistration; public class PasswordRecoveryCodeShouldNotExpired implements BusinessRule { private UserRegistration userRegistration; public PasswordRecoveryCodeShouldNotExpired(UserRegistration aUserRegistration) { this.userRegistration = aUserRegistration; } @Override public Boolean isRuleComplied() { return userRegistration.isRecoveryCodeUnexpired(); } @Override public String message() { return "User recovery code is not expired yet. You can't get new one after the current code expire."; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/rules/PasswordsMustMatch.java ================================================ package com.tomo.mcauthentication.domain.registration.rules; import com.tomo.ddd.domain.BusinessRule; public class PasswordsMustMatch implements BusinessRule { private String providedPassoword; private String storedPassword; public PasswordsMustMatch(String storedPassword, String providedPassoword) { this.storedPassword = storedPassword; this.providedPassoword = providedPassoword; } @Override public Boolean isRuleComplied() { return providedPassoword.equals(storedPassword); } @Override public String message() { return "Passwords dont match."; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/rules/RecoveryCodeMustMatch.java ================================================ package com.tomo.mcauthentication.domain.registration.rules; import com.tomo.ddd.domain.BusinessRule; public class RecoveryCodeMustMatch implements BusinessRule { private String providedCode; private String storedCode; public RecoveryCodeMustMatch(String aStoredCode, String aProvidedCode) { this.storedCode = aStoredCode; this.providedCode = aProvidedCode; } @Override public Boolean isRuleComplied() { return providedCode.equals(storedCode); } @Override public String message() { return "Passwords dont match."; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/rules/UserRegistrationCannotBeConfirmedAfterExpiration.java ================================================ package com.tomo.mcauthentication.domain.registration.rules; import com.tomo.ddd.domain.BusinessRule; import java.time.LocalDateTime; public class UserRegistrationCannotBeConfirmedAfterExpiration implements BusinessRule { public static final int CONFIRMATION_LINK_DURATION = 8; LocalDateTime registerDate; public UserRegistrationCannotBeConfirmedAfterExpiration(LocalDateTime aRegisterDate) { this.registerDate = aRegisterDate; } @Override public Boolean isRuleComplied() { return LocalDateTime.now().isBefore(this.registerDate.plusDays(CONFIRMATION_LINK_DURATION)); } @Override public String message() { return null; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/rules/UserRegistrationCannotBeConfirmedMoreThanOnce.java ================================================ package com.tomo.mcauthentication.domain.registration.rules; import com.tomo.ddd.domain.BusinessRule; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; public class UserRegistrationCannotBeConfirmedMoreThanOnce implements BusinessRule { UserRegistrationStatus status; public UserRegistrationCannotBeConfirmedMoreThanOnce(UserRegistrationStatus aStatus) { this.status = aStatus; } @Override public Boolean isRuleComplied() { return !this.status.equals(UserRegistrationStatus.Confirmed); } @Override public String message() { return "User Registration cannot be confirmed more than once"; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/rules/UserRegistrationMustBeConfirmed.java ================================================ package com.tomo.mcauthentication.domain.registration.rules; import com.tomo.ddd.domain.BusinessRule; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; public class UserRegistrationMustBeConfirmed implements BusinessRule { private UserRegistrationStatus userRegistrationStatus; public UserRegistrationMustBeConfirmed(UserRegistrationStatus anUserRegistrationStatus) { this.userRegistrationStatus = anUserRegistrationStatus; } @Override public Boolean isRuleComplied() { return userRegistrationStatus.equals(UserRegistrationStatus.Confirmed); } @Override public String message() { return "User registration is not confirmed yet."; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/registration/rules/UserRegistrationMustBeUnique.java ================================================ package com.tomo.mcauthentication.domain.registration.rules; import com.tomo.ddd.domain.BusinessRule; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; public class UserRegistrationMustBeUnique implements BusinessRule { private UserRegistrationRepository repository; private String email; public UserRegistrationMustBeUnique(UserRegistrationRepository usersCounter, String email) { this.repository = usersCounter; this.email = email; } @Override public Boolean isRuleComplied() { return repository.countByEmailAndStatus(email, UserRegistrationStatus.Confirmed) < 1; } @Override public String message() { return "User login must be unique"; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/session/JwtTokenProvider.java ================================================ package com.tomo.mcauthentication.domain.session; import com.tomo.mcauthentication.domain.session.Session.TokenType; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; import com.tomo.mcauthentication.infrastructure.springboot.configuration.AppProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import javax.crypto.SecretKey; import java.util.Date; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @Service @Qualifier("jwtTokenProvider") public class JwtTokenProvider implements TokenProvider { private static final Logger logger = LoggerFactory.getLogger(TokenProvider.class); private AppProperties appProperties; public JwtTokenProvider(AppProperties appProperties) { this.appProperties = appProperties; } @Override public String createToken(User user) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + appProperties.getAuth().getTokenExpirationMsec()); return Jwts.builder() .setSubject(user.getUserId().id().toString()) .setIssuedAt(new Date()) .setExpiration(expiryDate) .signWith(secretKey()) .compact(); } @Override public UserId getUserIdFromToken(String anAuthToken) { Claims claims = Jwts.parserBuilder() .setSigningKey(secretKey()) .build() .parseClaimsJws(anAuthToken) .getBody(); return new UserId(claims.getSubject()); } @Override public boolean validateToken(String anAuthToken) { try { Jwts.parserBuilder() .setSigningKey(secretKey()) .build() .parseClaimsJws(anAuthToken); return true; } catch (SecurityException ex) { logger.error("Invalid JWT signature"); } catch (MalformedJwtException ex) { logger.error("Invalid JWT token"); } catch (ExpiredJwtException ex) { logger.error("Expired JWT token"); throw ex; } catch (UnsupportedJwtException ex) { logger.error("Unsupported JWT token"); } catch (IllegalArgumentException ex) { logger.error("JWT claims string is empty."); } return false; } @Override public TokenType getTokenType() { return TokenType.CLIENT_SECRET_JWT; } @Override public String createRefreshToken(User user) { return null; } private SecretKey secretKey() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(appProperties.getAuth().getTokenSecret())); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/session/Session.java ================================================ package com.tomo.mcauthentication.domain.session; import com.tomo.ddd.domain.DomainEventPublisher; import com.tomo.ddd.domain.RootEntity; import com.tomo.mcauthentication.domain.DomainRegistry; import com.tomo.mcauthentication.domain.session.events.SessionCreated; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; import javax.persistence.AttributeOverride; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import java.time.LocalDateTime; import java.time.temporal.ChronoField; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Entity @NoArgsConstructor @Getter @Setter public class Session extends RootEntity { public static Long EXPIRATION_MSEC = 15000L; public enum TokenType { CLIENT_SECRET_JWT, PRIVATE_KEY_JWT, BASIC, API_KEY } @EmbeddedId private SessionId sessionId; private String accessToken; private LocalDateTime expirationDate; @Enumerated(EnumType.STRING) private TokenType tokenType; private String refreshToken; private String userAgent; private String ipAddress; private LocalDateTime lastActivity; @Embedded @AttributeOverride(name="id", column = @Column(name="user_id")) private UserId userId; public Session(SessionId sessionId, User user, TokenProvider tokenProvider, Boolean rememberMe, String userAgent, String ipAddress) { this.sessionId = sessionId; this.ipAddress = ipAddress; this.userAgent = userAgent; this.tokenType = tokenProvider.getTokenType(); this.userId = user.getUserId(); this.expirationDate = LocalDateTime.now().plus(EXPIRATION_MSEC, ChronoField.MILLI_OF_DAY.getBaseUnit()); this.accessToken = tokenProvider.createToken(user); if (Boolean.TRUE.equals(rememberMe)) { this.refreshToken = tokenProvider.createRefreshToken(user); } DomainEventPublisher.instance().publish( new SessionCreated(this.getSessionId(), this.getUserId()) ); } public boolean isExpired() { return !expirationDate.isAfter(LocalDateTime.now()); } private void protectedAccessToken(String anToken) { this.assertArgumentNotEmpty(anToken, "Access token cannot be empty."); this.setAccessToken(DomainRegistry.encryptionService().encryptedValue(anToken)); } private void protectedRefreshToken(String anToken) { this.assertArgumentNotEmpty(anToken, "Refresh token cannot be empty."); this.setRefreshToken(DomainRegistry.encryptionService().encryptedValue(anToken)); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/session/SessionAuthenticationService.java ================================================ package com.tomo.mcauthentication.domain.session; import com.tomo.ddd.domain.BusinessRuleValidator; import com.tomo.mcauthentication.domain.session.rule.SessionCannotBeExpiredWhenRefreshTokenIsMissing; import com.tomo.mcauthentication.domain.users.UserRepository; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @Service public class SessionAuthenticationService extends BusinessRuleValidator { TokenProvider tokenProvider; SessionRepository sessionRepository; UserRepository userRepository; public SessionAuthenticationService( TokenProvider tokenProvider, SessionRepository sessionRepository, @Qualifier("userRepositoryJpaAdapter") UserRepository userRepository) { this.tokenProvider = tokenProvider; this.sessionRepository = sessionRepository; this.userRepository = userRepository; } public Session authenticate(String anAccessToken) { assertArgumentNotEmpty(anAccessToken, "Session token cannot be empty."); Session session = sessionRepository.findByAccessToken(anAccessToken); if (session == null) { throw new IllegalStateException(String.format("Session with access code %s doesn't exist.", anAccessToken)); } checkRule(new SessionCannotBeExpiredWhenRefreshTokenIsMissing(session)); if (session.isExpired()) { return new Session( sessionRepository.nextIdentity(), userRepository.findById(session.getUserId()), tokenProvider, true, session.getUserAgent(), session.getIpAddress()); } session.setLastActivity(LocalDateTime.now()); return session; } public Session logout(String anAccessToken) { assertArgumentNotEmpty(anAccessToken, "Session token cannot be empty."); Session session = sessionRepository.findByAccessToken(anAccessToken); if (session == null) { throw new IllegalStateException(String.format("Session with access code %s doesn't exist.", anAccessToken)); } session.setExpirationDate(LocalDateTime.now()); session.setRefreshToken(null); return sessionRepository.save(session); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/session/SessionId.java ================================================ package com.tomo.mcauthentication.domain.session; import com.tomo.ddd.domain.AbstractId; import javax.persistence.Embeddable; import java.util.UUID; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Embeddable @Getter @Setter @NoArgsConstructor public class SessionId extends AbstractId { public SessionId(UUID id) { super(id); } @Override protected int hashOddValue() { return 5785; } @Override protected int hashPrimeValue() { return 31; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/session/SessionRepository.java ================================================ package com.tomo.mcauthentication.domain.session; import com.tomo.ddd.domain.BaseRepository; import com.tomo.mcauthentication.domain.users.UserId; import java.util.List; public interface SessionRepository extends BaseRepository { SessionId nextIdentity(); List findByUserId(UserId userId); Session findByAccessToken(String anAccessToken); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/session/TokenProvider.java ================================================ package com.tomo.mcauthentication.domain.session; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; public interface TokenProvider { String createToken(User user); String createRefreshToken(User user); UserId getUserIdFromToken(String anAuthToken); boolean validateToken(String anAuthToken); Session.TokenType getTokenType(); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/session/events/SessionCreated.java ================================================ package com.tomo.mcauthentication.domain.session.events; import com.tomo.ddd.domain.BaseDomainEvent; import com.tomo.mcauthentication.domain.session.SessionId; import com.tomo.mcauthentication.domain.users.UserId; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class SessionCreated extends BaseDomainEvent { private SessionId sessionId; private UserId userId; public SessionCreated(SessionId sessionId, UserId userId) { this.sessionId = sessionId; this.userId = userId; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/session/rule/SessionCannotBeExpiredWhenRefreshTokenIsMissing.java ================================================ package com.tomo.mcauthentication.domain.session.rule; import com.tomo.ddd.domain.BusinessRule; import com.tomo.mcauthentication.domain.session.Session; public class SessionCannotBeExpiredWhenRefreshTokenIsMissing implements BusinessRule { Session session; public SessionCannotBeExpiredWhenRefreshTokenIsMissing(Session session) { this.session = session; } @Override public Boolean isRuleComplied() { return (!session.isExpired()) || (session.isExpired() && session.getRefreshToken() != null); } @Override public String message() { return String.format("Session token is expired and refresh token is missing."); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/users/EmailLogin.java ================================================ package com.tomo.mcauthentication.domain.users; public class EmailLogin { User user; String username; String password; } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/users/User.java ================================================ package com.tomo.mcauthentication.domain.users; import com.tomo.ddd.domain.ConcurrencySafeEntity; import com.tomo.ddd.domain.DomainEvent; import com.tomo.ddd.domain.DomainEventPublisher; import com.tomo.mcauthentication.domain.DomainRegistry; import com.tomo.mcauthentication.domain.users.events.UserCreated; import com.tomo.mcauthentication.domain.users.events.UserNameChanged; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import lombok.Getter; import lombok.NoArgsConstructor; @Entity(name = "mcuser") @Getter @NoArgsConstructor public class User extends ConcurrencySafeEntity { public enum AuthProvider { EMAIL, FACEBOOK, GOOGLE } @EmbeddedId UserId userId; String firstName; String lastName; String email; @Enumerated(EnumType.STRING) AuthProvider provider; public User( UserId anId, String aFirstName, String aLastName, String anEmail, AuthProvider aProvider) { this.checkRule(DomainRegistry.userEmailMustBeUnique(anEmail)); this.userId = anId; this.firstName = aFirstName; this.lastName = aLastName; this.email = anEmail; this.provider = aProvider; this.publish(new UserCreated( this.getUserId(), this.getFirstName(), this.getLastName(), this.getEmail(), this.getProvider() )); } public void updateDetails(String aFirstName, String aLastName) { this.setFirstName(aFirstName); this.setLastName(aLastName); publish(new UserNameChanged( this.getEmail(), this.getFirstName(), this.getLastName())); } public void setLastName(String aLastName) { assertArgumentNotEmpty(lastName, "Last name cannot be empty."); this.lastName = aLastName; } public void setFirstName(String aFirstName) { assertArgumentNotEmpty(aFirstName, "First name cannot be empty."); this.firstName = aFirstName; } private void publish(DomainEvent event) { DomainEventPublisher.instance().publish(event); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/users/UserId.java ================================================ package com.tomo.mcauthentication.domain.users; import com.tomo.ddd.domain.AbstractId; import javax.persistence.Embeddable; import java.util.UUID; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Embeddable @Getter @Setter @NoArgsConstructor public class UserId extends AbstractId { public UserId(UUID id) { super(id); } public UserId(String id) { super(UUID.fromString(id)); } @Override protected int hashOddValue() { return 83811; } @Override protected int hashPrimeValue() { return 263; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/users/UserRepository.java ================================================ package com.tomo.mcauthentication.domain.users; import com.tomo.ddd.domain.BaseRepository; public interface UserRepository extends BaseRepository { UserId nextIdentity(); User findByEmail(String anEmail); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/users/events/UserCreated.java ================================================ package com.tomo.mcauthentication.domain.users.events; import com.tomo.ddd.domain.BaseDomainEvent; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class UserCreated extends BaseDomainEvent { UserId userId; String firstName; String lastName; String email; User.AuthProvider provider; public UserCreated(UserId userId, String firstName, String lastName, String email, User.AuthProvider provider) { this.userId = userId; this.firstName = firstName; this.lastName = lastName; this.email = email; this.provider = provider; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/users/events/UserNameChanged.java ================================================ package com.tomo.mcauthentication.domain.users.events; import com.tomo.ddd.domain.BaseDomainEvent; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class UserNameChanged extends BaseDomainEvent { String email; String firstName; String lastName; public UserNameChanged(String email, String firstName, String lastName) { this.email = email; this.firstName = firstName; this.lastName = lastName; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/domain/users/rules/UserEmailMustBeUnique.java ================================================ package com.tomo.mcauthentication.domain.users.rules; import com.tomo.ddd.domain.BusinessRule; import com.tomo.mcauthentication.domain.users.UserRepository; public class UserEmailMustBeUnique implements BusinessRule { UserRepository userRespository; String email; public UserEmailMustBeUnique(UserRepository userRespository, String email) { this.userRespository = userRespository; this.email = email; } @Override public Boolean isRuleComplied() { return userRespository.findByEmail(this.email) == null; } @Override public String message() { return "User with this email already exists."; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/McAuthenticationModuleExecutor.java ================================================ package com.tomo.mcauthentication.infrastructure; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.application.configuration.QueryHandler; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.McAuthenticationModule; import com.tomo.mcauthentication.application.contracts.Query; import com.tomo.mcauthentication.application.contracts.Response; import com.tomo.mcauthentication.infrastructure.processing.builder.CommandHandlerPipelineBuilder; import com.tomo.mcauthentication.infrastructure.processing.builder.QueryHandlerPipelineBuilder; import org.springframework.stereotype.Component; @Component public class McAuthenticationModuleExecutor implements McAuthenticationModule { CommandHandlerPipelineBuilder commandHandlerPipelineBuilder; QueryHandlerPipelineBuilder queryHandlerPipelineBuilder; public McAuthenticationModuleExecutor( CommandHandlerPipelineBuilder commandHandlerPipelineBuilder, QueryHandlerPipelineBuilder queryHandlerPipelineBuilder) { this.commandHandlerPipelineBuilder = commandHandlerPipelineBuilder; this.queryHandlerPipelineBuilder = queryHandlerPipelineBuilder; } @Override public Response executeCommand(Command command) { CommandHandler commandHandler = commandHandlerPipelineBuilder .with(command) .build(); return commandHandler.handle(command); } @Override public Response executeQuery(Query query) { QueryHandler queryHandler = queryHandlerPipelineBuilder .with(query) .build(); return queryHandler.handle(query); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/http/oauth2/AbstractOAuth2Authentication.java ================================================ package com.tomo.mcauthentication.infrastructure.http.oauth2; import com.tomo.mcauthentication.domain.oauth2.OAuth2Principal; import com.tomo.mcauthentication.infrastructure.http.oauth2.user.OAuth2UserInfo; import com.tomo.mcauthentication.infrastructure.http.oauth2.user.OAuth2UserInfoFactory; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.user.OAuth2User; import java.time.Instant; public abstract class AbstractOAuth2Authentication { ClientRegistration clientRegistration; CustomOAuth2UserService customOAuth2UserService; public AbstractOAuth2Authentication(ClientRegistration clientRegistration, CustomOAuth2UserService customOAuth2UserService) { this.clientRegistration = clientRegistration; this.customOAuth2UserService = customOAuth2UserService; } protected OAuth2Principal authenticateUser(String anAccessCode) { OAuth2UserRequest oAuth2UserRequest = new OAuth2UserRequest(clientRegistration, new OAuth2AccessToken( OAuth2AccessToken.TokenType.BEARER, anAccessCode, Instant.now(), Instant.now().plusSeconds(10000L))); OAuth2User oAuth2User = customOAuth2UserService.loadUser(oAuth2UserRequest); OAuth2UserInfo userInfo = OAuth2UserInfoFactory .getOAuth2UserInfo(clientRegistration.getRegistrationId(), oAuth2User.getAttributes()); return new OAuth2Principal( userInfo.getId(), userInfo.getEmail(), userInfo.getName(), userInfo.getName(), userInfo.getImageUrl(), clientRegistration.getRegistrationId()); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/http/oauth2/CustomOAuth2UserService.java ================================================ package com.tomo.mcauthentication.infrastructure.http.oauth2; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; @Service public class CustomOAuth2UserService extends DefaultOAuth2UserService { public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException { return super.loadUser(oAuth2UserRequest); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/http/oauth2/FacebookOAuth2Authentication.java ================================================ package com.tomo.mcauthentication.infrastructure.http.oauth2; import com.tomo.mcauthentication.domain.oauth2.OAuth2Authentication; import com.tomo.mcauthentication.domain.oauth2.OAuth2Principal; import com.tomo.mcauthentication.infrastructure.http.oauth2.user.FacebookOAuth2UserInfo; import com.tomo.mcauthentication.infrastructure.http.oauth2.user.OAuth2UserInfoFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; @Service public class FacebookOAuth2Authentication extends AbstractOAuth2Authentication implements OAuth2Authentication { public FacebookOAuth2Authentication( @Qualifier("facebookClientRegistration") ClientRegistration clientRegistration, CustomOAuth2UserService customOAuth2UserService) { super(clientRegistration, customOAuth2UserService); } @Override public OAuth2Principal authenticate(String anAccessCode) { return super.authenticateUser(anAccessCode); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/http/oauth2/GoogleOAuth2Authentication.java ================================================ package com.tomo.mcauthentication.infrastructure.http.oauth2; import com.tomo.mcauthentication.domain.oauth2.OAuth2Authentication; import com.tomo.mcauthentication.domain.oauth2.OAuth2Principal; import com.tomo.mcauthentication.infrastructure.http.oauth2.user.FacebookOAuth2UserInfo; import com.tomo.mcauthentication.infrastructure.http.oauth2.user.OAuth2UserInfoFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; @Service public class GoogleOAuth2Authentication extends AbstractOAuth2Authentication implements OAuth2Authentication { public GoogleOAuth2Authentication( @Qualifier("googleClientRegistration") ClientRegistration clientRegistration, CustomOAuth2UserService customOAuth2UserService) { super(clientRegistration, customOAuth2UserService); } @Override public OAuth2Principal authenticate(String anAccessCode) { return super.authenticateUser(anAccessCode); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/http/oauth2/user/FacebookOAuth2UserInfo.java ================================================ package com.tomo.mcauthentication.infrastructure.http.oauth2.user; import java.util.Map; public class FacebookOAuth2UserInfo extends OAuth2UserInfo { public FacebookOAuth2UserInfo(Map attributes) { super(attributes); } @Override public String getId() { return (String) attributes.get("id"); } @Override public String getName() { return (String) attributes.get("name"); } @Override public String getEmail() { return (String) attributes.get("email"); } @Override public String getImageUrl() { if(attributes.containsKey("picture")) { Map pictureObj = (Map) attributes.get("picture"); if(pictureObj.containsKey("data")) { Map dataObj = (Map) pictureObj.get("data"); if(dataObj.containsKey("url")) { return (String) dataObj.get("url"); } } } return null; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/http/oauth2/user/GoogleOAuth2UserInfo.java ================================================ package com.tomo.mcauthentication.infrastructure.http.oauth2.user; import java.util.Map; public class GoogleOAuth2UserInfo extends OAuth2UserInfo { public GoogleOAuth2UserInfo(Map attributes) { super(attributes); } @Override public String getId() { return (String) attributes.get("sub"); } @Override public String getName() { return (String) attributes.get("name"); } @Override public String getEmail() { return (String) attributes.get("email"); } @Override public String getImageUrl() { return (String) attributes.get("picture"); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/http/oauth2/user/OAuth2UserInfo.java ================================================ package com.tomo.mcauthentication.infrastructure.http.oauth2.user; import java.util.Map;; public abstract class OAuth2UserInfo { protected Map attributes; public OAuth2UserInfo(Map attributes) { this.attributes = attributes; } public Map getAttributes() { return attributes; } public abstract String getId(); public abstract String getName(); public abstract String getEmail(); public abstract String getImageUrl(); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/http/oauth2/user/OAuth2UserInfoFactory.java ================================================ package com.tomo.mcauthentication.infrastructure.http.oauth2.user; import com.tomo.mcauthentication.domain.users.User; import java.util.Map; public class OAuth2UserInfoFactory { public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map attributes) { if(registrationId.equalsIgnoreCase(User.AuthProvider.GOOGLE.toString().toLowerCase())) { return new GoogleOAuth2UserInfo(attributes); } else if (registrationId.equalsIgnoreCase(User.AuthProvider.FACEBOOK.toString().toLowerCase())) { return new FacebookOAuth2UserInfo(attributes); } else { throw new IllegalArgumentException("Sorry! Login with " + registrationId + " is not supported yet."); } } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/persistence/BaseJpaAdapter.java ================================================ package com.tomo.mcauthentication.infrastructure.persistence; import com.tomo.ddd.domain.BaseRepository; import com.tomo.ddd.infrastructure.persistence.springdata.jpa.McCrudRepository; import java.util.List; import java.util.Optional; //https://github.com/benthurley82/generic-type-resolver-test/blob/main/src/main/java/com/example/test/AbstractFoo.java public class BaseJpaAdapter implements BaseRepository { protected E jpaRepository; public BaseJpaAdapter(E jpaRepository) { this.jpaRepository = jpaRepository; } @Override public T save(T entity) { return (T) jpaRepository.save(entity); } @Override public T findById(ID id) { Optional entity = jpaRepository.findById(id); return entity.isPresent() ? entity.get() : null; } @Override public List findAll() { return jpaRepository.findAll(); } @Override public void saveAll(List entities) { jpaRepository.saveAll(entities); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/persistence/SessionJpaRepository.java ================================================ package com.tomo.mcauthentication.infrastructure.persistence; import com.tomo.ddd.infrastructure.persistence.springdata.jpa.McCrudRepository; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionId; import com.tomo.mcauthentication.domain.users.UserId; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface SessionJpaRepository extends McCrudRepository { List findAllByUserId(UserId userId); Session findSessionByAccessToken(String accessToken); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/persistence/SessionRepositoryJpaAdapter.java ================================================ package com.tomo.mcauthentication.infrastructure.persistence; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.SessionId; import com.tomo.mcauthentication.domain.session.SessionRepository; import com.tomo.mcauthentication.domain.users.UserId; import org.springframework.stereotype.Repository; import java.util.List; import java.util.UUID; @Repository public class SessionRepositoryJpaAdapter extends BaseJpaAdapter implements SessionRepository { public SessionRepositoryJpaAdapter(SessionJpaRepository jpaRepository) { super(jpaRepository); } @Override public SessionId nextIdentity() { return new SessionId(UUID.randomUUID()); } @Override public List findByUserId(UserId userId) { return jpaRepository.findAllByUserId(userId); } @Override public Session findByAccessToken(String anAccessToken) { return jpaRepository.findSessionByAccessToken(anAccessToken); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/persistence/UserJpaRepository.java ================================================ package com.tomo.mcauthentication.infrastructure.persistence; import com.tomo.ddd.infrastructure.persistence.springdata.jpa.McCrudRepository; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; import org.springframework.stereotype.Repository; @Repository public interface UserJpaRepository extends McCrudRepository { User findUserByEmail(String email); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/persistence/UserRegistrationJpaRepository.java ================================================ package com.tomo.mcauthentication.infrastructure.persistence; import com.tomo.ddd.infrastructure.persistence.springdata.jpa.McCrudRepository; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.users.UserId; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Repository; import java.util.List; @Repository @Qualifier("UserRegistrationJpaRepository") public interface UserRegistrationJpaRepository extends McCrudRepository { long countByEmail(String email); List findAllByEmailIn(List email); UserRegistration findUserRegistrationByConfirmationCode(String confirmLink); UserRegistration findUserRegistrationByEmail(String email); UserRegistration findUserRegistrationByRecoveryCode(String recoveryCode); UserRegistration findUserRegistrationByUserId(UserId userId); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/persistence/UserRegistrationJpaRepositoryAdapter.java ================================================ package com.tomo.mcauthentication.infrastructure.persistence; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; import com.tomo.mcauthentication.domain.users.UserId; import java.util.List; public class UserRegistrationJpaRepositoryAdapter extends BaseJpaAdapter implements UserRegistrationRepository { public UserRegistrationJpaRepositoryAdapter(UserRegistrationJpaRepository jpaRepository) { super(jpaRepository); } @Override public long countByEmailAndStatus(String email, UserRegistrationStatus status) { return jpaRepository.countByEmail(email); } @Override public List findAllByEmail(List emails) { return jpaRepository.findAllByEmailIn(emails); } @Override public UserRegistration findByEmail(String anEmail) { return jpaRepository.findUserRegistrationByEmail(anEmail); } @Override public UserRegistration findByConfirmationCode(String confirmationCode) { return jpaRepository.findUserRegistrationByConfirmationCode(confirmationCode); } @Override public UserRegistration findByRecoveryCode(String aRecoveryCode) { return jpaRepository.findUserRegistrationByRecoveryCode(aRecoveryCode); } @Override public UserRegistration findByUserId(UserId anUserId) { return jpaRepository.findUserRegistrationByUserId(anUserId); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/persistence/UserRepositoryJpaAdapter.java ================================================ package com.tomo.mcauthentication.infrastructure.persistence; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; import com.tomo.mcauthentication.domain.users.UserRepository; import org.springframework.stereotype.Component; import java.util.UUID; @Component public class UserRepositoryJpaAdapter extends BaseJpaAdapter implements UserRepository { public UserRepositoryJpaAdapter(UserJpaRepository userJpaRepository) { super(userJpaRepository); } @Override public UserId nextIdentity() { return new UserId(UUID.randomUUID()); } @Override public User findByEmail(String anEmail) { return jpaRepository.findUserByEmail(anEmail); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/processing/ErrorCommandHandlerDecorator.java ================================================ package com.tomo.mcauthentication.infrastructure.processing; import com.tomo.mcauthentication.application.configuration.AbstractVoidyCommandHandler; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.application.contracts.Command; public class ErrorCommandHandlerDecorator extends AbstractVoidyCommandHandler { CommandHandler commandHandler; public ErrorCommandHandlerDecorator(CommandHandler commandHandler) { this.commandHandler = commandHandler; } @Override protected void abstractHandle(T aCommand) { commandHandler.handle(aCommand); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/processing/LoggingCommandHandlerDecorator.java ================================================ package com.tomo.mcauthentication.infrastructure.processing; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.Response; public class LoggingCommandHandlerDecorator implements CommandHandler { CommandHandler commandHandler; public LoggingCommandHandlerDecorator(CommandHandler commandHandler) { this.commandHandler = commandHandler; } @Override public Response handle(Command aCommand) { //todo log return commandHandler.handle(aCommand); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/processing/LoggingQueryHandlerDecorator.java ================================================ package com.tomo.mcauthentication.infrastructure.processing; import com.tomo.mcauthentication.application.configuration.QueryHandler; import com.tomo.mcauthentication.application.contracts.Query; import com.tomo.mcauthentication.application.contracts.Response; public class LoggingQueryHandlerDecorator implements QueryHandler { QueryHandler queryHandler; public LoggingQueryHandlerDecorator(QueryHandler queryHandler) { this.queryHandler = queryHandler; } @Override public Response handle(Query query) { return queryHandler.handle(query); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/processing/PipelineBuilder.java ================================================ package com.tomo.mcauthentication.infrastructure.processing; public interface PipelineBuilder { PipelineBuilder with(C aRequest); R build(); } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/processing/builder/AbstractPipelineBuilder.java ================================================ package com.tomo.mcauthentication.infrastructure.processing.builder; import com.tomo.mcauthentication.application.configuration.RequestHandler; import com.tomo.mcauthentication.application.contracts.Request; import com.tomo.mcauthentication.infrastructure.processing.PipelineBuilder; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public abstract class AbstractPipelineBuilder implements PipelineBuilder, ApplicationContextAware { protected E request; protected S handler; protected ApplicationContext applicationContext; @Override public PipelineBuilder with(E aRequest) { this.request = aRequest; this.handler = this.getHandler(); return this; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } protected S getHandler() { String beanName = this.getHandlerName(); return (S) applicationContext.getBean(beanName); } protected String getHandlerName() { String fullHandlerName = this.request.getClass().getSimpleName() + "Handler"; return Character.toLowerCase(fullHandlerName.charAt(0)) + fullHandlerName.substring(1); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/processing/builder/CommandHandlerPipelineBuilder.java ================================================ package com.tomo.mcauthentication.infrastructure.processing.builder; import com.tomo.mcauthentication.application.configuration.CommandHandler; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.infrastructure.processing.LoggingCommandHandlerDecorator; import com.tomo.mcauthentication.infrastructure.processing.PipelineBuilder; public class CommandHandlerPipelineBuilder extends AbstractPipelineBuilder { public CommandHandlerPipelineBuilder() {} @Override public CommandHandlerPipelineBuilder with(Command aRequest) { return (CommandHandlerPipelineBuilder) super.with(aRequest); } @Override public CommandHandler build() { return new LoggingCommandHandlerDecorator(handler); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/processing/builder/QueryHandlerPipelineBuilder.java ================================================ package com.tomo.mcauthentication.infrastructure.processing.builder; import com.tomo.mcauthentication.application.configuration.QueryHandler; import com.tomo.mcauthentication.application.contracts.Query; import com.tomo.mcauthentication.infrastructure.processing.LoggingQueryHandlerDecorator; import com.tomo.mcauthentication.infrastructure.processing.PipelineBuilder; public class QueryHandlerPipelineBuilder extends AbstractPipelineBuilder { public QueryHandlerPipelineBuilder() { } @Override public QueryHandlerPipelineBuilder with(Query aRequest) { return (QueryHandlerPipelineBuilder) super.with(aRequest); } @Override public QueryHandler build() { return new LoggingQueryHandlerDecorator(handler); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/service/MD5EncryptionService.java ================================================ package com.tomo.mcauthentication.infrastructure.service; import com.tomo.ddd.AssertionConcern; import com.tomo.mcauthentication.domain.EncryptionService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.math.BigInteger; import java.security.MessageDigest; @Service @Qualifier("MD5EncryptionService") public class MD5EncryptionService extends AssertionConcern implements EncryptionService { public MD5EncryptionService() { super(); } @Override public String encryptedValue(String aPlainTextValue) { this.assertArgumentNotEmpty( aPlainTextValue, "Plain text value to encrypt must be provided."); String encryptedValue = null; try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); messageDigest.update(aPlainTextValue.getBytes("UTF-8")); BigInteger bigInt = new BigInteger(1, messageDigest.digest()); encryptedValue = bigInt.toString(16); } catch (Exception e) { throw new IllegalStateException(e); } return encryptedValue; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/configuration/AppConfig.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.configuration; import com.tomo.mcauthentication.application.contracts.McAuthenticationModule; import com.tomo.ddd.email.EmailSender; import com.tomo.ddd.event.EventStore; import com.tomo.ddd.infrastructure.persistence.springdata.jpa.EventStoreJpaRepositoryAdapter; import com.tomo.ddd.infrastructure.persistence.springdata.jpa.StoredEventJpaRepository; import com.tomo.ddd.port.adapter.message.email.MailGunMessageSender; import com.tomo.mcauthentication.domain.oauth2.OAuth2Service; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.users.UserRepository; import com.tomo.mcauthentication.infrastructure.McAuthenticationModuleExecutor; import com.tomo.mcauthentication.infrastructure.http.oauth2.FacebookOAuth2Authentication; import com.tomo.mcauthentication.infrastructure.http.oauth2.GoogleOAuth2Authentication; import com.tomo.mcauthentication.infrastructure.persistence.UserJpaRepository; import com.tomo.mcauthentication.infrastructure.persistence.UserRegistrationJpaRepository; import com.tomo.mcauthentication.infrastructure.persistence.UserRegistrationJpaRepositoryAdapter; import com.tomo.mcauthentication.infrastructure.persistence.UserRepositoryJpaAdapter; import com.tomo.mcauthentication.infrastructure.processing.builder.CommandHandlerPipelineBuilder; import com.tomo.mcauthentication.infrastructure.processing.builder.QueryHandlerPipelineBuilder; import com.tomo.mcauthentication.infrastructure.springboot.filter.TokenAuthenticationFilter; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.oauth2.client.registration.ClientRegistration; @Configuration @EnableJpaRepositories({ "com.tomo.ddd.infrastructure.persistence.springdata.jpa", "com.tomo.mcauthentication.infrastructure.persistence" }) public class AppConfig { @Autowired UserRegistrationJpaRepository userRegistrationJpaRepository; @Autowired UserJpaRepository userJpaRepository; @Autowired private Environment env; @Autowired AppProperties appProperties; @Autowired StoredEventJpaRepository storedEventJpaRepository; @Bean EventStore eventStore() { return new EventStoreJpaRepositoryAdapter(storedEventJpaRepository); } @Bean String recoveryLink() { return appProperties.getGui().getRecoveryRoute(); } @Bean String confirmationLink() { return appProperties.getGui().getConfirmationRoute(); } @Bean net.sargue.mailgun.Configuration mailGunConfiguration() { MessageProperties.Email.MailGun mailGun = appProperties.getMessage().getEmail().getMailGun(); net.sargue.mailgun.Configuration configuration = new net.sargue.mailgun.Configuration() .domain(mailGun.getDomains()) .apiUrl(mailGun.getApiUrl()) .apiKey(mailGun.getApiKey()) .from(mailGun.getFrom().getName(), mailGun.getFrom().getEmail()); return configuration; } @Bean EmailSender emailMessageSender(net.sargue.mailgun.Configuration mailGunConfiguration) { return new MailGunMessageSender(mailGunConfiguration); } @Bean public ModelMapper modelMapper() { return new ModelMapper(); } @Bean public TokenAuthenticationFilter tokenAuthenticationFilter() { return new TokenAuthenticationFilter(); } @Bean McAuthenticationModule authenticationModule(CommandHandlerPipelineBuilder commandHandlerPipelineBuilder, QueryHandlerPipelineBuilder queryHandlerPipelineBuilder) { return new McAuthenticationModuleExecutor(commandHandlerPipelineBuilder, queryHandlerPipelineBuilder); } @Bean CommandHandlerPipelineBuilder commandHandlerPipelineBuilder() { return new CommandHandlerPipelineBuilder(); } @Bean QueryHandlerPipelineBuilder queryHandlerPipelineBuilder() { return new QueryHandlerPipelineBuilder(); } @Bean UserRegistrationRepository userRegistrationRepository(){ return new UserRegistrationJpaRepositoryAdapter(userRegistrationJpaRepository); } @Bean UserRepository userRepository() { return new UserRepositoryJpaAdapter(userJpaRepository); } @Bean @Qualifier("facebookClientRegistration") ClientRegistration facebookClientRegistration() { String clientRootProperty = "spring.security.oauth2.client.registration.facebook"; String clientId = env.getProperty(clientRootProperty + ".client-id"); String clientSecret = env.getProperty(clientRootProperty + ".client-secret"); return CommonOAuth2Provider.FACEBOOK.getBuilder("facebook") .clientId(clientId).clientSecret(clientSecret).build(); } @Bean @Qualifier("googleClientRegistration") ClientRegistration googleClientRegistration() { String clientRootProperty = "spring.security.oauth2.client.registration.facebook"; String clientId = env.getProperty(clientRootProperty + ".client-id"); String clientSecret = env.getProperty(clientRootProperty + ".client-secret"); return CommonOAuth2Provider.FACEBOOK.getBuilder("facebook") .clientId(clientId).clientSecret(clientSecret).build(); } @Bean @Qualifier("facebookOAuth2Service") OAuth2Service facebookOAuth2Service( FacebookOAuth2Authentication facebookOAuth2Authentication, UserRepository userRepository) { return new OAuth2Service(facebookOAuth2Authentication, userRepository); } @Bean @Qualifier("googleOAuth2Service") OAuth2Service googleOAuth2Service( GoogleOAuth2Authentication googleOAuth2Authentication, UserRepository userRepository) { return new OAuth2Service(googleOAuth2Authentication, userRepository); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/configuration/AppProperties.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.ArrayList; import java.util.List; @ConfigurationProperties(prefix = "app") public class AppProperties { private final Auth auth = new Auth(); private final OAuth2 oauth2 = new OAuth2(); private final MessageProperties message = new MessageProperties(); private String baseUrl; private final GUIProperties gui = new GUIProperties(); public static class Auth { private String tokenSecret; private String sessionAuthTokenName; private long tokenExpirationMsec; public String getTokenSecret() { return tokenSecret; } public void setTokenSecret(String tokenSecret) { this.tokenSecret = tokenSecret; } public long getTokenExpirationMsec() { return tokenExpirationMsec; } public void setTokenExpirationMsec(long tokenExpirationMsec) { this.tokenExpirationMsec = tokenExpirationMsec; } public String getSessionAuthTokenName() { return sessionAuthTokenName; } public void setSessionAuthTokenName(String sessionAuthTokenName) { this.sessionAuthTokenName = sessionAuthTokenName; } } public static final class OAuth2 { private List authorizedRedirectUris = new ArrayList<>(); public List getAuthorizedRedirectUris() { return authorizedRedirectUris; } public OAuth2 authorizedRedirectUris(List authorizedRedirectUris) { this.authorizedRedirectUris = authorizedRedirectUris; return this; } } public Auth getAuth() { return auth; } public OAuth2 getOauth2() { return oauth2; } public MessageProperties getMessage() { return message; } public GUIProperties getGui() { return gui; } public String getBaseUrl() { return baseUrl; } public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/configuration/GUIProperties.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.configuration; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class GUIProperties { String baseUrl; String recoveryRoute; String confirmationRoute; } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/configuration/MessageProperties.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.configuration; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class MessageProperties { private Email email = new Email(); @Getter @Setter @NoArgsConstructor public static class Email { MailGun mailGun = new MailGun(); String fakeEmail = "tomo.landeka02@gmail.com"; @Getter @Setter @NoArgsConstructor public static class MailGun { String domains; String apiUrl; String apiKey; From from; @Getter @Setter @NoArgsConstructor public static class From { String name; String email; } } } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/configuration/SecurityConfig.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.configuration; import com.tomo.mcauthentication.infrastructure.springboot.security.OAuth2AuthenticationFailureHandler; import com.tomo.mcauthentication.infrastructure.springboot.security.OAuth2AuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( securedEnabled = true, jsr250Enabled = true, prePostEnabled = true ) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; @Autowired private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .anyRequest().permitAll() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) .and() .oauth2Login() .successHandler(oAuth2AuthenticationSuccessHandler) .failureHandler(oAuth2AuthenticationFailureHandler); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/configuration/SwaggerConfig.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; @Configuration public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/configuration/SwaggerUiWebMvcConfigurer.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.configuration; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Component public class SwaggerUiWebMvcConfigurer implements WebMvcConfigurer { private final String baseUrl; public SwaggerUiWebMvcConfigurer(@Value("${app.base-url:}") String baseUrl) { this.baseUrl = baseUrl; } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String baseUrl = StringUtils.trimTrailingCharacter(this.baseUrl, '/'); registry. addResourceHandler(baseUrl + "/swagger-ui/**") .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") .resourceChain(false); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(baseUrl + "/swagger-ui/") .setViewName("forward:" + baseUrl + "/swagger-ui/index.html"); } @Override public void addCorsMappings(CorsRegistry registry) { registry .addMapping("/api/pet") .allowedOrigins("http://editor.swagger.io"); registry .addMapping("/v2/api-docs.*") .allowedOrigins("http://editor.swagger.io"); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/controller/AbstractController.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.controller; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.application.contracts.McAuthenticationModule; import com.tomo.mcauthentication.application.contracts.Query; import com.tomo.mcauthentication.application.contracts.Request; import com.tomo.mcauthentication.application.contracts.Response; import com.tomo.mcauthentication.application.contracts.security.Authenticate; import com.tomo.mcauthentication.domain.session.TokenProvider; import com.tomo.mcauthentication.infrastructure.springboot.configuration.AppProperties; import com.tomo.mcauthentication.infrastructure.util.CookieUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; public abstract class AbstractController { @Autowired protected McAuthenticationModule authenticationModule; @Autowired protected AppProperties properties; @Autowired protected HttpServletRequest request; @Autowired protected TokenProvider tokenProvider; protected Response executeCommand(Command command) { this.setAuthToken(command); return authenticationModule.executeCommand(command); } protected T executeCommand(Command command, Class tclass) { this.setAuthToken(command); return tclass.cast(authenticationModule.executeCommand(command)); } protected T executeQuery(Query query, Class tclass) { this.setAuthToken(query); return tclass.cast(authenticationModule.executeQuery(query)); } private void setAuthToken(Request request) { if (request instanceof Authenticate) { String jwt = getJwtFromRequest(); if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { ((Authenticate) request).setAuthToken(jwt); } } } private String getJwtFromRequest() { return CookieUtils.getCookie(request, properties.getAuth().getSessionAuthTokenName()) .map(cookie -> CookieUtils.deserialize(cookie, String.class)) // .filter(cookie -> StringUtils.hasText(cookie) && cookie.startsWith("Bearer ")) // .map(cookie -> cookie.substring(7)) .orElse(null); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/controller/AuthenticationController.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.controller; import com.tomo.mcauthentication.application.authentication.command.EmailLoginCommand; import com.tomo.mcauthentication.application.authentication.command.FacebookLoginCommand; import com.tomo.mcauthentication.application.authentication.command.GoogleLoginCommand; import com.tomo.mcauthentication.application.authentication.command.LogoutCommand; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.contracts.Command; import com.tomo.mcauthentication.infrastructure.springboot.controller.RestApiRoutes.AuthRoutes; import com.tomo.mcauthentication.infrastructure.springboot.security.CurrentUser; import com.tomo.mcauthentication.infrastructure.springboot.security.UserAuthPrincipal; import com.tomo.mcauthentication.infrastructure.util.CookieUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.util.concurrent.TimeUnit; @RestController @RequestMapping(path = "/") public class AuthenticationController extends AbstractController { @Autowired private HttpServletResponse response; @RequestMapping(method = RequestMethod.POST, path = AuthRoutes.FORM_LOGIN) @ResponseStatus(HttpStatus.CREATED) public ResponseEntity formLogin(@CurrentUser UserAuthPrincipal user, @RequestBody @Validated EmailLoginCommand command) { return ResponseEntity.ok(this.executeCommand(command, user)); } @RequestMapping(method = RequestMethod.POST, path = AuthRoutes.FACEBOOK_LOGIN) @ResponseStatus(HttpStatus.CREATED) public ResponseEntity facebookLogin(@CurrentUser UserAuthPrincipal user, @RequestBody @Validated FacebookLoginCommand command){ return ResponseEntity.ok(this.executeCommand(command, user)); } @RequestMapping(method = RequestMethod.POST, path = AuthRoutes.GOOGLE_LOGIN) @ResponseStatus(HttpStatus.CREATED) public ResponseEntity googleLogin(@CurrentUser UserAuthPrincipal user, @RequestBody @Validated GoogleLoginCommand command) { return ResponseEntity.ok(this.executeCommand(command, user)); } @RequestMapping(method = RequestMethod.PATCH, path = AuthRoutes.LOGOUT) public ResponseEntity logout(@CurrentUser UserAuthPrincipal user, @RequestBody @Validated LogoutCommand command) { if (user == null) { return ResponseEntity.badRequest().body("Missing session cookie."); } command.setAuthToken(user.getSession().getAccessToken()); authenticationModule.executeCommand(command); return ResponseEntity.ok().build(); } protected SessionDto executeCommand(Command command, UserAuthPrincipal user) { if (user != null) { return user.getSession(); } SessionDto session = super.executeCommand(command, SessionDto.class); addAccessTokenToCookie(session.getAccessToken()); return session; } private void addAccessTokenToCookie(String accessToken) { CookieUtils.addCookie( response, properties.getAuth().getSessionAuthTokenName(), CookieUtils.serialize(accessToken), (int) TimeUnit.MILLISECONDS.toSeconds(properties.getAuth().getTokenExpirationMsec())); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/controller/RegistrationController.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.controller; import com.tomo.mcauthentication.application.recovery.command.CreatePasswordRecoveryCodeCommand; import com.tomo.mcauthentication.application.recovery.command.UpdatePasswordWithRecoveryCodeCommand; import com.tomo.mcauthentication.application.registration.command.ConfirmUserRegistrationCommand; import com.tomo.mcauthentication.application.registration.command.RegisterNewUserCommand; import com.tomo.mcauthentication.infrastructure.springboot.controller.RestApiRoutes.RegistrationRoutes; import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(path = "/") public class RegistrationController extends AbstractController { @RequestMapping(method = RequestMethod.POST, path = RegistrationRoutes.FORM_REGISTRATION) @ResponseStatus(HttpStatus.CREATED) public void formRegister(@RequestBody @Validated RegisterNewUserCommand command){ this.executeCommand(command); } @RequestMapping(method = RequestMethod.POST, path = RegistrationRoutes.CONFIRM_REGISTRATION) @ResponseStatus(HttpStatus.OK) public void formRegisterConfirmation(@RequestParam String confirmationCode){ this.executeCommand(new ConfirmUserRegistrationCommand(confirmationCode)); } @RequestMapping(method = RequestMethod.PATCH, path = RegistrationRoutes.CREATE_PASSWORD_RECOVERY_CODE) @ResponseStatus(HttpStatus.OK) public void formRegisterRecovery(@RequestBody @Validated CreatePasswordRecoveryCodeCommand command){ this.executeCommand(command); } @RequestMapping(method = RequestMethod.PATCH, path = RegistrationRoutes.PASSWORD_RESET) @ResponseStatus(HttpStatus.OK) public void passwordReset(@RequestBody @Validated UpdatePasswordWithRecoveryCodeCommand command) { this.executeCommand(command); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/controller/RestApiRoutes.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.controller; public class RestApiRoutes { public static final String REST = "/rest"; public static final String PUBLIC = REST + "/public"; public static final String PRIVATE = REST + "/private"; public static class RegistrationRoutes { public static final String FORM_REGISTRATION = PUBLIC + "/register/form"; public static final String CONFIRM_REGISTRATION = PUBLIC + "/register/confirm/"; public static final String CREATE_PASSWORD_RECOVERY_CODE = PUBLIC + "/register/password/recovery-code"; public static final String PASSWORD_RESET = PUBLIC + "/register/password/reset"; } public static class AuthRoutes { public static final String FORM_LOGIN = PUBLIC + "/login/form"; public static final String FACEBOOK_LOGIN = PUBLIC + "/login/facebook"; public static final String GOOGLE_LOGIN = PUBLIC + "/login/google"; public static final String LOGOUT = PRIVATE + "/logout"; } public static class User { public static final String USER = PRIVATE + "/user"; public static final String USER_DETAILS = USER + "/{userId}"; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/controller/UserController.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.controller; import com.tomo.mcauthentication.application.users.command.ChangeUserDetailsCommand; import com.tomo.mcauthentication.application.users.dto.BaseUserDto; import com.tomo.mcauthentication.application.users.query.GetUserQuery; import com.tomo.mcauthentication.infrastructure.springboot.controller.RestApiRoutes.User; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; @RestController @RequestMapping(path = "/") public class UserController extends AbstractController { @RequestMapping(method = RequestMethod.GET, path = User.USER_DETAILS) public ResponseEntity user(@PathVariable(value = "userId") String userId){ BaseUserDto dto = this.executeQuery(new GetUserQuery(userId), BaseUserDto.class); return ResponseEntity.ok(dto); } @RequestMapping(method = RequestMethod.PATCH, path = User.USER_DETAILS) public ResponseEntity user( @PathVariable(value = "userId") UUID userId, @RequestBody @Validated ChangeUserDetailsCommand command){ command.setUserId(userId); this.executeCommand(command); return new ResponseEntity(HttpStatus.OK); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/filter/TokenAuthenticationFilter.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.filter; import com.tomo.mcauthentication.application.authentication.command.SessionAuthenticationCommand; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.contracts.McAuthenticationModule; import com.tomo.mcauthentication.domain.session.TokenProvider; import com.tomo.mcauthentication.domain.users.UserId; import com.tomo.mcauthentication.domain.users.UserRepository; import com.tomo.mcauthentication.infrastructure.springboot.configuration.AppProperties; import com.tomo.mcauthentication.infrastructure.springboot.security.UserAuthPrincipal; import com.tomo.mcauthentication.infrastructure.springboot.security.UserAuthToken; import com.tomo.mcauthentication.infrastructure.util.CookieUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.TimeUnit; public class TokenAuthenticationFilter extends OncePerRequestFilter { @Autowired private TokenProvider tokenProvider; @Autowired McAuthenticationModule mcAuthenticationModuleExecutor; @Autowired protected AppProperties properties; private static final Logger logger = LoggerFactory.getLogger(TokenAuthenticationFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = getJwtFromRequest(request); if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { //maybe set authentication, UserId, SessionId, AccessToken //But only if the intention is to use hasRole, isAuthenticate on RestController SessionDto sessionDetails = (SessionDto) mcAuthenticationModuleExecutor.executeCommand(new SessionAuthenticationCommand(jwt)); jwt = sessionDetails.getAccessToken(); if (!sessionDetails.getAccessToken().equals(jwt)) { CookieUtils.updateCookie( request, response, properties.getAuth().getSessionAuthTokenName(), CookieUtils.serialize(sessionDetails.getAccessToken()), (int) TimeUnit.MILLISECONDS.toSeconds(properties.getAuth().getTokenExpirationMsec())); } setAuthentication(sessionDetails); } } catch (Exception ex) { logger.error("Could not set user authentication in security context", ex); SecurityContextHolder.getContext().setAuthentication(null); } filterChain.doFilter(request, response); } private void setAuthentication(SessionDto sessionDetails) { UserAuthToken currentAuthentication = null; try { currentAuthentication = (UserAuthToken) SecurityContextHolder.getContext().getAuthentication(); } catch (Exception e) { currentAuthentication = null; } UserAuthToken newAuthentication = null; if (currentAuthentication == null) { newAuthentication = new UserAuthToken(new UserAuthPrincipal(sessionDetails)); } else { UserAuthPrincipal principal = (UserAuthPrincipal) currentAuthentication.getPrincipal(); if (!principal.getSession().getAccessToken().equals(sessionDetails.getAccessToken())) { newAuthentication = new UserAuthToken(new UserAuthPrincipal(sessionDetails)); } } if (newAuthentication != null) { SecurityContextHolder.getContext().setAuthentication(newAuthentication); } } private String getJwtFromRequest(HttpServletRequest request) { return CookieUtils.getCookie(request, properties.getAuth().getSessionAuthTokenName()) .map(cookie -> CookieUtils.deserialize(cookie, String.class)) // .filter(cookie -> StringUtils.hasText(cookie) && cookie.startsWith("Bearer ")) // .map(cookie -> cookie.substring(7)) .orElse(null); } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/security/CurrentUser.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.security; import org.springframework.security.core.annotation.AuthenticationPrincipal; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @AuthenticationPrincipal public @interface CurrentUser { } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/security/OAuth2AuthenticationFailureHandler.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.security; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class OAuth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { int a = 1; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/security/OAuth2AuthenticationSuccessHandler.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URI; import java.util.Optional; @Component public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String targetUrl = determineTargetUrl(request, response, authentication); if (response.isCommitted()) { logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); return; } } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/security/UserAuthPrincipal.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.security; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.domain.users.User; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.UUID; public class UserAuthPrincipal implements UserDetails { private SessionDto session; private final Collection authorities; private boolean enabled = true; public UserAuthPrincipal(SessionDto session) { this.session = session; this.authorities = new ArrayList<>(); } public SessionDto getSession() { return session; } @Override public Collection getAuthorities() { return new ArrayList<>(); } @Override public String getPassword() { return null; } @Override public String getUsername() { return session.getUserId(); } @Override public boolean isAccountNonExpired() { return this.enabled; } @Override public boolean isAccountNonLocked() { return this.enabled; } @Override public boolean isCredentialsNonExpired() { return this.enabled; } @Override public boolean isEnabled() { return this.enabled; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/springboot/security/UserAuthToken.java ================================================ package com.tomo.mcauthentication.infrastructure.springboot.security; import com.tomo.mcauthentication.domain.users.User; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import java.util.Collection; public class UserAuthToken extends AbstractAuthenticationToken { private UserAuthPrincipal userPrincipal; public UserAuthToken(UserAuthPrincipal userPrincipal) { super(userPrincipal.getAuthorities()); super.setAuthenticated(true); this.userPrincipal = userPrincipal; } public UserAuthToken(String authToken, Collection authorities) { super(authorities); this.setAuthenticated(true); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return userPrincipal; } } ================================================ FILE: src/main/java/com/tomo/mcauthentication/infrastructure/util/CookieUtils.java ================================================ package com.tomo.mcauthentication.infrastructure.util; import org.springframework.util.SerializationUtils; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Base64; import java.util.Optional; public class CookieUtils { public static Optional getCookie(HttpServletRequest request, String name) { Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0) { for (Cookie cookie : cookies) { if (cookie.getName().equals(name)) { return Optional.of(cookie); } } } return Optional.empty(); } public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) { Cookie cookie = new Cookie(name, value); cookie.setPath("/"); cookie.setMaxAge(5000); response.addCookie(cookie); } public static void updateCookie(HttpServletRequest request, HttpServletResponse response, String name, String value, int maxAge) { Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0) { for (Cookie cookie: cookies) { if (cookie.getName().equals(name)) { cookie.setValue(value); cookie.setPath("/"); cookie.setMaxAge(maxAge); response.addCookie(cookie); } } } } public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) { Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0) { for (Cookie cookie: cookies) { if (cookie.getName().equals(name)) { cookie.setValue(""); cookie.setPath("/"); cookie.setMaxAge(0); response.addCookie(cookie); } } } } public static String serialize(Object object) { return Base64.getUrlEncoder() .encodeToString(SerializationUtils.serialize(object)); } public static T deserialize(Cookie cookie, Class cls) { return cls.cast(SerializationUtils.deserialize( Base64.getUrlDecoder().decode(cookie.getValue()))); } } ================================================ FILE: src/main/resources/application.yml ================================================ server: port: 8080 debug: true spring: datasource: url: jdbc:postgresql://localhost:5432/mc_authentication username: mcuser password: mcuser driver-class-name: org.postgresql.Driver jpa: show-sql: true hibernate: ddl-auto: none naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy properties: hibernate: dialect: org.hibernate.dialect.PostgreSQL9Dialect flyway: enabled: true baselineOnMigrate: true schemas: public security: oauth2: client: registration: google: clientId: your-client-id.apps.googleusercontent.com clientSecret: your-client-secret scope: - email - profile facebook: clientId: your-client-id clientSecret: your-client-secret scope: - email - public_profile provider: facebook: authorizationUri: https://www.facebook.com/v3.0/dialog/oauth tokenUri: https://graph.facebook.com/v3.0/oauth/access_token userInfoUri: https://graph.facebook.com/v3.0/me?fields=id,first_name,middle_name,last_name,name,email,verified,is_verified,picture.width(250).height(250) app: base-url: http://localhost:8080 auth: tokenSecret: 04ca023b39512e46d0c2cf4b48d5aac61d34302994c87ed4eff225dcf3b0a218739f3897051a057f9b846a69ea2927a587044164b7bae5e1306219d50b588cb1 tokenExpirationMsec: 864000000 sessionAuthTokenName: "dei-www" cors: allowedOrigins: http://localhost:3000,http://localhost:8080 message: email: mailgun: domains: "[your-sandbox].mailgun.org" api-url: "https://api.mailgun.net/v3/" api-key: "[mailgun-api-key]" from: name: "mc team" email: "tomo.landeka02@gmail.com" gui: base-url: "localhost:3000" recovery-route: "${app.gui.base-url}/reset-password/?recoveryCode=" confirmation-route: "${app.gui.base-url}/register/confirm/?confirmationCode=" ================================================ FILE: src/main/resources/db/migration/V2022_02_02_1124__initial_structure.sql ================================================ CREATE TABLE mcuser ( id UUID PRIMARY KEY NOT NULL, first_name VARCHAR(255), last_name VARCHAR(255), provider VARCHAR(50) NOT NULL, email VARCHAR(255) NOT NULL CONSTRAINT mcuser_email_unique unique, concurrency_version BIGINT, created TIMESTAMP NOT NULL DEFAULT Now(), modified TIMESTAMP ); CREATE TABLE user_registration ( id BIGSERIAL PRIMARY KEY, first_name VARCHAR(255), last_name VARCHAR(255), email VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, confirmation_code VARCHAR(255) NOT NULL, status VARCHAR(255) NOT NULL, register_date TIMESTAMP, recovery_code VARCHAR(255), recovery_code_expiration_date TIMESTAMP, user_id UUID CONSTRAINT user_registration_mcuser_id_fk REFERENCES mcuser, concurrency_version BIGINT, created TIMESTAMP DEFAULT now() NOT NULL, modified TIMESTAMP ); create table session ( id UUID PRIMARY KEY, user_id UUID NOT NULL CONSTRAINT session_mcuser_id_fk REFERENCES mcuser, access_token TEXT NOT NULL, expiration_date TIMESTAMP, token_type TEXT NOT NULL, refresh_token TEXT, user_agent TEXT, ip_address VARCHAR(45), last_activity TIMESTAMP, created TIMESTAMP DEFAULT now() NOT NULL, modified TIMESTAMP ); CREATE UNIQUE INDEX user_registration_user_id_uindex ON user_registration(user_id); CREATE TABLE stored_event ( event_id BIGSERIAL PRIMARY KEY, event_body TEXT NOT NULL, occurred_on TIMESTAMP NOT NULL, type_name VARCHAR (200) NOT NULL ); ================================================ FILE: src/main/resources/repo/org/tomo/ddd_common/1.0.0/ddd_common-1.0.0.jar.md5 ================================================ d6f732badc879e635745ef1ee13673d3 ================================================ FILE: src/main/resources/repo/org/tomo/ddd_common/1.0.0/ddd_common-1.0.0.jar.sha1 ================================================ 49221e5069bd616ba23339076cad6478d3762710 ================================================ FILE: src/main/resources/repo/org/tomo/ddd_common/1.0.0/ddd_common-1.0.0.pom ================================================ 4.0.0 org.tomo ddd_common 1.0.0 ================================================ FILE: src/main/resources/repo/org/tomo/ddd_common/1.0.0/ddd_common-1.0.0.pom.md5 ================================================ 5238d5a53bcbfbe70a6cb87ed493d6f3 ================================================ FILE: src/main/resources/repo/org/tomo/ddd_common/1.0.0/ddd_common-1.0.0.pom.sha1 ================================================ 423681301f4b73b38060c3c069f0141c0b54c660 ================================================ FILE: src/main/resources/repo/org/tomo/ddd_common/maven-metadata.xml ================================================ org.tomo ddd_common 1.0.0 1.0.0 20221113150911 ================================================ FILE: src/main/resources/repo/org/tomo/ddd_common/maven-metadata.xml.md5 ================================================ 2dfb0c4eb83fb0e21037e00e71ab51cb ================================================ FILE: src/main/resources/repo/org/tomo/ddd_common/maven-metadata.xml.sha1 ================================================ b984353efb5206ed31336cb8a1508c6453122adb ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/BaseIntegrationTest.java ================================================ package com.tomo.mcauthentication.integration; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public abstract class BaseIntegrationTest { } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/AbstractApplicationServiceTest.java ================================================ package com.tomo.mcauthentication.integration.application; import com.tomo.ddd.email.EmailSender; import com.tomo.mcauthentication.application.authentication.EmailLoginCommandHandler; import com.tomo.mcauthentication.application.authentication.FacebookLoginCommandHandler; import com.tomo.mcauthentication.application.authentication.GoogleLoginCommandHandler; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.recovery.SendPasswordRecoveryEmailCommandHandler; import com.tomo.mcauthentication.application.registration.ConfirmUserRegistrationCommandHandler; import com.tomo.mcauthentication.application.registration.RegisterNewUserCommandHandler; import com.tomo.mcauthentication.application.registration.SendRegistrationConfirmationEmailCommandHandler; import com.tomo.mcauthentication.application.registration.command.ConfirmUserRegistrationCommand; import com.tomo.mcauthentication.domain.oauth2.OAuth2Principal; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserRepository; import com.tomo.mcauthentication.infrastructure.http.oauth2.FacebookOAuth2Authentication; import com.tomo.mcauthentication.infrastructure.http.oauth2.GoogleOAuth2Authentication; import com.tomo.mcauthentication.integration.BaseIntegrationTest; import com.tomo.mcauthentication.testdata.CommandObjectMother; import com.tomo.mcauthentication.testdata.StaticFields; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import java.util.Arrays; import java.util.Optional; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; public abstract class AbstractApplicationServiceTest extends BaseIntegrationTest { @Autowired protected FacebookLoginCommandHandler facebookLoginCommandHandler; @Autowired protected GoogleLoginCommandHandler googleLoginCommandHandler; @Autowired protected EmailLoginCommandHandler emailLoginCommandHandler; @MockBean protected FacebookOAuth2Authentication facebookOAuth2Authentication; @MockBean protected GoogleOAuth2Authentication googleOAuth2Authentication; @Autowired protected ConfirmUserRegistrationCommandHandler confirmUserRegistrationCommandHandler; @Autowired protected RegisterNewUserCommandHandler registerNewUserCommandHandler; @Autowired protected UserRegistrationRepository userRegistrationRepository; @Autowired protected UserRepository userRepository; @SpyBean protected SendRegistrationConfirmationEmailCommandHandler sendRegistrationConfirmationEmailCommandHandler; @SpyBean protected SendPasswordRecoveryEmailCommandHandler sendPasswordRecoveryEmailCommandHandler; @MockBean protected EmailSender emailMessageSender; protected User createFormUser() { confirmUserRegistrationCommandHandler.handle( new ConfirmUserRegistrationCommand( createUserRegistration().getConfirmationCode() )); return userRepository.findByEmail(StaticFields.USER_EMAIL); } protected UserRegistration createUserRegistration() { registerNewUserCommandHandler.handle(CommandObjectMother.registerNewUserCommand()); Optional userRegistration = userRegistrationRepository .findAllByEmail(Arrays.asList(StaticFields.USER_EMAIL)) .stream() .findFirst(); return userRegistration.get(); } protected SessionDto formLogin(){ createFormUser(); return emailLoginCommandHandler.handle(CommandObjectMother.emailLoginCommand()); } protected User createFacbookUser() { when(facebookOAuth2Authentication.authenticate(anyString())) .thenReturn(oAuth2Principal(User.AuthProvider.FACEBOOK.toString())); facebookLoginCommandHandler.handle(CommandObjectMother.facebookLoginCommand()); return userRepository.findByEmail(StaticFields.USER_EMAIL); } protected User createGoogleUser() { when(googleOAuth2Authentication.authenticate(anyString())) .thenReturn(oAuth2Principal(User.AuthProvider.GOOGLE.toString())); googleLoginCommandHandler.handle(CommandObjectMother.googleLoginCommand()); return userRepository.findByEmail(StaticFields.USER_EMAIL); } private OAuth2Principal oAuth2Principal(String anProvider) { return new OAuth2Principal( StaticFields.USER_OAUTH_ID, StaticFields.USER_EMAIL, StaticFields.USER_FIRST_NAME, StaticFields.USER_LAST_NAME, "img", anProvider); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/authentication/EmailLoginCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.authentication; import com.tomo.ddd.domain.BusinessRuleValidationException; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import org.junit.Test; import javax.transaction.Transactional; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; public class EmailLoginCommandHandlerTest extends AbstractApplicationServiceTest { @Test @Transactional public void testUserFormLogin() { assertNotNull(formLogin()); } @Test @Transactional public void testNewFormUserFailedWhenGoogleUserExists() { createGoogleUser(); assertThrows(BusinessRuleValidationException.class, this::createFormUser); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/authentication/FacebookLoginCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.authentication; import com.tomo.ddd.domain.BusinessRuleValidationException; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import org.junit.Test; import javax.transaction.Transactional; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; public class FacebookLoginCommandHandlerTest extends AbstractApplicationServiceTest { @Test @Transactional public void testNewFacebookUserCreated() { assertNotNull(createFacbookUser()); } @Test @Transactional public void testNewFacebookUserFailedWhenGoogleUserExists() { createGoogleUser(); assertThrows(BusinessRuleValidationException.class, this::createFacbookUser); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/authentication/GoogleLoginCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.authentication; import com.tomo.ddd.domain.BusinessRuleValidationException; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import org.junit.Test; import javax.transaction.Transactional; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; public class GoogleLoginCommandHandlerTest extends AbstractApplicationServiceTest { @Test @Transactional public void testNewGoogleUserCreated() { assertNotNull(createGoogleUser()); } @Test @Transactional public void testNewGoogleUserFailedWhenFacebookUserExists() { createFacbookUser(); assertThrows(BusinessRuleValidationException.class, this::createGoogleUser); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/authentication/LogoutCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.authentication; import com.tomo.ddd.domain.BusinessRuleValidationException; import com.tomo.mcauthentication.application.authentication.LogoutCommandHandler; import com.tomo.mcauthentication.application.authentication.command.LogoutCommand; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.domain.session.SessionAuthenticationService; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertThrows; public class LogoutCommandHandlerTest extends AbstractApplicationServiceTest { @Autowired LogoutCommandHandler logoutCommandHandler; @Autowired SessionAuthenticationService sessionAuthenticationService; @Test @Transactional public void testLogout() { SessionDto sessionDto = formLogin(); logoutCommandHandler.handle(new LogoutCommand(sessionDto.getAccessToken())); assertThrows(BusinessRuleValidationException.class, () -> sessionAuthenticationService.authenticate(sessionDto.getAccessToken())); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/authentication/SessionAuthenticationCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.authentication; import com.tomo.mcauthentication.application.authentication.SessionAuthenticationCommandHandler; import com.tomo.mcauthentication.application.authentication.command.SessionAuthenticationCommand; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.domain.session.SessionAuthenticationService; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertEquals; public class SessionAuthenticationCommandHandlerTest extends AbstractApplicationServiceTest { @Autowired SessionAuthenticationCommandHandler commandHandler; @Autowired SessionAuthenticationService sessionAuthenticationService; @Test @Transactional public void testAuthenticateSession() { SessionDto initialSessionDto = formLogin(); SessionDto sessionDto = commandHandler.handle(new SessionAuthenticationCommand(initialSessionDto.getAccessToken())); assertEquals(initialSessionDto.getAccessToken(), sessionDto.getAccessToken()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/recovery/CreatePasswordRecoveryCodeCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.recovery; import com.tomo.mcauthentication.application.authentication.dto.RecoveryPasswordDto; import com.tomo.mcauthentication.application.recovery.CreatePasswordRecoveryCodeCommandHandler; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import com.tomo.mcauthentication.testdata.CommandObjectMother; import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; public class CreatePasswordRecoveryCodeCommandHandlerTest extends AbstractApplicationServiceTest { @Autowired CreatePasswordRecoveryCodeCommandHandler commandHandler; @Test @Transactional public void testRecoveryCodeCreated() { formLogin(); RecoveryPasswordDto recoveryPasswordDto = commandHandler.handle(CommandObjectMother.createPasswordRecoveryCodeCommand()); assertNotNull(recoveryPasswordDto); verify(this.sendPasswordRecoveryEmailCommandHandler, Mockito.times(1)).handle(any()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/recovery/GetUserRegistrationWithRecoveryCodeQueryHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.recovery; import com.tomo.mcauthentication.application.authentication.dto.RecoveryPasswordDto; import com.tomo.mcauthentication.application.recovery.CreatePasswordRecoveryCodeCommandHandler; import com.tomo.mcauthentication.application.recovery.GetUserRegistrationWithRecoveryCodeQueryHandler; import com.tomo.mcauthentication.application.recovery.dto.GetUserRegistrationWithRecoveryCodeQuery; import com.tomo.mcauthentication.application.registration.dto.UserRegistrationDto; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import com.tomo.mcauthentication.testdata.CommandObjectMother; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertNotNull; public class GetUserRegistrationWithRecoveryCodeQueryHandlerTest extends AbstractApplicationServiceTest { @Autowired GetUserRegistrationWithRecoveryCodeQueryHandler queryHandler; @Autowired CreatePasswordRecoveryCodeCommandHandler commandHandler; @Test @Transactional public void testGetUserRegistrationWithRecoveryCode() { formLogin(); RecoveryPasswordDto recoveryPasswordDto = commandHandler.handle(CommandObjectMother.createPasswordRecoveryCodeCommand()); UserRegistrationDto userRegistrationDto = queryHandler.handle(new GetUserRegistrationWithRecoveryCodeQuery(recoveryPasswordDto.getRecoveryCode())); assertNotNull(userRegistrationDto.getEmail()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/recovery/UpdatePasswordWithRecoveryCodeCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.recovery; import com.tomo.mcauthentication.application.authentication.command.EmailLoginCommand; import com.tomo.mcauthentication.application.authentication.dto.RecoveryPasswordDto; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.contracts.Voidy; import com.tomo.mcauthentication.application.recovery.CreatePasswordRecoveryCodeCommandHandler; import com.tomo.mcauthentication.application.recovery.UpdatePasswordWithRecoveryCodeCommandHandler; import com.tomo.mcauthentication.application.recovery.command.UpdatePasswordWithRecoveryCodeCommand; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import com.tomo.mcauthentication.testdata.CommandObjectMother; import com.tomo.mcauthentication.testdata.StaticFields; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertEquals; public class UpdatePasswordWithRecoveryCodeCommandHandlerTest extends AbstractApplicationServiceTest { @Autowired CreatePasswordRecoveryCodeCommandHandler createPasswordRecoveryCodeCommandHandler; @Autowired UpdatePasswordWithRecoveryCodeCommandHandler commandHandler; @Test @Transactional public void testUpdatePasswordWithRecoveryCode() { formLogin(); RecoveryPasswordDto recoveryPasswordDto = createPasswordRecoveryCodeCommandHandler.handle(CommandObjectMother.createPasswordRecoveryCodeCommand()); assertEquals(commandHandler.handle(new UpdatePasswordWithRecoveryCodeCommand( StaticFields.NEW_PASS, StaticFields.NEW_PASS, recoveryPasswordDto.getRecoveryCode())).getClass(), Voidy.class); assertEquals( emailLoginCommandHandler.handle(new EmailLoginCommand(StaticFields.USER_EMAIL, StaticFields.NEW_PASS)).getClass(), SessionDto.class ); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/registration/ChangePasswordCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.registration; import com.tomo.ddd.domain.BusinessRuleValidationException; import com.tomo.mcauthentication.application.authentication.command.EmailLoginCommand; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.contracts.Voidy; import com.tomo.mcauthentication.application.registration.ChangePasswordCommandHandler; import com.tomo.mcauthentication.application.registration.command.ChangePasswordCommand; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import com.tomo.mcauthentication.testdata.StaticFields; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; public class ChangePasswordCommandHandlerTest extends AbstractApplicationServiceTest { @Autowired ChangePasswordCommandHandler commandHandler; @Test @Transactional public void testChangePassword() { SessionDto sessionDto = formLogin(); assertEquals(commandHandler.handle(new ChangePasswordCommand( sessionDto.getAccessToken(), StaticFields.PASSWORD, StaticFields.NEW_PASS, StaticFields.NEW_PASS)).getClass(), Voidy.class); assertThrows(BusinessRuleValidationException.class, () -> emailLoginCommandHandler.handle(new EmailLoginCommand(StaticFields.USER_EMAIL, StaticFields.PASSWORD))); assertEquals( emailLoginCommandHandler.handle(new EmailLoginCommand(StaticFields.USER_EMAIL, StaticFields.NEW_PASS)).getClass(), SessionDto.class ); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/registration/ConfirmUserRegistrationCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.registration; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import org.junit.Test; import javax.transaction.Transactional; import static org.junit.Assert.assertNotNull; public class ConfirmUserRegistrationCommandHandlerTest extends AbstractApplicationServiceTest { @Test @Transactional public void testConfirmUserRegistration() { User user = createFormUser(); assertNotNull(user); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/registration/RegisterNewUserCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.registration; import com.tomo.mcauthentication.application.registration.ConfirmUserRegistrationCommandHandler; import com.tomo.mcauthentication.application.registration.NewUserRegisteredEventHandler; import com.tomo.mcauthentication.application.registration.RegisterNewUserCommandHandler; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; public class RegisterNewUserCommandHandlerTest extends AbstractApplicationServiceTest { @Autowired RegisterNewUserCommandHandler commandHandler; @Autowired ConfirmUserRegistrationCommandHandler confirmUserRegistrationCommandHandler; @Autowired UserRegistrationRepository userRegistrationRepository; @Autowired NewUserRegisteredEventHandler newUserRegisteredEventHandler; @Test @Transactional public void testNewUserRegistrationCreated() { UserRegistration userRegistration = createUserRegistration(); assertNotNull(userRegistration); verify(this.sendRegistrationConfirmationEmailCommandHandler, Mockito.times(1)).handle(any()); } @Test @Transactional public void testNewUserRegistrationFailedWhenUserExists() { createFormUser(); assertThrows(RuntimeException.class, () -> { createUserRegistration(); }); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/users/ChangeUserDetailsCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.users; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.users.ChangeUserDetailsCommandHandler; import com.tomo.mcauthentication.application.users.command.ChangeUserDetailsCommand; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserRepository; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import com.tomo.mcauthentication.testdata.CommandObjectMother; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertEquals; public class ChangeUserDetailsCommandHandlerTest extends AbstractApplicationServiceTest { @Autowired ChangeUserDetailsCommandHandler commandHandler; @Autowired UserRepository userRepository; @Test @Transactional public void testChangeUserDetails() { User user = createFormUser(); SessionDto sessionDto = emailLoginCommandHandler.handle(CommandObjectMother.emailLoginCommand()); ChangeUserDetailsCommand command = new ChangeUserDetailsCommand( user.getUserId().id(), "first name", "last name"); command.setAuthToken(sessionDto.getAccessToken()); commandHandler.handle(command); User userFromDb = userRepository.findById(user.getUserId()); assertEquals(userFromDb.getFirstName(), "first name"); assertEquals(userFromDb.getLastName(), "last name"); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/integration/application/users/GetUserQueryHandlerTest.java ================================================ package com.tomo.mcauthentication.integration.application.users; import com.tomo.mcauthentication.application.authentication.dto.SessionDto; import com.tomo.mcauthentication.application.users.GetUserQueryHandler; import com.tomo.mcauthentication.application.users.query.GetUserQuery; import com.tomo.mcauthentication.integration.application.AbstractApplicationServiceTest; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.junit.Assert.assertNotNull; public class GetUserQueryHandlerTest extends AbstractApplicationServiceTest { @Autowired GetUserQueryHandler getUserQueryHandler; @Test @Transactional public void testGetUser() { SessionDto sessionDto = formLogin(); GetUserQuery query = new GetUserQuery(sessionDto.getUserId()); assertNotNull(getUserQueryHandler.handle(query)); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/smoke/McAuthenticationApplicationSmokeTest.java ================================================ package com.tomo.mcauthentication.smoke; import com.tomo.mcauthentication.infrastructure.springboot.controller.UserController; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class McAuthenticationApplicationSmokeTest { @Autowired UserController userController; @Test void contextLoads() { assertThat(userController).isNotNull(); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/testdata/CommandObjectMother.java ================================================ package com.tomo.mcauthentication.testdata; import com.github.javafaker.Faker; import com.tomo.mcauthentication.application.authentication.command.EmailLoginCommand; import com.tomo.mcauthentication.application.authentication.command.FacebookLoginCommand; import com.tomo.mcauthentication.application.authentication.command.GoogleLoginCommand; import com.tomo.mcauthentication.application.recovery.command.CreatePasswordRecoveryCodeCommand; import com.tomo.mcauthentication.application.registration.command.RegisterNewUserCommand; public class CommandObjectMother extends StaticFields { private static Faker faker = new Faker(); private CommandObjectMother() { } public static RegisterNewUserCommand registerNewUserCommand() { return new RegisterNewUserCommand(USER_FIRST_NAME, USER_LAST_NAME, USER_EMAIL, PASSWORD); } public static RegisterNewUserCommand registerNewUserCommandWithFakerEmail() { return new RegisterNewUserCommand(USER_FIRST_NAME, USER_LAST_NAME, faker.internet().emailAddress(), PASSWORD); } public static EmailLoginCommand emailLoginCommand() { return new EmailLoginCommand(USER_EMAIL, PASSWORD); } public static FacebookLoginCommand facebookLoginCommand() { return new FacebookLoginCommand(ACCESS_CODE); } public static GoogleLoginCommand googleLoginCommand() { return new GoogleLoginCommand(ACCESS_CODE); } public static CreatePasswordRecoveryCodeCommand createPasswordRecoveryCodeCommand() { return new CreatePasswordRecoveryCodeCommand(USER_EMAIL); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/testdata/StaticFields.java ================================================ package com.tomo.mcauthentication.testdata; public class StaticFields { public static final String ACCESS_CODE = "anAccessCode"; public static final String USER_OAUTH_ID = "anAccessCode"; public static final String USER_FIRST_NAME = "Tom"; public static final String USER_LAST_NAME = "Land"; public static final String USER_EMAIL = "random@email.com"; public static final String PASSWORD = "AA123bb##"; public static final String NEW_PASS = "randomNewPass123"; public static final String RECOVERY_CODE = "randomNewPass123"; } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/application/registration/UserRegistrationCommandHandlerTest.java ================================================ package com.tomo.mcauthentication.unit.application.registration; import com.tomo.mcauthentication.domain.DomainRegistry; import com.tomo.mcauthentication.domain.EncryptionService; import com.tomo.mcauthentication.domain.registration.PasswordService; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; import com.tomo.mcauthentication.domain.users.UserRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ActiveProfiles; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.Mockito.any; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @ActiveProfiles(profiles = { "unit" }) public class UserRegistrationCommandHandlerTest { @Spy DomainRegistry domainRegistry; @Mock UserRegistrationRepository userRegistrationRepository; @Mock UserRepository userRepository; @Mock EncryptionService encryptionService; @Mock ApplicationContext applicationContext; protected MockitoSession mockito; // @BeforeAll // public void setup() { // mockito = Mockito.mockitoSession() // .initMocks(this) // .strictness(Strictness.STRICT_STUBS) // .startMocking(); // } // // @AfterAll // public void tearDown() { // mockito.finishMocking(); // } @BeforeEach public void setUp(){ domainRegistry.setApplicationContext(applicationContext); // when(encryptionService.encryptedValue(any())).thenReturn("randomString"); when(applicationContext.getBean("passwordService")).thenReturn(new PasswordService()); when(applicationContext.getBean("MD5EncryptionService")).thenReturn(encryptionService); when(applicationContext.getBean("userRepository")).thenReturn(userRepository); when(applicationContext.getBean("userRegistrationRepository")).thenReturn(userRegistrationRepository); DomainRegistry.encryptionService(); int a = 5; } @Test public void testCreateUserRegister() { when(userRegistrationRepository.countByEmailAndStatus(any(), any())).thenReturn(Long.valueOf(0)); when(userRepository.findByEmail(any())).thenReturn(null); UserRegistration ur = UserRegistration.registerNewUser("AA123bb##", "email", "firstName", "lastName"); assertEquals(ur.getEmail(), "email"); assertEquals(ur.getStatus(), UserRegistrationStatus.WaitingForConfirmation); assertNotEquals(ur.getEmail(), "email1"); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/AbstractUnitTest.java ================================================ package com.tomo.mcauthentication.unit.domain; import org.junit.jupiter.api.TestInstance; import org.junit.runner.RunWith; import org.mockito.MockitoSession; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.context.ActiveProfiles; @RunWith(MockitoJUnitRunner.StrictStubs.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @ActiveProfiles(profiles = { "unit" }) public abstract class AbstractUnitTest { protected MockitoSession mockito; } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/UserRegistrationTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration; import com.tomo.ddd.domain.DomainEventPublisher; import com.tomo.mcauthentication.domain.DomainRegistry; import com.tomo.mcauthentication.domain.EncryptionService; import com.tomo.mcauthentication.domain.registration.PasswordService; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; import com.tomo.mcauthentication.domain.registration.events.*; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserId; import com.tomo.mcauthentication.domain.users.UserRepository; import com.tomo.mcauthentication.testdata.StaticFields; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.MockedConstruction; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; import java.util.UUID; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @ActiveProfiles(profiles = { "unit" }) public class UserRegistrationTest { private static boolean setUpIsDone = false; private static final String WEAK_PASS = "abc"; DomainRegistry domainRegistry; @Mock UserRegistrationRepository userRegistrationRepository; @Mock UserRepository userRepository; @Mock EncryptionService encryptionService; @Mock ApplicationContext applicationContext; @Mock DomainEventPublisher domainEventPublisher; MockedStatic publisher; MockedConstruction mockedUser; @BeforeEach public void setUp() { publisher = Mockito.mockStatic(DomainEventPublisher.class); publisher.when(DomainEventPublisher::instance).thenReturn(domainEventPublisher); mockedUser = Mockito.mockConstruction(User.class); domainRegistry = new DomainRegistry(); domainRegistry.setApplicationContext(applicationContext); when(applicationContext.getBean("passwordService")).thenReturn(new PasswordService()); when(applicationContext.getBean("MD5EncryptionService")).thenReturn(encryptionService); when(applicationContext.getBean("userRepository")).thenReturn(userRepository); when(applicationContext.getBean("userRegistrationRepository")).thenReturn(userRegistrationRepository); when(encryptionService.encryptedValue(anyString())).thenReturn("randomEncrypted"); when(encryptionService.encryptedValue(WEAK_PASS)).thenReturn("weekPassEncrypted"); when(encryptionService.encryptedValue(StaticFields.PASSWORD)).thenReturn("currentPassEncrypted"); when(encryptionService.encryptedValue(StaticFields.NEW_PASS)).thenReturn("newPassEncrypted"); when(userRegistrationRepository.countByEmailAndStatus(any(), any())).thenReturn(Long.valueOf(0)); when(userRepository.findByEmail(any())).thenReturn(null); setUpIsDone = true; } @AfterEach public void after() { mockedUser.close(); publisher.close(); Mockito.reset(domainEventPublisher); } @Order(1) @Test public void testCreateUserRegistration() { UserRegistration ur = userRegistration(); assertEquals(ur.getEmail(), StaticFields.USER_EMAIL); assertEquals(ur.getFirstName(), StaticFields.USER_FIRST_NAME); assertEquals(ur.getLastName(), StaticFields.USER_LAST_NAME); assertEquals(ur.getStatus(), UserRegistrationStatus.WaitingForConfirmation); assertNotEquals(ur.getPassword(), StaticFields.PASSWORD); assertNotNull(ur.getConfirmationCode()); verify(domainEventPublisher, times(1)).publish(isA(UserRegistrationRequested.class)); } @Test @Order(2) public void testCreateUserFromUserRegistration() { UserId userId = new UserId(UUID.randomUUID()); when(userRepository.nextIdentity()).thenReturn(userId); UserRegistration ur = userRegistration(); ur.createUser(userRepository); assertEquals(ur.getStatus(), UserRegistrationStatus.Confirmed); assertEquals(ur.getUserId(), userId); verify(domainEventPublisher, times(1)).publish(isA(UserRegistrationConfirmed.class)); } @Test @Order(3) public void testCreateRecoveryCodeAndRecoverPassword() { UserId userId = new UserId(UUID.randomUUID()); when(userRepository.nextIdentity()).thenReturn(userId); UserRegistration ur = userRegistration(); ur.createUser(userRepository); String recoveryCode = ur.createRecoveryCode(); assertNotNull(ur.getRecoveryCode()); assertTrue(ur.getRecoveryCode().length() > 0); assertNotNull(ur.getRecoveryCodeExpirationDate()); assertTrue(ur.getRecoveryCodeExpirationDate().isAfter(LocalDateTime.now())); verify(domainEventPublisher, times(1)).publish(isA(PasswordRecoveryCodeCreated.class)); assertThrows(RuntimeException.class, () -> { ur.changePasswordWithRecoveryCode(recoveryCode, StaticFields.NEW_PASS, StaticFields.PASSWORD); //repeted pass not equal }); assertThrows(RuntimeException.class, () -> { ur.changePasswordWithRecoveryCode(recoveryCode, StaticFields.PASSWORD, StaticFields.PASSWORD); //same pass }); assertThrows(RuntimeException.class, () -> { ur.changePasswordWithRecoveryCode(recoveryCode, WEAK_PASS, WEAK_PASS); //weak pass }); assertThrows(RuntimeException.class, () -> { ur.changePasswordWithRecoveryCode(recoveryCode, StaticFields.USER_EMAIL, StaticFields.USER_EMAIL); //pass not equal to email }); ur.changePasswordWithRecoveryCode(recoveryCode, StaticFields.NEW_PASS, StaticFields.NEW_PASS); verify(domainEventPublisher, times(1)).publish(isA(PasswordRecovered.class)); } @Test @Order(4) public void testChangePassword() { UserId userId = new UserId(UUID.randomUUID()); when(userRepository.nextIdentity()).thenReturn(userId); UserRegistration ur = userRegistration(); ur.createUser(userRepository); String oldPassword = ur.getPassword(); assertThrows(RuntimeException.class, () -> { ur.changePassword(StaticFields.PASSWORD, StaticFields.NEW_PASS, StaticFields.PASSWORD); //repeted pass not equal }); assertThrows(RuntimeException.class, () -> { ur.changePassword(StaticFields.PASSWORD, StaticFields.PASSWORD, StaticFields.PASSWORD); //same pass }); assertThrows(RuntimeException.class, () -> { ur.changePassword(StaticFields.PASSWORD, WEAK_PASS, WEAK_PASS); //weak pass }); assertThrows(RuntimeException.class, () -> { ur.changePassword(StaticFields.PASSWORD, StaticFields.USER_EMAIL, StaticFields.USER_EMAIL); //pass not equal to email }); ur.changePassword(StaticFields.PASSWORD, StaticFields.NEW_PASS, StaticFields.NEW_PASS); assertNotEquals(oldPassword, ur.getPassword()); assertEquals(this.encryptionService.encryptedValue(StaticFields.NEW_PASS), ur.getPassword()); verify(domainEventPublisher, times(1)).publish(isA(PasswordChanged.class)); } private UserRegistration userRegistration() { return UserRegistration.registerNewUser( StaticFields.PASSWORD, StaticFields.USER_EMAIL, StaticFields.USER_FIRST_NAME, StaticFields.USER_LAST_NAME); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/rules/PasswordRecoveryCodeShouldBeExpiredOrNullTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration.rules; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.rules.PasswordRecoveryCodeShouldBeExpiredOrNull; import com.tomo.mcauthentication.unit.domain.AbstractUnitTest; import org.junit.Test; import java.time.LocalDateTime; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class PasswordRecoveryCodeShouldBeExpiredOrNullTest extends AbstractUnitTest { @Test public void testRuleIsNotBroken() { UserRegistration userRegistration = new UserRegistration(); userRegistration.setRecoveryCodeExpirationDate(LocalDateTime.now().minusSeconds(60)); assertTrue((new PasswordRecoveryCodeShouldBeExpiredOrNull(userRegistration)).isRuleComplied()); assertTrue((new PasswordRecoveryCodeShouldBeExpiredOrNull(new UserRegistration())).isRuleComplied()); } @Test public void testRuleIsBroken() { UserRegistration userRegistration = new UserRegistration(); userRegistration.setRecoveryCodeExpirationDate(LocalDateTime.now().plusSeconds(60)); userRegistration.setRecoveryCode("123"); assertFalse((new PasswordRecoveryCodeShouldBeExpiredOrNull(userRegistration)).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/rules/PasswordRecoveryCodeShouldNotExpiredTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration.rules; import com.tomo.mcauthentication.domain.registration.UserRegistration; import com.tomo.mcauthentication.domain.registration.rules.PasswordRecoveryCodeShouldNotExpired; import com.tomo.mcauthentication.unit.domain.AbstractUnitTest; import org.junit.Test; import java.time.LocalDateTime; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class PasswordRecoveryCodeShouldNotExpiredTest extends AbstractUnitTest { @Test public void testRuleIsNotBroken() { UserRegistration userRegistration = new UserRegistration(); userRegistration.setRecoveryCodeExpirationDate(LocalDateTime.now().plusSeconds(60)); assertTrue((new PasswordRecoveryCodeShouldNotExpired(userRegistration)).isRuleComplied()); } @Test public void testRuleIsBroken() { UserRegistration userRegistration = new UserRegistration(); userRegistration.setRecoveryCodeExpirationDate(LocalDateTime.now()); assertFalse((new PasswordRecoveryCodeShouldNotExpired(userRegistration)).isRuleComplied()); assertFalse((new PasswordRecoveryCodeShouldNotExpired(new UserRegistration())).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/rules/PasswordsMustMatchTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration.rules; import com.tomo.mcauthentication.domain.registration.rules.PasswordsMustMatch; import com.tomo.mcauthentication.unit.domain.AbstractUnitTest; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class PasswordsMustMatchTest extends AbstractUnitTest { @Test public void testRuleIsNotBroken() { assertTrue((new PasswordsMustMatch("abc", "abc")).isRuleComplied()); } @Test public void testRuleIsBroken() { assertFalse((new PasswordsMustMatch("abc1", "abc")).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/rules/RecoveryCodeMustMatchTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration.rules; import com.tomo.mcauthentication.domain.registration.rules.RecoveryCodeMustMatch; import com.tomo.mcauthentication.unit.domain.AbstractUnitTest; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class RecoveryCodeMustMatchTest extends AbstractUnitTest { @Test public void testRuleIsNotBroken() { assertTrue((new RecoveryCodeMustMatch("abc", "abc")).isRuleComplied()); } @Test public void testRuleIsBroken() { assertFalse((new RecoveryCodeMustMatch("abc1", "abc")).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/rules/UserRegistrationCannotBeConfirmedAfterExpirationTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration.rules; import com.tomo.mcauthentication.domain.registration.rules.UserRegistrationCannotBeConfirmedAfterExpiration; import com.tomo.mcauthentication.unit.domain.AbstractUnitTest; import org.junit.Test; import java.time.LocalDateTime; import static com.tomo.mcauthentication.domain.registration.rules.UserRegistrationCannotBeConfirmedAfterExpiration.CONFIRMATION_LINK_DURATION; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class UserRegistrationCannotBeConfirmedAfterExpirationTest extends AbstractUnitTest { @Test public void testRuleIsNotBroken() { LocalDateTime registerDate1 = LocalDateTime.now(); assertTrue((new UserRegistrationCannotBeConfirmedAfterExpiration(registerDate1)).isRuleComplied()); LocalDateTime registerDate2 = LocalDateTime.now().minusDays(CONFIRMATION_LINK_DURATION).plusSeconds(5); assertTrue((new UserRegistrationCannotBeConfirmedAfterExpiration(registerDate2)).isRuleComplied()); } @Test public void testRuleIsBroken() { LocalDateTime registerDate1 = LocalDateTime.now().minusDays(CONFIRMATION_LINK_DURATION); assertFalse((new UserRegistrationCannotBeConfirmedAfterExpiration(registerDate1)).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/rules/UserRegistrationCannotBeConfirmedMoreThanOnceTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration.rules; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; import com.tomo.mcauthentication.domain.registration.rules.UserRegistrationCannotBeConfirmedMoreThanOnce; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class UserRegistrationCannotBeConfirmedMoreThanOnceTest { @Test public void testRuleIsNotBroken() { assertTrue((new UserRegistrationCannotBeConfirmedMoreThanOnce(UserRegistrationStatus.WaitingForConfirmation)).isRuleComplied()); assertTrue((new UserRegistrationCannotBeConfirmedMoreThanOnce(UserRegistrationStatus.Expired)).isRuleComplied()); } @Test public void testRuleIsBroken() { assertFalse((new UserRegistrationCannotBeConfirmedMoreThanOnce(UserRegistrationStatus.Confirmed)).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/rules/UserRegistrationMustBeConfirmedTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration.rules; import com.tomo.mcauthentication.domain.registration.UserRegistrationStatus; import com.tomo.mcauthentication.domain.registration.rules.UserRegistrationMustBeConfirmed; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class UserRegistrationMustBeConfirmedTest { @Test public void testRuleIsNotBroken() { assertTrue((new UserRegistrationMustBeConfirmed(UserRegistrationStatus.Confirmed)).isRuleComplied()); } @Test public void testRuleIsBroken() { assertFalse((new UserRegistrationMustBeConfirmed(UserRegistrationStatus.WaitingForConfirmation)).isRuleComplied()); assertFalse((new UserRegistrationMustBeConfirmed(UserRegistrationStatus.Expired)).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/registration/rules/UserRegistrationMustBeUniqueTest.java ================================================ package com.tomo.mcauthentication.unit.domain.registration.rules; import com.tomo.mcauthentication.domain.registration.UserRegistrationRepository; import com.tomo.mcauthentication.domain.registration.rules.UserRegistrationMustBeUnique; import com.tomo.mcauthentication.unit.domain.AbstractUnitTest; import org.junit.Test; import org.mockito.Mockito; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.when; public class UserRegistrationMustBeUniqueTest extends AbstractUnitTest { UserRegistrationRepository repository = Mockito.mock(UserRegistrationRepository.class); @Test public void testRuleIsNotBroken() { when(repository.countByEmailAndStatus(any(), any())).thenReturn(0L); assertTrue((new UserRegistrationMustBeUnique(repository, "")).isRuleComplied()); } @Test public void testRuleIsBroken() { when(repository.countByEmailAndStatus(any(), any())).thenReturn(1L); assertFalse((new UserRegistrationMustBeUnique(repository, "")).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/session/rules/SessionCannotBeExpiredWhenRefreshTokenIsMissingTest.java ================================================ package com.tomo.mcauthentication.unit.domain.session.rules; import com.tomo.mcauthentication.domain.session.Session; import com.tomo.mcauthentication.domain.session.rule.SessionCannotBeExpiredWhenRefreshTokenIsMissing; import com.tomo.mcauthentication.unit.domain.AbstractUnitTest; import org.junit.Test; import java.time.LocalDateTime; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class SessionCannotBeExpiredWhenRefreshTokenIsMissingTest extends AbstractUnitTest { @Test public void testRuleIsNotBroken() { Session session1 = new Session(); session1.setExpirationDate(LocalDateTime.now().plusDays(2)); Session session2 = new Session(); session2.setExpirationDate(LocalDateTime.now().minusDays(2)); session2.setRefreshToken("randomToken"); assertTrue((new SessionCannotBeExpiredWhenRefreshTokenIsMissing(session1)).isRuleComplied()); assertTrue((new SessionCannotBeExpiredWhenRefreshTokenIsMissing(session2)).isRuleComplied()); } @Test public void testRuleIsBroken() { Session session1 = new Session(); session1.setExpirationDate(LocalDateTime.now().minusDays(2)); assertFalse((new SessionCannotBeExpiredWhenRefreshTokenIsMissing(session1)).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/unit/domain/user/rules/UserEmailMustBeUniqueTest.java ================================================ package com.tomo.mcauthentication.unit.domain.user.rules; import com.tomo.mcauthentication.domain.users.User; import com.tomo.mcauthentication.domain.users.UserRepository; import com.tomo.mcauthentication.domain.users.rules.UserEmailMustBeUnique; import com.tomo.mcauthentication.unit.domain.AbstractUnitTest; import org.junit.Test; import org.mockito.Mockito; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; public class UserEmailMustBeUniqueTest extends AbstractUnitTest { UserRepository repository = Mockito.mock(UserRepository.class); @Test public void testRuleIsNotBroken() { when(repository.findByEmail(any())).thenReturn(null); assertTrue((new UserEmailMustBeUnique(repository, "random@email.com")).isRuleComplied()); } @Test public void testRuleIsBroken() { when(repository.findByEmail(any())).thenReturn(new User()); assertFalse((new UserEmailMustBeUnique(repository, "random@email.com")).isRuleComplied()); } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/weblayer/BaseWebLayerTest.java ================================================ package com.tomo.mcauthentication.weblayer; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Profile; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public abstract class BaseWebLayerTest { } ================================================ FILE: src/test/java/com/tomo/mcauthentication/weblayer/springboot/controller/AbstractControllerTest.java ================================================ package com.tomo.mcauthentication.weblayer.springboot.controller; import com.tomo.ddd.email.EmailSender; import com.tomo.mcauthentication.weblayer.BaseWebLayerTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.http.HttpHeaders; import org.springframework.test.web.servlet.MockMvc; import java.net.URI; import java.net.URISyntaxException; @AutoConfigureMockMvc public abstract class AbstractControllerTest extends BaseWebLayerTest { @MockBean protected EmailSender emailMessageSender; @LocalServerPort protected int randomServerPort; @Autowired protected MockMvc mockMvc; protected URI url(String uri) throws URISyntaxException { final String baseUrl = "http://localhost:" + randomServerPort+ uri; return new URI(baseUrl); } protected HttpHeaders baseHeaders() { HttpHeaders headers = new org.springframework.http.HttpHeaders(); headers.set("X-COM-PERSIST", "true"); return headers; } } ================================================ FILE: src/test/java/com/tomo/mcauthentication/weblayer/springboot/controller/RegistrationControllerTest.java ================================================ package com.tomo.mcauthentication.weblayer.springboot.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.tomo.mcauthentication.application.registration.command.RegisterNewUserCommand; import com.tomo.mcauthentication.infrastructure.springboot.controller.RestApiRoutes.RegistrationRoutes; import com.tomo.mcauthentication.testdata.CommandObjectMother; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class RegistrationControllerTest extends AbstractControllerTest { @Autowired private ObjectMapper objectMapper; @Test @Transactional public void shouldCreateUserRegistration() throws Exception { RegisterNewUserCommand command = CommandObjectMother.registerNewUserCommandWithFakerEmail(); this.mockMvc.perform( post(RegistrationRoutes.FORM_REGISTRATION) .contentType(APPLICATION_JSON) .content(objectMapper.writeValueAsString(command))) .andDo(print()) .andExpect(status().isCreated()); } } ================================================ FILE: src/test/resources/application-ci.yml ================================================ debug: true spring: datasource: url: jdbc:postgresql://mc-authentication-db-test:5432/mc_authentication