From c5f37a9b5bef2b000610c326f1014be141aa00c6 Mon Sep 17 00:00:00 2001 From: Rasmus Neikes Date: Sat, 21 Dec 2024 15:48:49 +0100 Subject: [PATCH] initial setup before cleaning --- pom.xml | 27 ++++- .../com/stktrk/app/BasicConfiguration.java | 51 -------- .../profile/ProfileController.java | 76 ------------ .../app/domain/profile/ProfileRepository.java | 17 --- .../app/domain/profile/ProfileService.java | 31 ----- .../app/domain/profile/types/Profile.java | 20 --- .../flyway/migrations/V1__CreateTest.java | 25 ---- .../flyway/migrations/V2__CreateUser.java | 25 ---- .../profile/ProfileRepositoryImpl.java | 70 ----------- .../stktrk/{app => auth}/AppApplication.java | 2 +- .../stktrk/auth/CustomGrantedAuthority.java | 17 +++ .../stktrk/auth/CustomOAuth2UserService.java | 54 +++++++++ src/main/java/com/stktrk/auth/JwtConfig.java | 47 ++++++++ src/main/java/com/stktrk/auth/RBACConfig.java | 18 +++ .../com/stktrk/auth/RoleAuthentication.java | 64 ++++++++++ .../java/com/stktrk/auth/SecurityConfig.java | 86 +++++++++++++ .../com/stktrk/auth/TokenValidatorFilter.java | 63 ++++++++++ .../auth/application/AuthController.java | 114 ++++++++++++++++++ .../application/LocalSignupController.java | 41 +++++++ .../auth/application/TestController.java | 20 +++ .../domain/account/AccountRepository.java | 36 ++++++ .../auth/domain/account/AccountService.java | 61 ++++++++++ .../auth/domain/account/types/Account.java | 19 +++ .../auth/domain/account/types/Role.java | 6 + .../account/AccountRepositoryImpl.java | 111 +++++++++++++++++ .../configuration/types/GraphDbConfig.java | 2 +- .../integration/flyway/ConnectionPool.java | 14 ++- .../integration/flyway/FlywayConfig.java | 4 +- .../integration/flyway/MigrationWrapper.java | 18 ++- .../flyway/migrations/V1__CreateAccount.java} | 12 +- .../flyway/migrations/V2__CreateRoles.java | 25 ++++ .../stktrk/{app => auth}/utils/EncryptId.java | 2 +- .../resources/application-rasmus.properties | 13 +- src/main/resources/application.properties | 4 +- .../{app => auth}/AppApplicationTests.java | 2 +- .../{app => auth}/TestAppApplication.java | 2 +- .../TestcontainersConfiguration.java | 2 +- .../{app => auth}/utils/EncryptIdTest.java | 2 +- 38 files changed, 851 insertions(+), 352 deletions(-) delete mode 100644 src/main/java/com/stktrk/app/BasicConfiguration.java delete mode 100644 src/main/java/com/stktrk/app/application/profile/ProfileController.java delete mode 100644 src/main/java/com/stktrk/app/domain/profile/ProfileRepository.java delete mode 100644 src/main/java/com/stktrk/app/domain/profile/ProfileService.java delete mode 100644 src/main/java/com/stktrk/app/domain/profile/types/Profile.java delete mode 100644 src/main/java/com/stktrk/app/integration/flyway/migrations/V1__CreateTest.java delete mode 100644 src/main/java/com/stktrk/app/integration/flyway/migrations/V2__CreateUser.java delete mode 100644 src/main/java/com/stktrk/app/integration/profile/ProfileRepositoryImpl.java rename src/main/java/com/stktrk/{app => auth}/AppApplication.java (96%) create mode 100644 src/main/java/com/stktrk/auth/CustomGrantedAuthority.java create mode 100644 src/main/java/com/stktrk/auth/CustomOAuth2UserService.java create mode 100644 src/main/java/com/stktrk/auth/JwtConfig.java create mode 100644 src/main/java/com/stktrk/auth/RBACConfig.java create mode 100644 src/main/java/com/stktrk/auth/RoleAuthentication.java create mode 100644 src/main/java/com/stktrk/auth/SecurityConfig.java create mode 100644 src/main/java/com/stktrk/auth/TokenValidatorFilter.java create mode 100644 src/main/java/com/stktrk/auth/application/AuthController.java create mode 100644 src/main/java/com/stktrk/auth/application/LocalSignupController.java create mode 100644 src/main/java/com/stktrk/auth/application/TestController.java create mode 100644 src/main/java/com/stktrk/auth/domain/account/AccountRepository.java create mode 100644 src/main/java/com/stktrk/auth/domain/account/AccountService.java create mode 100644 src/main/java/com/stktrk/auth/domain/account/types/Account.java create mode 100644 src/main/java/com/stktrk/auth/domain/account/types/Role.java create mode 100644 src/main/java/com/stktrk/auth/integration/account/AccountRepositoryImpl.java rename src/main/java/com/stktrk/{app => auth}/integration/configuration/types/GraphDbConfig.java (89%) rename src/main/java/com/stktrk/{app => auth}/integration/flyway/ConnectionPool.java (69%) rename src/main/java/com/stktrk/{app => auth}/integration/flyway/FlywayConfig.java (97%) rename src/main/java/com/stktrk/{app => auth}/integration/flyway/MigrationWrapper.java (66%) rename src/main/java/com/stktrk/{app/integration/flyway/migrations/V3__CreateProfile.java => auth/integration/flyway/migrations/V1__CreateAccount.java} (52%) create mode 100644 src/main/java/com/stktrk/auth/integration/flyway/migrations/V2__CreateRoles.java rename src/main/java/com/stktrk/{app => auth}/utils/EncryptId.java (97%) rename src/test/java/com/stktrk/{app => auth}/AppApplicationTests.java (91%) rename src/test/java/com/stktrk/{app => auth}/TestAppApplication.java (91%) rename src/test/java/com/stktrk/{app => auth}/TestcontainersConfiguration.java (95%) rename src/test/java/com/stktrk/{app => auth}/utils/EncryptIdTest.java (93%) diff --git a/pom.xml b/pom.xml index d17c193..77849c6 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,27 @@ 21 + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot spring-boot-starter-validation @@ -138,20 +159,20 @@ 3.7.2 - org.apache.tinkerpop gremlin-server 3.7.2 - org.opencypher util-9.0 9.0.1 - + diff --git a/src/main/java/com/stktrk/app/BasicConfiguration.java b/src/main/java/com/stktrk/app/BasicConfiguration.java deleted file mode 100644 index 514dd99..0000000 --- a/src/main/java/com/stktrk/app/BasicConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.stktrk.app; - -import jakarta.annotation.Nonnull; -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.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; - -/** - * Code was copied from here - */ -@Configuration -@EnableWebSecurity -public class BasicConfiguration { - - @Bean - @Nonnull - public InMemoryUserDetailsManager userDetailsService(@Nonnull PasswordEncoder passwordEncoder) { - UserDetails user = User.withUsername("Rasmus") - .password(passwordEncoder.encode("password")) - .roles("USER") - .build(); - UserDetails admin = User.withUsername("Christian") - .password(passwordEncoder.encode("password")) - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user, admin); - } - - @Bean - @Nonnull - public SecurityFilterChain filterChain(@Nonnull HttpSecurity http) throws Exception { - return http.authorizeHttpRequests(request -> request.anyRequest() - .authenticated()) - .httpBasic(Customizer.withDefaults()) - .build(); - } - - @Bean - @Nonnull - public PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } -} \ No newline at end of file diff --git a/src/main/java/com/stktrk/app/application/profile/ProfileController.java b/src/main/java/com/stktrk/app/application/profile/ProfileController.java deleted file mode 100644 index bab7ba3..0000000 --- a/src/main/java/com/stktrk/app/application/profile/ProfileController.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.stktrk.app.application.profile; - -import com.stktrk.app.domain.profile.ProfileService; -import com.stktrk.app.domain.profile.types.Profile; -import jakarta.annotation.Nonnull; -import jakarta.validation.Valid; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.annotation.Secured; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.security.RolesAllowed; -import java.security.InvalidKeyException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@RestController -@RequestMapping("profile") -@AllArgsConstructor(access = AccessLevel.PROTECTED) -public class ProfileController { - - @Autowired - @Nonnull - private final ProfileService profileService; - - // TODO figure out how to handle the exception. - @Nonnull - @GetMapping("/") - public List findAll(@Nonnull Authentication authentication) { - return profileService.findAll(); - } - - @PostMapping ("/") - public void addProfile (@Nonnull @Valid @RequestBody Profile profile){ - profileService.addProfile(profile); - } - - - @GetMapping("/_delete") - @Nonnull - public ResponseEntity delete() { - profileService.deleteAll (); - return ResponseEntity.status(HttpStatus.OK) - .body("Deleted all Profiles"); - } - - @GetMapping ("/_fake") - @Nonnull - public ResponseEntity _fake() { - profileService.createFakeProfile(); - return ResponseEntity.status(HttpStatus.OK) - .body("Created Profile"); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(MethodArgumentNotValidException.class) - @Nonnull - public Map handleValidationExceptions( - @Nonnull MethodArgumentNotValidException ex) { - Map errors = new HashMap<>(); - ex.getBindingResult().getAllErrors().forEach((error) -> { - String fieldName = ((FieldError) error).getField(); - String errorMessage = error.getDefaultMessage(); - errors.put(fieldName, errorMessage); - }); - return errors; - } -} diff --git a/src/main/java/com/stktrk/app/domain/profile/ProfileRepository.java b/src/main/java/com/stktrk/app/domain/profile/ProfileRepository.java deleted file mode 100644 index 16cd786..0000000 --- a/src/main/java/com/stktrk/app/domain/profile/ProfileRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.stktrk.app.domain.profile; - -import com.stktrk.app.domain.profile.types.Profile; -import jakarta.annotation.Nonnull; - -import java.util.List; - -public interface ProfileRepository { - @Nonnull - List findAll(); - - void addProfile(@Nonnull Profile profile); - - void createFakeProfile(); - - void deleteAll(); -} diff --git a/src/main/java/com/stktrk/app/domain/profile/ProfileService.java b/src/main/java/com/stktrk/app/domain/profile/ProfileService.java deleted file mode 100644 index 23b9f26..0000000 --- a/src/main/java/com/stktrk/app/domain/profile/ProfileService.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.stktrk.app.domain.profile; - -import com.stktrk.app.domain.profile.types.Profile; -import jakarta.annotation.Nonnull; -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@AllArgsConstructor -public class ProfileService { - @Nonnull private final ProfileRepository profileRepository; - - @Nonnull - public List findAll() { - return profileRepository.findAll (); - } - - public void addProfile (@Nonnull Profile profile) { - profileRepository.addProfile (profile); - } - - public void createFakeProfile() { - profileRepository.createFakeProfile(); - } - - public void deleteAll() { - profileRepository.deleteAll(); - } -} diff --git a/src/main/java/com/stktrk/app/domain/profile/types/Profile.java b/src/main/java/com/stktrk/app/domain/profile/types/Profile.java deleted file mode 100644 index dec3485..0000000 --- a/src/main/java/com/stktrk/app/domain/profile/types/Profile.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.stktrk.app.domain.profile.types; - - -import jakarta.annotation.Nullable; -import jakarta.validation.constraints.NotEmpty; -import lombok.Builder; -import lombok.Data; - -@Builder -@Data -public class Profile { - - @Nullable - private String id; - @NotEmpty(message = "Name is mandatory") - private String name; - @NotEmpty(message = "Last name is mandatory") - private String lastName; - -} diff --git a/src/main/java/com/stktrk/app/integration/flyway/migrations/V1__CreateTest.java b/src/main/java/com/stktrk/app/integration/flyway/migrations/V1__CreateTest.java deleted file mode 100644 index 213e0cf..0000000 --- a/src/main/java/com/stktrk/app/integration/flyway/migrations/V1__CreateTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.stktrk.app.integration.flyway.migrations; - -import com.stktrk.app.integration.configuration.types.GraphDbConfig; -import com.stktrk.app.integration.flyway.MigrationWrapper; -import jakarta.annotation.Nonnull; -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.flywaydb.core.api.migration.BaseJavaMigration; -import org.flywaydb.core.api.migration.Context; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@EqualsAndHashCode(callSuper = true) -@Component -@Value -public class V1__CreateTest extends BaseJavaMigration implements MigrationWrapper { - @Autowired - @Nonnull - GraphDbConfig graphDbConfig; - - public void migrate(@Nonnull Context context) throws Exception { - - this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE test"); - } -} diff --git a/src/main/java/com/stktrk/app/integration/flyway/migrations/V2__CreateUser.java b/src/main/java/com/stktrk/app/integration/flyway/migrations/V2__CreateUser.java deleted file mode 100644 index 23b034b..0000000 --- a/src/main/java/com/stktrk/app/integration/flyway/migrations/V2__CreateUser.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.stktrk.app.integration.flyway.migrations; - -import com.stktrk.app.integration.configuration.types.GraphDbConfig; -import com.stktrk.app.integration.flyway.MigrationWrapper; -import jakarta.annotation.Nonnull; -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.flywaydb.core.api.migration.BaseJavaMigration; -import org.flywaydb.core.api.migration.Context; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@EqualsAndHashCode(callSuper = true) -@Component -@Value -public class V2__CreateUser extends BaseJavaMigration implements MigrationWrapper { - @Autowired - @Nonnull - GraphDbConfig graphDbConfig; - - public void migrate(@Nonnull Context context) throws Exception { - - this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE user"); - } -} diff --git a/src/main/java/com/stktrk/app/integration/profile/ProfileRepositoryImpl.java b/src/main/java/com/stktrk/app/integration/profile/ProfileRepositoryImpl.java deleted file mode 100644 index 508a93a..0000000 --- a/src/main/java/com/stktrk/app/integration/profile/ProfileRepositoryImpl.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.stktrk.app.integration.profile; - -import com.arcadedb.gremlin.ArcadeGraph; -import com.github.javafaker.Faker; -import com.stktrk.app.integration.flyway.ConnectionPool; -import com.stktrk.app.domain.profile.ProfileRepository; -import com.stktrk.app.domain.profile.types.Profile; -import com.stktrk.app.utils.EncryptId; -import jakarta.annotation.Nonnull; -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Random; - -@Repository -public class ProfileRepositoryImpl implements ProfileRepository { - @Nonnull - @Override - public List findAll() { - ArcadeGraph g = ConnectionPool.getGraph(); - List profiles = g.traversal().V().hasLabel("Profile").toList(); - g.close(); - - return profiles.stream() - .map(this::buildProfile) - .toList(); - } - - @Override - public void addProfile(@Nonnull Profile profile) { - ArcadeGraph g = ConnectionPool.getGraph(); - Vertex profiles = g.traversal() - .addV("Profile") - .property("name", profile.getName()) - .property("lastName", profile.getLastName()) - .next(); - g.close(); - } - - @Override - public void createFakeProfile() { - Faker faker = new Faker(); - ArcadeGraph g = ConnectionPool.getGraph(); - List vertices = g.traversal().V().hasLabel("Profile").toList(); - Vertex target = vertices.isEmpty() ? null : vertices.get(new Random().nextInt(vertices.size())); - - Vertex res = g.traversal().addV("Profile").property("name", faker.name().firstName()).property("lastName", faker.name().lastName()) - .next(); - if (target != null) { - g.traversal().V(res.id()).addE("friend").to(target).iterate(); - } - g.close(); - } - - @Override - public void deleteAll () { - ArcadeGraph g = ConnectionPool.getGraph(); - g.traversal().V().hasLabel("Profile").drop().iterate(); - } - @Nonnull - private Profile buildProfile (@Nonnull Vertex v) { - return Profile.builder() - .name((String) v.property("name").value()) - .lastName((String) v.property("lastName").value()) - .id(EncryptId.encrypt ((String)v.id())) - .build(); - } - -} diff --git a/src/main/java/com/stktrk/app/AppApplication.java b/src/main/java/com/stktrk/auth/AppApplication.java similarity index 96% rename from src/main/java/com/stktrk/app/AppApplication.java rename to src/main/java/com/stktrk/auth/AppApplication.java index 3265a3e..bf06477 100644 --- a/src/main/java/com/stktrk/app/AppApplication.java +++ b/src/main/java/com/stktrk/auth/AppApplication.java @@ -1,4 +1,4 @@ -package com.stktrk.app; +package com.stktrk.auth; import jakarta.annotation.Nullable; import org.springframework.boot.SpringApplication; diff --git a/src/main/java/com/stktrk/auth/CustomGrantedAuthority.java b/src/main/java/com/stktrk/auth/CustomGrantedAuthority.java new file mode 100644 index 0000000..98be48d --- /dev/null +++ b/src/main/java/com/stktrk/auth/CustomGrantedAuthority.java @@ -0,0 +1,17 @@ +package com.stktrk.auth; + +import org.springframework.security.core.GrantedAuthority; + +public class CustomGrantedAuthority implements GrantedAuthority { + + private final String role; + + public CustomGrantedAuthority(String role) { + this.role = role; + } + + @Override + public String getAuthority() { + return "ROLE_" + role.toUpperCase(); // Converts to Spring-standard role format + } +} diff --git a/src/main/java/com/stktrk/auth/CustomOAuth2UserService.java b/src/main/java/com/stktrk/auth/CustomOAuth2UserService.java new file mode 100644 index 0000000..9412933 --- /dev/null +++ b/src/main/java/com/stktrk/auth/CustomOAuth2UserService.java @@ -0,0 +1,54 @@ +package com.stktrk.auth; + +import com.stktrk.auth.domain.account.AccountService; +import com.stktrk.auth.domain.account.types.Account; +import com.stktrk.auth.domain.account.types.Role; +import jakarta.annotation.Nonnull; +import lombok.SneakyThrows; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Service +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + @Nonnull + private final AccountService accountService; + + @Nonnull public CustomOAuth2UserService(@Nonnull AccountService accountService) { + this.accountService = accountService; + } + + + @Override + @Nonnull public OAuth2User loadUser(@Nonnull OAuth2UserRequest userRequest) { + OAuth2User oAuth2User = super.loadUser(userRequest); + + Map attributes = oAuth2User.getAttributes(); + String email = (String) attributes.get("email"); + + // Check if user exists, else create a new user + Account existingAccount = accountService.findByEmail(email); + if (existingAccount == null) { + try { + accountService.registerUser(email, "generated-password", List.of(Role.USER)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + return new DefaultOAuth2User( + Collections.singleton(() -> "ROLE_USER"), + attributes, + "id" + ); + } +} + diff --git a/src/main/java/com/stktrk/auth/JwtConfig.java b/src/main/java/com/stktrk/auth/JwtConfig.java new file mode 100644 index 0000000..4887f7a --- /dev/null +++ b/src/main/java/com/stktrk/auth/JwtConfig.java @@ -0,0 +1,47 @@ +package com.stktrk.auth; + +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +@Configuration +public class JwtConfig { + + @Bean + public KeyPair keyPair() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + return keyPairGenerator.generateKeyPair(); + } + + @Bean + public JWKSource jwkSource(KeyPair keyPair) { + RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) + .privateKey((RSAPrivateKey) keyPair.getPrivate()) + .keyID("my-key-id") + .build(); + return new ImmutableJWKSet<>(new com.nimbusds.jose.jwk.JWKSet(rsaKey)); + } + + @Bean + public JwtEncoder jwtEncoder(JWKSource jwkSource) { + return new NimbusJwtEncoder(jwkSource); + } + + @Bean + public JwtDecoder jwtDecoder(KeyPair keyPair) { + return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build(); + } +} diff --git a/src/main/java/com/stktrk/auth/RBACConfig.java b/src/main/java/com/stktrk/auth/RBACConfig.java new file mode 100644 index 0000000..1bcdea6 --- /dev/null +++ b/src/main/java/com/stktrk/auth/RBACConfig.java @@ -0,0 +1,18 @@ +package com.stktrk.auth; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; + +@Service +public class RBACConfig { + + @PreAuthorize("hasRole('ADMIN')") + public String adminOnlyOperation() { + return "Admin operation success!"; + } + + @PreAuthorize("hasRole('USER')") + public String userOperation() { + return "User operation success!"; + } +} \ No newline at end of file diff --git a/src/main/java/com/stktrk/auth/RoleAuthentication.java b/src/main/java/com/stktrk/auth/RoleAuthentication.java new file mode 100644 index 0000000..25eb88d --- /dev/null +++ b/src/main/java/com/stktrk/auth/RoleAuthentication.java @@ -0,0 +1,64 @@ +package com.stktrk.auth; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import javax.security.auth.Subject; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +class RoleAuthentication implements Authentication { + private final List roles; + private boolean authenticated = false; + + public RoleAuthentication(List roles) { + this.roles = roles; + } + + + @Override + public String getName() { + return "roles"; + } + + @Override + public boolean implies(Subject subject) { + return Authentication.super.implies(subject); + } + + @Override + public Collection getAuthorities() { + return roles.stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) + .collect(Collectors.toList()); + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + this.authenticated = isAuthenticated; + } + + // Implement other methods as required +} \ No newline at end of file diff --git a/src/main/java/com/stktrk/auth/SecurityConfig.java b/src/main/java/com/stktrk/auth/SecurityConfig.java new file mode 100644 index 0000000..d9a637e --- /dev/null +++ b/src/main/java/com/stktrk/auth/SecurityConfig.java @@ -0,0 +1,86 @@ +package com.stktrk.auth; + +// **SecurityConfig.java** +import jakarta.annotation.Nonnull; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true) +@AllArgsConstructor +public class SecurityConfig { + + @Autowired + @Nonnull + private final JwtDecoder jwtDecoder; + + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth + .requestMatchers("/public/**", "/auth/**", "/test/signup").permitAll() + .requestMatchers("/v3/api-docs/**", "/swagger-ui/**").permitAll() + //.requestMatchers("/bearer/**").authenticated() ANNOTATION SEEMS TO WORK? + .anyRequest().authenticated() + ) + + .addFilterBefore(new TokenValidatorFilter(jwtDecoder), UsernamePasswordAuthenticationFilter.class) + + .oauth2Login(oauth2 -> oauth2 + .defaultSuccessUrl("/auth/success") + .failureUrl("/auth/failure") + ) + // FiXME !!!! this is a security risk. + + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + .csrf(csrf -> csrf.disable()); + + + return http.build(); + } + + @Bean + public ClientRegistrationRepository clientRegistrationRepository() { + ClientRegistration facebookRegistration = ClientRegistration.withRegistrationId("facebook") + .clientId("your-facebook-client-id") + .clientSecret("your-facebook-client-secret") + .redirectUri("{baseUrl}/auth/login/oauth2/code/facebook") + .authorizationUri("https://www.facebook.com/v11.0/dialog/oauth") + .tokenUri("https://graph.facebook.com/v11.0/oauth/access_token") + .userInfoUri("https://graph.facebook.com/me?fields=id,name,email") + .userNameAttributeName("id") + .clientName("Facebook") + .scope("email", "public_profile") + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .build(); + + return new InMemoryClientRegistrationRepository(facebookRegistration); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/stktrk/auth/TokenValidatorFilter.java b/src/main/java/com/stktrk/auth/TokenValidatorFilter.java new file mode 100644 index 0000000..a01eb9c --- /dev/null +++ b/src/main/java/com/stktrk/auth/TokenValidatorFilter.java @@ -0,0 +1,63 @@ +package com.stktrk.auth; + + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.util.Collections; + +import java.io.IOException; +import java.util.List; + +@AllArgsConstructor +public class TokenValidatorFilter extends OncePerRequestFilter { + + private final JwtDecoder jwtDecoder; + + + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String authHeader = request.getHeader("Authorization"); + + if (authHeader != null && authHeader.startsWith("Bearer ")) { + String token = authHeader.substring(7); + try { + // Decode and validate the JWT token + Jwt jwt = jwtDecoder.decode(token); + + // Extract claims (e.g., role and username) + String username = jwt.getSubject(); + List roles = jwt.getClaim("role"); // Assumes your JWT has a 'role' claim + + // Set up Spring Security's authentication + if (roles != null && !roles.isEmpty()) { + RoleAuthentication roleAuth = new RoleAuthentication(roles); + roleAuth.setAuthenticated(true); + SecurityContextHolder.getContext() + .setAuthentication(roleAuth); + } + + + } catch (Exception e) { + // Log error and send unauthorized response + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT"); + return; + } + } + + // Proceed with the filter chain + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/stktrk/auth/application/AuthController.java b/src/main/java/com/stktrk/auth/application/AuthController.java new file mode 100644 index 0000000..b4f53cb --- /dev/null +++ b/src/main/java/com/stktrk/auth/application/AuthController.java @@ -0,0 +1,114 @@ +package com.stktrk.auth.application; + +import com.stktrk.auth.domain.account.AccountService; +import com.stktrk.auth.domain.account.types.Account; +import jakarta.annotation.Nonnull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.oauth2.jwt.JwtClaimsSet; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.security.KeyPair; +import java.time.Instant; +import java.util.HexFormat; + +@RestController +@RequestMapping("/auth") +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AuthController { + @Nonnull + @Autowired + private final AccountService accountService; + + @Autowired + @Nonnull + private final JwtEncoder jwtEncoder; + + @Autowired + @Nonnull + private final KeyPair keyPair; + + + /** + * @return success notification for social login + */ + @Nonnull + @GetMapping("/success") + public String success() { + return "Login successful!"; + } + + /** + * @return failure notification for social login + */ + @Nonnull + @GetMapping("/failure") + public String failure() { + return "Login failed."; + } + + + // FIXME this creates valid tokens on the fly, should be removed from production, or protected for local and dev use only + @PostMapping("/token") + public String generateToken(@RequestParam String username, @RequestParam String role) { + JwtClaimsSet claims = JwtClaimsSet.builder() + .issuer("auth-service") + .issuedAt(Instant.now()) + .expiresAt(Instant.now().plusSeconds(3600)) + .subject(username) + .claim("role", role) + .build(); + + return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + } + + /** + * + * Login to local account + * + * @param username usanername + * @param password password + * @return JWT token + */ + @Nonnull + @PostMapping("/login") + public String login(@RequestParam String username, @RequestParam String password) { + Account account = accountService.findByEmail(username); + if (account == null || !accountService.verifyPassword(password, account.getPassword())) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials"); + } + + + // Generate JWT + JwtClaimsSet claims = JwtClaimsSet.builder() + .issuer("auth-service") + .issuedAt(Instant.now()) + .expiresAt(Instant.now().plusSeconds(3600)) + .subject(username) + .claim("role", account.getRoles().stream().map ( r-> r.name()).toList()) + .build(); + + return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + } + // check result in https://fusionauth.io/dev-tools/jwt-decoder + + /** + * Offers the possibility for other severs to get to the public key + * FIXME should probably not be a thing in production? + * @return public key used by the server + */ + @GetMapping("/publicKey") + public String publicKey() { + + + return "{\"public\":\"" + HexFormat.of().formatHex(keyPair.getPublic().getEncoded()) + "\"}"; + + } + + +} diff --git a/src/main/java/com/stktrk/auth/application/LocalSignupController.java b/src/main/java/com/stktrk/auth/application/LocalSignupController.java new file mode 100644 index 0000000..fe13efb --- /dev/null +++ b/src/main/java/com/stktrk/auth/application/LocalSignupController.java @@ -0,0 +1,41 @@ +package com.stktrk.auth.application; + +import com.stktrk.auth.domain.account.types.Role; +import com.stktrk.auth.domain.account.AccountService; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.sql.SQLException; +import java.util.List; + +@RestController +@RequestMapping("/test") +public class LocalSignupController { + + @Nullable + private final AccountService accountService; + + public LocalSignupController(@Nullable AccountService accountService) { + this.accountService = accountService; + } + + + @Nonnull + @PostMapping("/signup") + public String signUp(@Nonnull @RequestParam String email, @Nonnull @RequestParam String password, @Nonnull @RequestParam List roles) { + + + // Register the user + try { + accountService.registerUser(email, password, roles); + } catch (SQLException e) { + return e.getMessage(); + } + return "User registered successfully!"; + } + +} diff --git a/src/main/java/com/stktrk/auth/application/TestController.java b/src/main/java/com/stktrk/auth/application/TestController.java new file mode 100644 index 0000000..d305ea7 --- /dev/null +++ b/src/main/java/com/stktrk/auth/application/TestController.java @@ -0,0 +1,20 @@ +package com.stktrk.auth.application; + +import jakarta.annotation.Nonnull; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/bearer") +public class TestController { + + @PreAuthorize("hasRole('USER')") +@Nonnull + @GetMapping ("/user") + public String isUser () { + return "OK"; +} + +} diff --git a/src/main/java/com/stktrk/auth/domain/account/AccountRepository.java b/src/main/java/com/stktrk/auth/domain/account/AccountRepository.java new file mode 100644 index 0000000..b144172 --- /dev/null +++ b/src/main/java/com/stktrk/auth/domain/account/AccountRepository.java @@ -0,0 +1,36 @@ +package com.stktrk.auth.domain.account; + +import com.stktrk.auth.domain.account.types.Role; +import com.stktrk.auth.domain.account.types.Account; +import jakarta.annotation.Nonnull; + +import java.util.List; +/** + * Profile repository + */ +public interface AccountRepository { + /** + * Check if a user profile exists, by email + * @param email account email + * @return account exits + */ + boolean accountExists(@Nonnull String email); + + /** + * returns a single account, with roles + * @param email account email + * @return account, incl Roles. + */ + @Nonnull + Account findByEmail (@Nonnull String email); + + /** + * Create a new user account + * + * @param email account email serves as unique id + * @param password password. + * @param roles roles the account is created with. + */ + void createAccount(@Nonnull String email, @Nonnull String password, List roles); + +} diff --git a/src/main/java/com/stktrk/auth/domain/account/AccountService.java b/src/main/java/com/stktrk/auth/domain/account/AccountService.java new file mode 100644 index 0000000..4c7aee8 --- /dev/null +++ b/src/main/java/com/stktrk/auth/domain/account/AccountService.java @@ -0,0 +1,61 @@ +package com.stktrk.auth.domain.account; + +import com.stktrk.auth.domain.account.types.Role; +import com.stktrk.auth.domain.account.types.Account; +import jakarta.annotation.Nonnull; +import lombok.AllArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.List; + +/** + * account service + */ +@Service +@AllArgsConstructor +public class AccountService { + @Nonnull + private final AccountRepository accountRepository; + @Nonnull + private final PasswordEncoder passwordEncoder; + + /** + * register a new user account + * @param email account email, serves as uniwue ID + * @param password password + * @param roles roles to be assigned to the account + * @throws SQLException thrown on duplicate account creation FIXME + */ + public void registerUser(@Nonnull String email, @Nonnull String password,@Nonnull List roles) throws SQLException { + + String encodedPassword = passwordEncoder.encode(password); + if (accountRepository.accountExists(email)) { + throw new SQLException("User already exists."); + } + accountRepository.createAccount (email,encodedPassword, roles); + } + + /** + * used during login to check password validity FIXME we should not expose the password to anywhere should we? + * @param rawPassword + * @param encodedPassword + * @return + */ + public boolean verifyPassword(@Nonnull String rawPassword, @Nonnull String encodedPassword) { + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * search account by email + * @param email account email + * @return account + */ + public Account findByEmail (@Nonnull String email) { + return accountRepository.findByEmail (email); + } +} + + + diff --git a/src/main/java/com/stktrk/auth/domain/account/types/Account.java b/src/main/java/com/stktrk/auth/domain/account/types/Account.java new file mode 100644 index 0000000..39046df --- /dev/null +++ b/src/main/java/com/stktrk/auth/domain/account/types/Account.java @@ -0,0 +1,19 @@ +package com.stktrk.auth.domain.account.types; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +@Value +@Builder +public class Account { + @Nonnull + private final String email; + @Nullable + private final String password; + @Nullable private final List roles; + @Nullable private final String id; +} diff --git a/src/main/java/com/stktrk/auth/domain/account/types/Role.java b/src/main/java/com/stktrk/auth/domain/account/types/Role.java new file mode 100644 index 0000000..71cfbc3 --- /dev/null +++ b/src/main/java/com/stktrk/auth/domain/account/types/Role.java @@ -0,0 +1,6 @@ +package com.stktrk.auth.domain.account.types; + +public enum Role { + ADMIN, + USER +} diff --git a/src/main/java/com/stktrk/auth/integration/account/AccountRepositoryImpl.java b/src/main/java/com/stktrk/auth/integration/account/AccountRepositoryImpl.java new file mode 100644 index 0000000..2a549ce --- /dev/null +++ b/src/main/java/com/stktrk/auth/integration/account/AccountRepositoryImpl.java @@ -0,0 +1,111 @@ +package com.stktrk.auth.integration.account; + +import com.arcadedb.gremlin.ArcadeGraph; +import com.stktrk.auth.domain.account.types.Role; +import com.stktrk.auth.domain.account.AccountRepository; +import com.stktrk.auth.domain.account.types.Account; +import com.stktrk.auth.integration.flyway.ConnectionPool; +import com.stktrk.auth.utils.EncryptId; +import jakarta.annotation.Nonnull; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Map; +import java.util.stream.StreamSupport; + +import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.outE; + +/** + * Profile repository + */ +@Repository +public class AccountRepositoryImpl implements AccountRepository { + /** + * Check if a user profile exists, by email + * @param email account email + * @return account exits + */ + @Override + public boolean accountExists(@Nonnull String email) { + ArcadeGraph g = ConnectionPool.getGraph(); + List accounts = g.traversal().V().hasLabel("Account").has("email", email).toList(); + g.close(); + + return !accounts.isEmpty(); + } + + /** + * returns a single account, with roles + * @param email account email + * @return account, incl Roles. + */ + @Override + public Account findByEmail(@Nonnull String email) { + ArcadeGraph g = ConnectionPool.getGraph(); + List> accounts = g.traversal().V().hasLabel("Account").has("email", email) .project("vertex" ,"roles") // removed "edges" + .by() // Include the vertex itself + //.by(outE("hasRole").fold()) // Include outgoing edges with "hasRole" + .by(outE("hasRole").inV().fold()) // Include target vertices + .toList(); + g.close(); + + return accounts.isEmpty() ? null : buildAccount(accounts.getFirst()); + + } + + /** + * Create a new user account + * + * @param email account email serves as unique id + * @param password password. + * @param roles roles the account is created with. + */ + @Override + public void createAccount(@Nonnull String email, @Nonnull String password, List roles) { + ArcadeGraph g = ConnectionPool.getGraph(); + + // TODO remove inject, see if it still works + // FIXME we need to deal with roles properly. + GraphTraversal ts = g.traversal().inject(0) + .addV("Account") + .as("a") + .property("email", email) + .property("password", password); + + roles.forEach(r -> { + ts.V().hasLabel("Role").has("name", r.name()).as(r.name()) + .addE("hasRole").from ("a"). to (r.name()); + }); + + ts.iterate(); + g.close(); + } + + /** + * create an account from the DB + * @param entry Map with the vettices for account and roles + * @return account + */ + @Nonnull + private Account buildAccount(@Nonnull Map entry) { + + Iterable targets = (Iterable) entry.get("roles"); + List roles = StreamSupport.stream(targets.spliterator(), false) + .map (e -> e.property("name")) + .map (p ->(String) p.value()) + .map (Role::valueOf) + .toList(); + + Vertex v = (Vertex) entry.get("vertex"); + + // FIXMe we should probably ot return the password? + return Account.builder() + .email((String) v.property("email").value()) + .password((String) v.property("password").value()) + .id(EncryptId.encrypt((String) v.id())) + .roles(roles) + .build(); + } +} diff --git a/src/main/java/com/stktrk/app/integration/configuration/types/GraphDbConfig.java b/src/main/java/com/stktrk/auth/integration/configuration/types/GraphDbConfig.java similarity index 89% rename from src/main/java/com/stktrk/app/integration/configuration/types/GraphDbConfig.java rename to src/main/java/com/stktrk/auth/integration/configuration/types/GraphDbConfig.java index 612938c..f70251e 100644 --- a/src/main/java/com/stktrk/app/integration/configuration/types/GraphDbConfig.java +++ b/src/main/java/com/stktrk/auth/integration/configuration/types/GraphDbConfig.java @@ -1,4 +1,4 @@ -package com.stktrk.app.integration.configuration.types; +package com.stktrk.auth.integration.configuration.types; import jakarta.annotation.Nonnull; import lombok.Getter; diff --git a/src/main/java/com/stktrk/app/integration/flyway/ConnectionPool.java b/src/main/java/com/stktrk/auth/integration/flyway/ConnectionPool.java similarity index 69% rename from src/main/java/com/stktrk/app/integration/flyway/ConnectionPool.java rename to src/main/java/com/stktrk/auth/integration/flyway/ConnectionPool.java index 86c72e7..5cc419b 100644 --- a/src/main/java/com/stktrk/app/integration/flyway/ConnectionPool.java +++ b/src/main/java/com/stktrk/auth/integration/flyway/ConnectionPool.java @@ -1,8 +1,8 @@ -package com.stktrk.app.integration.flyway; +package com.stktrk.auth.integration.flyway; import com.arcadedb.gremlin.ArcadeGraph; import com.arcadedb.gremlin.ArcadeGraphFactory; -import com.stktrk.app.integration.configuration.types.GraphDbConfig; +import com.stktrk.auth.integration.configuration.types.GraphDbConfig; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import org.springframework.beans.factory.annotation.Autowired; @@ -10,6 +10,9 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; +/** + * provides connection pool for arcade DB + */ @Component public class ConnectionPool { @Nullable @@ -19,6 +22,9 @@ public class ConnectionPool { @Nullable GraphDbConfig graphDbConfig; + /** + * runautomatically after construction of bean, to late-grab config parameters + */ @PostConstruct void init() { pool = ArcadeGraphFactory.withRemote(graphDbConfig.getHost(), @@ -28,6 +34,10 @@ public class ConnectionPool { graphDbConfig.getPassword()); } + /** + * Provides DB connection from pool + * @return graphFactory to run queries against + */ @Nonnull public static ArcadeGraph getGraph() { return pool.get(); diff --git a/src/main/java/com/stktrk/app/integration/flyway/FlywayConfig.java b/src/main/java/com/stktrk/auth/integration/flyway/FlywayConfig.java similarity index 97% rename from src/main/java/com/stktrk/app/integration/flyway/FlywayConfig.java rename to src/main/java/com/stktrk/auth/integration/flyway/FlywayConfig.java index e578207..f7e4e68 100644 --- a/src/main/java/com/stktrk/app/integration/flyway/FlywayConfig.java +++ b/src/main/java/com/stktrk/auth/integration/flyway/FlywayConfig.java @@ -1,4 +1,4 @@ -package com.stktrk.app.integration.flyway; +package com.stktrk.auth.integration.flyway; import jakarta.annotation.Nullable; import jakarta.annotation.PostConstruct; @@ -17,7 +17,7 @@ public class FlywayConfig { // TODO move to properties // Custom migration history table name @Nullable - private static final String SCHEMA_HISTORY_TABLE = "flyway_schema_history"; + private static final String SCHEMA_HISTORY_TABLE = "flyway_schema_history_auth_server"; @Autowired @Nullable diff --git a/src/main/java/com/stktrk/app/integration/flyway/MigrationWrapper.java b/src/main/java/com/stktrk/auth/integration/flyway/MigrationWrapper.java similarity index 66% rename from src/main/java/com/stktrk/app/integration/flyway/MigrationWrapper.java rename to src/main/java/com/stktrk/auth/integration/flyway/MigrationWrapper.java index 702badc..c64d304 100644 --- a/src/main/java/com/stktrk/app/integration/flyway/MigrationWrapper.java +++ b/src/main/java/com/stktrk/auth/integration/flyway/MigrationWrapper.java @@ -1,6 +1,6 @@ -package com.stktrk.app.integration.flyway; +package com.stktrk.auth.integration.flyway; -import com.stktrk.app.integration.configuration.types.GraphDbConfig; +import com.stktrk.auth.integration.configuration.types.GraphDbConfig; import jakarta.annotation.Nonnull; import java.sql.Connection; @@ -9,8 +9,18 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; +/** + * helper interface to reduce overhead on DB migration classes + */ public interface MigrationWrapper { - + /** + * default method to allow easy application of arbitrary SQL + * + * @param graphDbConfig DB configuration + * @param query SQL query + * @throws ClassNotFoundException Driver missing + * @throws SQLException SQL error + */ default void executeQuery(@Nonnull GraphDbConfig graphDbConfig, @Nonnull String query) throws ClassNotFoundException, SQLException { Class.forName("org.postgresql.Driver"); @@ -21,8 +31,6 @@ public interface MigrationWrapper { props.setProperty("ssl", "false"); props.setProperty("sslmode", "disable"); - System.out.println ( graphDbConfig.getHost()); - try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + graphDbConfig.getHost() + ":" + graphDbConfig.getPostgresPort() + "/" + graphDbConfig.getGraphName(), props)) { Statement st = connection.createStatement(); st.execute(query); diff --git a/src/main/java/com/stktrk/app/integration/flyway/migrations/V3__CreateProfile.java b/src/main/java/com/stktrk/auth/integration/flyway/migrations/V1__CreateAccount.java similarity index 52% rename from src/main/java/com/stktrk/app/integration/flyway/migrations/V3__CreateProfile.java rename to src/main/java/com/stktrk/auth/integration/flyway/migrations/V1__CreateAccount.java index a0d2a6f..7d71bf8 100644 --- a/src/main/java/com/stktrk/app/integration/flyway/migrations/V3__CreateProfile.java +++ b/src/main/java/com/stktrk/auth/integration/flyway/migrations/V1__CreateAccount.java @@ -1,7 +1,7 @@ -package com.stktrk.app.integration.flyway.migrations; +package com.stktrk.auth.integration.flyway.migrations; -import com.stktrk.app.integration.configuration.types.GraphDbConfig; -import com.stktrk.app.integration.flyway.MigrationWrapper; +import com.stktrk.auth.integration.configuration.types.GraphDbConfig; +import com.stktrk.auth.integration.flyway.MigrationWrapper; import jakarta.annotation.Nonnull; import lombok.EqualsAndHashCode; import lombok.Value; @@ -13,12 +13,12 @@ import org.springframework.stereotype.Component; @EqualsAndHashCode(callSuper = true) @Component @Value -public class V3__CreateProfile extends BaseJavaMigration implements MigrationWrapper { +public class V1__CreateAccount extends BaseJavaMigration implements MigrationWrapper { @Nonnull @Autowired GraphDbConfig graphDbConfig; public void migrate(@Nonnull Context context) throws Exception { - this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE Profile IF NOT EXISTS; CREATE PROPERTY Profile.name STRING (MANDATORY true); CREATE PROPERTY Profile.lastName STRING (MANDATORY true, notnull true);"); + this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE Account IF NOT EXISTS; CREATE PROPERTY Account.email STRING (MANDATORY true, notnull true); CREATE PROPERTY Account.password STRING (MANDATORY true, notnull true);"); } -} +} \ No newline at end of file diff --git a/src/main/java/com/stktrk/auth/integration/flyway/migrations/V2__CreateRoles.java b/src/main/java/com/stktrk/auth/integration/flyway/migrations/V2__CreateRoles.java new file mode 100644 index 0000000..8633b1c --- /dev/null +++ b/src/main/java/com/stktrk/auth/integration/flyway/migrations/V2__CreateRoles.java @@ -0,0 +1,25 @@ +package com.stktrk.auth.integration.flyway.migrations; + +import com.stktrk.auth.integration.configuration.types.GraphDbConfig; +import com.stktrk.auth.integration.flyway.MigrationWrapper; +import jakarta.annotation.Nonnull; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@EqualsAndHashCode(callSuper = true) +@Component +@Value +public class V2__CreateRoles extends BaseJavaMigration implements MigrationWrapper { + @Nonnull + @Autowired + GraphDbConfig graphDbConfig; + + public void migrate(@Nonnull Context context) throws Exception { + this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE Role IF NOT EXISTS; CREATE PROPERTY Role.name STRING (MANDATORY true, notnull true); CREATE VERTEX Role SET name = 'USER'; CREATE VERTEX Role SET name = 'ADMIN'; CREATE EDGE TYPE hasRole IF NOT EXISTS; create index on hasRole (`@in`, `@out`) UNIQUE ; create property hasRole.`@out` LINK OF Account; create property hasRole.`@in` LINK OF Role;"); + } +} + diff --git a/src/main/java/com/stktrk/app/utils/EncryptId.java b/src/main/java/com/stktrk/auth/utils/EncryptId.java similarity index 97% rename from src/main/java/com/stktrk/app/utils/EncryptId.java rename to src/main/java/com/stktrk/auth/utils/EncryptId.java index b0e9746..3742608 100644 --- a/src/main/java/com/stktrk/app/utils/EncryptId.java +++ b/src/main/java/com/stktrk/auth/utils/EncryptId.java @@ -1,4 +1,4 @@ -package com.stktrk.app.utils; +package com.stktrk.auth.utils; import com.arcadedb.database.RID; import jakarta.annotation.Nonnull; diff --git a/src/main/resources/application-rasmus.properties b/src/main/resources/application-rasmus.properties index 4a5d075..dffbfda 100644 --- a/src/main/resources/application-rasmus.properties +++ b/src/main/resources/application-rasmus.properties @@ -1,14 +1,7 @@ spring.application.name=app -server.port=9090 +server.port=9091 springdoc.swagger-ui.path=/swagger-ui.html -#spring.flyway.user=flyway_user -#spring.flyway.password=7e7v55UcYGrY0e3UPYI0qtyMA4YJ1ZkTEaoyZ252GluFkiEMHVT9U5ULS7Rg2rGi -#spring.flyway.schemas=flyway_db -#spring.flyway.url=jdbc:postgresql://192.168.178.50:7654/flyway_db -#spring.flyway.locations=classpath:com/stktrk/app/db/migrations - - spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url= jdbc:postgresql://192.168.178.50:7654/flyway_db spring.datasource.username=flyway_user @@ -18,8 +11,8 @@ spring.datasource.hikari.schema=flyway_db graph.user=root graph.password=playwithdata -graph.connection=jdbc:postgresql://192.168.178.50:5432/graph +graph.connection=jdbc:postgresql://192.168.178.50:5432/auth graph.http-port:2480 graph.postgres-port:5432 graph.host=192.168.178.50 -graph.graph-name=graph \ No newline at end of file +graph.graph-name=auth \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 434e620..17ece79 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ -spring.application.name=app -server.port=9090 +spring.application.name=auth +server.port=9091 springdoc.swagger-ui.path=/swagger-ui.html #spring.flyway.user=flyway_user diff --git a/src/test/java/com/stktrk/app/AppApplicationTests.java b/src/test/java/com/stktrk/auth/AppApplicationTests.java similarity index 91% rename from src/test/java/com/stktrk/app/AppApplicationTests.java rename to src/test/java/com/stktrk/auth/AppApplicationTests.java index a7bf00e..205dfe9 100644 --- a/src/test/java/com/stktrk/app/AppApplicationTests.java +++ b/src/test/java/com/stktrk/auth/AppApplicationTests.java @@ -1,4 +1,4 @@ -package com.stktrk.app; +package com.stktrk.auth; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/com/stktrk/app/TestAppApplication.java b/src/test/java/com/stktrk/auth/TestAppApplication.java similarity index 91% rename from src/test/java/com/stktrk/app/TestAppApplication.java rename to src/test/java/com/stktrk/auth/TestAppApplication.java index b263882..44603d3 100644 --- a/src/test/java/com/stktrk/app/TestAppApplication.java +++ b/src/test/java/com/stktrk/auth/TestAppApplication.java @@ -1,4 +1,4 @@ -package com.stktrk.app; +package com.stktrk.auth; import jakarta.annotation.Nonnull; import org.springframework.boot.SpringApplication; diff --git a/src/test/java/com/stktrk/app/TestcontainersConfiguration.java b/src/test/java/com/stktrk/auth/TestcontainersConfiguration.java similarity index 95% rename from src/test/java/com/stktrk/app/TestcontainersConfiguration.java rename to src/test/java/com/stktrk/auth/TestcontainersConfiguration.java index 4248f99..ed7f1e8 100644 --- a/src/test/java/com/stktrk/app/TestcontainersConfiguration.java +++ b/src/test/java/com/stktrk/auth/TestcontainersConfiguration.java @@ -1,4 +1,4 @@ -package com.stktrk.app; +package com.stktrk.auth; import jakarta.annotation.Nonnull; import org.springframework.boot.test.context.TestConfiguration; diff --git a/src/test/java/com/stktrk/app/utils/EncryptIdTest.java b/src/test/java/com/stktrk/auth/utils/EncryptIdTest.java similarity index 93% rename from src/test/java/com/stktrk/app/utils/EncryptIdTest.java rename to src/test/java/com/stktrk/auth/utils/EncryptIdTest.java index 190e4a1..4badc28 100644 --- a/src/test/java/com/stktrk/app/utils/EncryptIdTest.java +++ b/src/test/java/com/stktrk/auth/utils/EncryptIdTest.java @@ -1,4 +1,4 @@ -package com.stktrk.app.utils; +package com.stktrk.auth.utils; import com.arcadedb.database.RID; import org.junit.jupiter.api.Assertions;