diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..0da67df
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,87 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "main", "development", "feature/*" ]
+ pull_request:
+ branches: [ "main", "development", "feature/*" ]
+ schedule:
+ - cron: '40 16 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
+ # - https://gh.io/supported-runners-and-hardware-resources
+ # - https://gh.io/using-larger-runners
+ # Consider using larger runners for possible analysis time improvements.
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
+ timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'java-kotlin' ]
+ # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ]
+ # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both
+ # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Java
+ uses: actions/setup-java@v3
+ with:
+ distribution: liberica
+ java-version: 21
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..4a16b68
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,112 @@
+name: Java CI/CD with Maven and Docker for Dev
+
+on:
+ push:
+ branches:
+ - 'development'
+ - 'main'
+env:
+ NA_DB_URL: 'jdbc:mariadb://localhost:3306/NA'
+ NA_DB_USER: 'NA'
+ NA_DB_PASSWORD: 'NA'
+ REGISTRY_IMAGE: keke125/ntou-auction-java
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ platform:
+ - linux/amd64
+ - linux/arm64
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ # list of Docker images to use as base name for tags
+ images: |
+ keke125/ntou-auction-java
+ - name: Set up JDK 21
+ uses: actions/setup-java@v3
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+ cache: maven
+ - name: Start MariaDB
+ uses: getong/mariadb-action@v1.1
+ with:
+ mysql database: 'NA'
+ mysql user: 'NA'
+ mysql password: 'NA'
+ - name: Build with Maven
+ run: mvn -B package --file pom.xml
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+ - name: Login to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Build and push by digest
+ id: build
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ platforms: ${{ matrix.platform }}
+ labels: ${{ steps.meta.outputs.labels }}
+ outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
+ - name: Export digest
+ run: |
+ mkdir -p /tmp/digests
+ digest="${{ steps.build.outputs.digest }}"
+ touch "/tmp/digests/${digest#sha256:}"
+ - name: Upload digest
+ uses: actions/upload-artifact@v3
+ with:
+ name: digests
+ path: /tmp/digests/*
+ if-no-files-found: error
+ retention-days: 1
+ merge:
+ runs-on: ubuntu-latest
+ needs:
+ - build
+ steps:
+ -
+ name: Download digests
+ uses: actions/download-artifact@v3
+ with:
+ name: digests
+ path: /tmp/digests
+ -
+ name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+ -
+ name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY_IMAGE }}
+ -
+ name: Login to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ -
+ name: Create manifest list and push
+ working-directory: /tmp/digests
+ run: |
+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+ $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
+ -
+ name: Inspect image
+ run: |
+ docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..40be12d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,5 @@
+FROM eclipse-temurin:21-jre
+
+COPY target/*.jar /app/na.jar
+EXPOSE 8080
+ENTRYPOINT ["java", "-jar", "/app/na.jar"]
diff --git a/pom.xml b/pom.xml
index 0d5aa0d..f5eabeb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,41 @@
bcprov-jdk18on
1.76
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.12.3
+
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+ org.springframework
+ spring-messaging
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+
+ jakarta.mail
+ jakarta.mail-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.12.3
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.12.3
+ runtime
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/ntou/auction/spring/NtouAuctionJavaApplication.java b/src/main/java/ntou/auction/spring/NtouAuctionJavaApplication.java
index ed9e574..4bedc9e 100644
--- a/src/main/java/ntou/auction/spring/NtouAuctionJavaApplication.java
+++ b/src/main/java/ntou/auction/spring/NtouAuctionJavaApplication.java
@@ -1,15 +1,17 @@
package ntou.auction.spring;
-import ntou.auction.spring.data.service.UserRepository;
+import ntou.auction.spring.account.repository.UserRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableScheduling;
import javax.sql.DataSource;
@SpringBootApplication
+@EnableScheduling
public class NtouAuctionJavaApplication {
public static void main(String[] args) {
diff --git a/src/main/java/ntou/auction/spring/account/config/JWTRequestFilter.java b/src/main/java/ntou/auction/spring/account/config/JWTRequestFilter.java
new file mode 100644
index 0000000..0aeaf71
--- /dev/null
+++ b/src/main/java/ntou/auction/spring/account/config/JWTRequestFilter.java
@@ -0,0 +1,55 @@
+package ntou.auction.spring.account.config;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import ntou.auction.spring.account.service.JWTService;
+import ntou.auction.spring.account.service.UserDetailsServiceImpl;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+public class JWTRequestFilter extends OncePerRequestFilter {
+
+ private final UserDetailsServiceImpl userDetailsServiceImpl;
+
+ public JWTRequestFilter(UserDetailsServiceImpl userDetailsServiceImpl) {
+ this.userDetailsServiceImpl = userDetailsServiceImpl;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ // look for AUTHORIZATION
+ final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
+ if (header == null || !header.startsWith("Bearer ")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ final String token = header.substring(7);
+ final String username = JWTService.validateTokenAndGetUsername(token);
+ if (username == null) {
+ // validation failed or token expired
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ // set user details on spring security context
+ final UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(username);
+ final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+ userDetails, null, userDetails.getAuthorities());
+ authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ // continue with authenticated user
+ filterChain.doFilter(request, response);
+ }
+}
diff --git a/src/main/java/ntou/auction/spring/account/config/SecurityConfiguration.java b/src/main/java/ntou/auction/spring/account/config/SecurityConfiguration.java
new file mode 100644
index 0000000..5af12bb
--- /dev/null
+++ b/src/main/java/ntou/auction/spring/account/config/SecurityConfiguration.java
@@ -0,0 +1,94 @@
+package ntou.auction.spring.account.config;
+
+import ntou.auction.spring.util.AppConfig;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+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.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@EnableWebSecurity
+@Configuration
+public class SecurityConfiguration {
+ private final AppConfig appConfig;
+ private final JWTRequestFilter jwtRequestFilter;
+
+ public SecurityConfiguration(AppConfig appConfig, JWTRequestFilter jwtRequestFilter) {
+ this.appConfig = appConfig;
+ this.jwtRequestFilter = jwtRequestFilter;
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ // the following value can be changed to meet your need
+ // pbkdf2
+ String secret = "";
+ // byte
+ int pbkdf2SaltLength = 16;
+ int pbkdf2Iterations = 310000;
+ Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm secretKeyFactoryAlgorithm =
+ Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256;
+ // argon2
+ int argon2SaltLength = 16;
+ int hashLength = 32;
+ int parallelism = 1;
+ int memory = 1 << 14;
+ int argon2Iterations = 2;
+ Map encoders = new HashMap<>();
+ encoders.put("BCrypt", new BCryptPasswordEncoder());
+ encoders.put("pbkdf2", new Pbkdf2PasswordEncoder(secret,
+ pbkdf2SaltLength, pbkdf2Iterations, secretKeyFactoryAlgorithm));
+ encoders.put("pbkdf2@SpringSecurity_v5_8",
+ Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
+ encoders.put("argon2", new Argon2PasswordEncoder(argon2SaltLength,
+ hashLength, parallelism, memory, argon2Iterations));
+ encoders.put("argon2@SpringSecurity_v5_8",
+ Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
+ return new DelegatingPasswordEncoder(appConfig.getIdForEncode(),
+ encoders);
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(AbstractHttpConfigurer::disable)
+ .cors(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests(authorize -> authorize
+ .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
+ .requestMatchers(HttpMethod.POST, "/api/v1/auth/log-in").permitAll()
+ .requestMatchers(HttpMethod.POST, "/api/v1/auth/sign-up").permitAll()
+ .requestMatchers(HttpMethod.GET, "/api/v1/product/products").permitAll()
+ .requestMatchers(HttpMethod.GET, "/api/v1/product/product/name/**").permitAll()
+ .requestMatchers(HttpMethod.GET, "/api/v1/product/product/classification/**").permitAll()
+ .requestMatchers(HttpMethod.GET, "/api/v1/product/product/{ID}").permitAll()
+ .requestMatchers("/ws/**").permitAll()
+ .requestMatchers("/sockjs/**").permitAll()
+ .anyRequest().authenticated())
+ .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
+ .sessionManagement((session) -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ );
+ return http.build();
+ }
+
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+}
diff --git a/src/main/java/ntou/auction/spring/account/controller/AuthController.java b/src/main/java/ntou/auction/spring/account/controller/AuthController.java
new file mode 100644
index 0000000..ffef66b
--- /dev/null
+++ b/src/main/java/ntou/auction/spring/account/controller/AuthController.java
@@ -0,0 +1,75 @@
+package ntou.auction.spring.account.controller;
+
+import jakarta.validation.Valid;
+import ntou.auction.spring.account.entity.Role;
+import ntou.auction.spring.account.entity.User;
+import ntou.auction.spring.account.service.UserService;
+import ntou.auction.spring.account.request.AuthRequest;
+import ntou.auction.spring.account.service.JWTService;
+import ntou.auction.spring.account.request.SignupRequest;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+@RestController
+@RequestMapping(value = "/api/v1/auth", produces = MediaType.APPLICATION_JSON_VALUE)
+@CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
+public class AuthController {
+
+ private final UserService userService;
+
+ public AuthController(UserService userService) {
+ this.userService = userService;
+ }
+
+
+ @PostMapping("/log-in")
+ public ResponseEntity