From 49d47b8838857711488b47f099e1f83293e89824 Mon Sep 17 00:00:00 2001 From: PALASH2201 Date: Sat, 5 Oct 2024 18:18:37 +0530 Subject: [PATCH 1/4] Implemented CRUD operations for Product Module --- .gitignore | 3 + product/.gitignore | 3 + product/pom.xml | 14 +++ .../java/com/fjb/product/config/Security.java | 21 ++++ .../product/controller/ProductController.java | 96 ++++++++++++++++++- .../dto/response/ProductResponseDto.java | 2 +- .../java/com/fjb/product/entity/Product.java | 5 +- .../product/exception/ErrorCreatingEntry.java | 7 ++ .../exception/ProductNotFoundException.java | 7 ++ .../com/fjb/product/mapper/ProductMapper.java | 1 + .../product/repository/ProductRepository.java | 3 + .../fjb/product/service/ProductService.java | 39 +++++++- product/src/main/resources/.env.example | 3 + .../src/main/resources/application.properties | 6 +- 14 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 product/src/main/java/com/fjb/product/config/Security.java create mode 100644 product/src/main/java/com/fjb/product/exception/ErrorCreatingEntry.java create mode 100644 product/src/main/java/com/fjb/product/exception/ProductNotFoundException.java create mode 100644 product/src/main/resources/.env.example diff --git a/.gitignore b/.gitignore index 549e00a..2bbfaf6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ build/ ### VS Code ### .vscode/ + +### ENV ### +*.env diff --git a/product/.gitignore b/product/.gitignore index 549e00a..b4a6017 100644 --- a/product/.gitignore +++ b/product/.gitignore @@ -31,3 +31,6 @@ build/ ### VS Code ### .vscode/ + +### ENV ### +.env \ No newline at end of file diff --git a/product/pom.xml b/product/pom.xml index 94d0749..ee980c9 100644 --- a/product/pom.xml +++ b/product/pom.xml @@ -61,6 +61,20 @@ spring-security-test test + + mysql + mysql-connector-java + 8.0.33 + + + me.paulschwarz + spring-dotenv + 4.0.0 + + + org.springframework.boot + spring-boot-starter-security + diff --git a/product/src/main/java/com/fjb/product/config/Security.java b/product/src/main/java/com/fjb/product/config/Security.java new file mode 100644 index 0000000..7bc8744 --- /dev/null +++ b/product/src/main/java/com/fjb/product/config/Security.java @@ -0,0 +1,21 @@ +package com.fjb.product.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class Security { + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests(request -> request + .requestMatchers("/api/**").permitAll()) + .csrf(AbstractHttpConfigurer::disable) + .build(); + } + +} diff --git a/product/src/main/java/com/fjb/product/controller/ProductController.java b/product/src/main/java/com/fjb/product/controller/ProductController.java index c0aee16..7fa3dfd 100644 --- a/product/src/main/java/com/fjb/product/controller/ProductController.java +++ b/product/src/main/java/com/fjb/product/controller/ProductController.java @@ -2,15 +2,25 @@ import com.fjb.product.dto.request.ProductCreateDto; import com.fjb.product.dto.response.ProductResponseDto; +import com.fjb.product.exception.ErrorCreatingEntry; +import com.fjb.product.exception.ProductNotFoundException; import com.fjb.product.service.ProductService; +import java.math.BigDecimal; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; + + @RestController @RequestMapping("/api/product") @RequiredArgsConstructor @@ -19,8 +29,86 @@ public class ProductController { private final ProductService productService; @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public ProductResponseDto createProduct(@RequestBody ProductCreateDto productCreateDto) { - return productService.createProduct(productCreateDto); + public ResponseEntity createProduct(@RequestBody ProductCreateDto productCreateDto) { + try { + ProductResponseDto productResponseDto = productService.createProduct(productCreateDto); + if (productResponseDto == null) { + throw new ErrorCreatingEntry("Could not create entry"); + } else { + return new ResponseEntity<>(productResponseDto, HttpStatus.CREATED); + } + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + } + + @GetMapping + public ResponseEntity> getAllProducts() { + try { + List list = productService.getAllProducts(); + if (list.isEmpty()) { + throw new ProductNotFoundException("No Entries found!"); + } else { + return new ResponseEntity<>(list, HttpStatus.OK); + } + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @GetMapping("/id/{id}") + public ResponseEntity getProductById(@PathVariable Long id) { + try { + ProductResponseDto productResponseDto = productService.getProduct(id); + if (productResponseDto == null) { + throw new ProductNotFoundException("No Entry found"); + } + return new ResponseEntity<>(productResponseDto, HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @PutMapping("/id/{id}") + public ResponseEntity updateProduct( + @PathVariable Long id, @RequestBody ProductCreateDto newProductCreateDto + ) { + try { + ProductResponseDto existingProduct = productService.getProduct(id); + if (existingProduct != null) { + String newName = newProductCreateDto.getName(); + String newDescription = newProductCreateDto.getDescription(); + BigDecimal newPrice = newProductCreateDto.getPrice(); + + existingProduct.setName( + newName != null && !newName.isEmpty() + ? newName : existingProduct.getName() + ); + existingProduct.setDescription( + newDescription != null && !newDescription.isEmpty() + ? newDescription : existingProduct.getDescription() + ); + existingProduct.setPrice( + newPrice != null + ? newPrice : existingProduct.getPrice() + ); + ProductResponseDto updated = productService.saveExistingProduct(existingProduct); + return new ResponseEntity<>(updated, HttpStatus.CREATED); + } else { + throw new ProductNotFoundException("Entry not found"); + } + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + } + + @DeleteMapping("/id/{id}") + public ResponseEntity deleteProduct(@PathVariable Long id) { + try { + productService.deleteProductById(id); + return new ResponseEntity<>(HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } } } diff --git a/product/src/main/java/com/fjb/product/dto/response/ProductResponseDto.java b/product/src/main/java/com/fjb/product/dto/response/ProductResponseDto.java index 5497a81..823857c 100644 --- a/product/src/main/java/com/fjb/product/dto/response/ProductResponseDto.java +++ b/product/src/main/java/com/fjb/product/dto/response/ProductResponseDto.java @@ -13,7 +13,7 @@ @NoArgsConstructor @AllArgsConstructor public class ProductResponseDto { - private String id; + private Long id; private String name; private String description; private BigDecimal price; diff --git a/product/src/main/java/com/fjb/product/entity/Product.java b/product/src/main/java/com/fjb/product/entity/Product.java index fbab6b1..8c3f2d4 100644 --- a/product/src/main/java/com/fjb/product/entity/Product.java +++ b/product/src/main/java/com/fjb/product/entity/Product.java @@ -1,6 +1,8 @@ package com.fjb.product.entity; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import java.math.BigDecimal; @@ -19,7 +21,8 @@ @AllArgsConstructor public class Product { @Id - private String id; + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; private String name; private String description; private BigDecimal price; diff --git a/product/src/main/java/com/fjb/product/exception/ErrorCreatingEntry.java b/product/src/main/java/com/fjb/product/exception/ErrorCreatingEntry.java new file mode 100644 index 0000000..059705f --- /dev/null +++ b/product/src/main/java/com/fjb/product/exception/ErrorCreatingEntry.java @@ -0,0 +1,7 @@ +package com.fjb.product.exception; + +public class ErrorCreatingEntry extends RuntimeException { + public ErrorCreatingEntry(String message) { + super(message); + } +} \ No newline at end of file diff --git a/product/src/main/java/com/fjb/product/exception/ProductNotFoundException.java b/product/src/main/java/com/fjb/product/exception/ProductNotFoundException.java new file mode 100644 index 0000000..f9923b1 --- /dev/null +++ b/product/src/main/java/com/fjb/product/exception/ProductNotFoundException.java @@ -0,0 +1,7 @@ +package com.fjb.product.exception; + +public class ProductNotFoundException extends RuntimeException { + public ProductNotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/product/src/main/java/com/fjb/product/mapper/ProductMapper.java b/product/src/main/java/com/fjb/product/mapper/ProductMapper.java index cb76e34..ffa9e07 100644 --- a/product/src/main/java/com/fjb/product/mapper/ProductMapper.java +++ b/product/src/main/java/com/fjb/product/mapper/ProductMapper.java @@ -10,4 +10,5 @@ public interface ProductMapper { Product toProduct(ProductCreateDto productCreateDto); ProductResponseDto toProductResponseDto(Product product); + } diff --git a/product/src/main/java/com/fjb/product/repository/ProductRepository.java b/product/src/main/java/com/fjb/product/repository/ProductRepository.java index 8b620af..9404ff4 100644 --- a/product/src/main/java/com/fjb/product/repository/ProductRepository.java +++ b/product/src/main/java/com/fjb/product/repository/ProductRepository.java @@ -1,8 +1,11 @@ package com.fjb.product.repository; import com.fjb.product.entity.Product; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository { + Product findById(Long id); + List findAllByOrderByIdAsc(); } diff --git a/product/src/main/java/com/fjb/product/service/ProductService.java b/product/src/main/java/com/fjb/product/service/ProductService.java index efb019b..fc740ea 100644 --- a/product/src/main/java/com/fjb/product/service/ProductService.java +++ b/product/src/main/java/com/fjb/product/service/ProductService.java @@ -5,13 +5,14 @@ import com.fjb.product.entity.Product; import com.fjb.product.mapper.ProductMapper; import com.fjb.product.repository.ProductRepository; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; + @Service @RequiredArgsConstructor -@Transactional(readOnly = true) public class ProductService { private final ProductMapper productMapper; @@ -19,6 +20,38 @@ public class ProductService { public ProductResponseDto createProduct(ProductCreateDto productCreateDto) { Product product = productMapper.toProduct(productCreateDto); - return productMapper.toProductResponseDto(productRepository.save(product)); + product = productRepository.save(product); + return productMapper.toProductResponseDto(product); + } + + public List getAllProducts() { + List list = productRepository.findAllByOrderByIdAsc(); + List reqList = new ArrayList<>(); + for (Product product : list) { + reqList.add(productMapper.toProductResponseDto(product)); + } + return reqList; + } + + public ProductResponseDto getProduct(Long id) { + Product product = productRepository.findById(id); + return productMapper.toProductResponseDto(product); + } + + public ProductResponseDto saveExistingProduct(ProductResponseDto productResponseDto) { + Product existingProduct = productRepository.findById(productResponseDto.getId()); + if (existingProduct == null) { + throw new IllegalArgumentException("Product not found"); + } else { + existingProduct.setName(productResponseDto.getName()); + existingProduct.setDescription(productResponseDto.getDescription()); + existingProduct.setPrice(productResponseDto.getPrice()); + Product product = productRepository.save(existingProduct); + return productMapper.toProductResponseDto(product); + } + } + + public void deleteProductById(Long id) { + productRepository.deleteById(String.valueOf(id)); } } diff --git a/product/src/main/resources/.env.example b/product/src/main/resources/.env.example new file mode 100644 index 0000000..fa26e2e --- /dev/null +++ b/product/src/main/resources/.env.example @@ -0,0 +1,3 @@ +POSTGRES_USERNAME= +POSTGRES_PASSWORD= +POSTGRES_DATABASE_URL="jdbc:postgresql://localhost:5432/product" \ No newline at end of file diff --git a/product/src/main/resources/application.properties b/product/src/main/resources/application.properties index 81d683e..22bf4df 100644 --- a/product/src/main/resources/application.properties +++ b/product/src/main/resources/application.properties @@ -1,8 +1,8 @@ spring.application.name=product -spring.datasource.url=jdbc:postgresql://localhost:5432/product -spring.datasource.username=admin -spring.datasource.password=admin +spring.datasource.url=${POSTGRES_DATABASE_URL} +spring.datasource.username=${POSTGRES_USERNAME} +spring.datasource.password=${POSTGRES_PASSWORD} spring.datasource.driver-class-name=org.postgresql.Driver # JPA & Hibernate settings From c0644251f2d7a76aeb26f3a23223339e93d6cbf2 Mon Sep 17 00:00:00 2001 From: PALASH2201 Date: Sat, 5 Oct 2024 23:25:20 +0530 Subject: [PATCH 2/4] Security Hotspot issue fixed --- product/src/main/java/com/fjb/product/config/Security.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/product/src/main/java/com/fjb/product/config/Security.java b/product/src/main/java/com/fjb/product/config/Security.java index 7bc8744..e65d01e 100644 --- a/product/src/main/java/com/fjb/product/config/Security.java +++ b/product/src/main/java/com/fjb/product/config/Security.java @@ -11,10 +11,12 @@ @EnableWebSecurity public class Security { @Bean + @SuppressWarnings("java:S4502") public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.authorizeHttpRequests(request -> request .requestMatchers("/api/**").permitAll()) - .csrf(AbstractHttpConfigurer::disable) + .csrf(csrf -> csrf + .ignoringRequestMatchers("/api/**")) .build(); } From 324701a5ea3dace5b82c60fa8f0f27af3ce9f757 Mon Sep 17 00:00:00 2001 From: PALASH2201 Date: Tue, 15 Oct 2024 21:19:02 +0530 Subject: [PATCH 3/4] Merge Conflicts resolved for "add security and swagger in product (#60)" --- .env.sample | 21 + .github/workflows/sell-bff-ci.yaml | 168 ++ CODE_OF_CONDUCT.md | 94 + docker-compose.yml | 71 + docker/postgres/Dockerfile | 7 + docker/postgres/postgresql.conf.sample | 16 + docs/diagram.md | 4 +- identity/realm-export.json | 2333 +++++++++++++++++ identity/themes/README.md | 14 + .../matcha/META-INF/keycloak-themes.json | 6 + .../matcha/login/components/link/primary.ftl | 10 + .../login/components/link/secondary.ftl | 10 + .../matcha/theme/matcha/login/login.ftl | 52 + .../matcha/theme/matcha/login/register.ftl | 133 + .../matcha/login/resources/css/login.css | 242 ++ .../matcha/login/resources/img/background.svg | 39 + .../matcha/login/resources/img/eye-off.png | Bin 0 -> 692 bytes .../theme/matcha/login/resources/img/eye.png | Bin 0 -> 639 bytes .../login/resources/img/matcha-logo.png | Bin 0 -> 11511 bytes .../matcha/theme/matcha/login/template.ftl | 36 + .../theme/matcha/login/theme.properties | 10 + .../configuration/custom_proxy_settings.conf | 1 + nginx/templates/default.conf.template | 26 + pom.xml | 19 + postgres_init.sql | 14 + product/pom.xml | 9 +- .../com/fjb/product/config/CorsConfig.java | 25 + .../java/com/fjb/product/config/Security.java | 23 - .../fjb/product/config/SecurityConfig.java | 45 + .../com/fjb/product/config/SwaggerConfig.java | 32 + .../product/controller/ProductController.java | 12 +- .../fjb/product/service/ProductService.java | 2 + .../src/main/resources/application.properties | 14 - product/src/main/resources/application.yml | 43 + .../src/test/resources/application.properties | 12 +- sell-bff/.gitignore | 33 + .../.mvn/wrapper/maven-wrapper.properties | 19 + sell-bff/Dockerfile | 6 + sell-bff/mvnw | 259 ++ sell-bff/mvnw.cmd | 149 ++ sell-bff/pom.xml | 54 + .../com/fjb/sellbff/SellBffApplication.java | 13 + .../fjb/sellbff/config/SecurityConfig.java | 97 + .../controller/AuthenticationController.java | 17 + .../fjb/sellbff/dto/AuthenticatedUser.java | 4 + sell-bff/src/main/resources/application.yaml | 60 + sell-bff/wait-for-it.sh | 182 ++ 47 files changed, 4384 insertions(+), 52 deletions(-) create mode 100644 .env.sample create mode 100644 .github/workflows/sell-bff-ci.yaml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 docker-compose.yml create mode 100644 docker/postgres/Dockerfile create mode 100644 docker/postgres/postgresql.conf.sample create mode 100644 identity/realm-export.json create mode 100644 identity/themes/README.md create mode 100644 identity/themes/matcha/META-INF/keycloak-themes.json create mode 100644 identity/themes/matcha/theme/matcha/login/components/link/primary.ftl create mode 100644 identity/themes/matcha/theme/matcha/login/components/link/secondary.ftl create mode 100644 identity/themes/matcha/theme/matcha/login/login.ftl create mode 100644 identity/themes/matcha/theme/matcha/login/register.ftl create mode 100644 identity/themes/matcha/theme/matcha/login/resources/css/login.css create mode 100644 identity/themes/matcha/theme/matcha/login/resources/img/background.svg create mode 100644 identity/themes/matcha/theme/matcha/login/resources/img/eye-off.png create mode 100644 identity/themes/matcha/theme/matcha/login/resources/img/eye.png create mode 100644 identity/themes/matcha/theme/matcha/login/resources/img/matcha-logo.png create mode 100644 identity/themes/matcha/theme/matcha/login/template.ftl create mode 100644 identity/themes/matcha/theme/matcha/login/theme.properties create mode 100644 nginx/configuration/custom_proxy_settings.conf create mode 100644 nginx/templates/default.conf.template create mode 100644 postgres_init.sql create mode 100644 product/src/main/java/com/fjb/product/config/CorsConfig.java delete mode 100644 product/src/main/java/com/fjb/product/config/Security.java create mode 100644 product/src/main/java/com/fjb/product/config/SecurityConfig.java create mode 100644 product/src/main/java/com/fjb/product/config/SwaggerConfig.java delete mode 100644 product/src/main/resources/application.properties create mode 100644 product/src/main/resources/application.yml create mode 100644 sell-bff/.gitignore create mode 100644 sell-bff/.mvn/wrapper/maven-wrapper.properties create mode 100644 sell-bff/Dockerfile create mode 100644 sell-bff/mvnw create mode 100644 sell-bff/mvnw.cmd create mode 100644 sell-bff/pom.xml create mode 100644 sell-bff/src/main/java/com/fjb/sellbff/SellBffApplication.java create mode 100644 sell-bff/src/main/java/com/fjb/sellbff/config/SecurityConfig.java create mode 100644 sell-bff/src/main/java/com/fjb/sellbff/controller/AuthenticationController.java create mode 100644 sell-bff/src/main/java/com/fjb/sellbff/dto/AuthenticatedUser.java create mode 100644 sell-bff/src/main/resources/application.yaml create mode 100644 sell-bff/wait-for-it.sh diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..f23c6fa --- /dev/null +++ b/.env.sample @@ -0,0 +1,21 @@ +# Postgres +POSTGRES_USER= +POSTGRES_PASSWORD= +POSTGRES_HOST= +POSTGRES_PORT= + +SERVER_PORT= + +# Swagger UI +URLS= + +# Start all service when run docker compose up +COMPOSE_FILE=docker-compose.yml + +# ********************* # +# SERVICES +# ********************* # +# Product +PRODUCT_DATASOURCE_URL= +PRODUCT_DATA_USERNAME= +PRODUCT_DATA_PASSWORD= \ No newline at end of file diff --git a/.github/workflows/sell-bff-ci.yaml b/.github/workflows/sell-bff-ci.yaml new file mode 100644 index 0000000..eea5a10 --- /dev/null +++ b/.github/workflows/sell-bff-ci.yaml @@ -0,0 +1,168 @@ +name: Sell bff service CI + +on: + push: + branches: [ "main" ] + paths: + - "sell-bff/**" + - ".github/workflows/actions/action.yaml" + - ".github/workflows/sell-bff-ci.yaml" + - "pom.xml" + pull_request: + branches: [ "main" ] + paths: + - "sell-bff/**" + - ".github/workflows/actions/action.yaml" + - ".github/workflows/sell-bff-ci.yaml" + - "pom.xml" + workflow_dispatch: + +jobs: + style: + runs-on: ubuntu-latest + name: Check style + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Run maven checkstyle + run: mvn checkstyle:checkstyle -f sell-bff + compile: + runs-on: ubuntu-latest + name: Compile project + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'adopt' + cache: maven + + - name: Compile project + run: mvn clean compile -f sell-bff + + unit-tests: + runs-on: ubuntu-latest + name: Unit tests + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'adopt' + cache: maven + + - name: Running unit tests + run: mvn test -f sell-bff jacoco:report + + build: + runs-on: ubuntu-latest + name: Build project + needs: [compile, unit-tests] + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'adopt' + cache: maven + + - name: Building project + run: mvn package -f sell-bff + + coverage: + runs-on: ubuntu-latest + env: + FROM_ORIGINAL_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.ref == 'refs/heads/main' }} + permissions: + pull-requests: write + packages: write + name: Coverage and Package + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'adopt' + cache: maven + - name: Building project + run: mvn package -f sell-bff + - name: Add coverage report to PR + uses: madrapps/jacoco-report@v1.6.1 + if: ${{ env.FROM_ORIGINAL_REPOSITORY == 'true' }} + with: + paths: ${{github.workspace}}/sell-bff/target/site/jacoco/jacoco.xml + token: ${{secrets.GITHUB_TOKEN}} + min-coverage-overall: 30 + min-coverage-changed-files: 20 + title: 'Sell-bff Coverage Report' + update-comment: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + continue-on-error: false + + - name: OWASP Dependency Check + if: ${{ env.FROM_ORIGINAL_REPOSITORY == 'true' }} + uses: dependency-check/Dependency-Check_Action@main + env: + JAVA_HOME: /opt/jdk + with: + project: 'matcha' + path: '.' + format: 'HTML' + - name: Upload OWASP Dependency Check results + if: ${{ env.FROM_ORIGINAL_REPOSITORY == 'true' }} + uses: actions/upload-artifact@master + with: + name: OWASP Dependency Check Report + path: ${{github.workspace}}/reports + + - name: Log in to the Container registry + if: ${{ github.ref == 'refs/heads/main' }} + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push Docker images + if: ${{ github.ref == 'refs/heads/main' }} + uses: docker/build-push-action@v6 + with: + context: ./sell-bff + push: true + file: ./sell-bff/Dockerfile + platforms: linux/amd64 + tags: ghcr.io/${{ github.repository_owner }}/matcha-sell-bff:latest + + check: + runs-on: ubuntu-latest + name: Git-leaks check + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Gitleaks check + run: | + docker pull zricethezav/gitleaks:v8.18.4 + docker run --rm -v ${{ github.workspace }}:/work -w /work zricethezav/gitleaks:v8.18.4 detect --source="." --config="/work/gitleaks.toml" --verbose --no-git diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3bbb7d6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,94 @@ +# Citizen Code of Conduct + +## 1. Purpose + +A primary goal of Matcha is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). + +This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. + +We invite all those who participate in Matcha to help us create safe and positive experiences for everyone. + +## 2. Open [Source/Culture/Tech] Citizenship + +A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. + +Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. + +If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. + +## 3. Expected Behavior + +The following behaviors are expected and requested of all community members: + + * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. + * Exercise consideration and respect in your speech and actions. + * Attempt collaboration before conflict. + * Refrain from demeaning, discriminatory, or harassing behavior and speech. + * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. + * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. + +## 4. Unacceptable Behavior + +The following behaviors are considered harassment and are unacceptable within our community: + + * Violence, threats of violence or violent language directed against another person. + * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. + * Posting or displaying sexually explicit or violent material. + * Posting or threatening to post other people's personally identifying information ("doxing"). + * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. + * Inappropriate photography or recording. + * Inappropriate physical contact. You should have someone's consent before touching them. + * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. + * Deliberate intimidation, stalking or following (online or in person). + * Advocating for, or encouraging, any of the above behavior. + * Sustained disruption of community events, including talks and presentations. + +## 5. Weapons Policy + +No weapons will be allowed at Matcha events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter. + +## 6. Consequences of Unacceptable Behavior + +Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). + +## 7. Reporting Guidelines + +If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. . + + + +Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. + +## 8. Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. + + + +## 9. Scope + +We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business. + +This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. + +## 10. Contact info + + + +## 11. License and attribution + +The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). + +Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). + +_Revision 2.3. Posted 6 March 2017._ + +_Revision 2.2. Posted 4 February 2016._ + +_Revision 2.1. Posted 23 June 2014._ + +_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8e5da66 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,71 @@ +version: "3.9" + +services: +# nginx: +# image: nginx:1.25.3 +# restart: unless-stopped +# volumes: +# - ./nginx/templates:/etc/nginx/templates +# - ./nginx/configuration/custom_proxy_settings.conf:/etc/nginx/conf.d/custom_proxy_settings.conf +# ports: +# - "80:80" +# networks: +# - matcha-network + + keycloak: + image: quay.io/keycloak/keycloak:21.0.2 + command: [ 'start-dev --import-realm' ] + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_PROXY: passthrough + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak + KC_DB_USERNAME: ${POSTGRES_USER} + KC_DB_PASSWORD: ${POSTGRES_PASSWORD} + KC_HTTP_PORT: 80 + volumes: + - ./identity/realm-export.json:/opt/keycloak/data/import/realm-export.json + - ./identity/themes/matcha/theme:/opt/keycloak/themes + networks: + - matcha-network + ports: + - "8080:80" + + postgres: + image: debezium/postgres:15-alpine + build: ./docker/postgres + hostname: ${POSTGRES_HOST} + ports: + - "${POSTGRES_PORT}:${POSTGRES_PORT}" + volumes: + - ./docker/postgres/postgresql.conf.sample:/usr/share/postgresql/postgresql.conf.sample + - ./postgres_init.sql:/docker-entrypoint-initdb.d/postgres_init.sql + - postgres:/var/lib/postgresql/data + command: postgres -c 'max_connections=500' + environment: + - POSTGRES_USER + - POSTGRES_PASSWORD + networks: + - matcha-network + + swagger-ui: + image: swaggerapi/swagger-ui:v4.16.0 + environment: + - BASE_URL=/swagger-ui + - URLS + - OAUTH_CLIENT_ID=swagger + - OAUTH_USE_PKCE=true + networks: + - matcha-network + ports: + - "8090:8080" + +networks: + matcha-network: + driver: bridge + name: matcha-network + +volumes: + postgres: + pgadmin: \ No newline at end of file diff --git a/docker/postgres/Dockerfile b/docker/postgres/Dockerfile new file mode 100644 index 0000000..2f66a80 --- /dev/null +++ b/docker/postgres/Dockerfile @@ -0,0 +1,7 @@ +FROM debezium/postgres:15-alpine +ENV WAL2JSON_TAG="wal2json_2_5" +RUN apk add --no-cache --virtual .debezium-build-deps clang15 gcc git llvm15 make musl-dev pkgconf \ + && git clone https://github.com/eulerto/wal2json -b master --single-branch \ + && (cd /wal2json && git checkout tags/"$WAL2JSON_TAG" -b "$WAL2JSON_TAG" && make && make install) \ + && rm -rf wal2json \ + && apk del .debezium-build-deps diff --git a/docker/postgres/postgresql.conf.sample b/docker/postgres/postgresql.conf.sample new file mode 100644 index 0000000..3857e7f --- /dev/null +++ b/docker/postgres/postgresql.conf.sample @@ -0,0 +1,16 @@ +# LOGGING +# log_min_error_statement = fatal +# log_min_messages = DEBUG1 + +# CONNECTION +listen_addresses = '*' + +# MODULES +shared_preload_libraries = 'decoderbufs,wal2json' + +# REPLICATION +wal_level = logical # minimal, archive, hot_standby, or logical (change requires restart) +max_wal_senders = 20 # max number of walsender processes (change requires restart) +#wal_keep_segments = 4 # in logfile segments, 16MB each; 0 disables +#wal_sender_timeout = 60s # in milliseconds; 0 disables +max_replication_slots = 20 # max number of replication slots (change requires restart) \ No newline at end of file diff --git a/docs/diagram.md b/docs/diagram.md index 3d6548f..eca149f 100644 --- a/docs/diagram.md +++ b/docs/diagram.md @@ -1,9 +1,11 @@ # Description database in this project ## Diagram -![Screenshot 2024-10-02 234532](https://github.com/user-attachments/assets/6f9d1806-9b10-4d96-ae4b-63ad9ca2da61) +![Screenshot 2024-10-09 080050](https://github.com/user-attachments/assets/7a392dcd-8058-4d0a-8cdf-d59517fe2b62) ## Behavior of buyer +Ảnh màn hình 2024-10-04 lúc 16 08 55 ## Behavior of seller +![seller diagram](https://github.com/user-attachments/assets/05b0d10c-b8cf-46df-bdf4-9fd3508f8e69) ## Behavior of manager diff --git a/identity/realm-export.json b/identity/realm-export.json new file mode 100644 index 0000000..f546d47 --- /dev/null +++ b/identity/realm-export.json @@ -0,0 +1,2333 @@ +{ + "id": "afd1cd83-b112-4046-87f8-34033f756cb2", + "realm": "Matcha", + "displayName": "", + "displayNameHtml": "", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "295a9bc1-2236-4b32-9b77-d813e0c3b2d9", + "name": "ADMIN", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "afd1cd83-b112-4046-87f8-34033f756cb2", + "attributes": {} + }, + { + "id": "c223cfc5-79ae-4891-86a5-f77ec3c824dc", + "name": "USER", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "afd1cd83-b112-4046-87f8-34033f756cb2", + "attributes": {} + }, + { + "id": "2a53720a-aabb-453f-8423-c11f9d9e81ee", + "name": "CUSTOMER", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "afd1cd83-b112-4046-87f8-34033f756cb2", + "attributes": {} + }, + { + "id": "1fdb059a-9cf1-4aee-ad6a-c3ffb5248e8d", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "afd1cd83-b112-4046-87f8-34033f756cb2", + "attributes": {} + }, + { + "id": "07170153-590d-4252-9c97-2d7810000a05", + "name": "default-roles-test-keycloak", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "afd1cd83-b112-4046-87f8-34033f756cb2", + "attributes": {} + }, + { + "id": "0ab340e5-7ba2-4308-957d-6d9681578d94", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "afd1cd83-b112-4046-87f8-34033f756cb2", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "63896506-bec3-45e6-9765-bbf0957debbe", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "87a3faa0-118b-4ae6-9d51-38e1b7b00111", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "0c88ea7d-e73b-412a-9a40-8f7712164f6d", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "057eb64c-1b72-4220-9861-fa7d63a12e61", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "bbd09874-10e3-4a21-b032-0681fcada724", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "85da91a7-fca3-4c7c-8dd0-b00adb73d36a", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "9013b700-1add-42f5-a804-af6da75f644f", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "84c85c03-5cca-441c-b3bd-e5ee674785a6", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "9e32f7d8-a9e8-418f-b25e-a057627b7640", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-authorization", + "manage-realm", + "manage-users", + "view-users", + "query-realms", + "impersonation", + "view-realm", + "view-clients", + "manage-clients", + "view-identity-providers", + "query-users", + "query-clients", + "query-groups", + "view-authorization", + "manage-events", + "view-events", + "create-client", + "manage-identity-providers" + ] + } + }, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "4bc08965-d55e-449e-afb7-cca5477d1bcf", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "81b39189-2413-49de-9ac8-053c099b4c9e", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "c976a000-ef88-42dc-ad07-dc88c116ecd4", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "0d173837-6d17-46fd-936a-a351ae113fb2", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "28aa736f-6ee5-46b0-b5d1-cba2a70fbf61", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "3b76148d-e07f-43dc-bb8f-27dd12fb1bdd", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "0e997feb-2ab8-4675-a3dd-57ae7992a8fc", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "9d520d40-9ef0-44bd-b122-761defe3a26f", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "adbae435-a4ee-474b-b8f5-9012fad1e86f", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + }, + { + "id": "fa8d473d-3a52-497c-9360-f783434216d7", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "attributes": {} + } + ], + "security-admin-console": [], + "sell-bff": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "2d633964-3ce8-4720-988b-2aabcc5a1ebd", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "14764fde-c413-4e7e-bf1d-6fe8737a435e", + "attributes": {} + } + ], + "account": [ + { + "id": "2401b2fe-d1f5-4282-988c-e3330cad73af", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "attributes": {} + }, + { + "id": "f62031b4-f82e-4bb9-bf45-07dd50bd46fa", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "attributes": {} + }, + { + "id": "3b8b2c30-6c1a-4ffd-955d-2a5073bd2fd8", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "attributes": {} + }, + { + "id": "6e948186-90e0-461c-bd40-0b213af5d67c", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "attributes": {} + }, + { + "id": "f072bcd1-c0d8-42d7-b297-7ad1d860f1cd", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "attributes": {} + }, + { + "id": "4b966cdc-ced3-47bc-a7ef-d15b3c55c558", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "attributes": {} + }, + { + "id": "6de51541-79b3-4998-bec4-1f91e6a8ffca", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "attributes": {} + }, + { + "id": "3f6953a3-1eaf-42ea-bd9d-0a80fc6ac5a7", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "attributes": {} + } + ], + "swagger": [] + } + }, + "groups": [], + "defaultRole": { + "id": "07170153-590d-4252-9c97-2d7810000a05", + "name": "default-roles-test-keycloak", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "afd1cd83-b112-4046-87f8-34033f756cb2" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppMicrosoftAuthenticatorName", + "totpAppGoogleName", + "totpAppFreeOTPName" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "cadc1e7e-2d39-41d4-a83d-9d7e3eaedf7c", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/Matcha/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/Matcha/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "4a652799-02ae-4321-bf44-43e136dc11ba", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/test-keycloak/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/test-keycloak/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "49289456-8f28-478f-b594-dd237d64dd14", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a6f544ea-505f-4b2c-bb7c-3d6fcdd6304d", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "14764fde-c413-4e7e-bf1d-6fe8737a435e", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "3ef58adb-1d0b-4624-a3e5-29a4ef8b5d40", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "82cd2703-63d7-4d56-8953-c6a7a31d516d", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/Matcha/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/Matcha/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "1fb665ab-f04f-4768-b7fb-41eb1cd22e2a", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "4cc98eb8-0e4d-44f2-8b56-ae1b74eed070", + "clientId": "sell-bff", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "http://localhost:8181/*" + ], + "webOrigins": [ + "" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1728283344", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "94a9e898-dfa9-468e-8ee7-ec8311a58ca1", + "clientId": "swagger", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "http://localhost:8081/*", + "http://localhost:8083/*", + "http://localhost:8082/*", + "http://localhost:8090/*", + "http://localhost:8084/*" + ], + "webOrigins": [ + "http://localhost:8084", + "http://localhost:8083", + "http://localhost:8090", + "http://localhost:8082", + "http://localhost:8081" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1728283468", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "4a1549bd-330c-433b-94e5-665fa3fe29de", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "34a9dab7-24b5-4d42-82f7-6837ec6e0c71", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "4cea12e2-86eb-47fe-a459-5dd1338eef64", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "d1432388-1850-4291-a692-bdcfdb898292", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "f5233c3b-4f6c-42f6-87f7-fb30608d4bda", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "bec3d10b-b8cc-49dc-9247-74d1355e487e", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "d774dfef-c9dd-43e9-88c1-34f73216688d", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "f1768b14-0a7f-4ec1-86f3-32026092b9d3", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "acc1e20e-b220-49c2-a82d-bd6cb67da8f4", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "d1aa1140-706d-41d1-b563-e681a32371ad", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "1ce6e266-b90b-49a2-ab85-772f445e49b1", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "404d8273-512b-45b3-b1e8-faeabe816f6a", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "d0052477-e819-4620-a1dd-c9e94228626f", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "d74896ce-6562-4419-a27c-7175a49c0d00", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "5391a5a6-c370-4f87-b9e1-0c0b5969b4b6", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "fd53411f-6ac1-44e8-a3d2-460d9e2ca3a3", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "64d8c661-33cf-4f4d-9919-c729370e1493", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "6c79ff15-c6f3-4890-b07d-83604d01df13", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "0979ac3e-f0ca-430e-8214-7ce8dbe8ca91", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "d2b01cbb-1bfb-432e-83a1-b9bfbde8e732", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "1db56fc6-4c45-4631-94e1-4df208d300fe", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "3497b203-7231-4f04-b9fd-fcbbc9ae4067", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "01cb8f1a-d6b1-4d13-88e8-7f243728440a", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "4f3950d6-dac2-4af5-819b-1f1d78ce6ebf", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "d8449d79-4db0-48ae-b411-42d1fd61c67d", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "e1af49a4-09f3-4a6a-ae3f-369ca9d3e0dc", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "cf2e0453-ec64-4a0c-9da2-0fb4bfb8d4f7", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "16b5985c-10ba-4557-89ec-e64bc14d557e", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "0883eccb-5761-4165-acfe-4bfdb0b5f415", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "c1402889-be5d-476b-8543-95ebd291c98f", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "e6491343-88cc-408e-b37d-9544d8b367c3", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "2c7594c4-d052-42cd-b49b-a31702c72c9b", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "89400813-989e-46bc-9b2e-ff9745c4fe62", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "ffa75c9f-0c2d-4ec1-a784-57ef12fc8982", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "98bc8965-5764-4417-ae05-5102dd4695f3", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "00e2f2c6-2ac1-4709-9563-86e20c59821e", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "367185cb-e58a-43a9-8623-dc8f49cfb1d0", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "loginTheme": "matcha", + "accountTheme": "", + "adminTheme": "", + "emailTheme": "", + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "5e6394a1-34ab-4393-83da-c924cd7a56ca", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "61b670cf-6abb-4e31-8f49-a0031005e199", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "60e7eafd-f287-4c17-bfe9-c1030a061afc", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + }, + { + "id": "e757e3ec-4c90-4770-b8fe-0828ff0ce91c", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "5f79b6b4-f389-4868-a010-e247f96dd37a", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "41e9597e-380a-4fc7-9d06-6ee709afed29", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "427e3a62-9c6e-46ac-8635-684780aebe59", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper" + ] + } + }, + { + "id": "fb26be1f-ee08-4af2-8a41-83836370fa76", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "2b9e6a0b-5280-40c2-81a2-572af9e51055", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "9ab2e087-0c14-4f7c-a876-f8dd7abab8a8", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "f690ae81-46b3-4b70-841c-cf32284086e6", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "0da400d4-8016-455e-8e89-d9f102f3095c", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "2a0a36ef-b62f-49be-978b-24cc880b1b54", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "147f91aa-f621-48e5-9f68-348201ae372a", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "84489d60-7cf6-4a0e-b8ca-39956ea26eed", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "d10308f6-47ee-4e13-97d1-f3b450777156", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f2b5a308-27e4-4dd5-a5c5-ea6c6f27a6a4", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "e7c6b899-7d9b-4a0f-950f-3f0036d0bd4e", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "911fabdf-794d-4f3f-bcc7-490b31c36a7a", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "9dbd7169-ade3-45e0-b7b7-bfd6c24cc81e", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "5725ac63-fcf2-41d5-a807-10ab2f74c9cc", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "ebeb0d36-462c-4b35-a656-63524d9b6897", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "c6c1cf66-6c48-4134-a5bd-884111e3d841", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "ee8aa0d6-ff49-4f7d-a32d-fec6657bf353", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "4fd49588-5f2e-4494-9ce1-550c6d215f62", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "24e39ff1-9319-4285-b001-71a2911ff30b", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "76efbb1d-8c65-4b3c-a683-2499bf613725", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "0f4a6fc5-67da-4a71-8bc1-40c8219395a6", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "a5501ab2-81dd-42a4-a5bc-eb41c8055c57", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "14268cf1-a68d-4471-9b8e-075e8f8f1ed5", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "c2764c2c-b551-41fc-adc5-3bc433984aff", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "c71edbdc-b642-4297-b0ee-994ae082d4f8", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "6fc120c1-e738-4cfe-90db-a1feaa20a486", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "bacb22d1-671d-4465-a2a5-bb8d0d82f9ab", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "b22b7cbb-0fd7-48f1-90f2-49df01a2caf7", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaAuthRequestedUserHint": "login_hint", + "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", + "clientSessionIdleTimeout": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "frontendUrl": "", + "acr.loa.map": "{}" + }, + "keycloakVersion": "21.0.2", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/identity/themes/README.md b/identity/themes/README.md new file mode 100644 index 0000000..b7c7d18 --- /dev/null +++ b/identity/themes/README.md @@ -0,0 +1,14 @@ +# Update Matcha theme into k8s deployment +According to the guideline of theme deployment from Keycloak https://www.keycloak.org/docs/latest/server_development/#deploying-themes + +Bellow is steps to create a jar file the theme folder and update to k8s deployment folder + +- Go to `matcha` folder +```shell +cd matcha +``` +- Run the jar command line to create a jar file +```shell +jar cvf matcha.jar * +``` +- Copy `matcha.jar` file to `k8s/deploy/keycloak/themes` folder \ No newline at end of file diff --git a/identity/themes/matcha/META-INF/keycloak-themes.json b/identity/themes/matcha/META-INF/keycloak-themes.json new file mode 100644 index 0000000..2c2b8af --- /dev/null +++ b/identity/themes/matcha/META-INF/keycloak-themes.json @@ -0,0 +1,6 @@ +{ + "themes": [{ + "name" : "matcha", + "types": [ "login" ] + }] +} \ No newline at end of file diff --git a/identity/themes/matcha/theme/matcha/login/components/link/primary.ftl b/identity/themes/matcha/theme/matcha/login/components/link/primary.ftl new file mode 100644 index 0000000..8629108 --- /dev/null +++ b/identity/themes/matcha/theme/matcha/login/components/link/primary.ftl @@ -0,0 +1,10 @@ +<#macro kw component="a" rest...> + <${component} + class="flex text-primary-600 hover:text-primary-500" + <#list rest as attrName, attrValue> + ${attrName}="${attrValue}" + + > + <#nested> + + diff --git a/identity/themes/matcha/theme/matcha/login/components/link/secondary.ftl b/identity/themes/matcha/theme/matcha/login/components/link/secondary.ftl new file mode 100644 index 0000000..431bb75 --- /dev/null +++ b/identity/themes/matcha/theme/matcha/login/components/link/secondary.ftl @@ -0,0 +1,10 @@ +<#macro kw component="a" rest...> + <${component} + class="flex text-secondary-600 hover:text-secondary-900" + <#list rest as attrName, attrValue> + ${attrName}="${attrValue}" + + > + <#nested> + + diff --git a/identity/themes/matcha/theme/matcha/login/login.ftl b/identity/themes/matcha/theme/matcha/login/login.ftl new file mode 100644 index 0000000..b55ddf2 --- /dev/null +++ b/identity/themes/matcha/theme/matcha/login/login.ftl @@ -0,0 +1,52 @@ +<#import "template.ftl" as layout> +<#import "components/link/primary.ftl" as linkPrimary> + +<@layout.registrationLayout displayInfo=social.displayInfo; section> + <#if section = "title"> + ${msg("loginTitle",(realm.displayName!''))} + <#elseif section = "header"> + + +
+ +
+ <#elseif section = "form"> + +
+
+

Welcome to matcha

+
+ <#if realm.password> +
+
+ +
+ +
+ + +
+
+
+ <@linkPrimary.kw href=url.registrationUrl> + + +
+ +
+ +
+ + diff --git a/identity/themes/matcha/theme/matcha/login/register.ftl b/identity/themes/matcha/theme/matcha/login/register.ftl new file mode 100644 index 0000000..a7814db --- /dev/null +++ b/identity/themes/matcha/theme/matcha/login/register.ftl @@ -0,0 +1,133 @@ +<#import "template.ftl" as layout> +<#import "components/link/primary.ftl" as linkPrimary> + +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section> + <#if section = "title"> + ${msg("registerTitle",(realm.displayName!''))} + <#elseif section = "header"> + + +
+ +
+ <#elseif section = "form"> +
+
+

Welcome to matcha store

+
+ +
+ +
+ + <#if messagesPerField.existsError('firstName')> + + ${kcSanitize(messagesPerField.get('firstName'))?no_esc} + + +
+ +
+ + <#if messagesPerField.existsError('lastName')> + + ${kcSanitize(messagesPerField.get('lastName'))?no_esc} + + +
+ +
+ + <#if messagesPerField.existsError('email')> + + ${kcSanitize(messagesPerField.get('email'))?no_esc} + + +
+ + <#if !realm.registrationEmailAsUsername> +
+ + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + + +
+ + + <#if passwordRequired??> +
+ + <#if messagesPerField.existsError('password')> + + ${kcSanitize(messagesPerField.get('password'))?no_esc} + + +
+ +
+ + <#if messagesPerField.existsError('password-confirm')> + + ${kcSanitize(messagesPerField.get('password-confirm'))?no_esc} + + +
+ + + <#if recaptchaRequired??> +
+
+
+ + + +
+ +
+ +
+ + \ No newline at end of file diff --git a/identity/themes/matcha/theme/matcha/login/resources/css/login.css b/identity/themes/matcha/theme/matcha/login/resources/css/login.css new file mode 100644 index 0000000..6a6152c --- /dev/null +++ b/identity/themes/matcha/theme/matcha/login/resources/css/login.css @@ -0,0 +1,242 @@ +body, +html { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + min-height: 0; + font-size: 14px; + color: rgba(0, 0, 0, 0.87); + margin: 0; + height: 100%; +} +body { + margin: 0; +} +.login-content { + background-size: cover; + background-position: center; + min-height: 100%; + min-width: 320px; + display: flex; + justify-content: center; + flex-direction: column; +} +.login-content .box { + width: 448px; + border-radius: 8px; + background-color: #ffffff; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 0 2px 0 rgba(0, 0, 0, 0.12); + margin: auto; +} +.login-content .box .box-container { + width: 100%; + padding-left: 32px; +} +.login-content .box .logo { + display: block; + margin-left: auto; + margin-right: auto; + margin-top: 30px; +} +.login-content .box .application-name { + width: 270px; + font-family: Muli, sans-serif; + font-size: 24px; + font-weight: 600; + font-style: normal; + font-stretch: normal; + line-height: 0.83; + letter-spacing: -0.4px; + text-align: left; + color: #0055b8; + margin-left: 32px; + margin-top: 40px; +} +input { + cursor: pointer; +} +.login-content .box .form { + margin-left: 32px; + margin-top: 55px; + margin-bottom: -55px; +} +.login-content .box .login-field { + width: 320px; + height: 24px; + font-family: Muli, sans-serif; + font-size: 14px; + font-weight: normal; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.4px; + text-align: left; + color: #000000; + border: none; + border-bottom: 1px solid #ccc; + outline: none; + background-color: #fff; +} +.login-content .box input[type="text"]:focus, +.login-content .box input[type="password"]:focus { + border-bottom: 2px solid #0055b8; +} +.login-content .box input[type="text"]:-webkit-autofill, +.login-content .box input[type="password"]:-webkit-autofill { + box-shadow: 0 0 0px 1000px white inset; +} +.login-content .box .submit { + width: 320px; + height: 36px; + border-radius: 2px; + background-color: #00754a; + font-family: Muli, sans-serif; + font-size: 14px; + font-weight: bold; + font-style: normal; + font-stretch: normal; + line-height: normal; + letter-spacing: normal; + text-align: center; + color: #ffffff; + border-style: none; + margin-top: 5rem; + margin-bottom: 4rem; +} +.login-content .box .visibility { + position: relative; + left: 296px; + top: 20px; + width: 24px; + height: 24px; + opacity: 0.54; + border: none; +} +.login-content .box .copyright { + min-width: 320px; + text-align: center; + font-size: 14px; + opacity: 0.54; + position: relative; + top: 50px; +} +.message-text { + width: 322px; + height: 22px; + opacity: 0.87; + font-family: Muli, sans-serif; + font-size: 14px; + font-weight: normal; + font-style: normal; + font-stretch: normal; + line-height: 1.3; + letter-spacing: -0.4px; + text-align: left; + color: #b8082a; + border: 0px; + position: relative; + top: 235px; + left: 64px; + display: inline-flex; + align-items: center; +} +.identity-providers { + width: 270px; + font-family: Muli, sans-serif; + font-size: 16px; + font-weight: 600; + font-style: normal; + font-stretch: normal; + line-height: 0.83; + letter-spacing: -0.4px; + text-align: left; + color: #0055b8; + margin-left: 32px; + margin-top: 20px; +} + +.login-content .box .social-link-style { + width: 320px; + height: 36px; + border-radius: 2px; + background-color: var(--white); + font-family: Muli, sans-serif; + font-size: 14px; + font-weight: bold; + font-style: normal; + font-stretch: normal; + line-height: normal; + letter-spacing: normal; + text-align: center; + color: #00754a; + border-style: solid; + border-color: #00754a; + margin-top: 0px; + margin-left: 30px; + + margin-bottom: 10px; +} + +.login-content .box .para { + font-family: Muli, sans-serif; + font-size: 14px; + margin-left: 30px; +} +.logomatcha { + max-width: 10px; + max-height: 10px; +} + +a { + text-decoration: none; +} + +.register { + width: 320px; + height: 36px; + border-radius: 2px; + background-color: white; + font-family: Muli, sans-serif; + font-size: 14px; + font-weight: bold; + text-align: center; + color: black; + margin-left: 16px; + margin-top: 5px; + border-style: 1px solid black; +} + +.backtologin { + font-size: 14px; + font-weight: bold; + margin-top: 1rem; +} + +.register-submit { + width: 320px; + height: 36px; + border-radius: 2px; + background-color: #00754a; + font-family: Muli, sans-serif; + font-size: 14px; + font-weight: bold; + font-style: normal; + font-stretch: normal; + line-height: normal; + letter-spacing: normal; + text-align: center; + color: #ffffff; + border-style: none; + margin-top: 2rem; + margin-bottom: 3rem; +} + +.input-group { + margin-bottom: 1rem; + display: flex; + flex-direction: column; +} + +.error-message { + color: red; +} diff --git a/identity/themes/matcha/theme/matcha/login/resources/img/background.svg b/identity/themes/matcha/theme/matcha/login/resources/img/background.svg new file mode 100644 index 0000000..bb1327e --- /dev/null +++ b/identity/themes/matcha/theme/matcha/login/resources/img/background.svg @@ -0,0 +1,39 @@ + + + + 157422A6-29B2-4F17-81E3-A7274C0B39D1 + Created with sketchtool. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/identity/themes/matcha/theme/matcha/login/resources/img/eye-off.png b/identity/themes/matcha/theme/matcha/login/resources/img/eye-off.png new file mode 100644 index 0000000000000000000000000000000000000000..c99e6ed3bf10f0b8ce395fd57914e40ad16211bb GIT binary patch literal 692 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ua371Hn({-St3?EavGta96gl>B{?4oqyrzox z9kd;N^cSgVPX1Au6T~T&w0LSqf-=i)?Hd^j<9M97T3J4EvIsV%iU@7l;T~~q=GU&U zN$k_^&b560XXa<4Ki8Q}8gswcq+Zx|s9aWlW4Heg;n(^f#FQ(9|L_0EeEi34tu@@% zJNnAQ*Q`Gh^=jt(t?SG zulg5E=RD!`<6COY_wI(~p1}M!^VPC~cC%m5V7iyuqn2F1Z_x+gwM#rimgR)>?>*Nr zCynd>)+gJiy;_u4!~vgYD)CACgJfqV*H1l} z`)Z}su{CWQmQ*f$$~6CBy6BR%tO2~q5=XQD9JZ``$f9pL`{CZ*;eRKujQH_OIXToR z&;L_T3G?=f1J2OC7#SE^=o(n+8XJTd8CV%wS{Ybq z8yHv_7%cpGFbqXQZhlH;S|x4`7nW4M0&36z*-%`TZk3c+oT^(|l*y2mnUiXzudiQ{ lTcDqtn4X!Oty>gq=Im+c>KM-Z%^awQ!PC{xWt~$(69CAQ4Y>dS literal 0 HcmV?d00001 diff --git a/identity/themes/matcha/theme/matcha/login/resources/img/eye.png b/identity/themes/matcha/theme/matcha/login/resources/img/eye.png new file mode 100644 index 0000000000000000000000000000000000000000..419b21e40dc6d0750a4fcc30ab17d811d0d6e2ed GIT binary patch literal 639 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 G#Qz6tm($Py literal 0 HcmV?d00001 diff --git a/identity/themes/matcha/theme/matcha/login/template.ftl b/identity/themes/matcha/theme/matcha/login/template.ftl new file mode 100644 index 0000000..c470ecc --- /dev/null +++ b/identity/themes/matcha/theme/matcha/login/template.ftl @@ -0,0 +1,36 @@ +<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true> + + + + + + + + + <#nested "title"> + <#if properties.styles?has_content> + <#list properties.styles?split(' ') as style> + + + + + + + <#nested "header"> + + + + diff --git a/identity/themes/matcha/theme/matcha/login/theme.properties b/identity/themes/matcha/theme/matcha/login/theme.properties new file mode 100644 index 0000000..483cc85 --- /dev/null +++ b/identity/themes/matcha/theme/matcha/login/theme.properties @@ -0,0 +1,10 @@ +parent=keycloak +import=common/keycloak +locales=ca,de,en,es,fr,it,ja,lt,nl,no,pt-BR,ru,sv,zh-CN +styles=css/login.css + +kcFeedbackErrorIcon=pficon pficon-error-circle-o +kcFeedbackWarningIcon=pficon pficon-warning-triangle-o +kcFeedbackSuccessIcon=pficon pficon-ok +kcFeedbackInfoIcon=pficon pficon-info + diff --git a/nginx/configuration/custom_proxy_settings.conf b/nginx/configuration/custom_proxy_settings.conf new file mode 100644 index 0000000..87153c7 --- /dev/null +++ b/nginx/configuration/custom_proxy_settings.conf @@ -0,0 +1 @@ +client_max_body_size 30m; \ No newline at end of file diff --git a/nginx/templates/default.conf.template b/nginx/templates/default.conf.template new file mode 100644 index 0000000..68e435b --- /dev/null +++ b/nginx/templates/default.conf.template @@ -0,0 +1,26 @@ +server { +} + +#server { +# server_name sell; +# location / { +# proxy_pass http://localhost:8181; +# } +#} + +server { + server_name identity; + large_client_header_buffers 8 32k; + location / { + proxy_pass http://keycloak:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Workaround to void CORS error from Reactjs + if ($uri ~ "^(.*)/(protocol/openid-connect/auth|login/oauth2/code)(.*)") { + add_header Access-Control-Allow-Origin *; + } + } +} diff --git a/pom.xml b/pom.xml index 93ed8a7..19f4a46 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ inventory order product + sell-bff @@ -55,6 +56,8 @@ 10.17.0 0.8.12 0.00 + 2023.0.3 + 4.0.0 @@ -85,6 +88,18 @@ ${instancio-junit.version} test + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + me.paulschwarz + spring-dotenv + ${dotenv-version} + @@ -126,6 +141,10 @@ org.springdoc springdoc-openapi-starter-webmvc-ui + + me.paulschwarz + spring-dotenv + diff --git a/postgres_init.sql b/postgres_init.sql new file mode 100644 index 0000000..090ffdf --- /dev/null +++ b/postgres_init.sql @@ -0,0 +1,14 @@ +CREATE DATABASE keycloak WITH OWNER = admin ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8' TABLESPACE = pg_default CONNECTION +LIMIT = -1; + +CREATE DATABASE product WITH OWNER = admin ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8' TABLESPACE = pg_default CONNECTION +LIMIT = -1; + +CREATE DATABASE "order" WITH OWNER = admin ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8' TABLESPACE = pg_default CONNECTION +LIMIT = -1; + +CREATE DATABASE inventory WITH OWNER = admin ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8' TABLESPACE = pg_default CONNECTION +LIMIT = -1; + +CREATE DATABASE customer WITH OWNER = admin ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8' TABLESPACE = pg_default CONNECTION +LIMIT = -1; \ No newline at end of file diff --git a/product/pom.xml b/product/pom.xml index ee980c9..f54c90e 100644 --- a/product/pom.xml +++ b/product/pom.xml @@ -38,6 +38,10 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + org.springframework.boot spring-boot-starter-validation @@ -61,11 +65,6 @@ spring-security-test test - - mysql - mysql-connector-java - 8.0.33 - me.paulschwarz spring-dotenv diff --git a/product/src/main/java/com/fjb/product/config/CorsConfig.java b/product/src/main/java/com/fjb/product/config/CorsConfig.java new file mode 100644 index 0000000..7d729bf --- /dev/null +++ b/product/src/main/java/com/fjb/product/config/CorsConfig.java @@ -0,0 +1,25 @@ +package com.fjb.product.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig { + + @Value("${cors.allowed}") + private String corsAllowed; + + @Bean + public WebMvcConfigurer corsConfigure() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedMethods(corsAllowed) + .allowedOrigins(corsAllowed).allowedHeaders(corsAllowed); + } + }; + } +} diff --git a/product/src/main/java/com/fjb/product/config/Security.java b/product/src/main/java/com/fjb/product/config/Security.java deleted file mode 100644 index e65d01e..0000000 --- a/product/src/main/java/com/fjb/product/config/Security.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fjb.product.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -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.configurers.AbstractHttpConfigurer; -import org.springframework.security.web.SecurityFilterChain; - -@Configuration -@EnableWebSecurity -public class Security { - @Bean - @SuppressWarnings("java:S4502") - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - return http.authorizeHttpRequests(request -> request - .requestMatchers("/api/**").permitAll()) - .csrf(csrf -> csrf - .ignoringRequestMatchers("/api/**")) - .build(); - } - -} diff --git a/product/src/main/java/com/fjb/product/config/SecurityConfig.java b/product/src/main/java/com/fjb/product/config/SecurityConfig.java new file mode 100644 index 0000000..bf0240a --- /dev/null +++ b/product/src/main/java/com/fjb/product/config/SecurityConfig.java @@ -0,0 +1,45 @@ +package com.fjb.product.config; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + return http + .csrf(csrf -> csrf.ignoringRequestMatchers("/api/**")) + .authorizeHttpRequests(auth -> auth + .anyRequest().permitAll()) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) + .build(); + } + + @Bean + public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() { + Converter> jwtGrantedAuthoritiesConverter = jwt -> { + Map> realmAccess = jwt.getClaim("realm_access"); + Collection roles = realmAccess.get("roles"); + return roles.stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) + .collect(Collectors.toList()); + }; + + var jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); + + return jwtAuthenticationConverter; + } +} diff --git a/product/src/main/java/com/fjb/product/config/SwaggerConfig.java b/product/src/main/java/com/fjb/product/config/SwaggerConfig.java new file mode 100644 index 0000000..50420ee --- /dev/null +++ b/product/src/main/java/com/fjb/product/config/SwaggerConfig.java @@ -0,0 +1,32 @@ +package com.fjb.product.config; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.OAuthFlow; +import io.swagger.v3.oas.annotations.security.OAuthFlows; +import io.swagger.v3.oas.annotations.security.OAuthScope; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import io.swagger.v3.oas.annotations.servers.Server; + +@OpenAPIDefinition( + info = @Info( + title = "Product Service API", + description = "Product API documentation", + version = "1.0" + ), + security = @SecurityRequirement(name = "oauth2_bearer"), + servers = { + @Server(url = "${server.servlet.context-path}", + description = "Default Server URL") + }) +@SecurityScheme(name = "oauth2_bearer", type = SecuritySchemeType.OAUTH2, + flows = @OAuthFlows( + authorizationCode = @OAuthFlow( + authorizationUrl = "${springdoc.oauthflow.authorization-url}", + tokenUrl = "${springdoc.oauthflow.token-url}", + scopes = {@OAuthScope(name = "openid", description = "openid") + }))) +public class SwaggerConfig { +} diff --git a/product/src/main/java/com/fjb/product/controller/ProductController.java b/product/src/main/java/com/fjb/product/controller/ProductController.java index 7fa3dfd..f3852af 100644 --- a/product/src/main/java/com/fjb/product/controller/ProductController.java +++ b/product/src/main/java/com/fjb/product/controller/ProductController.java @@ -5,11 +5,13 @@ import com.fjb.product.exception.ErrorCreatingEntry; import com.fjb.product.exception.ProductNotFoundException; import com.fjb.product.service.ProductService; +import jakarta.validation.Valid; import java.math.BigDecimal; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -20,10 +22,10 @@ import org.springframework.web.bind.annotation.RestController; - @RestController @RequestMapping("/api/product") @RequiredArgsConstructor +@CrossOrigin(origins = "http://localhost:8090") public class ProductController { private final ProductService productService; @@ -56,7 +58,7 @@ public ResponseEntity> getAllProducts() { } } - @GetMapping("/id/{id}") + @GetMapping("/{id}") public ResponseEntity getProductById(@PathVariable Long id) { try { ProductResponseDto productResponseDto = productService.getProduct(id); @@ -69,9 +71,9 @@ public ResponseEntity getProductById(@PathVariable Long id) } } - @PutMapping("/id/{id}") + @PutMapping("/{id}") public ResponseEntity updateProduct( - @PathVariable Long id, @RequestBody ProductCreateDto newProductCreateDto + @PathVariable Long id, @Valid @RequestBody ProductCreateDto newProductCreateDto ) { try { ProductResponseDto existingProduct = productService.getProduct(id); @@ -102,7 +104,7 @@ public ResponseEntity updateProduct( } } - @DeleteMapping("/id/{id}") + @DeleteMapping("/{id}") public ResponseEntity deleteProduct(@PathVariable Long id) { try { productService.deleteProductById(id); diff --git a/product/src/main/java/com/fjb/product/service/ProductService.java b/product/src/main/java/com/fjb/product/service/ProductService.java index fc740ea..5e8d6d1 100644 --- a/product/src/main/java/com/fjb/product/service/ProductService.java +++ b/product/src/main/java/com/fjb/product/service/ProductService.java @@ -9,6 +9,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @@ -18,6 +19,7 @@ public class ProductService { private final ProductMapper productMapper; private final ProductRepository productRepository; + @Transactional public ProductResponseDto createProduct(ProductCreateDto productCreateDto) { Product product = productMapper.toProduct(productCreateDto); product = productRepository.save(product); diff --git a/product/src/main/resources/application.properties b/product/src/main/resources/application.properties deleted file mode 100644 index 22bf4df..0000000 --- a/product/src/main/resources/application.properties +++ /dev/null @@ -1,14 +0,0 @@ -spring.application.name=product - -spring.datasource.url=${POSTGRES_DATABASE_URL} -spring.datasource.username=${POSTGRES_USERNAME} -spring.datasource.password=${POSTGRES_PASSWORD} -spring.datasource.driver-class-name=org.postgresql.Driver - -# JPA & Hibernate settings -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.hibernate.ddl-auto=update - -# Optional settings -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true diff --git a/product/src/main/resources/application.yml b/product/src/main/resources/application.yml new file mode 100644 index 0000000..031f330 --- /dev/null +++ b/product/src/main/resources/application.yml @@ -0,0 +1,43 @@ +spring: + application: + name: product + datasource: + url: ${PRODUCT_DATASOURCE_URL} + username: ${PRODUCT_DATA_USERNAME} + password: ${PRODUCT_DATA_PASSWORD} + driver-class-name: org.postgresql.Driver + + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: false + database: postgresql + database-platform: org.hibernate.dialect.PostgreSQLDialect + open-in-view: false + + security: + oauth2: + resourceserver: + jwt: + issuer-uri: http://localhost:8080/realms/Matcha +springdoc: + oauthflow: + authorization-url: http://localhost:8080/realms/Matcha/protocol/openid-connect/auth + token-url: http://localhost:8080/realms/Matcha/protocol/openid-connect/token + swagger-ui: + oauth: + client-id: swagger + use-pkce-with-authorization-code-grant: true + packagesToScan: com.fjb.product + path: /swagger-ui + +server: + port: 8081 + servlet: + context-path: /product +cors: + allowed: "*" + diff --git a/product/src/test/resources/application.properties b/product/src/test/resources/application.properties index 5b0d418..3527c8e 100644 --- a/product/src/test/resources/application.properties +++ b/product/src/test/resources/application.properties @@ -3,11 +3,15 @@ server.servlet.context-path=/product spring.profiles.active=test -spring.data.mongodb.uri=jdbc:h2:mem:testdb;NON_KEYWORDS=VALUE -spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password= -spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=update -spring.liquibase.enabled=false \ No newline at end of file +spring.security.oauth2.resourceserver.jwt.issuer-uri=test +springdoc.oauthflow.authorization-url=test +springdoc.oauthflow.token-url=test + +cors.allowed=* diff --git a/sell-bff/.gitignore b/sell-bff/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/sell-bff/.gitignore @@ -0,0 +1,33 @@ +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/ diff --git a/sell-bff/.mvn/wrapper/maven-wrapper.properties b/sell-bff/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/sell-bff/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# 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 +# +# http://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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/sell-bff/Dockerfile b/sell-bff/Dockerfile new file mode 100644 index 0000000..d333d8f --- /dev/null +++ b/sell-bff/Dockerfile @@ -0,0 +1,6 @@ +FROM eclipse-temurin:21-jre-alpine +RUN apk add --no-cache bash +COPY target/sell-bff*.jar app.jar +COPY wait-for-it.sh wait-for-it.sh +RUN chmod +x wait-for-it.sh +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/sell-bff/mvnw b/sell-bff/mvnw new file mode 100644 index 0000000..19529dd --- /dev/null +++ b/sell-bff/mvnw @@ -0,0 +1,259 @@ +#!/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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/sell-bff/mvnw.cmd b/sell-bff/mvnw.cmd new file mode 100644 index 0000000..249bdf3 --- /dev/null +++ b/sell-bff/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@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 http://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 Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/sell-bff/pom.xml b/sell-bff/pom.xml new file mode 100644 index 0000000..1f2c5b5 --- /dev/null +++ b/sell-bff/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + com.fjb + matcha + ${revision} + ../pom.xml + + sell-bff + ${revision} + sell-bff + Backend for sell + + + + + + + + + + + + + + + + + khanhduzz_matcha-sell-bff + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/sell-bff/src/main/java/com/fjb/sellbff/SellBffApplication.java b/sell-bff/src/main/java/com/fjb/sellbff/SellBffApplication.java new file mode 100644 index 0000000..a0ee573 --- /dev/null +++ b/sell-bff/src/main/java/com/fjb/sellbff/SellBffApplication.java @@ -0,0 +1,13 @@ +package com.fjb.sellbff; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SellBffApplication { + + public static void main(String[] args) { + SpringApplication.run(SellBffApplication.class, args); + } + +} diff --git a/sell-bff/src/main/java/com/fjb/sellbff/config/SecurityConfig.java b/sell-bff/src/main/java/com/fjb/sellbff/config/SecurityConfig.java new file mode 100644 index 0000000..4937f1c --- /dev/null +++ b/sell-bff/src/main/java/com/fjb/sellbff/config/SecurityConfig.java @@ -0,0 +1,97 @@ +package com.fjb.sellbff.config; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; +import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; + +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig { + private static final String REALM_ACCESS_CLAIM = "realm_access"; + private static final String ROLES_CLAIM = "roles"; + + private final ReactiveClientRegistrationRepository clientRegistrationRepository; + + public SecurityConfig(ReactiveClientRegistrationRepository clientRegistrationRepository) { + this.clientRegistrationRepository = clientRegistrationRepository; + } + + @Bean + public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + return http + .authorizeExchange(auth -> auth + .pathMatchers("/health").permitAll() + // .anyExchange().hasAnyRole("ADMIN")) + .anyExchange().permitAll()) + .oauth2Login(Customizer.withDefaults()) + .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) + .formLogin(ServerHttpSecurity.FormLoginSpec::disable) + .csrf(ServerHttpSecurity.CsrfSpec::disable) + .logout(logout -> logout + .logoutSuccessHandler(oidcLogoutSuccessHandler()) + ) + .build(); + } + + private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() { + OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler = + new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository); + oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}"); + + return oidcLogoutSuccessHandler; + } + + @Bean + public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() { + return authorities -> { + Set mappedAuthorities = new HashSet<>(); + var authority = authorities.iterator().next(); + boolean isOidc = authority instanceof OidcUserAuthority; + + if (isOidc) { + var oidcUserAuthority = (OidcUserAuthority) authority; + var userInfo = oidcUserAuthority.getUserInfo(); + + if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) { + var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM); + var roles = (Collection) realmAccess.get(ROLES_CLAIM); + mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles)); + } + } else { + var oauth2UserAuthority = (OAuth2UserAuthority) authority; + Map userAttributes = oauth2UserAuthority.getAttributes(); + + if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) { + var realmAccess = (Map) userAttributes.get(REALM_ACCESS_CLAIM); + var roles = (Collection) realmAccess.get(ROLES_CLAIM); + mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles)); + } + } + + return mappedAuthorities; + }; + } + + + Collection generateAuthoritiesFromClaim(Collection roles) { + return roles.stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) + .collect(Collectors.toList()); + } +} diff --git a/sell-bff/src/main/java/com/fjb/sellbff/controller/AuthenticationController.java b/sell-bff/src/main/java/com/fjb/sellbff/controller/AuthenticationController.java new file mode 100644 index 0000000..639d56e --- /dev/null +++ b/sell-bff/src/main/java/com/fjb/sellbff/controller/AuthenticationController.java @@ -0,0 +1,17 @@ +package com.fjb.sellbff.controller; + +import com.fjb.sellbff.dto.AuthenticatedUser; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class AuthenticationController { + @GetMapping("/authentication/user") + public ResponseEntity user(@AuthenticationPrincipal OAuth2User principal) { + AuthenticatedUser authenticatedUser = new AuthenticatedUser(principal.getAttribute("preferred_username")); + return ResponseEntity.ok(authenticatedUser); + } +} diff --git a/sell-bff/src/main/java/com/fjb/sellbff/dto/AuthenticatedUser.java b/sell-bff/src/main/java/com/fjb/sellbff/dto/AuthenticatedUser.java new file mode 100644 index 0000000..8b8b373 --- /dev/null +++ b/sell-bff/src/main/java/com/fjb/sellbff/dto/AuthenticatedUser.java @@ -0,0 +1,4 @@ +package com.fjb.sellbff.dto; + +public record AuthenticatedUser(String username) { +} diff --git a/sell-bff/src/main/resources/application.yaml b/sell-bff/src/main/resources/application.yaml new file mode 100644 index 0000000..c50cb37 --- /dev/null +++ b/sell-bff/src/main/resources/application.yaml @@ -0,0 +1,60 @@ +server: + port: 8181 +spring: + application: + name: sell-bff + threads: + virtual: + enabled: true + profiles: + active: dev + security: + oauth2: + client: + provider: + keycloak: + issuer-uri: http://localhost:8080/realms/Matcha + registration: + api-client: + provider: keycloak + client-id: sell-bff + client-secret: L1ISNRVOUfQONER3aTs1OPaXGg0EZMTe #gitleaks:allow + scope: openid, profile, email, roles +management: + tracing: + sampling: + probability: "1.0" + metrics: + distribution: + percentiles-histogram: + http: + server: + requests: true + tags: + application: ${spring.application.name} +logging: + pattern: + level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]" +--- +spring: + config: + activate: + on-profile: "dev" + cloud: + gateway: + routes: + # Changes for your local development should not be committed + - id: product + uri: http://localhost:8081 + predicates: + - Path=/api/product/** + filters: + - RewritePath=/api/(?.*), /$\{segment} + - TokenRelay= + - id: localhost + uri: http://localhost:80 + predicates: + - Path=/api/** + filters: + - RewritePath=/api/(?.*), /$\{segment} + - TokenRelay= diff --git a/sell-bff/wait-for-it.sh b/sell-bff/wait-for-it.sh new file mode 100644 index 0000000..3974640 --- /dev/null +++ b/sell-bff/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi \ No newline at end of file From ef2e9e53772de9d83c5b5f285f9d9cd9877a7aeb Mon Sep 17 00:00:00 2001 From: PALASH2201 Date: Sat, 19 Oct 2024 12:26:05 +0530 Subject: [PATCH 4/4] Made requested changes --- docker-compose.yml | 6 +++-- product/.gitignore | 3 --- product/pom.xml | 4 --- .../product/controller/ProductController.java | 14 +++++------ .../com/fjb/product/mapper/ProductMapper.java | 2 ++ .../product/repository/ProductRepository.java | 2 -- .../fjb/product/service/ProductService.java | 25 +++++-------------- product/src/main/resources/.env.example | 6 ++--- 8 files changed, 21 insertions(+), 41 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8e5da66..116431c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,8 +21,10 @@ services: KC_PROXY: passthrough KC_DB: postgres KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak - KC_DB_USERNAME: ${POSTGRES_USER} - KC_DB_PASSWORD: ${POSTGRES_PASSWORD} +# KC_DB_USERNAME: ${POSTGRES_USER} +# KC_DB_PASSWORD: ${POSTGRES_PASSWORD} + KC_DB_USERNAME: admin + KC_DB_PASSWORD: admin KC_HTTP_PORT: 80 volumes: - ./identity/realm-export.json:/opt/keycloak/data/import/realm-export.json diff --git a/product/.gitignore b/product/.gitignore index b4a6017..549e00a 100644 --- a/product/.gitignore +++ b/product/.gitignore @@ -31,6 +31,3 @@ build/ ### VS Code ### .vscode/ - -### ENV ### -.env \ No newline at end of file diff --git a/product/pom.xml b/product/pom.xml index ec9da4b..ae2ba0c 100644 --- a/product/pom.xml +++ b/product/pom.xml @@ -70,10 +70,6 @@ spring-dotenv 4.0.0 - - org.springframework.boot - spring-boot-starter-security - org.mapstruct mapstruct diff --git a/product/src/main/java/com/fjb/product/controller/ProductController.java b/product/src/main/java/com/fjb/product/controller/ProductController.java index a8ae41e..4c94104 100644 --- a/product/src/main/java/com/fjb/product/controller/ProductController.java +++ b/product/src/main/java/com/fjb/product/controller/ProductController.java @@ -8,7 +8,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -21,7 +20,6 @@ @RestController @RequestMapping("/api/product") @RequiredArgsConstructor -@CrossOrigin(origins = "http://localhost:8090") public class ProductController { private final ProductService productService; @@ -29,31 +27,31 @@ public class ProductController { @PostMapping public ResponseEntity createProduct(@Valid @RequestBody ProductCreateDto productCreateDto) { ProductResponseDto productResponseDto = productService.createProduct(productCreateDto); - return new ResponseEntity<>(productResponseDto, HttpStatus.CREATED); + return ResponseEntity.status(HttpStatus.OK).body(productResponseDto); } @GetMapping public ResponseEntity> getAllProducts() { List list = productService.getAllProducts(); - return new ResponseEntity<>(list, HttpStatus.OK); + return ResponseEntity.status(HttpStatus.OK).body(list); } @GetMapping("/{id}") public ResponseEntity getProductById(@PathVariable Long id) { ProductResponseDto productResponseDto = productService.getProduct(id); - return new ResponseEntity<>(productResponseDto, HttpStatus.OK); + return ResponseEntity.status(HttpStatus.OK).body(productResponseDto); } @PutMapping("/{id}") public ResponseEntity updateProduct( @PathVariable Long id, @Valid @RequestBody ProductCreateDto newProductCreateDto) { ProductResponseDto updatedProduct = productService.updateProduct(id, newProductCreateDto); - return new ResponseEntity<>(updatedProduct, HttpStatus.OK); + return ResponseEntity.status(HttpStatus.OK).body(updatedProduct); } @DeleteMapping("/{id}") - public ResponseEntity deleteProduct(@PathVariable Long id) { + public ResponseEntity.BodyBuilder deleteProduct(@PathVariable Long id) { productService.deleteProductById(id); - return new ResponseEntity<>(HttpStatus.NO_CONTENT); + return ResponseEntity.status(HttpStatus.CREATED); } } diff --git a/product/src/main/java/com/fjb/product/mapper/ProductMapper.java b/product/src/main/java/com/fjb/product/mapper/ProductMapper.java index ffa9e07..12a12bd 100644 --- a/product/src/main/java/com/fjb/product/mapper/ProductMapper.java +++ b/product/src/main/java/com/fjb/product/mapper/ProductMapper.java @@ -4,6 +4,7 @@ import com.fjb.product.dto.response.ProductResponseDto; import com.fjb.product.entity.Product; import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; @Mapper(componentModel = "spring") public interface ProductMapper { @@ -11,4 +12,5 @@ public interface ProductMapper { ProductResponseDto toProductResponseDto(Product product); + Product updateProduct(@MappingTarget Product product, ProductCreateDto productCreateDto); } diff --git a/product/src/main/java/com/fjb/product/repository/ProductRepository.java b/product/src/main/java/com/fjb/product/repository/ProductRepository.java index 4261548..0f6107d 100644 --- a/product/src/main/java/com/fjb/product/repository/ProductRepository.java +++ b/product/src/main/java/com/fjb/product/repository/ProductRepository.java @@ -6,7 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository { - Optional findById(Long id); - List findAllByOrderByIdAsc(); } diff --git a/product/src/main/java/com/fjb/product/service/ProductService.java b/product/src/main/java/com/fjb/product/service/ProductService.java index 7b890f4..1574121 100644 --- a/product/src/main/java/com/fjb/product/service/ProductService.java +++ b/product/src/main/java/com/fjb/product/service/ProductService.java @@ -33,7 +33,7 @@ public ProductResponseDto createProduct(ProductCreateDto productCreateDto) { public List getAllProducts() { List list = productRepository.findAllByOrderByIdAsc(); if (list.isEmpty()) { - throw new NotFoundException("No products found"); + return new ArrayList<>(); } List responseList = new ArrayList<>(); list.forEach(product -> responseList.add(productMapper.toProductResponseDto(product))); @@ -46,24 +46,11 @@ public ProductResponseDto getProduct(Long id) { return productMapper.toProductResponseDto(product); } - public ProductResponseDto updateProduct(Long id, ProductCreateDto newProductCreateDto) { - Product existingProduct = productRepository.findById(id) - .orElseThrow(() -> new NotFoundException("Product not found with id: " + id)); - - existingProduct.setName( - newProductCreateDto.getName() != null - ? newProductCreateDto.getName() : existingProduct.getName() - ); - existingProduct.setDescription( - newProductCreateDto.getDescription() != null - ? newProductCreateDto.getDescription() : existingProduct.getDescription() - ); - existingProduct.setPrice( - newProductCreateDto.getPrice() != null - ? newProductCreateDto.getPrice() : existingProduct.getPrice() - ); - - return productMapper.toProductResponseDto(productRepository.save(existingProduct)); + public ProductResponseDto updateProduct(Long id, ProductCreateDto productCreateDto) { + Product product = productRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Product not found")); + product = productMapper.updateProduct(product, productCreateDto); + return productMapper.toProductResponseDto(product); } public void deleteProductById(Long id) { diff --git a/product/src/main/resources/.env.example b/product/src/main/resources/.env.example index fa26e2e..c7e9366 100644 --- a/product/src/main/resources/.env.example +++ b/product/src/main/resources/.env.example @@ -1,3 +1,3 @@ -POSTGRES_USERNAME= -POSTGRES_PASSWORD= -POSTGRES_DATABASE_URL="jdbc:postgresql://localhost:5432/product" \ No newline at end of file +PRODUCT_DATA_USERNAME= +PRODUCT_DATA_PASSWORD= +PRODUCT_DATASOURCE_URL=jdbc:postgresql://localhost:5432/product \ No newline at end of file