From 5546b0ba3b93d1818e0a5cd0420dcc7371ac1b25 Mon Sep 17 00:00:00 2001 From: Cedric Date: Tue, 7 Jan 2025 23:40:00 +0100 Subject: [PATCH] better secret handling docker api upgrade --- .gitignore | 1 + docker-compose.yml | 30 ++++++++++++++ dockerfile | 7 ++++ pom.xml | 10 +++-- .../XpenselyServerApplication.java | 9 +++++ .../controller/AppUserController.java | 33 ++++++++++----- .../controller/ExpenseListController.java | 2 +- .../app/xpensely_server/model/AppUser.java | 16 ++++++++ .../model/AppUserCreateRequest.java | 21 ++++++++++ .../model/UsernameAlreadyExistsException.java | 11 +++++ .../xpensely_server/repo/UserRepository.java | 4 ++ .../security/SecurityConfig.java | 40 +++++++++---------- .../xpensely_server/services/UserService.java | 12 ++++++ src/main/resources/application.properties | 10 +++-- 14 files changed, 168 insertions(+), 38 deletions(-) create mode 100644 docker-compose.yml create mode 100644 dockerfile create mode 100644 src/main/java/de/zendric/app/xpensely_server/model/AppUserCreateRequest.java create mode 100644 src/main/java/de/zendric/app/xpensely_server/model/UsernameAlreadyExistsException.java diff --git a/.gitignore b/.gitignore index f8185d3..37fdfda 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +.env ### STS ### .apt_generated diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..59136de --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3" + +services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: XpenselyServer + ports: + - "8080:8080" + environment: + GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} + GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET} + DB_PORT: 5432 + DB_CONTAINER: ${DB_P_CONTAINER} + DB_NAME: ${DB_P_NAME} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + depends_on: + - postgresdb + + postgresdb: + image: postgres:14 + container_name: postgresdb + ports: + - "5432:5432" + environment: + POSTGRES_DB: ${DB_P_NAME} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..f7e9a76 --- /dev/null +++ b/dockerfile @@ -0,0 +1,7 @@ +FROM openjdk:17-jdk-slim + +COPY ./target/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6abd81e..f7c275f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,9 +10,9 @@ de.zendric.app XpenselyServer - 0.0.1-SNAPSHOT + 1.0.0 XpenselyServer - Demo project for Spring Boot + XpenselyServer used to handle the Xpensely App @@ -50,7 +50,11 @@ org.springframework.boot spring-boot-starter-web - + + io.github.cdimascio + dotenv-java + 3.1.0 + org.springframework.boot spring-boot-devtools diff --git a/src/main/java/de/zendric/app/xpensely_server/XpenselyServerApplication.java b/src/main/java/de/zendric/app/xpensely_server/XpenselyServerApplication.java index ae78652..d9678e8 100644 --- a/src/main/java/de/zendric/app/xpensely_server/XpenselyServerApplication.java +++ b/src/main/java/de/zendric/app/xpensely_server/XpenselyServerApplication.java @@ -4,11 +4,20 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; +import io.github.cdimascio.dotenv.Dotenv; + @SpringBootApplication @EnableScheduling public class XpenselyServerApplication { public static void main(String[] args) { + Dotenv dotenv = Dotenv.load(); // Loads the .env file + System.setProperty("GOOGLE_CLIENT_ID", dotenv.get("GOOGLE_CLIENT_ID")); + System.setProperty("GOOGLE_CLIENT_SECRET", dotenv.get("GOOGLE_CLIENT_SECRET")); + System.setProperty("DB_USERNAME", dotenv.get("DB_USERNAME")); + System.setProperty("DB_PASSWORD", dotenv.get("DB_PASSWORD")); + System.setProperty("DB_DEV_CONTAINER", dotenv.get("DB_DEV_CONTAINER")); + System.setProperty("DB_DEV_NAME", dotenv.get("DB_DEV_NAME")); SpringApplication.run(XpenselyServerApplication.class, args); } diff --git a/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java b/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java index 4332c3c..e0b9994 100644 --- a/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java +++ b/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java @@ -3,7 +3,6 @@ package de.zendric.app.xpensely_server.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -13,6 +12,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import de.zendric.app.xpensely_server.model.AppUser; +import de.zendric.app.xpensely_server.model.AppUserCreateRequest; +import de.zendric.app.xpensely_server.model.UsernameAlreadyExistsException; import de.zendric.app.xpensely_server.services.UserService; @RestController @@ -36,18 +37,32 @@ public class AppUserController { return userService.getUserByName(username); } - @PostMapping - public ResponseEntity createUser(@RequestBody AppUser user) { - AppUser appUser = userService.createUser(user); - return ResponseEntity.status(HttpStatus.CREATED).body(appUser); + @GetMapping("/byGoogleId") + public ResponseEntity getUserByGoogleId(@RequestParam String id) { + try { + AppUser userByGoogleId = userService.getUserByGoogleId(id); + return new ResponseEntity<>(userByGoogleId, HttpStatus.OK); + + } catch (IllegalArgumentException e) { + return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); + } catch (Exception e) { + return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); + } } @PostMapping("/createUser") - public ResponseEntity createUsername(@RequestBody AppUser user, Authentication authentication) { - String googleUserId = authentication.getName(); - // Validate and store the username with googleUserId + public ResponseEntity createUser(@RequestBody AppUserCreateRequest userRequest) { + try { + AppUser convertedUser = userRequest.convertToAppUser(); + + AppUser nUser = userService.createUser(convertedUser); + return new ResponseEntity<>(nUser, HttpStatus.CREATED); + } catch (UsernameAlreadyExistsException e) { + return new ResponseEntity<>(null, HttpStatus.CONFLICT); + } catch (Exception e) { + return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); + } - return ResponseEntity.ok("Username created successfully"); } @DeleteMapping diff --git a/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java b/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java index dfec7ae..3a407fb 100644 --- a/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java +++ b/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java @@ -95,7 +95,7 @@ class ExpenseListController { } } - @PostMapping + @PostMapping("/create") public ResponseEntity create(@RequestBody ExpenseList expenseList) { try { if (expenseList.getOwner() != null) { diff --git a/src/main/java/de/zendric/app/xpensely_server/model/AppUser.java b/src/main/java/de/zendric/app/xpensely_server/model/AppUser.java index 2b495e2..90a0cd9 100644 --- a/src/main/java/de/zendric/app/xpensely_server/model/AppUser.java +++ b/src/main/java/de/zendric/app/xpensely_server/model/AppUser.java @@ -1,10 +1,17 @@ package de.zendric.app.xpensely_server.model; +import java.time.LocalDateTime; + +import org.hibernate.annotations.CreationTimestamp; + +import com.fasterxml.jackson.annotation.JsonIgnore; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -13,6 +20,7 @@ import lombok.Setter; @Setter @NoArgsConstructor @Entity +@EqualsAndHashCode public class AppUser { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -21,4 +29,12 @@ public class AppUser { @Column(name = "username", nullable = false, unique = true) private String username; + @JsonIgnore + private String googleId; + + @Column(updatable = false) + @CreationTimestamp + @JsonIgnore + private LocalDateTime createdAt; + } diff --git a/src/main/java/de/zendric/app/xpensely_server/model/AppUserCreateRequest.java b/src/main/java/de/zendric/app/xpensely_server/model/AppUserCreateRequest.java new file mode 100644 index 0000000..3ae678d --- /dev/null +++ b/src/main/java/de/zendric/app/xpensely_server/model/AppUserCreateRequest.java @@ -0,0 +1,21 @@ +package de.zendric.app.xpensely_server.model; + +import jakarta.persistence.Column; +import lombok.Data; + +@Data +public class AppUserCreateRequest { + + @Column(name = "username", nullable = false, unique = true) + private String username; + + private String googleId; + + public AppUser convertToAppUser() { + AppUser appUser = new AppUser(); + appUser.setGoogleId(googleId); + appUser.setUsername(username); + + return appUser; + } +} diff --git a/src/main/java/de/zendric/app/xpensely_server/model/UsernameAlreadyExistsException.java b/src/main/java/de/zendric/app/xpensely_server/model/UsernameAlreadyExistsException.java new file mode 100644 index 0000000..b0645d8 --- /dev/null +++ b/src/main/java/de/zendric/app/xpensely_server/model/UsernameAlreadyExistsException.java @@ -0,0 +1,11 @@ +package de.zendric.app.xpensely_server.model; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.CONFLICT) +public class UsernameAlreadyExistsException extends RuntimeException { + public UsernameAlreadyExistsException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/de/zendric/app/xpensely_server/repo/UserRepository.java b/src/main/java/de/zendric/app/xpensely_server/repo/UserRepository.java index 23bb4ee..a35ba4f 100644 --- a/src/main/java/de/zendric/app/xpensely_server/repo/UserRepository.java +++ b/src/main/java/de/zendric/app/xpensely_server/repo/UserRepository.java @@ -10,4 +10,8 @@ import de.zendric.app.xpensely_server.model.AppUser; @Repository public interface UserRepository extends JpaRepository { Optional findByUsername(String username); + + Optional findByGoogleId(String id); + + Boolean existsByUsername(String username); } diff --git a/src/main/java/de/zendric/app/xpensely_server/security/SecurityConfig.java b/src/main/java/de/zendric/app/xpensely_server/security/SecurityConfig.java index 72d7e71..b4883a3 100644 --- a/src/main/java/de/zendric/app/xpensely_server/security/SecurityConfig.java +++ b/src/main/java/de/zendric/app/xpensely_server/security/SecurityConfig.java @@ -10,28 +10,26 @@ import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { - // @Bean - // public SecurityFilterChain securityFilterChain(HttpSecurity http) throws - // Exception { - // http.authorizeHttpRequests(auth -> auth - // .anyRequest().permitAll()).csrf().disable(); - // ; + // @Bean + // public SecurityFilterChain securityFilterChain(HttpSecurity http) throws + // Exception { + // http.authorizeHttpRequests(auth -> auth + // .anyRequest().permitAll()).csrf().disable(); + // ; - // return http.build(); - // } + // return http.build(); + // } - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(auth -> auth - .anyRequest().authenticated() // Require authentication for all requests - ) - .oauth2ResourceServer(oauth2 -> oauth2 - .jwt(Customizer.withDefaults()) // Enable JWT validation - ) - .oauth2Login(Customizer.withDefaults()) // Optional if you want OAuth2 login - .csrf().disable(); // Disable CSRF for simplicity in APIs (consider enabling it for forms) + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth + .anyRequest().authenticated()) + .oauth2ResourceServer(oauth2 -> oauth2 + .jwt(Customizer.withDefaults())) + .oauth2Login(Customizer.withDefaults()) + .csrf().disable(); - return http.build(); - } + return http.build(); + } } diff --git a/src/main/java/de/zendric/app/xpensely_server/services/UserService.java b/src/main/java/de/zendric/app/xpensely_server/services/UserService.java index 6475a49..58298dc 100644 --- a/src/main/java/de/zendric/app/xpensely_server/services/UserService.java +++ b/src/main/java/de/zendric/app/xpensely_server/services/UserService.java @@ -6,6 +6,7 @@ import java.util.Optional; import org.springframework.stereotype.Service; import de.zendric.app.xpensely_server.model.AppUser; +import de.zendric.app.xpensely_server.model.UsernameAlreadyExistsException; import de.zendric.app.xpensely_server.repo.UserRepository; @Service @@ -21,6 +22,9 @@ public class UserService { } public AppUser createUser(AppUser user) { + if (Boolean.TRUE.equals(userRepository.existsByUsername(user.getUsername()))) { + throw new UsernameAlreadyExistsException("Username already exists"); + } return userRepository.save(user); } @@ -49,4 +53,12 @@ public class UserService { return null; } + public AppUser getUserByGoogleId(String id) { + Optional optUser = userRepository.findByGoogleId(id); + if (optUser.isPresent()) { + return optUser.get(); + } else + return null; + } + } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5591bdb..6983f6d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,12 +3,14 @@ spring.application.name=XpenselyServer #Security spring.security.enabled=false -#logging.level.org.springframework.security=TRACE +spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} +spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} +spring.security.oauth2.resourceserver.jwt.issuer-uri=https://accounts.google.com # PostgreSQL Configuration -spring.datasource.url=jdbc:postgresql://localhost:5432/Xpensely -spring.datasource.username=${XpenselyDBUser} -spring.datasource.password=${XpenselyDBPW} +spring.datasource.url=jdbc:postgresql://${DB_DEV_CONTAINER}:5432/${DB_DEV_NAME} +spring.datasource.username=${DB_USERNAME} +spring.datasource.password=${DB_PASSWORD} spring.datasource.driver-class-name=org.postgresql.Driver # Hibernate configuration